ngc-webpack
@ngtools/webpack wrapper with hooks into the compilation process
Background:
ngc-webpack
started as a wrapper for @angular/compiler-cli
when angular
build tools were limited.
It offered non @angular/cli
users the ability to perform an AOT builds
with all the required operations while still using a dedicated typescript
loader (e.g. ts-loader
, awesome-typescript-loader
).
With version 5 of angular, the compiler-cli
introduces a dramatic
refactor in the compilation process, enabling watch mode for AOT and
moving to a (almost) native TS compilation process using transformers.
The support angular 5, a complete rewrite for ngc-webpack
was required.
Since @ngtools/webpack
is now a mature plugin with a rich feature set
and core team support it is not smart (IMHO) to try and re-implement it.
This is why, from version 4 of ngc-webpack
, the library will wrap
@ngtools/webpack
and only provide hooks into the compilation process.
The implications are:
- Using
ngc-webpack
is safe, at any point you can move to @ngtools/webpack
. - All features of
@ngtools/webpack
will work since ngc-webpack
acts as a proxy.
This includes i18n support which was not included in ngc-webpack
3.x.x - You can hack your way into the AOT compilation process, which opens
a lot of options, especially for library compilation.
- Using a custom typescript loader is no longer supported, you need to
use the loader provided with
@ngtools/webpack
(for JIT see Using custom TypeScript loaders)
Usage:
npm install ngc-webpack -D
webpack.config.js
{
module: {
rules: [
{
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
use: [ '@ngtools/webpack' ]
}
]
},
plugins: [
new ngcWebpack.NgcWebpackPlugin({
AOT: true,
tsConfigPath: './tsconfig.json',
mainPath: 'src/main.ts'
})
]
}
Advanced AOT production builds:
Production builds must be AOT compiled, this is clear, but we can optimize
the build even further, and the angular team has us covered using
'@angular-devkit/build-optimizer
:
webpack.config.js
const PurifyPlugin = require('@angular-devkit/build-optimizer').PurifyPlugin;
const AOT = true;
const tsLoader = {
test: /(?:\.ngfactory\.js|\.ngstyle\.js|\.ts)$/,
use: [ '@ngtools/webpack' ]
};
if (AOT) {
tsLoader.use.unshift({
loader: '@angular-devkit/build-optimizer/webpack-loader',
});
}
return {
module: {
rules: [
tsLoader
]
},
plugins: [
new ngcWebpack.NgcWebpackPlugin({
AOT,
tsConfigPath: './tsconfig.json',
mainPath: 'src/main.ts'
}).concat(AOT ? [ new PurifyPlugin() ] : []),
]
}
The examples above are super simplified and describe the basic units
for compilation, the @angular/cli
uses them but with a lot more loaders/plugins/logic.
For more information about setting up the plugin see @ngtools/webpack
NgcWebpackPluginOptions:
The plugin accepts an options object of type NgcWebpackPluginOptions
.
NgcWebpackPluginOptions
extends AngularCompilerPluginOptions so
all @ngtools/webpack
options apply.
NgcWebpackPluginOptions
adds the following options:
export interface NgcWebpackPluginOptions extends AngularCompilerPluginOptions {
AOT?: boolean;
beforeRun?: BeforeRunHandler
readFileTransformer?: ReadFileTransformer;
resourcePathTransformer?: ResourcePathTransformer;
resourceTransformer?: ResourceTransformer;
tsTransformers?: ts.CustomTransformers;
}
Patching @angular/compiler-cli
:
The compiler-cli
(version 5.0.0) comes with a new feature called
lowering expressions which basically means we can now use arrow
functions in decorator metadata (usually provider metadata)
This feature has bug the will throw when setting an arrow function:
export function MyPropDecorator(value: () => any) {
return (target: Object, key: string) => { }
}
export class MyClass {
@MyPropDecorator(() => 15)
prop: string;
}
The compiler will lower the expression to:
export const ɵ0 = function () { return 15; };
but in the TS compilation process will fail because of a TS bug.
This is an edge case which you probably don't care about, but if so
there are 2 options to workaround:
- Set
disableExpressionLowering
to false in tsconfig.json
angularCompilerOptions
- Import a patch, at the top of your webpack config module:
require('ngc-webpack/src/patch-angular-compiler-cli');
The issue should be fixed in next versions.
See https://github.com/angular/angular/issues/20216
Using custom TypeScript loaders
From ngc-webpack
4 using a custom ts loader is not supported for AOT
compilation and partially supported for JIT.
If you must use your own TS Loader for JIT, you can do so.
This is not recommended mainly because of the mis alignment between the
compilations.
To use a custom loader (JIT only), remove the @ngtools/webpack
loader
and set your own loader. To support lazy loaded modules, use a module
loader that can detect them (e.g. ng-router-loader)
Use case
The feature set within ngc-webpack
is getting more and more specific.
The target audience is small as most developers will not require hooking
into the compilation.
It is mostly suitable for library builds, where you can control the
metadata output, inline code and more...
I personally use it to restyle material from the ground.
The plugin enables re-writing of the index.metadata.json
files on
the fly which allows sending custom styles to the compiler instead of
the ones that comes with material.
Future
Because ngc-webpack
becomes a niche, I believe integrating the hooks
into @ngtools/webpack
makes sense and then deprecating the library while
easy porting to @ngtools/webpack
. If someone would like to help working
on it, please come forward :)
I believe it angular team is open to such idea since @ngtools/webpack
is separated from the cli.