UNPKG

19 kBMarkdownView Raw
1# Custom webpack [builders](#builders) for Angular build facade
2
3[![npm version](https://img.shields.io/npm/v/@angular-builders/custom-webpack.svg) ![npm (tag)](https://img.shields.io/npm/v/@angular-builders/custom-webpack/next.svg) ![npm](https://img.shields.io/npm/dm/@angular-builders/custom-webpack.svg)](https://www.npmjs.com/package/@angular-builders/custom-webpack)
4
5Allow customizing build configuration without ejecting webpack configuration (`ng eject`)
6
7# This documentation is for the latest major version only
8
9## Previous versions
10
11<details>
12 <summary>Click to expand</summary>
13
14- [Version 13](https://github.com/just-jeb/angular-builders/blob/13.x.x/packages/custom-webpack/README.md)
15- [Version 12](https://github.com/just-jeb/angular-builders/blob/12.x.x/packages/custom-webpack/README.md)
16- [Version 11](https://github.com/just-jeb/angular-builders/blob/11.x.x/packages/custom-webpack/README.md)
17- [Version 10](https://github.com/just-jeb/angular-builders/blob/10.x.x/packages/custom-webpack/README.md)
18- [Version 9](https://github.com/just-jeb/angular-builders/blob/9.x.x/packages/custom-webpack/README.md)
19- [Version 8](https://github.com/just-jeb/angular-builders/blob/8.x.x/packages/custom-webpack/README.md)
20- [Version 7](https://github.com/just-jeb/angular-builders/blob/7.x.x/packages/custom-webpack/README.md)
21
22</details>
23
24## [Quick guide](https://www.justjeb.com/post/customizing-angular-cli-build)
25
26## Prerequisites:
27
28- [Angular CLI 14](https://www.npmjs.com/package/@angular/cli)
29
30# Usage
31
321. `npm i -D @angular-builders/custom-webpack`
332. In your `angular.json`:
34 ```js
35 "projects": {
36 ...
37 "[project]": {
38 ...
39 "architect": {
40 ...
41 "[architect-target]": {
42 "builder": "@angular-builders/custom-webpack:[browser|server|karma|dev-server|extract-i18n]"
43 "options": {
44 ...
45 }
46 ```
47 Where:
48 - [project] is the name of the project to which you want to add the builder
49 - [architect-target] is the name of build target you want to run (build, serve, test etc. or any custom target)
50 - [browser|server|karma|dev-server|extract-i18n] one of the supported builders - [browser](#Custom-webpack-browser), [server](#Custom-webpack-server), [karma](#Custom-webpack-Karma), [dev-server](#Custom-webpack-dev-server) or [extract-i18n](#Custom-webpack-extract-i18n)
513. If `[architect-target]` is not one of the predefined targets (like build, serve, test etc.) then run it like this:
52 `ng run [project]:[architect-target]`
53 If it is one of the predefined targets, you can run it with `ng [architect-target]`
54
55## For Example
56
57- angular.json:
58 ```js
59 "projects": {
60 ...
61 "example-app": {
62 ...
63 "architect": {
64 ...
65 "build": {
66 "builder": "@angular-builders/custom-webpack:browser"
67 "options": {
68 ...
69 }
70 ```
71- Run the build: `ng build`
72
73# Builders
74
75- [@angular-builders/custom-webpack:browser](#Custom-webpack-browser)
76- [@angular-builders/custom-webpack:server](#Custom-webpack-server)
77- [@angular-builders/custom-webpack:karma](#Custom-webpack-Karma)
78- [@angular-builders/custom-webpack:dev-server](#Custom-webpack-dev-server)
79- [@angular-builders/custom-webpack:extract-i18n](#Custom-webpack-extract-i18n)
80
81## Custom Webpack `browser`
82
83Extended `@angular-devkit/build-angular:browser` builder that allows to specify additional webpack configuration (on top of the existing under the hood) and `index.html` transformations.
84The builder will run the same build as `@angular-devkit/build-angular:browser` does with extra parameters that are specified in the provided webpack configuration. It will also run transformation on `index.html` if specified.
85
86Builder options:
87
88- All the `@angular-devkit/build-angular:browser` options
89- `customWebpackConfig`: [see below](#custom-webpack-config-object)
90- `indexTransform`: [see below](#index-transform)
91
92`angular.json` Example:
93
94```js
95"architect": {
96 ...
97 "build": {
98 "builder": "@angular-builders/custom-webpack:browser"
99 "options": {
100 "customWebpackConfig": {
101 "path": "./extra-webpack.config.js",
102 "mergeRules": {
103 "externals": "replace"
104 }
105 },
106 "indexTransform": "./index-html-transform.js",
107 "outputPath": "dist/my-cool-client",
108 "index": "src/index.html",
109 "main": "src/main.ts",
110 "polyfills": "src/polyfills.ts",
111 "tsConfig": "src/tsconfig.app.json"
112 }
113```
114
115In this example `externals` entry from `extra-webpack.config.js` will replace `externals` entry from Angular CLI underlying webpack config while all the rest will be appended. In addition `index.html` will be modified by the function exported from `./index-html-transform.js`.
116
117## Custom Webpack `dev-server`
118
119Enhanced `@angular-devkit/build-angular:dev-server` builder that leverages the custom webpack builder to get webpack configuration.
120
121Unlike the default `@angular-devkit/build-angular:dev-server` it doesn't use `@angular-devkit/build-angular:browser` configuration to run the dev server. Instead it uses `customWebpackConfiguration` from `browserTarget` and runs custom webpack dev server build.
122
123Thus, if you use `@angular-builders/custom-webpack:dev-server` along with `@angular-builders/custom-webpack:browser`, `ng serve` will run with custom configuration provided in the latter.
124
125### Example
126
127`angular.json`:
128
129```js
130"architect": {
131 ...
132 "build": {
133 "builder": "@angular-builders/custom-webpack:browser",
134 "options": {
135 "customWebpackConfig": {
136 "path": "./extra-webpack.config.js"
137 },
138 ...
139 }
140 },
141 "serve": {
142 "builder": "@angular-builders/custom-webpack:dev-server",
143 "options": {
144 "browserTarget": "my-project:build"
145 }
146 }
147```
148
149In this example `dev-server` will use `custom-webpack:browser` builder, hence modified webpack config, when invoking the serve target.
150
151## Custom Webpack `server`
152
153Extended `@angular-devkit/build-angular:server` builder that allows to specify additional webpack configuration (on top of the existing under the hood) and `index.html` transformations.
154The builder will run the same build as `@angular-devkit/build-angular:server` does with extra parameters that are specified in the provided webpack configuration.
155
156Builder options:
157
158- All the `@angular-devkit/build-angular:server` options
159- `customWebpackConfig`: [see below](#custom-webpack-config-object)
160
161`angular.json` Example:
162
163```js
164"architect": {
165 ...
166 "build": {
167 "builder": "@angular-builders/custom-webpack:server"
168 "options": {
169 "customWebpackConfig": {
170 "path": "./extra-webpack.config.js",
171 "mergeRules": {
172 "module": {
173 "rules": "prepend"
174 }
175 },
176 "replaceDuplicatePlugins": true
177 },
178 "outputPath": "dist/my-cool-server",
179 "main": "src/main.server.ts",
180 "tsConfig": "src/tsconfig.server.json"
181 }
182```
183
184In this example `module.rules` entry from `extra-webpack.config.js` will be prepended to `module.rules` entry from Angular CLI underlying webpack config while all the rest will be appended.
185Since loaders are evaluated [from right to left](https://webpack.js.org/concepts/loaders/#configuration) this will effectively mean that the loaders you define in your custom configuration will be applied **after** the loaders defined by Angular CLI.
186
187## Custom Webpack `karma`
188
189Extended `@angular-devkit/build-angular:karma` builder that allows to specify additional webpack configuration (on top of the existing under the hood) and `index.html` transformations.
190The builder will run the same build as `@angular-devkit/build-angular:karma` does with extra parameters that are specified in the provided webpack configuration.
191
192Builder options:
193
194- All the `@angular-devkit/build-angular:karma` options
195- `customWebpackConfig`: [see below](#custom-webpack-config-object)
196
197`angular.json` Example:
198
199```js
200"architect": {
201 ...
202 "test": {
203 "builder": "@angular-builders/custom-webpack:karma"
204 "options": {
205 "customWebpackConfig": {
206 "path": "./extra-webpack.config.js"
207 },
208 "main": "src/test.ts",
209 "polyfills": "src/polyfills.ts",
210 "tsConfig": "src/tsconfig.spec.json",
211 "karmaConfig": "src/karma.conf.js",
212 }
213```
214
215## Custom Webpack `extract-i18n`
216
217Enhanced `@angular-devkit/build-angular:extract-i18n` builder that leverages the custom webpack builder to get webpack configuration.
218
219The builder uses `customWebpackConfiguration` from `browserTarget` to run the extraction process while taking into account changes in your custom webpack config.
220
221Thus, if you use `@angular-builders/custom-webpack:extract-i18n` along with `@angular-builders/custom-webpack:browser`, `ng extract-i18n` will run with custom configuration provided in the latter.
222
223### Example
224
225`angular.json`:
226
227```js
228"architect": {
229 ...
230 "build": {
231 "builder": "@angular-builders/custom-webpack:browser",
232 "options": {
233 "customWebpackConfig": {
234 "path": "./extra-webpack.config.js"
235 },
236 ...
237 }
238 },
239 "extract-i18n": {
240 "builder": "@angular-builders/custom-webpack:extract-i18n",
241 "options": {
242 "browserTarget": "my-project:build"
243 }
244 }
245```
246
247In this example `extract-i18n` will use `custom-webpack:browser` builder, hence modified webpack config, when invoking the extract-i18n target.
248
249# Custom Webpack Config Object
250
251This option defines your custom webpack configuration. If not specified at all, plain Angular build will run.
252The following properties are available:
253
254- `path`: path to the extra webpack configuration, defaults to `webpack.config.js`.
255 The configuration file can export either an object or a function. If it is an object it shall contain only modifications and additions, you don't have to specify the whole webpack configuration.
256 Thus, if you'd like to add some options to `style-loader` (which already there because of default Angular configuration), you only have to specify this part of the loader:
257 ```js
258 {
259 test: /\.css$/,
260 use: [
261 {
262 loader: 'style-loader',
263 options: {
264 // `style-loader` options here...
265 }
266 }
267 ]
268 }
269 ```
270 The builder will take care of merging the delta with the existing configuration provided by Angular.
271 In more complicated cases you'd probably want to [use a function](#custom-webpack-config-function) instead of an object.
272- `mergeRules`: webpack config merge rules, as described [here](https://github.com/survivejs/webpack-merge#mergewithrules). Defaults to:
273
274 ```ts
275 {
276 module: {
277 rules: {
278 test: "match",
279 use: {
280 loader: "match",
281 options: "merge",
282 },
283 },
284 },
285 };
286 ```
287
288- `replaceDuplicatePlugins`: Defaults to `false`. If `true`, the plugins in custom webpack config will replace the corresponding plugins in default Angular CLI webpack configuration. If `false`, the [default behavior](#merging-plugins-configuration) will be applied.
289 **Note that if `true`, this option will override `mergeRules` for `plugins` field.**
290
291Webpack configuration can be also written in TypeScript. In this case, it is the application's `tsConfig` file which will be used by `tsnode` for `customWebpackConfig.ts` execution. Given the following example:
292
293```ts
294// extra-webpack.config.ts
295import { Configuration } from 'webpack';
296
297export default {
298 output: {
299 library: 'shop',
300 libraryTarget: 'umd',
301 },
302} as Configuration;
303```
304
305Do not forget to specify the correct path to this file:
306
307```js
308"customWebpackConfig": {
309 "path": "./extra-webpack.config.ts"
310},
311```
312
313## Merging Plugins Configuration:
314
315If in your custom configuration you specify a plugin that is already added by Angular CLI then by default the two instances will be merged.
316In case of the conflicts your configuration will override the existing one.
317Thus, if you'd like to modify an existing plugin configuration, all you have to do is specify the _delta_ you want to change.
318For example, if you'd like to allow cyclic dependencies that include dynamic imports you only have to specify this single entry:
319
320```js
321module.exports = {
322 plugins: [
323 new CircularDependencyPlugin({
324 allowAsyncCycles: true,
325 }),
326 ],
327};
328```
329
330Keep in mind though that if there are default values in the plugin's constructor, they would override the corresponding values in the existing instance. So these you have to set explicitly to the same values Angular sets.
331You can check out an example for plugins merge in the [unit tests](./src/webpack-config-merger.spec.ts) and in [this](https://github.com/just-jeb/angular-builders/issues/13) issue.
332
333## Custom Webpack Promisified Config
334
335Webpack config can also export a `Promise` object that resolves custom config. Given the following example:
336
337```js
338// extra-webpack.config.js
339const fs = require('fs');
340const util = require('util');
341const webpack = require('webpack');
342
343const readFile = util.promisify(fs.readFile);
344
345module.exports = readFile('./LICENSE', {
346 encoding: 'utf-8',
347}).then(license => ({
348 plugins: [new webpack.BannerPlugin(license)],
349}));
350```
351
352In this case, the behavior will be the same as when exporting a plain object — the resolved configuration will be merged with the base one.
353
354## Custom Webpack Config Function
355
356If `customWebpackConfig.path` file exports a function, the behaviour of the builder changes : no more automatic merge is applied, instead the function
357is called with the base Webpack configuration and must return the new configuration.
358
359The function is called with the base config the builder options and the target options as parameters.
360`TargetOptions` follows `target` definition from [this](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/architect/src/input-schema.json) schema
361and can be used to manipulate your build based on the build target.
362
363In this case, `mergeRules` and `replaceDuplicatePlugins` options have no effect.
364
365`custom-webpack.config.js` example :
366
367```js
368const webpack = require('webpack');
369const pkg = require('./package.json');
370
371module.exports = (config, options, targetOptions) => {
372 config.plugins.push(
373 new webpack.DefinePlugin({
374 APP_VERSION: JSON.stringify(pkg.version),
375 })
376 );
377
378 return config;
379};
380```
381
382Alternatively, using TypeScript:
383
384```ts
385import { CustomWebpackBrowserSchema, TargetOptions } from '@angular-builders/custom-webpack';
386import * as webpack from 'webpack';
387import * as pkg from './package.json';
388
389export default (
390 config: webpack.Configuration,
391 options: CustomWebpackBrowserSchema,
392 targetOptions: TargetOptions
393) => {
394 config.plugins.push(
395 new webpack.DefinePlugin({
396 APP_VERSION: JSON.stringify(pkg.version),
397 })
398 );
399
400 return config;
401};
402```
403
404It's also possible to export an asynchronous factory (factory that returns a `Promise` object). Let's look at the following example:
405
406```js
407// extra-webpack.config.js
408const axios = require('axios');
409const webpack = require('webpack');
410
411async function getPortalVersion() {
412 const response = await axios.get('http://portal.com/version');
413 return response.data.version;
414}
415
416module.exports = async config => {
417 const version = await getPortalVersion();
418
419 config.plugins.push(
420 new webpack.DefinePlugin({
421 APP_VERSION: JSON.stringify(version),
422 })
423 );
424
425 return config;
426};
427```
428
429# Index Transform
430
431Since Angular 8 `index.html` is not generated as part of the Webpack build. If you want to modify your `index.html` you should use `indexTransform` option.
432`indexTransform` is a path (relative to workspace root) to a `.js` or `.ts` file that exports transformation function for `index.html`.
433Function signature is as following:
434If `indexTransform` is written in TypeScript, it is the application's `tsConfig` file which will be use by `tsnode` for `indexTransform.ts` execution.
435
436```typescript
437(options: TargetOptions, indexHtmlContent: string) => string|Promise<string>;
438```
439
440or, in other words, the function receives target options and original `index.html` content (generated by Angular CLI) and returns a new content as `string` or `Promise`.
441`TargetOptions` follows `target` definition from [this](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/architect/src/input-schema.json) schema and looks like this:
442
443```typescript
444export interface Target {
445 configuration?: string;
446 project: string;
447 target: string;
448}
449```
450
451It is useful when you want to transform your `index.html` according to the build options.
452
453## Example
454
455`angular.json`:
456
457```js
458"architect": {
459 ...
460 "build": {
461 "builder": "@angular-builders/custom-webpack:browser"
462 "options": {
463 "indexTransform": "./index-html-transform.js"
464 ...
465 }
466```
467
468`index-html-transform.js`:
469
470```js
471module.exports = (targetOptions, indexHtml) => {
472 const i = indexHtml.indexOf('</body>');
473 const config = `<p>Configuration: ${targetOptions.configuration}</p>`;
474 return `${indexHtml.slice(0, i)}
475 ${config}
476 ${indexHtml.slice(i)}`;
477};
478```
479
480Alternatively, using TypeScript:
481
482```ts
483import { TargetOptions } from '@angular-builders/custom-webpack';
484
485export default (targetOptions: TargetOptions, indexHtml: string) => {
486 const i = indexHtml.indexOf('</body>');
487 const config = `<p>Configuration: ${targetOptions.configuration}</p>`;
488 return `${indexHtml.slice(0, i)}
489 ${config}
490 ${indexHtml.slice(i)}`;
491};
492```
493
494In the example we add a paragraph with build configuration to your `index.html`. It is a very simple example without any asynchronous code but you can also return a `Promise` from this function.
495
496Full example [here](../../examples/custom-webpack/full-cycle-app).
497
498# ES Modules (ESM) Support
499
500Custom Webpack builder fully supports ESM.
501
502- If your app has `"type": "module"` both `custom-webpack.js` and `index-transform.js` will be treated as ES modules, unless you change their file extension to `.cjs`. In that case they'll be treated as CommonJS Modules. [Example](../../examples/custom-webpack/sanity-app-esm).
503- For `"type": "commonjs"` (or unspecified type) both `custom-webpack.js` and `index-transform.js` will be treated as CommonJS modules unless you change their file extension to `.mjs`. In that case they'll be treated as ES Modules. [Example](../../examples/custom-webpack/sanity-app).
504- If you want to use TS config in ESM app, you must set the loader to `ts-node/esm` when running `ng build`. Also, in that case `tsconfig.json` for `ts-node` no longer defaults to `tsConfig` from the `browser` target - you have to specify it manually via environment variable. [Example](../../examples/custom-webpack/sanity-app-esm/package.json#L10).
505 _Note that tsconfig paths are not supported in TS configs within ESM apps. That is because [tsconfig-paths](https://github.com/dividab/tsconfig-paths) do not support ESM._
506
507# Further Reading
508
509- [Customizing Angular CLI build - an alternative to ng eject](https://medium.com/angular-in-depth/customizing-angular-cli-build-an-alternative-to-ng-eject-v2-c655768b48cc)