@angular-devkit/build-angular 12.0.0 → 12.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/package.json +21 -21
  2. package/src/browser/index.js +1 -3
  3. package/src/browser/schema.d.ts +1 -1
  4. package/src/browser/schema.json +1 -1
  5. package/src/dev-server/index.js +38 -37
  6. package/src/dev-server/schema.d.ts +2 -1
  7. package/src/dev-server/schema.json +1 -1
  8. package/src/extract-i18n/index.js +6 -3
  9. package/src/karma/index.js +1 -0
  10. package/src/karma/schema.d.ts +1 -1
  11. package/src/karma/schema.json +1 -1
  12. package/src/karma/tests/setup.d.ts +18 -0
  13. package/src/karma/tests/setup.js +29 -0
  14. package/src/sass/sass-service.d.ts +50 -0
  15. package/src/sass/sass-service.js +185 -0
  16. package/src/sass/worker.d.ts +8 -0
  17. package/src/sass/worker.js +49 -0
  18. package/src/server/schema.d.ts +1 -1
  19. package/src/server/schema.json +1 -1
  20. package/src/utils/action-executor.js +2 -2
  21. package/src/utils/environment-options.d.ts +1 -0
  22. package/src/utils/environment-options.js +12 -1
  23. package/src/utils/index.d.ts +0 -1
  24. package/src/utils/index.js +0 -1
  25. package/src/utils/service-worker.js +1 -1
  26. package/src/utils/spinner.d.ts +2 -0
  27. package/src/utils/spinner.js +14 -0
  28. package/src/webpack/configs/common.js +36 -44
  29. package/src/webpack/configs/server.js +8 -0
  30. package/src/webpack/configs/stats.d.ts +6 -14
  31. package/src/webpack/configs/stats.js +5 -8
  32. package/src/webpack/configs/styles.js +54 -7
  33. package/src/webpack/plugins/index-html-webpack-plugin.js +1 -1
  34. package/src/webpack/plugins/index.d.ts +0 -1
  35. package/src/webpack/plugins/index.js +1 -3
  36. package/src/webpack/plugins/karma/karma-context.html +1 -1
  37. package/src/webpack/plugins/karma/karma-debug.html +1 -1
  38. package/src/webpack/plugins/karma/karma.js +1 -1
  39. package/src/webpack/plugins/postcss-cli-resources.js +1 -1
  40. package/src/webpack/utils/async-chunks.js +11 -11
  41. package/src/webpack/utils/stats.d.ts +2 -2
  42. package/src/webpack/utils/stats.js +16 -9
  43. package/src/utils/workers.d.ts +0 -22
  44. package/src/utils/workers.js +0 -26
  45. package/src/webpack/plugins/optimize-css-webpack-plugin.d.ts +0 -17
  46. package/src/webpack/plugins/optimize-css-webpack-plugin.js +0 -106
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
2
  "name": "@angular-devkit/build-angular",
3
- "version": "12.0.0",
3
+ "version": "12.0.4",
4
4
  "description": "Angular Webpack Build Facade",
5
5
  "main": "src/index.js",
6
6
  "typings": "src/index.d.ts",
7
7
  "builders": "builders.json",
