UNPKG

21.3 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# Table of Contents
8
9- [Usage](#usage)
10 - [For Example](#for-example)
11- [Builders](#builders)
12 - [Custom Webpack `browser`](#custom-webpack-browser)
13 - [Custom Webpack `dev-server`](#custom-webpack-dev-server)
14 - [Example](#example)
15 - [Custom Webpack `server`](#custom-webpack-server)
16 - [Custom Webpack `karma`](#custom-webpack-karma)
17 - [Custom Webpack `extract-i18n`](#custom-webpack-extract-i18n)
18 - [Example](#example-1)
19- [Custom Webpack Config Object](#custom-webpack-config-object)
20 - [Merging Plugins Configuration:](#merging-plugins-configuration-)
21 - [Custom Webpack Promisified Config](#custom-webpack-promisified-config)
22 - [Custom Webpack Config Function](#custom-webpack-config-function)
23- [Index Transform](#index-transform)
24 - [Example](#example-2)
25- [ES Modules (ESM) Support](#es-modules-esm-support)
26- [Verbose Logging](#verbose-logging)
27- [Further Reading](#further-reading)
28
29# This documentation is for the latest major version only
30
31## Previous versions
32
33<details>
34 <summary>Click to expand</summary>
35
36- [Version 17](https://github.com/just-jeb/angular-builders/blob/17.x.x/packages/custom-webpack/README.md)
37- [Version 16](https://github.com/just-jeb/angular-builders/blob/16.x.x/packages/custom-webpack/README.md)
38- [Version 15](https://github.com/just-jeb/angular-builders/blob/15.x.x/packages/custom-webpack/README.md)
39- [Version 14](https://github.com/just-jeb/angular-builders/blob/14.x.x/packages/custom-webpack/README.md)
40- [Version 13](https://github.com/just-jeb/angular-builders/blob/13.x.x/packages/custom-webpack/README.md)
41- [Version 12](https://github.com/just-jeb/angular-builders/blob/12.x.x/packages/custom-webpack/README.md)
42- [Version 11](https://github.com/just-jeb/angular-builders/blob/11.x.x/packages/custom-webpack/README.md)
43- [Version 10](https://github.com/just-jeb/angular-builders/blob/10.x.x/packages/custom-webpack/README.md)
44- [Version 9](https://github.com/just-jeb/angular-builders/blob/9.x.x/packages/custom-webpack/README.md)
45- [Version 8](https://github.com/just-jeb/angular-builders/blob/8.x.x/packages/custom-webpack/README.md)
46- [Version 7](https://github.com/just-jeb/angular-builders/blob/7.x.x/packages/custom-webpack/README.md)
47
48</details>
49
50## [Quick guide](https://www.justjeb.com/post/customizing-angular-cli-build)
51
52## Prerequisites:
53
54- [Angular CLI 18](https://www.npmjs.com/package/@angular/cli)
55
56# Usage
57
581. `npm i -D @angular-builders/custom-webpack`
592. In your `angular.json`:
60 ```js
61 "projects": {
62 ...
63 "[project]": {
64 ...
65 "architect": {
66 ...
67 "[architect-target]": {
68 "builder": "@angular-builders/custom-webpack:[browser|server|karma|dev-server|extract-i18n]",
69 "options": {
70 ...
71 }
72 ```
73 Where:
74 - [project] is the name of the project to which you want to add the builder
75 - [architect-target] is the name of build target you want to run (build, serve, test etc. or any custom target)
76 - [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)
773. If `[architect-target]` is not one of the predefined targets (like build, serve, test etc.) then run it like this:
78 `ng run [project]:[architect-target]`
79 If it is one of the predefined targets, you can run it with `ng [architect-target]`
80
81## For Example
82
83- angular.json:
84 ```js
85 "projects": {
86 ...
87 "example-app": {
88 ...
89 "architect": {
90 ...
91 "build": {
92 "builder": "@angular-builders/custom-webpack:browser",
93 "options": {
94 ...
95 }
96 ```
97- Run the build: `ng build`
98
99# Builders
100
101- [@angular-builders/custom-webpack:browser](#Custom-webpack-browser)
102- [@angular-builders/custom-webpack:server](#Custom-webpack-server)
103- [@angular-builders/custom-webpack:karma](#Custom-webpack-Karma)
104- [@angular-builders/custom-webpack:dev-server](#Custom-webpack-dev-server)
105- [@angular-builders/custom-webpack:extract-i18n](#Custom-webpack-extract-i18n)
106
107## Custom Webpack `browser`
108
109Extended `@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.
110The 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.
111
112Builder options:
113
114- All the `@angular-devkit/build-angular:browser` options
115- `customWebpackConfig`: [see below](#custom-webpack-config-object)
116- `indexTransform`: [see below](#index-transform)
117
118`angular.json` Example:
119
120```js
121"architect": {
122 ...
123 "build": {
124 "builder": "@angular-builders/custom-webpack:browser",
125 "options": {
126 "customWebpackConfig": {
127 "path": "./extra-webpack.config.js",
128 "mergeRules": {
129 "externals": "replace"
130 }
131 },
132 "indexTransform": "./index-html-transform.js",
133 "outputPath": "dist/my-cool-client",
134 "index": "src/index.html",
135 "main": "src/main.ts",
136 "polyfills": ["zone.js"],
137 "tsConfig": "src/tsconfig.app.json"
138 }
139```
140
141In 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`.
142
143## Custom Webpack `dev-server`
144
145Enhanced `@angular-devkit/build-angular:dev-server` builder that leverages the custom webpack builder to get webpack configuration.
146
147Unlike 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.
148
149Thus, 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.
150
151### Example
152
153`angular.json`:
154
155```js
156"architect": {
157 ...
158 "build": {
159 "builder": "@angular-builders/custom-webpack:browser",
160 "options": {
161 "customWebpackConfig": {
162 "path": "./extra-webpack.config.js"
163 },
164 ...
165 }
166 },
167 "serve": {
168 "builder": "@angular-builders/custom-webpack:dev-server",
169 "options": {
170 "browserTarget": "my-project:build"
171 }
172 }
173```
174
175In this example `dev-server` will use `custom-webpack:browser` builder, hence modified webpack config, when invoking the serve target.
176
177## Custom Webpack `server`
178
179Extended `@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.
180The builder will run the same build as `@angular-devkit/build-angular:server` does with extra parameters that are specified in the provided webpack configuration.
181
182Builder options:
183
184- All the `@angular-devkit/build-angular:server` options
185- `customWebpackConfig`: [see below](#custom-webpack-config-object)
186
187`angular.json` Example:
188
189```js
190"architect": {
191 ...
192 "build": {
193 "builder": "@angular-builders/custom-webpack:server",
194 "options": {
195 "customWebpackConfig": {
196 "path": "./extra-webpack.config.js",
197 "mergeRules": {
198 "module": {
199 "rules": "prepend"
200 }
201 },
202 "replaceDuplicatePlugins": true
203 },
204 "outputPath": "dist/my-cool-server",
205 "main": "src/main.server.ts",
206 "tsConfig": "src/tsconfig.server.json"
207 }
208```
209
210In 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.
211Since 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.
212
213## Custom Webpack `karma`
214
215Extended `@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.
216The builder will run the same build as `@angular-devkit/build-angular:karma` does with extra parameters that are specified in the provided webpack configuration.
217
218Builder options:
219
220- All the `@angular-devkit/build-angular:karma` options
221- `customWebpackConfig`: [see below](#custom-webpack-config-object)
222
223`angular.json` Example:
224
225```js
226"architect": {
227 ...
228 "test": {
229 "builder": "@angular-builders/custom-webpack:karma",
230 "options": {
231 "customWebpackConfig": {
232 "path": "./extra-webpack.config.js"
233 },
234 "main": "src/test.ts",
235 "polyfills": ["zone.js"],
236 "tsConfig": "src/tsconfig.spec.json",
237 "karmaConfig": "src/karma.conf.js",
238 }
239```
240
241## Custom Webpack `extract-i18n`
242
243Enhanced `@angular-devkit/build-angular:extract-i18n` builder that leverages the custom webpack builder to get webpack configuration.
244
245The builder uses `customWebpackConfiguration` from `browserTarget` to run the extraction process while taking into account changes in your custom webpack config.
246
247Thus, 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.
248
249### Example
250
251`angular.json`:
252
253```js
254"architect": {
255 ...
256 "build": {
257 "builder": "@angular-builders/custom-webpack:browser",
258 "options": {
259 "customWebpackConfig": {
260 "path": "./extra-webpack.config.js"
261 },
262 ...
263 }
264 },
265 "extract-i18n": {
266 "builder": "@angular-builders/custom-webpack:extract-i18n",
267 "options": {
268 "browserTarget": "my-project:build"
269 }
270 }
271```
272
273In this example `extract-i18n` will use `custom-webpack:browser` builder, hence modified webpack config, when invoking the extract-i18n target.
274
275# Custom Webpack Config Object
276
277This option defines your custom webpack configuration. If not specified at all, plain Angular build will run.
278The following properties are available:
279
280- `path`: path to the extra webpack configuration, defaults to `webpack.config.js`.
281 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.
282 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:
283 ```js
284 {
285 test: /\.css$/,
286 use: [
287 {
288 loader: 'style-loader',
289 options: {
290 // `style-loader` options here...
291 }
292 }
293 ]
294 }
295 ```
296 The builder will take care of merging the delta with the existing configuration provided by Angular.
297 In more complicated cases you'd probably want to [use a function](#custom-webpack-config-function) instead of an object.
298- `mergeRules`: webpack config merge rules, as described [here](https://github.com/survivejs/webpack-merge#mergewithrules). Defaults to:
299
300 ```ts
301 {
302 module: {
303 rules: {
304 test: "match",
305 use: {
306 loader: "match",
307 options: "merge",
308 },
309 },
310 },
311 };
312 ```
313
314- `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.
315 **Note that if `true`, this option will override `mergeRules` for `plugins` field.**
316
317Webpack 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:
318
319```ts
320// extra-webpack.config.ts
321import { Configuration } from 'webpack';
322
323export default {
324 output: {
325 library: 'shop',
326 libraryTarget: 'umd',
327 },
328} as Configuration;
329```
330
331Do not forget to specify the correct path to this file:
332
333```js
334"customWebpackConfig": {
335 "path": "./extra-webpack.config.ts"
336},
337```
338
339## Merging Plugins Configuration:
340
341If in your custom configuration you specify a plugin that is already added by Angular CLI then by default the two instances will be merged.
342In case of the conflicts your configuration will override the existing one.
343Thus, if you'd like to modify an existing plugin configuration, all you have to do is specify the _delta_ you want to change.
344For example, if you'd like to allow cyclic dependencies that include dynamic imports you only have to specify this single entry:
345
346```js
347module.exports = {
348 plugins: [
349 new CircularDependencyPlugin({
350 allowAsyncCycles: true,
351 }),
352 ],
353};
354```
355
356Keep 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.
357You 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.
358
359## Custom Webpack Promisified Config
360
361Webpack config can also export a `Promise` object that resolves custom config. Given the following example:
362
363```js
364// extra-webpack.config.js
365const fs = require('fs');
366const util = require('util');
367const webpack = require('webpack');
368
369const readFile = util.promisify(fs.readFile);
370
371module.exports = readFile('./LICENSE', {
372 encoding: 'utf-8',
373}).then(license => ({
374 plugins: [new webpack.BannerPlugin(license)],
375}));
376```
377
378In this case, the behavior will be the same as when exporting a plain object — the resolved configuration will be merged with the base one.
379
380## Custom Webpack Config Function
381
382If `customWebpackConfig.path` file exports a function, the behaviour of the builder changes : no more automatic merge is applied, instead the function
383is called with the base Webpack configuration and must return the new configuration.
384
385The function is called with the base config, the builder options, and the target options as parameters.
386`TargetOptions` follows `target` definition from [this](https://github.com/angular/angular-cli/blob/master/packages/angular_devkit/architect/src/input-schema.json) schema
387and can be used to manipulate your build based on the build target.
388
389In this case, `mergeRules` and `replaceDuplicatePlugins` options have no effect.
390
391`custom-webpack.config.js` example :
392
393```js
394const webpack = require('webpack');
395const pkg = require('./package.json');
396
397module.exports = (config, options, targetOptions) => {
398 config.plugins.push(
399 new webpack.DefinePlugin({
400 APP_VERSION: JSON.stringify(pkg.version),
401 })
402 );
403
404 return config;
405};
406```
407
408Alternatively, using TypeScript:
409
410```ts
411import { CustomWebpackBrowserSchema, TargetOptions } from '@angular-builders/custom-webpack';
412import * as webpack from 'webpack';
413import * as pkg from './package.json';
414
415export default (
416 config: webpack.Configuration,
417 options: CustomWebpackBrowserSchema,
418 targetOptions: TargetOptions
419) => {
420 config.plugins.push(
421 new webpack.DefinePlugin({
422 APP_VERSION: JSON.stringify(pkg.version),
423 })
424 );
425
426 return config;
427};
428```
429
430It's also possible to export an asynchronous factory (factory that returns a `Promise` object). Let's look at the following example:
431
432```js
433// extra-webpack.config.js
434const axios = require('axios');
435const webpack = require('webpack');
436
437async function getPortalVersion() {
438 const response = await axios.get('http://portal.com/version');
439 return response.data.version;
440}
441
442module.exports = async config => {
443 const version = await getPortalVersion();
444
445 config.plugins.push(
446 new webpack.DefinePlugin({
447 APP_VERSION: JSON.stringify(version),
448 })
449 );
450
451 return config;
452};
453```
454
455# Index Transform
456
457Since 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.
458`indexTransform` is a path (relative to workspace root) to a `.js` or `.ts` file that exports transformation function for `index.html`.
459Function signature is as following:
460If `indexTransform` is written in TypeScript, it is the application's `tsConfig` file which will be use by `tsnode` for `indexTransform.ts` execution.
461
462```typescript
463(options: TargetOptions, indexHtmlContent: string) => string | Promise<string>;
464```
465
466or, 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`.
467`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:
468
469```typescript
470export interface Target {
471 configuration?: string;
472 project: string;
473 target: string;
474}
475```
476
477It is useful when you want to transform your `index.html` according to the build options.
478
479## Example
480
481`angular.json`:
482
483```js
484"architect": {
485 ...
486 "build": {
487 "builder": "@angular-builders/custom-webpack:browser",
488 "options": {
489 "indexTransform": "./index-html-transform.js"
490 ...
491 }
492```
493
494`index-html-transform.js`:
495
496```js
497module.exports = (targetOptions, indexHtml) => {
498 const i = indexHtml.indexOf('</body>');
499 const config = `<p>Configuration: ${targetOptions.configuration}</p>`;
500 return `${indexHtml.slice(0, i)}
501 ${config}
502 ${indexHtml.slice(i)}`;
503};
504```
505
506Alternatively, using TypeScript:
507
508```ts
509import { TargetOptions } from '@angular-builders/custom-webpack';
510
511export default (targetOptions: TargetOptions, indexHtml: string) => {
512 const i = indexHtml.indexOf('</body>');
513 const config = `<p>Configuration: ${targetOptions.configuration}</p>`;
514 return `${indexHtml.slice(0, i)}
515 ${config}
516 ${indexHtml.slice(i)}`;
517};
518```
519
520In 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.
521
522Full example [here](../../examples/custom-webpack/full-cycle-app).
523
524# ES Modules (ESM) Support
525
526Custom Webpack builder fully supports ESM.
527
528- 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).
529- 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).
530- 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).
531 _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._
532
533# Verbose Logging
534
535Custom Webpack allows enabling verbose logging for configuration properties. This can be achieved by providing the `verbose` object in builder options. Given the following example:
536
537```json
538{
539 "builder": "@angular-builders/custom-webpack:browser",
540 "options": {
541 "customWebpackConfig": {
542 "verbose": {
543 "properties": ["entry"]
544 }
545 }
546 }
547}
548```
549
550`properties` is an array of strings that supports individual or deeply nested keys (`output.publicPath` and `plugins[0]` are valid keys). The number of times to recurse the object while formatting before it's logged is controlled by the `serializationDepth` property:
551
552```json
553{
554 "builder": "@angular-builders/custom-webpack:browser",
555 "options": {
556 "customWebpackConfig": {
557 "verbose": {
558 "properties": ["plugins[0]"],
559 "serializationDepth": 5
560 }
561 }
562 }
563}
564```
565
566# Further Reading
567
568- [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)