UNPKG

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