8
8
  "dependencies": {
9
- "@angular-devkit/architect": "0.1200.0",
10
- "@angular-devkit/build-optimizer": "0.1200.0",
11
- "@angular-devkit/build-webpack": "0.1200.0",
12
- "@angular-devkit/core": "12.0.0",
13
- "@babel/core": "7.14.0",
14
- "@babel/generator": "7.14.1",
9
+ "@angular-devkit/architect": "0.1200.4",
10
+ "@angular-devkit/build-optimizer": "0.1200.4",
11
+ "@angular-devkit/build-webpack": "0.1200.4",
12
+ "@angular-devkit/core": "12.0.4",
13
+ "@babel/core": "7.14.3",
14
+ "@babel/generator": "7.14.3",
15
15
  "@babel/plugin-transform-async-to-generator": "7.13.0",
16
- "@babel/plugin-transform-runtime": "7.13.15",
17
- "@babel/preset-env": "7.14.1",
16
+ "@babel/plugin-transform-runtime": "7.14.3",
17
+ "@babel/preset-env": "7.14.2",
18
18
  "@babel/runtime": "7.14.0",
19
19
  "@babel/template": "7.12.13",
20
20
  "@discoveryjs/json-ext": "0.5.2",
21
21
  "@jsdevtools/coverage-istanbul-loader": "3.0.5",
22
- "@ngtools/webpack": "12.0.0",
22
+ "@ngtools/webpack": "12.0.4",
23
23
  "ansi-colors": "4.1.1",
24
24
  "babel-loader": "8.2.2",
25
25
  "browserslist": "^4.9.1",
@@ -30,7 +30,7 @@
30
30
  "core-js": "3.12.0",
31
31
  "critters": "0.0.10",
32
32
  "css-loader": "5.2.4",
33
- "cssnano": "5.0.2",
33
+ "css-minimizer-webpack-plugin": "3.0.0",
34
34
  "find-cache-dir": "3.3.1",
35
35
  "glob": "7.1.7",
36
36
  "https-proxy-agent": "5.0.0",
@@ -39,14 +39,14 @@
39
39
  "karma-source-map-support": "1.4.0",
40
40
  "less": "4.1.1",
41
41
  "less-loader": "8.1.1",
42
- "license-webpack-plugin": "2.3.17",
42
+ "license-webpack-plugin": "2.3.19",
43
43
  "loader-utils": "2.0.0",
44
44
  "mini-css-extract-plugin": "1.5.1",
45
45
  "minimatch": "3.0.4",
46
- "open": "8.0.7",
46
+ "open": "8.0.2",
47
47
  "ora": "5.4.0",
48
48
  "parse5-html-rewriting-stream": "6.0.1",
49
- "postcss": "8.2.14",
49
+ "postcss": "8.3.0",
50
50
  "postcss-import": "14.0.1",
51
51
  "postcss-loader": "5.2.0",
52
52
  "postcss-preset-env": "6.7.0",
@@ -65,21 +65,21 @@
65
65
  "stylus": "0.54.8",
66
66
  "stylus-loader": "5.0.0",
67
67
  "terser": "5.7.0",
68
- "terser-webpack-plugin": "4.2.3",
68
+ "terser-webpack-plugin": "5.1.2",
69
69
  "text-table": "0.2.0",
70
70
  "tree-kill": "1.2.2",
71
- "webpack": "5.36.2",
71
+ "webpack": "5.38.1",
72
72
  "webpack-dev-middleware": "4.1.0",
73
73
  "webpack-dev-server": "3.11.2",
74
74
  "webpack-merge": "5.7.3",
75
75
  "webpack-subresource-integrity": "1.5.2"
76
76
  },
77
77
  "peerDependencies": {
78
- "@angular/compiler-cli": "^12.0.0-next",
79
- "@angular/localize": "^12.0.0-next",
80
- "@angular/service-worker": "^12.0.0-next",
78
+ "@angular/compiler-cli": "^12.0.0",
79
+ "@angular/localize": "^12.0.0",
80
+ "@angular/service-worker": "^12.0.0",
81
81
  "karma": "^6.3.0",
82
- "ng-packagr": "^12.0.0-next",
82
+ "ng-packagr": "^12.0.0",
83
83
  "protractor": "^7.0.0",
84
84
  "tailwindcss": "^2.0.0",
85
85
  "tslint": "^6.1.0",
@@ -120,7 +120,7 @@
120
120
  "url": "https://github.com/angular/angular-cli.git"
121
121
  },
122
122
  "engines": {
123
- "node": "^12.14.1 || ^14.0.0",
123
+ "node": "^12.14.1 || >=14.0.0",
124
124
  "npm": "^6.11.0 || ^7.5.6",
125
125
  "yarn": ">= 1.13.0"
126
126
  },
@@ -69,7 +69,7 @@ async function initialize(options, context, differentialLoadingNeeded, webpackCo
69
69
  const originalOutputPath = options.outputPath;
70
70
  // Assets are processed directly by the builder except when watching
71
71
  const adjustedOptions = options.watch ? options : { ...options, assets: [] };
72
- const { config, projectRoot, projectSourceRoot, i18n, } = await webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext(adjustedOptions, context, (wco) => [
72
+ const { config, projectRoot, projectSourceRoot, i18n } = await webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext(adjustedOptions, context, (wco) => [
73
73
  configs_1.getCommonConfig(wco),
74
74
  configs_1.getBrowserConfig(wco),
75
75
  configs_1.getStylesConfig(wco),
@@ -544,12 +544,10 @@ function assertNever(input) {
544
544
  throw new Error(`Unexpected call to assertNever() with input: ${JSON.stringify(input, null /* replacer */, 4 /* tabSize */)}`);
545
545
  }
546
546
  function generateBundleInfoStats(bundle, chunk, chunkType) {
547
- var _a;
548
547
  return stats_1.generateBundleStats({
549
548
  size: bundle.size,
550
549
  files: bundle.map ? [bundle.filename, bundle.map.filename] : [bundle.filename],
551
550
  names: chunk === null || chunk === void 0 ? void 0 : chunk.names,
552
- entry: !!((_a = chunk === null || chunk === void 0 ? void 0 : chunk.names) === null || _a === void 0 ? void 0 : _a.includes('runtime')),
553
551
  initial: !!(chunk === null || chunk === void 0 ? void 0 : chunk.initial),
554
552
  rendered: true,
555
553
  chunkType,
@@ -407,7 +407,7 @@ export interface SourceMapClass {
407
407
  */
408
408
  export interface StylePreprocessorOptions {
409
409
  /**
410
- * Paths to include. Paths will be resolved to project root.
410
+ * Paths to include. Paths will be resolved to workspace root.
411
411
  */
412
412
  includePaths?: string[];
413
413
  }
@@ -51,7 +51,7 @@
51
51
  "type": "object",
52
52
  "properties": {
53
53
  "includePaths": {
54
- "description": "Paths to include. Paths will be resolved to project root.",
54
+ "description": "Paths to include. Paths will be resolved to workspace root.",
55
55
  "type": "array",
56
56
  "items": {
57
57
  "type": "string"
@@ -84,11 +84,47 @@ function serveWebpackBrowser(options, context, transforms = {}) {
84
84
  overrides.outputHashing = schema_1.OutputHashing.None;
85
85
  logger.warn(`Warning: 'outputHashing' option is disabled when using the dev-server.`);
86
86
  }
87
+ if (options.hmr) {
88
+ logger.warn(core_1.tags.stripIndents `NOTICE: Hot Module Replacement (HMR) is enabled for the dev server.
89
+ See https://webpack.js.org/guides/hot-module-replacement for information on working with HMR for Webpack.`);
90
+ }
91
+ if (!options.disableHostCheck &&
92
+ options.host &&
93
+ !/^127\.\d+\.\d+\.\d+/g.test(options.host) &&
94
+ options.host !== 'localhost') {
95
+ logger.warn(core_1.tags.stripIndent `
96
+ Warning: This is a simple server for use in testing or debugging Angular applications
97
+ locally. It hasn't been reviewed for security issues.
98
+
99
+ Binding this server to an open connection can result in compromising your application or
100
+ computer. Using a different host than the one passed to the "--host" flag might result in
101
+ websocket connection issues. You might need to use "--disableHostCheck" if that's the
102
+ case.
103
+ `);
104
+ }
105
+ if (options.disableHostCheck) {
106
+ logger.warn(core_1.tags.oneLine `
107
+ Warning: Running a server with --disable-host-check is a security risk.
108
+ See https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
109
+ for more information.
110
+ `);
111
+ }
87
112
  // Webpack's live reload functionality adds the `strip-ansi` package which is commonJS
88
113
  (_b = rawBrowserOptions.allowedCommonJsDependencies) !== null && _b !== void 0 ? _b : (rawBrowserOptions.allowedCommonJsDependencies = []);
89
114
  rawBrowserOptions.allowedCommonJsDependencies.push('strip-ansi');
90
115
  const browserName = await context.getBuilderNameForTarget(browserTarget);
91
116
  const browserOptions = (await context.validateOptions({ ...rawBrowserOptions, ...overrides }, browserName));
117
+ const { styles, scripts } = utils_1.normalizeOptimization(browserOptions.optimization);
118
+ if (scripts || styles.minify) {
119
+ logger.error(core_1.tags.stripIndents `
120
+ ****************************************************************************************
121
+ This is a simple server for use in testing or debugging Angular applications locally.
122
+ It hasn't been reviewed for security issues.
123
+
124
+ DON'T USE IT FOR PRODUCTION!
125
+ ****************************************************************************************
126
+ `);
127
+ }
92
128
  const { config, projectRoot, i18n } = await webpack_browser_config_1.generateI18nBrowserWebpackConfigFromContext(browserOptions, context, (wco) => [
93
129
  configs_1.getDevServerConfig(wco),
94
130
  configs_1.getCommonConfig(wco),
@@ -129,30 +165,6 @@ function serveWebpackBrowser(options, context, transforms = {}) {
129
165
  }
130
166
  }
131
167
  }
132
- if (options.hmr) {
133
- logger.warn(core_1.tags.stripIndents `NOTICE: Hot Module Replacement (HMR) is enabled for the dev server.
134
- See https://webpack.js.org/guides/hot-module-replacement for information on working with HMR for Webpack.`);
135
- }
136
- if (options.host &&
137
- !/^127\.\d+\.\d+\.\d+/g.test(options.host) &&
138
- options.host !== 'localhost') {
139
- logger.warn(core_1.tags.stripIndent `
140
- Warning: This is a simple server for use in testing or debugging Angular applications
141
- locally. It hasn't been reviewed for security issues.
142
-
143
- Binding this server to an open connection can result in compromising your application or
144
- computer. Using a different host than the one passed to the "--host" flag might result in
145
- websocket connection issues. You might need to use "--disableHostCheck" if that's the
146
- case.
147
- `);
148
- }
149
- if (options.disableHostCheck) {
150
- logger.warn(core_1.tags.oneLine `
151
- Warning: Running a server with --disable-host-check is a security risk.
152
- See https://medium.com/webpack/webpack-dev-server-middleware-security-issues-1489d950874a
153
- for more information.
154
- `);
155
- }
156
168
  let locale;
157
169
  if (i18n.shouldInline) {
158
170
  // Dev-server only supports one locale
@@ -185,7 +197,6 @@ function serveWebpackBrowser(options, context, transforms = {}) {
185
197
  };
186
198
  }
187
199
  return rxjs_1.from(setup()).pipe(operators_1.switchMap(({ browserOptions, webpackConfig, projectRoot, locale }) => {
188
- const normalizedOptimization = utils_1.normalizeOptimization(browserOptions.optimization);
189
200
  if (browserOptions.index) {
190
201
  const { scripts = [], styles = [], baseHref, tsConfig } = browserOptions;
191
202
  const { options: compilerOptions } = read_tsconfig_1.readTsconfig(tsConfig, workspaceRoot);
@@ -206,24 +217,14 @@ function serveWebpackBrowser(options, context, transforms = {}) {
206
217
  deployUrl: browserOptions.deployUrl,
207
218
  sri: browserOptions.subresourceIntegrity,
208
219
  postTransform: transforms.indexHtml,
209
- optimization: normalizedOptimization,
220
+ optimization: utils_1.normalizeOptimization(browserOptions.optimization),
210
221
  WOFFSupportNeeded: !buildBrowserFeatures.isFeatureSupported('woff2'),
211
222
  crossOrigin: browserOptions.crossOrigin,
212
223
  lang: locale,
213
224
  }));
214
225
  }
215
- if (normalizedOptimization.scripts || normalizedOptimization.styles.minify) {
216
- logger.error(core_1.tags.stripIndents `
217
- ****************************************************************************************
218
- This is a simple server for use in testing or debugging Angular applications locally.
219
- It hasn't been reviewed for security issues.
220
-
221
- DON'T USE IT FOR PRODUCTION!
222
- ****************************************************************************************
223
- `);
224
- }
225
226
  return build_webpack_1.runWebpackDevServer(webpackConfig, context, {
226
- logging: transforms.logging || stats_1.createWebpackLoggingCallback(!!options.verbose, logger),
227
+ logging: transforms.logging || stats_1.createWebpackLoggingCallback(browserOptions, logger),
227
228
  webpackFactory: require('webpack'),
228
229
  webpackDevServerFactory: require('webpack-dev-server'),
229
230
  }).pipe(operators_1.concatMap(async (buildEvent, index) => {
@@ -84,7 +84,8 @@ export interface Schema {
84
84
  */
85
85
  progress?: boolean;
86
86
  /**
87
- * Proxy configuration file.
87
+ * Proxy configuration file. For more information, see
88
+ * https://angular.io/guide/build#proxying-to-a-backend-server.
88
89
  */
89
90
  proxyConfig?: string;
90
91
  /**
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "proxyConfig": {
23
23
  "type": "string",
24
- "description": "Proxy configuration file."
24
+ "description": "Proxy configuration file. For more information, see https://angular.io/guide/build#proxying-to-a-backend-server."
25
25
  },
26
26
  "ssl": {
27
27
  "type": "boolean",
@@ -118,7 +118,7 @@ async function execute(options, context, transforms) {
118
118
  const i18n = i18n_options_1.createI18nOptions(metadata);
119
119
  let useLegacyIds = true;
120
120
  const ivyMessages = [];
121
- const { config, projectRoot } = await webpack_browser_config_1.generateBrowserWebpackConfigFromContext({
121
+ const builderOptions = {
122
122
  ...browserOptions,
123
123
  optimization: false,
124
124
  sourceMap: {
@@ -138,7 +138,8 @@ async function execute(options, context, transforms) {
138
138
  subresourceIntegrity: false,
139
139
  outputHashing: schema_1.OutputHashing.None,
140
140
  namedChunks: true,
141
- }, context, (wco) => {
141
+ };
142
+ const { config, projectRoot } = await webpack_browser_config_1.generateBrowserWebpackConfigFromContext(builderOptions, context, (wco) => {
142
143
  var _a;
143
144
  if (wco.tsConfig.options.enableIvy === false) {
144
145
  context.logger.warn('Ivy extraction enabled but application is not Ivy enabled. Extraction may fail.');
@@ -150,6 +151,7 @@ async function execute(options, context, transforms) {
150
151
  configs_1.getCommonConfig(wco),
151
152
  configs_1.getBrowserConfig(wco),
152
153
  configs_1.getTypeScriptConfig(wco),
154
+ configs_1.getWorkerConfig(wco),
153
155
  configs_1.getStatsConfig(wco),
154
156
  ];
155
157
  // Add Ivy application file extractor support
@@ -170,6 +172,7 @@ async function execute(options, context, transforms) {
170
172
  partials.push({
171
173
  plugins: [
172
174
  new webpack.NormalModuleReplacementPlugin(/\.(css|scss|sass|styl|less)$/, path.join(__dirname, 'empty-export-default.js')),
175
+ new webpack.NormalModuleReplacementPlugin(/^angular-resource:style,/, path.join(__dirname, 'empty-export-default.js')),
173
176
  ],
174
177
  });
175
178
  return partials;
@@ -185,7 +188,7 @@ async function execute(options, context, transforms) {
185
188
  };
186
189
  }
187
190
  const webpackResult = await build_webpack_1.runWebpack((await ((_a = transforms === null || transforms === void 0 ? void 0 : transforms.webpackConfiguration) === null || _a === void 0 ? void 0 : _a.call(transforms, config))) || config, context, {
188
- logging: stats_1.createWebpackLoggingCallback(false, context.logger),
191
+ logging: stats_1.createWebpackLoggingCallback(builderOptions, context.logger),
189
192
  webpackFactory: webpack,
190
193
  }).toPromise();
191
194
  // Set the outputPath to the extraction output location for downstream consumers
@@ -40,6 +40,7 @@ async function initialize(options, context, webpackConfigurationTransformer) {
40
40
  // https://github.com/webpack/webpack-dev-middleware/blob/698c9ae5e9bb9a013985add6189ff21c1a1ec185/src/index.js#L65
41
41
  // https://github.com/webpack/webpack/blob/cde1b73e12eb8a77eb9ba42e7920c9ec5d29c2c9/lib/Compiler.js#L379-L388
42
42
  watch: true,
43
+ extractCss: true,
43
44
  }, context, (wco) => [
44
45
  configs_1.getCommonConfig(wco),
45
46
  configs_1.getStylesConfig(wco),
@@ -167,7 +167,7 @@ export interface SourceMapClass {
167
167
  */
168
168
  export interface StylePreprocessorOptions {
169
169
  /**
170
- * Paths to include. Paths will be resolved to project root.
170
+ * Paths to include. Paths will be resolved to workspace root.
171
171
  */
172
172
  includePaths?: string[];
173
173
  }
@@ -55,7 +55,7 @@
55
55
  "type": "object",
56
56
  "properties": {
57
57
  "includePaths": {
58
- "description": "Paths to include. Paths will be resolved to project root.",
58
+ "description": "Paths to include. Paths will be resolved to workspace root.",
59
59
  "type": "array",
60
60
  "items": {
61
61
  "type": "string"
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ import { Schema } from '../schema';
9
+ export { describeBuilder } from '../../testing';
10
+ export declare const KARMA_BUILDER_INFO: Readonly<{
11
+ name: string;
12
+ schemaPath: string;
13
+ }>;
14
+ /**
15
+ * Contains all required karma builder fields.
16
+ * Also disables progress reporting to minimize logging output.
17
+ */
18
+ export declare const BASE_OPTIONS: Readonly<Schema>;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.io/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.BASE_OPTIONS = exports.KARMA_BUILDER_INFO = exports.describeBuilder = void 0;
11
+ var testing_1 = require("../../testing");
12
+ Object.defineProperty(exports, "describeBuilder", { enumerable: true, get: function () { return testing_1.describeBuilder; } });
13
+ exports.KARMA_BUILDER_INFO = Object.freeze({
14
+ name: '@angular-devkit/build-angular:karma',
15
+ schemaPath: __dirname + '/../schema.json',
16
+ });
17
+ /**
18
+ * Contains all required karma builder fields.
19
+ * Also disables progress reporting to minimize logging output.
20
+ */
21
+ exports.BASE_OPTIONS = Object.freeze({
22
+ main: 'src/test.ts',
23
+ polyfills: 'src/polyfills.ts',
24
+ tsConfig: 'src/tsconfig.spec.json',
25
+ karmaConfig: 'karma.conf.js',
26
+ browsers: 'ChromeHeadlessCI',
27
+ progress: false,
28
+ watch: false,
29
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ import { Options, Result, SassException } from 'sass';
9
+ /**
10
+ * The callback type for the `dart-sass` asynchronous render function.
11
+ */
12
+ declare type RenderCallback = (error?: SassException, result?: Result) => void;
13
+ /**
14
+ * A Sass renderer implementation that provides an interface that can be used by Webpack's
15
+ * `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering
16
+ * with the `dart-sass` package. The `dart-sass` synchronous render function is used within
17
+ * the worker which can be up to two times faster than the asynchronous variant.
18
+ */
19
+ export declare class SassWorkerImplementation {
20
+ private readonly workers;
21
+ private readonly availableWorkers;
22
+ private readonly requests;
23
+ private idCounter;
24
+ private nextWorkerIndex;
25
+ /**
26
+ * Provides information about the Sass implementation.
27
+ * This mimics enough of the `dart-sass` value to be used with the `sass-loader`.
28
+ */
29
+ get info(): string;
30
+ /**
31
+ * The synchronous render function is not used by the `sass-loader`.
32
+ */
33
+ renderSync(): never;
34
+ /**
35
+ * Asynchronously request a Sass stylesheet to be renderered.
36
+ *
37
+ * @param options The `dart-sass` options to use when rendering the stylesheet.
38
+ * @param callback The function to execute when the rendering is complete.
39
+ */
40
+ render(options: Options, callback: RenderCallback): void;
41
+ /**
42
+ * Shutdown the Sass render worker.
43
+ * Executing this method will stop any pending render requests.
44
+ */
45
+ close(): void;
46
+ private createWorker;
47
+ private processImporters;
48
+ private createRequest;
49
+ }
50
+ export {};
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ /**
3
+ * @license
4
+ * Copyright Google LLC All Rights Reserved.
5
+ *
6
+ * Use of this source code is governed by an MIT-style license that can be
7
+ * found in the LICENSE file at https://angular.io/license
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.SassWorkerImplementation = void 0;
11
+ const worker_threads_1 = require("worker_threads");
12
+ const environment_options_1 = require("../utils/environment-options");
13
+ /**
14
+ * The maximum number of Workers that will be created to execute render requests.
15
+ */
16
+ const MAX_RENDER_WORKERS = environment_options_1.maxWorkers;
17
+ /**
18
+ * Workaround required for lack of new Worker transfer list support in Node.js prior to 12.17
19
+ */
20
+ let transferListWorkaround = false;
21
+ const version = process.versions.node.split('.').map((part) => Number(part));
22
+ if (version[0] === 12 && version[1] < 17) {
23
+ transferListWorkaround = true;
24
+ }
25
+ /**
26
+ * A Sass renderer implementation that provides an interface that can be used by Webpack's
27
+ * `sass-loader`. The implementation uses a Worker thread to perform the Sass rendering
28
+ * with the `dart-sass` package. The `dart-sass` synchronous render function is used within
29
+ * the worker which can be up to two times faster than the asynchronous variant.
30
+ */
31
+ class SassWorkerImplementation {
32
+ constructor() {
33
+ this.workers = [];
34
+ this.availableWorkers = [];
35
+ this.requests = new Map();
36
+ this.idCounter = 1;
37
+ this.nextWorkerIndex = 0;
38
+ }
39
+ /**
40
+ * Provides information about the Sass implementation.
41
+ * This mimics enough of the `dart-sass` value to be used with the `sass-loader`.
42
+ */
43
+ get info() {
44
+ return 'dart-sass\tworker';
45
+ }
46
+ /**
47
+ * The synchronous render function is not used by the `sass-loader`.
48
+ */
49
+ renderSync() {
50
+ throw new Error('Sass renderSync is not supported.');
51
+ }
52
+ /**
53
+ * Asynchronously request a Sass stylesheet to be renderered.
54
+ *
55
+ * @param options The `dart-sass` options to use when rendering the stylesheet.
56
+ * @param callback The function to execute when the rendering is complete.
57
+ */
58
+ render(options, callback) {
59
+ // The `functions` and `importer` options are JavaScript functions that cannot be transferred.
60
+ // If any additional function options are added in the future, they must be excluded as well.
61
+ const { functions, importer, ...serializableOptions } = options;
62
+ // The CLI's configuration does not use or expose the ability to defined custom Sass functions
63
+ if (functions && Object.keys(functions).length > 0) {
64
+ throw new Error('Sass custom functions are not supported.');
65
+ }
66
+ let workerIndex = this.availableWorkers.pop();
67
+ if (workerIndex === undefined) {
68
+ if (this.workers.length < MAX_RENDER_WORKERS) {
69
+ workerIndex = this.workers.length;
70
+ this.workers.push(this.createWorker());
71
+ }
72
+ else {
73
+ workerIndex = this.nextWorkerIndex++;
74
+ if (this.nextWorkerIndex >= this.workers.length) {
75
+ this.nextWorkerIndex = 0;
76
+ }
77
+ }
78
+ }
79
+ const request = this.createRequest(workerIndex, callback, importer);
80
+ this.requests.set(request.id, request);
81
+ this.workers[workerIndex].postMessage({
82
+ id: request.id,
83
+ hasImporter: !!importer,
84
+ options: serializableOptions,
85
+ });
86
+ }
87
+ /**
88
+ * Shutdown the Sass render worker.
89
+ * Executing this method will stop any pending render requests.
90
+ */
91
+ close() {
92
+ for (const worker of this.workers) {
93
+ try {
94
+ void worker.terminate();
95
+ }
96
+ catch { }
97
+ }
98
+ this.requests.clear();
99
+ }
100
+ createWorker() {
101
+ const { port1: mainImporterPort, port2: workerImporterPort } = new worker_threads_1.MessageChannel();
102
+ const importerSignal = new Int32Array(new SharedArrayBuffer(4));
103
+ const workerPath = require.resolve('./worker');
104
+ const worker = new worker_threads_1.Worker(workerPath, {
105
+ workerData: transferListWorkaround ? undefined : { workerImporterPort, importerSignal },
106
+ transferList: transferListWorkaround ? undefined : [workerImporterPort],
107
+ });
108
+ if (transferListWorkaround) {
109
+ worker.postMessage({ init: true, workerImporterPort, importerSignal }, [workerImporterPort]);
110
+ }
111
+ worker.on('message', (response) => {
112
+ const request = this.requests.get(response.id);
113
+ if (!request) {
114
+ return;
115
+ }
116
+ this.requests.delete(response.id);
117
+ this.availableWorkers.push(request.workerIndex);
118
+ if (response.result) {
119
+ // The results are expected to be Node.js `Buffer` objects but will each be transferred as
120
+ // a Uint8Array that does not have the expected `toString` behavior of a `Buffer`.
121
+ const { css, map, stats } = response.result;
122
+ const result = {
123
+ // This `Buffer.from` override will use the memory directly and avoid making a copy
124
+ css: Buffer.from(css.buffer, css.byteOffset, css.byteLength),
125
+ stats,
126
+ };
127
+ if (map) {
128
+ // This `Buffer.from` override will use the memory directly and avoid making a copy
129
+ result.map = Buffer.from(map.buffer, map.byteOffset, map.byteLength);
130
+ }
131
+ request.callback(undefined, result);
132
+ }
133
+ else {
134
+ request.callback(response.error);
135
+ }
136
+ });
137
+ mainImporterPort.on('message', ({ id, url, prev }) => {
138
+ const request = this.requests.get(id);
139
+ if (!(request === null || request === void 0 ? void 0 : request.importers)) {
140
+ mainImporterPort.postMessage(null);
141
+ Atomics.store(importerSignal, 0, 1);
142
+ Atomics.notify(importerSignal, 0);
143
+ return;
144
+ }
145
+ this.processImporters(request.importers, url, prev)
146
+ .then((result) => {
147
+ mainImporterPort.postMessage(result);
148
+ })
149
+ .catch((error) => {
150
+ mainImporterPort.postMessage(error);
151
+ })
152
+ .finally(() => {
153
+ Atomics.store(importerSignal, 0, 1);
154
+ Atomics.notify(importerSignal, 0);
155
+ });
156
+ });
157
+ mainImporterPort.unref();
158
+ return worker;
159
+ }
160
+ async processImporters(importers, url, prev) {
161
+ let result = null;
162
+ for (const importer of importers) {
163
+ result = await new Promise((resolve) => {
164
+ // Importers can be both sync and async
165
+ const innerResult = importer(url, prev, resolve);
166
+ if (innerResult !== undefined) {
167
+ resolve(innerResult);
168
+ }
169
+ });
170
+ if (result) {
171
+ break;
172
+ }
173
+ }
174
+ return result;
175
+ }
176
+ createRequest(workerIndex, callback, importer) {
177
+ return {
178
+ id: this.idCounter++,
179
+ workerIndex,
180
+ callback,
181
+ importers: !importer || Array.isArray(importer) ? importer : [importer],
182
+ };
183
+ }
184
+ }
185
+ exports.SassWorkerImplementation = SassWorkerImplementation;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @license
3
+ * Copyright Google LLC All Rights Reserved.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license that can be
6
+ * found in the LICENSE file at https://angular.io/license
7
+ */
8
+ export {};