@angular-devkit/build-angular 13.1.0-next.2 → 13.1.0-next.3

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.
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@angular-devkit/build-angular",
3
- "version": "13.1.0-next.2",
3
+ "version": "13.1.0-next.3",
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
- "@ampproject/remapping": "1.0.1",
10
- "@angular-devkit/architect": "0.1301.0-next.2",
11
- "@angular-devkit/build-webpack": "0.1301.0-next.2",
12
- "@angular-devkit/core": "13.1.0-next.2",
9
+ "@ampproject/remapping": "1.0.2",
10
+ "@angular-devkit/architect": "0.1301.0-next.3",
11
+ "@angular-devkit/build-webpack": "0.1301.0-next.3",
12
+ "@angular-devkit/core": "13.1.0-next.3",
13
13
  "@babel/core": "7.16.0",
14
14
  "@babel/generator": "7.16.0",
15
15
  "@babel/helper-annotate-as-pure": "7.16.0",
@@ -19,19 +19,19 @@
19
19
  "@babel/preset-env": "7.16.4",
20
20
  "@babel/runtime": "7.16.3",
21
21
  "@babel/template": "7.16.0",
22
- "@discoveryjs/json-ext": "0.5.5",
23
- "@ngtools/webpack": "13.1.0-next.2",
22
+ "@discoveryjs/json-ext": "0.5.6",
23
+ "@ngtools/webpack": "13.1.0-next.3",
24
24
  "ansi-colors": "4.1.1",
25
25
  "babel-loader": "8.2.3",
26
26
  "babel-plugin-istanbul": "6.1.1",
27
27
  "browserslist": "^4.9.1",
28
28
  "cacache": "15.3.0",
29
29
  "circular-dependency-plugin": "5.2.2",
30
- "copy-webpack-plugin": "9.1.0",
31
- "core-js": "3.19.1",
32
- "critters": "0.0.14",
30
+ "copy-webpack-plugin": "10.0.0",
31
+ "core-js": "3.19.2",
32
+ "critters": "0.0.15",
33
33
  "css-loader": "6.5.1",
34
- "esbuild-wasm": "0.13.14",
34
+ "esbuild-wasm": "0.14.1",
35
35
  "glob": "7.2.0",
36
36
  "https-proxy-agent": "5.0.0",
37
37
  "inquirer": "8.2.0",
@@ -41,38 +41,38 @@
41
41
  "less-loader": "10.2.0",
42
42
  "license-webpack-plugin": "4.0.0",
43
43
  "loader-utils": "3.2.0",
44
- "mini-css-extract-plugin": "2.4.4",
44
+ "mini-css-extract-plugin": "2.4.5",
45
45
  "minimatch": "3.0.4",
46
46
  "open": "8.4.0",
47
47
  "ora": "5.4.1",
48
48
  "parse5-html-rewriting-stream": "6.0.1",
49
49
  "piscina": "3.1.0",
50
- "postcss": "8.3.11",
50
+ "postcss": "8.4.4",
51
51
  "postcss-import": "14.0.2",
52
- "postcss-loader": "6.2.0",
52
+ "postcss-loader": "6.2.1",
53
53
  "postcss-preset-env": "6.7.0",
54
54
  "regenerator-runtime": "0.13.9",
55
55
  "resolve-url-loader": "4.0.0",
56
56
  "rxjs": "6.6.7",
57
- "sass": "1.43.4",
57
+ "sass": "1.44.0",
58
58
  "sass-loader": "12.3.0",
59
59
  "semver": "7.3.5",
60
60
  "source-map-loader": "3.0.0",
61
- "source-map-support": "0.5.20",
61
+ "source-map-support": "0.5.21",
62
62
  "stylus": "0.55.0",
63
63
  "stylus-loader": "6.2.0",
64
64
  "terser": "5.10.0",
65
65
  "text-table": "0.2.0",
66
66
  "tree-kill": "1.2.2",
67
67
  "tslib": "2.3.1",
68
- "webpack": "5.64.1",
69
- "webpack-dev-middleware": "5.2.1",
70
- "webpack-dev-server": "4.5.0",
68
+ "webpack": "5.64.4",
69
+ "webpack-dev-middleware": "5.2.2",
70
+ "webpack-dev-server": "4.6.0",
71
71
  "webpack-merge": "5.8.0",
72
72
  "webpack-subresource-integrity": "5.0.0"
73
73
  },
74
74
  "optionalDependencies": {
75
- "esbuild": "0.13.14"
75
+ "esbuild": "0.14.1"
76
76
  },
77
77
  "peerDependencies": {
78
78
  "@angular/compiler-cli": "^13.0.0 || ^13.1.0-next",
@@ -23,6 +23,7 @@ export interface ApplicationPresetOptions {
23
23
  locale: string;
24
24
  missingTranslationBehavior?: 'error' | 'warning' | 'ignore';
25
25
  translation?: unknown;
26
+ translationFiles?: string[];
26
27
  pluginCreators?: I18nPluginCreators;
27
28
  };
28
29
  angularLinker?: {
@@ -41,6 +41,7 @@ async function requiresLinking(path, source) {
41
41
  }
42
42
  return needsLinking(path, source);
43
43
  }
44
+ // eslint-disable-next-line max-lines-per-function
44
45
  exports.default = (0, babel_loader_1.custom)(() => {
45
46
  const baseOptions = Object.freeze({
46
47
  babelrc: false,
@@ -118,6 +119,14 @@ exports.default = (0, babel_loader_1.custom)(() => {
118
119
  ...i18n,
119
120
  pluginCreators: i18nPluginCreators,
120
121
  };
122
+ // Add translation files as dependencies of the file to support rebuilds
123
+ // Except for `@angular/core` which needs locale injection but has no translations
124
+ if (customOptions.i18n.translationFiles &&
125
+ !/[\\/]@angular[\\/]core/.test(this.resourcePath)) {
126
+ for (const file of customOptions.i18n.translationFiles) {
127
+ this.addDependency(file);
128
+ }
129
+ }
121
130
  shouldProcess = true;
122
131
  }
123
132
  if (optimize) {
@@ -199,7 +208,13 @@ exports.default = (0, babel_loader_1.custom)(() => {
199
208
  // `@ampproject/remapping` source map objects but both are compatible with Webpack.
200
209
  // This method for merging is used because it provides more accurate output
201
210
  // and is faster while using less memory.
202
- result.map = (0, remapping_1.default)([result.map, inputSourceMap], () => null);
211
+ result.map = {
212
+ // Convert the SourceMap back to simple plain object.
213
+ // This is needed because otherwise code-coverage will fail with `don't know how to turn this value into a node`
214
+ // Which is thrown by Babel if it is invoked again from `istanbul-lib-instrument`.
215
+ // https://github.com/babel/babel/blob/780aa48d2a34dc55f556843074b6aed45e7eabeb/packages/babel-types/src/converters/valueToNode.ts#L115-L130
216
+ ...(0, remapping_1.default)([result.map, inputSourceMap], () => null),
217
+ };
203
218
  }
204
219
  return result;
205
220
  },
@@ -155,8 +155,9 @@ function buildWebpackBrowser(options, context, transforms = {}) {
155
155
  }
156
156
  // Check for budget errors and display them to the user.
157
157
  const budgets = options.budgets;
158
+ let budgetFailures;
158
159
  if (budgets === null || budgets === void 0 ? void 0 : budgets.length) {
159
- const budgetFailures = (0, bundle_calculator_1.checkBudgets)(budgets, webpackStats);
160
+ budgetFailures = [...(0, bundle_calculator_1.checkBudgets)(budgets, webpackStats)];
160
161
  for (const { severity, message } of budgetFailures) {
161
162
  switch (severity) {
162
163
  case bundle_calculator_1.ThresholdSeverity.Warning:
@@ -250,7 +251,7 @@ function buildWebpackBrowser(options, context, transforms = {}) {
250
251
  spinner.succeed('Service worker generation complete.');
251
252
  }
252
253
  }
253
- (0, stats_1.webpackStatsLogger)(context.logger, webpackStats, config);
254
+ (0, stats_1.webpackStatsLogger)(context.logger, webpackStats, config, budgetFailures);
254
255
  return { success: buildSuccess };
255
256
  }
256
257
  }), (0, operators_1.map)((event) => ({
@@ -37,10 +37,13 @@ const url = __importStar(require("url"));
37
37
  const utils_1 = require("../../utils");
38
38
  const check_port_1 = require("../../utils/check-port");
39
39
  const color_1 = require("../../utils/color");
40
+ const i18n_options_1 = require("../../utils/i18n-options");
41
+ const load_translations_1 = require("../../utils/load-translations");
40
42
  const normalize_cache_1 = require("../../utils/normalize-cache");
41
43
  const package_chunk_sort_1 = require("../../utils/package-chunk-sort");
42
44
  const version_1 = require("../../utils/version");
43
45
  const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
46
+ const webpack_diagnostics_1 = require("../../utils/webpack-diagnostics");
44
47
  const configs_1 = require("../../webpack/configs");
45
48
  const index_html_webpack_plugin_1 = require("../../webpack/plugins/index-html-webpack-plugin");
46
49
  const stats_1 = require("../../webpack/utils/stats");
@@ -145,7 +148,7 @@ function serveWebpackBrowser(options, context, transforms = {}) {
145
148
  if (i18n.inlineLocales.size > 1) {
146
149
  throw new Error('The development server only supports localizing a single locale per build.');
147
150
  }
148
- await setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheOptions);
151
+ await setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheOptions, context);
149
152
  }
150
153
  if (transforms.webpackConfiguration) {
151
154
  webpackConfig = await transforms.webpackConfiguration(webpackConfig);
@@ -220,7 +223,7 @@ function serveWebpackBrowser(options, context, transforms = {}) {
220
223
  }));
221
224
  }
222
225
  exports.serveWebpackBrowser = serveWebpackBrowser;
223
- async function setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheOptions) {
226
+ async function setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheOptions, context) {
224
227
  var _a;
225
228
  const localeDescription = i18n.locales[locale];
226
229
  // Modify main entrypoint to include locale data
@@ -248,6 +251,7 @@ async function setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheO
248
251
  locale,
249
252
  missingTranslationBehavior,
250
253
  translation: i18n.shouldInline ? translation : undefined,
254
+ translationFiles: localeDescription === null || localeDescription === void 0 ? void 0 : localeDescription.files.map((file) => path.resolve(context.workspaceRoot, file.path)),
251
255
  };
252
256
  const i18nRule = {
253
257
  test: /\.[cm]?[tj]sx?$/,
@@ -276,5 +280,30 @@ async function setupLocalize(locale, i18n, browserOptions, webpackConfig, cacheO
276
280
  webpackConfig.module.rules = rules;
277
281
  }
278
282
  rules.push(i18nRule);
283
+ // Add a plugin to reload translation files on rebuilds
284
+ const loader = await (0, load_translations_1.createTranslationLoader)();
285
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
286
+ webpackConfig.plugins.push({
287
+ apply: (compiler) => {
288
+ compiler.hooks.thisCompilation.tap('build-angular', (compilation) => {
289
+ if (i18n.shouldInline && i18nLoaderOptions.translation === undefined) {
290
+ // Reload translations
291
+ (0, i18n_options_1.loadTranslations)(locale, localeDescription, context.workspaceRoot, loader, {
292
+ warn(message) {
293
+ (0, webpack_diagnostics_1.addWarning)(compilation, message);
294
+ },
295
+ error(message) {
296
+ (0, webpack_diagnostics_1.addError)(compilation, message);
297
+ },
298
+ });
299
+ i18nLoaderOptions.translation = localeDescription.translation;
300
+ }
301
+ compilation.hooks.finishModules.tap('build-angular', () => {
302
+ // After loaders are finished, clear out the now unneeded translations
303
+ i18nLoaderOptions.translation = undefined;
304
+ });
305
+ });
306
+ },
307
+ });
279
308
  }
280
309
  exports.default = (0, architect_1.createBuilder)(serveWebpackBrowser);
@@ -29,6 +29,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  const nodePath = __importStar(require("path"));
30
30
  const load_esm_1 = require("../../utils/load-esm");
31
31
  function localizeExtractLoader(content, map) {
32
+ // This loader is not cacheable due to how message extraction works.
33
+ // Extracted messages are not part of webpack pipeline and hence they cannot be retrieved from cache.
34
+ // TODO: We should investigate in the future on making this deterministic and more cacheable.
35
+ this.cacheable(false);
32
36
  const options = this.getOptions();
33
37
  const callback = this.async();
34
38
  extract(this, content, map, options).then(() => {
@@ -20,13 +20,12 @@ export declare enum ThresholdSeverity {
20
20
  Warning = "warning",
21
21
  Error = "error"
22
22
  }
23
- export declare function calculateThresholds(budget: Budget): IterableIterator<Threshold>;
24
- export declare function checkBudgets(budgets: Budget[], webpackStats: StatsCompilation): IterableIterator<{
25
- severity: ThresholdSeverity;
26
- message: string;
27
- }>;
28
- export declare function checkThresholds(thresholds: IterableIterator<Threshold>, size: number, label?: string): IterableIterator<{
23
+ export interface BudgetCalculatorResult {
29
24
  severity: ThresholdSeverity;
30
25
  message: string;
31
- }>;
26
+ label?: string;
27
+ }
28
+ export declare function calculateThresholds(budget: Budget): IterableIterator<Threshold>;
29
+ export declare function checkBudgets(budgets: Budget[], webpackStats: StatsCompilation): IterableIterator<BudgetCalculatorResult>;
30
+ export declare function checkThresholds(thresholds: IterableIterator<Threshold>, size: number, label?: string): IterableIterator<BudgetCalculatorResult>;
32
31
  export {};
@@ -143,7 +143,7 @@ class BundleCalculator extends Calculator {
143
143
  .filter((chunk) => { var _a; return (_a = chunk === null || chunk === void 0 ? void 0 : chunk.names) === null || _a === void 0 ? void 0 : _a.includes(budgetName); })
144
144
  .map((chunk) => this.calculateChunkSize(chunk))
145
145
  .reduce((l, r) => l + r, 0);
146
- return [{ size, label: `bundle ${this.budget.name}` }];
146
+ return [{ size, label: this.budget.name }];
147
147
  }
148
148
  }
149
149
  /**
@@ -262,6 +262,7 @@ function* checkThresholds(thresholds, size, label) {
262
262
  const sizeDifference = (0, stats_1.formatSize)(size - threshold.limit);
263
263
  yield {
264
264
  severity: threshold.severity,
265
+ label,
265
266
  message: `${label} exceeded maximum budget. Budget ${(0, stats_1.formatSize)(threshold.limit)} was not met by ${sizeDifference} with a total of ${(0, stats_1.formatSize)(size)}.`,
266
267
  };
267
268
  break;
@@ -273,6 +274,7 @@ function* checkThresholds(thresholds, size, label) {
273
274
  const sizeDifference = (0, stats_1.formatSize)(threshold.limit - size);
274
275
  yield {
275
276
  severity: threshold.severity,
277
+ label,
276
278
  message: `${label} failed to meet minimum budget. Budget ${(0, stats_1.formatSize)(threshold.limit)} was not met by ${sizeDifference} with a total of ${(0, stats_1.formatSize)(size)}.`,
277
279
  };
278
280
  break;
@@ -9,19 +9,21 @@ import { BuilderContext } from '@angular-devkit/architect';
9
9
  import { json } from '@angular-devkit/core';
10
10
  import { Schema as BrowserBuilderSchema } from '../builders/browser/schema';
11
11
  import { Schema as ServerBuilderSchema } from '../builders/server/schema';
12
+ import { TranslationLoader } from './load-translations';
13
+ export interface LocaleDescription {
14
+ files: {
15
+ path: string;
16
+ integrity?: string;
17
+ format?: string;
18
+ }[];
19
+ translation?: Record<string, unknown>;
20
+ dataPath?: string;
21
+ baseHref?: string;
22
+ }
12
23
  export interface I18nOptions {
13
24
  inlineLocales: Set<string>;
14
25
  sourceLocale: string;
15
- locales: Record<string, {
16
- files: {
17
- path: string;
18
- integrity?: string;
19
- format?: string;
20
- }[];
21
- translation?: Record<string, unknown>;
22
- dataPath?: string;
23
- baseHref?: string;
24
- }>;
26
+ locales: Record<string, LocaleDescription>;
25
27
  flatOutput?: boolean;
26
28
  readonly shouldInline: boolean;
27
29
  hasDefinedSourceLocale?: boolean;
@@ -31,3 +33,7 @@ export declare function configureI18nBuild<T extends BrowserBuilderSchema | Serv
31
33
  buildOptions: T;
32
34
  i18n: I18nOptions;
33
35
  }>;
36
+ export declare function loadTranslations(locale: string, desc: LocaleDescription, workspaceRoot: string, loader: TranslationLoader, logger: {
37
+ warn: (message: string) => void;
38
+ error: (message: string) => void;
39
+ }, usedFormats?: Set<string>): void;
@@ -10,7 +10,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  return (mod && mod.__esModule) ? mod : { "default": mod };
11
11
  };
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.configureI18nBuild = exports.createI18nOptions = void 0;
13
+ exports.loadTranslations = exports.configureI18nBuild = exports.createI18nOptions = void 0;
14
14
  const core_1 = require("@angular-devkit/core");
15
15
  const fs_1 = __importDefault(require("fs"));
16
16
  const module_1 = __importDefault(require("module"));
@@ -161,39 +161,17 @@ async function configureI18nBuild(context, options) {
161
161
  if (!loader) {
162
162
  loader = await (0, load_translations_1.createTranslationLoader)();
163
163
  }
164
- for (const file of desc.files) {
165
- const loadResult = loader(path_1.default.join(context.workspaceRoot, file.path));
166
- for (const diagnostics of loadResult.diagnostics.messages) {
167
- if (diagnostics.type === 'error') {
168
- throw new Error(`Error parsing translation file '${file.path}': ${diagnostics.message}`);
169
- }
170
- else {
171
- context.logger.warn(`WARNING [${file.path}]: ${diagnostics.message}`);
172
- }
173
- }
174
- if (loadResult.locale !== undefined && loadResult.locale !== locale) {
175
- context.logger.warn(`WARNING [${file.path}]: File target locale ('${loadResult.locale}') does not match configured locale ('${locale}')`);
176
- }
177
- usedFormats.add(loadResult.format);
178
- if (usedFormats.size > 1 && tsConfig.options.enableI18nLegacyMessageIdFormat !== false) {
179
- // This limitation is only for legacy message id support (defaults to true as of 9.0)
180
- throw new Error('Localization currently only supports using one type of translation file format for the entire application.');
181
- }
182
- file.format = loadResult.format;
183
- file.integrity = loadResult.integrity;
184
- if (desc.translation) {
185
- // Merge translations
186
- for (const [id, message] of Object.entries(loadResult.translations)) {
187
- if (desc.translation[id] !== undefined) {
188
- context.logger.warn(`WARNING [${file.path}]: Duplicate translations for message '${id}' when merging`);
189
- }
190
- desc.translation[id] = message;
191
- }
192
- }
193
- else {
194
- // First or only translation file
195
- desc.translation = loadResult.translations;
196
- }
164
+ loadTranslations(locale, desc, context.workspaceRoot, loader, {
165
+ warn(message) {
166
+ context.logger.warn(message);
167
+ },
168
+ error(message) {
169
+ throw new Error(message);
170
+ },
171
+ }, usedFormats);
172
+ if (usedFormats.size > 1 && tsConfig.options.enableI18nLegacyMessageIdFormat !== false) {
173
+ // This limitation is only for legacy message id support (defaults to true as of 9.0)
174
+ throw new Error('Localization currently only supports using one type of translation file format for the entire application.');
197
175
  }
198
176
  }
199
177
  // If inlining store the output in a temporary location to facilitate post-processing
@@ -225,3 +203,36 @@ function findLocaleDataPath(locale, resolver) {
225
203
  return null;
226
204
  }
227
205
  }
206
+ function loadTranslations(locale, desc, workspaceRoot, loader, logger, usedFormats) {
207
+ for (const file of desc.files) {
208
+ const loadResult = loader(path_1.default.join(workspaceRoot, file.path));
209
+ for (const diagnostics of loadResult.diagnostics.messages) {
210
+ if (diagnostics.type === 'error') {
211
+ logger.error(`Error parsing translation file '${file.path}': ${diagnostics.message}`);
212
+ }
213
+ else {
214
+ logger.warn(`WARNING [${file.path}]: ${diagnostics.message}`);
215
+ }
216
+ }
217
+ if (loadResult.locale !== undefined && loadResult.locale !== locale) {
218
+ logger.warn(`WARNING [${file.path}]: File target locale ('${loadResult.locale}') does not match configured locale ('${locale}')`);
219
+ }
220
+ usedFormats === null || usedFormats === void 0 ? void 0 : usedFormats.add(loadResult.format);
221
+ file.format = loadResult.format;
222
+ file.integrity = loadResult.integrity;
223
+ if (desc.translation) {
224
+ // Merge translations
225
+ for (const [id, message] of Object.entries(loadResult.translations)) {
226
+ if (desc.translation[id] !== undefined) {
227
+ logger.warn(`WARNING [${file.path}]: Duplicate translations for message '${id}' when merging`);
228
+ }
229
+ desc.translation[id] = message;
230
+ }
231
+ }
232
+ else {
233
+ // First or only translation file
234
+ desc.translation = loadResult.translations;
235
+ }
236
+ }
237
+ }
238
+ exports.loadTranslations = loadTranslations;
@@ -27,4 +27,8 @@ export interface FileInfo {
27
27
  name: string;
28
28
  extension: string;
29
29
  }
30
- export declare function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<string>;
30
+ export declare function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<{
31
+ content: string;
32
+ warnings: string[];
33
+ errors: string[];
34
+ }>;
@@ -9,6 +9,7 @@
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
10
  exports.augmentIndexHtml = void 0;
11
11
  const crypto_1 = require("crypto");
12
+ const load_esm_1 = require("../load-esm");
12
13
  const html_rewriting_stream_1 = require("./html-rewriting-stream");
13
14
  /*
14
15
  * Helper function used by the IndexHtmlWebpackPlugin.
@@ -18,6 +19,8 @@ const html_rewriting_stream_1 = require("./html-rewriting-stream");
18
19
  */
19
20
  async function augmentIndexHtml(params) {
20
21
  const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html } = params;
22
+ const warnings = [];
23
+ const errors = [];
21
24
  let { crossOrigin = 'none' } = params;
22
25
  if (sri && crossOrigin === 'none') {
23
26
  crossOrigin = 'anonymous';
@@ -72,6 +75,7 @@ async function augmentIndexHtml(params) {
72
75
  }
73
76
  linkTags.push(`<link ${attrs.join(' ')}>`);
74
77
  }
78
+ const dir = lang ? await getLanguageDirection(lang, warnings) : undefined;
75
79
  const { rewriter, transformedContent } = await (0, html_rewriting_stream_1.htmlRewritingStream)(html);
76
80
  const baseTagExists = html.includes('<base');
77
81
  rewriter
@@ -82,6 +86,9 @@ async function augmentIndexHtml(params) {
82
86
  if (isString(lang)) {
83
87
  updateAttribute(tag, 'lang', lang);
84
88
  }
89
+ if (dir) {
90
+ updateAttribute(tag, 'dir', dir);
91
+ }
85
92
  break;
86
93
  case 'head':
87
94
  // Base href should be added before any link, meta tags
@@ -119,11 +126,14 @@ async function augmentIndexHtml(params) {
119
126
  rewriter.emitEndTag(tag);
120
127
  });
121
128
  const content = await transformedContent;
122
- if (linkTags.length || scriptTags.length) {
123
- // In case no body/head tags are not present (dotnet partial templates)
124
- return linkTags.join('') + scriptTags.join('') + content;
125
- }
126
- return content;
129
+ return {
130
+ content: linkTags.length || scriptTags.length
131
+ ? // In case no body/head tags are not present (dotnet partial templates)
132
+ linkTags.join('') + scriptTags.join('') + content
133
+ : content,
134
+ warnings,
135
+ errors,
136
+ };
127
137
  }
128
138
  exports.augmentIndexHtml = augmentIndexHtml;
129
139
  function generateSriAttributes(content) {
@@ -144,3 +154,13 @@ function updateAttribute(tag, name, value) {
144
154
  function isString(value) {
145
155
  return typeof value === 'string';
146
156
  }
157
+ async function getLanguageDirection(lang, warnings) {
158
+ try {
159
+ const localeData = (await (0, load_esm_1.loadEsmModule)(`@angular/common/locales/${lang}`)).default;
160
+ const dir = localeData[localeData.length - 2];
161
+ return isString(dir) ? dir : undefined;
162
+ }
163
+ catch {
164
+ warnings.push(`Locale data for '${lang}' cannot be found. 'dir' attribute will not be set for this locale.`);
165
+ }
166
+ }
@@ -39,6 +39,7 @@ const webpack_subresource_integrity_1 = require("webpack-subresource-integrity")
39
39
  const environment_options_1 = require("../../utils/environment-options");
40
40
  const load_esm_1 = require("../../utils/load-esm");
41
41
  const plugins_1 = require("../plugins");
42
+ const named_chunks_plugin_1 = require("../plugins/named-chunks-plugin");
42
43
  const progress_plugin_1 = require("../plugins/progress-plugin");
43
44
  const transfer_size_plugin_1 = require("../plugins/transfer-size-plugin");
44
45
  const typescript_2 = require("../plugins/typescript");
@@ -189,8 +190,10 @@ async function getCommonConfig(wco) {
189
190
  }
190
191
  if (main || polyfills) {
191
192
  extraRules.push({
192
- test: /\.[cm]?[tj]sx?$/,
193
+ test: tsConfig.options.allowJs ? /\.[cm]?[tj]sx?$/ : /\.[cm]?tsx?$/,
193
194
  loader: webpack_1.AngularWebpackLoaderPath,
195
+ // The below are known paths that are not part of the TypeScript compilation even when allowJs is enabled.
196
+ exclude: [/[/\\](?:css-loader|mini-css-extract-plugin|webpack-dev-server|webpack)[/\\]/],
194
197
  });
195
198
  extraPlugins.push((0, typescript_2.createIvyPlugin)(wco, aot, tsConfigPath));
196
199
  }
@@ -262,7 +265,7 @@ async function getCommonConfig(wco) {
262
265
  watch: buildOptions.watch,
263
266
  watchOptions: {
264
267
  poll,
265
- ignored: poll === undefined ? undefined : 'node_modules/**',
268
+ ignored: poll === undefined ? undefined : '**/node_modules/**',
266
269
  },
267
270
  performance: {
268
271
  hints: false,
@@ -362,7 +365,7 @@ async function getCommonConfig(wco) {
362
365
  },
363
366
  },
364
367
  },
365
- plugins: [new plugins_1.DedupeModuleResolvePlugin({ verbose }), ...extraPlugins],
368
+ plugins: [new named_chunks_plugin_1.NamedChunksPlugin(), new plugins_1.DedupeModuleResolvePlugin({ verbose }), ...extraPlugins],
366
369
  node: false,
367
370
  };
368
371
  }
@@ -0,0 +1,17 @@
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 { Compiler } from 'webpack';
9
+ /**
10
+ * Webpack will not populate the chunk `name` property unless `webpackChunkName` magic comment is used.
11
+ * This however will also effect the filename which is not desired when using `deterministic` chunkIds.
12
+ * This plugin will populate the chunk `name` which is mainly used so that users can set bundle budgets on lazy chunks.
13
+ */
14
+ export declare class NamedChunksPlugin {
15
+ apply(compiler: Compiler): void;
16
+ private generateName;
17
+ }
@@ -0,0 +1,49 @@
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.NamedChunksPlugin = void 0;
11
+ const webpack_1 = require("webpack");
12
+ // `ImportDependency` is not part of Webpack's depenencies typings.
13
+ const ImportDependency = require('webpack/lib/dependencies/ImportDependency');
14
+ const PLUGIN_NAME = 'named-chunks-plugin';
15
+ /**
16
+ * Webpack will not populate the chunk `name` property unless `webpackChunkName` magic comment is used.
17
+ * This however will also effect the filename which is not desired when using `deterministic` chunkIds.
18
+ * This plugin will populate the chunk `name` which is mainly used so that users can set bundle budgets on lazy chunks.
19
+ */
20
+ class NamedChunksPlugin {
21
+ apply(compiler) {
22
+ compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
23
+ compilation.hooks.chunkAsset.tap(PLUGIN_NAME, (chunk) => {
24
+ if (chunk.name) {
25
+ return;
26
+ }
27
+ const name = this.generateName(chunk);
28
+ if (name) {
29
+ chunk.name = name;
30
+ }
31
+ });
32
+ });
33
+ }
34
+ generateName(chunk) {
35
+ for (const group of chunk.groupsIterable) {
36
+ const [block] = group.getBlocks();
37
+ if (!(block instanceof webpack_1.AsyncDependenciesBlock)) {
38
+ continue;
39
+ }
40
+ for (const dependency of block.dependencies) {
41
+ if (dependency instanceof ImportDependency) {
42
+ return webpack_1.Template.toPath(dependency.request);
43
+ }
44
+ }
45
+ }
46
+ return undefined;
47
+ }
48
+ }
49
+ exports.NamedChunksPlugin = NamedChunksPlugin;
@@ -9,6 +9,7 @@ import { WebpackLoggingCallback } from '@angular-devkit/build-webpack';
9
9
  import { logging } from '@angular-devkit/core';
10
10
  import { Configuration, StatsCompilation } from 'webpack';
11
11
  import { Schema as BrowserBuilderOptions } from '../../builders/browser/schema';
12
+ import { BudgetCalculatorResult } from '../../utils/bundle-calculator';
12
13
  export declare function formatSize(size: number): string;
13
14
  export declare type BundleStatsData = [
14
15
  files: string,
@@ -33,4 +34,4 @@ export declare function statsErrorsToString(json: StatsCompilation, statsConfig:
33
34
  export declare function statsHasErrors(json: StatsCompilation): boolean;
34
35
  export declare function statsHasWarnings(json: StatsCompilation): boolean;
35
36
  export declare function createWebpackLoggingCallback(options: BrowserBuilderOptions, logger: logging.LoggerApi): WebpackLoggingCallback;
36
- export declare function webpackStatsLogger(logger: logging.LoggerApi, json: StatsCompilation, config: Configuration, bundleStats?: BundleStats[]): void;
37
+ export declare function webpackStatsLogger(logger: logging.LoggerApi, json: StatsCompilation, config: Configuration, budgetFailures?: BudgetCalculatorResult[]): void;
@@ -61,30 +61,59 @@ function generateBundleStats(info) {
61
61
  };
62
62
  }
63
63
  exports.generateBundleStats = generateBundleStats;
64
- function generateBuildStatsTable(data, colors, showTotalSize, showEstimatedTransferSize) {
64
+ function generateBuildStatsTable(data, colors, showTotalSize, showEstimatedTransferSize, budgetFailures) {
65
65
  const g = (x) => (colors ? color_1.colors.greenBright(x) : x);
66
66
  const c = (x) => (colors ? color_1.colors.cyanBright(x) : x);
67
+ const r = (x) => (colors ? color_1.colors.redBright(x) : x);
68
+ const y = (x) => (colors ? color_1.colors.yellowBright(x) : x);
67
69
  const bold = (x) => (colors ? color_1.colors.bold(x) : x);
68
70
  const dim = (x) => (colors ? color_1.colors.dim(x) : x);
71
+ const getSizeColor = (name, file, defaultColor = c) => {
72
+ const severity = budgets.get(name) || (file && budgets.get(file));
73
+ switch (severity) {
74
+ case 'warning':
75
+ return y;
76
+ case 'error':
77
+ return r;
78
+ default:
79
+ return defaultColor;
80
+ }
81
+ };
69
82
  const changedEntryChunksStats = [];
70
83
  const changedLazyChunksStats = [];
71
84
  let initialTotalRawSize = 0;
72
85
  let initialTotalEstimatedTransferSize;
86
+ const budgets = new Map();
87
+ if (budgetFailures) {
88
+ for (const { label, severity } of budgetFailures) {
89
+ // In some cases a file can have multiple budget failures.
90
+ // Favor error.
91
+ if (label && (!budgets.has(label) || budgets.get(label) === 'warning')) {
92
+ budgets.set(label, severity);
93
+ }
94
+ }
95
+ }
73
96
  for (const { initial, stats } of data) {
74
97
  const [files, names, rawSize, estimatedTransferSize] = stats;
98
+ const getRawSizeColor = getSizeColor(names, files);
75
99
  let data;
76
100
  if (showEstimatedTransferSize) {
77
101
  data = [
78
102
  g(files),
79
103
  names,
80
- c(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
104
+ getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
81
105
  c(typeof estimatedTransferSize === 'number'
82
106
  ? formatSize(estimatedTransferSize)
83
107
  : estimatedTransferSize),
84
108
  ];
85
109
  }
86
110
  else {
87
- data = [g(files), names, c(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize), ''];
111
+ data = [
112
+ g(files),
113
+ names,
114
+ getRawSizeColor(typeof rawSize === 'number' ? formatSize(rawSize) : rawSize),
115
+ '',
116
+ ];
88
117
  }
89
118
  if (initial) {
90
119
  changedEntryChunksStats.push(data);
@@ -114,7 +143,12 @@ function generateBuildStatsTable(data, colors, showTotalSize, showEstimatedTrans
114
143
  bundleInfo.push(['Initial Chunk Files', ...baseTitles].map(bold), ...changedEntryChunksStats);
115
144
  if (showTotalSize) {
116
145
  bundleInfo.push([]);
117
- const totalSizeElements = [' ', 'Initial Total', formatSize(initialTotalRawSize)];
146
+ const initialSizeTotalColor = getSizeColor('bundle initial', undefined, (x) => x);
147
+ const totalSizeElements = [
148
+ ' ',
149
+ 'Initial Total',
150
+ initialSizeTotalColor(formatSize(initialTotalRawSize)),
151
+ ];
118
152
  if (showEstimatedTransferSize) {
119
153
  totalSizeElements.push(typeof initialTotalEstimatedTransferSize === 'number'
120
154
  ? formatSize(initialTotalEstimatedTransferSize)
@@ -147,47 +181,45 @@ function generateBuildStats(hash, time, colors) {
147
181
  const runsCache = new Set();
148
182
  function statsToString(json,
149
183
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
150
- statsConfig, bundleState) {
184
+ statsConfig, budgetFailures) {
151
185
  var _a, _b;
152
186
  if (!((_a = json.chunks) === null || _a === void 0 ? void 0 : _a.length)) {
153
187
  return '';
154
188
  }
155
189
  const colors = statsConfig.colors;
156
190
  const rs = (x) => (colors ? color_1.colors.reset(x) : x);
157
- const changedChunksStats = bundleState !== null && bundleState !== void 0 ? bundleState : [];
191
+ const changedChunksStats = [];
158
192
  let unchangedChunkNumber = 0;
159
193
  let hasEstimatedTransferSizes = false;
160
- if (!(bundleState === null || bundleState === void 0 ? void 0 : bundleState.length)) {
161
- const isFirstRun = !runsCache.has(json.outputPath || '');
162
- for (const chunk of json.chunks) {
163
- // During first build we want to display unchanged chunks
164
- // but unchanged cached chunks are always marked as not rendered.
165
- if (!isFirstRun && !chunk.rendered) {
166
- continue;
167
- }
168
- const assets = (_b = json.assets) === null || _b === void 0 ? void 0 : _b.filter((asset) => { var _a; return (_a = chunk.files) === null || _a === void 0 ? void 0 : _a.includes(asset.name); });
169
- let rawSize = 0;
170
- let estimatedTransferSize;
171
- if (assets) {
172
- for (const asset of assets) {
173
- if (asset.name.endsWith('.map')) {
174
- continue;
175
- }
176
- rawSize += asset.size;
177
- if (typeof asset.info.estimatedTransferSize === 'number') {
178
- if (estimatedTransferSize === undefined) {
179
- estimatedTransferSize = 0;
180
- hasEstimatedTransferSizes = true;
181
- }
182
- estimatedTransferSize += asset.info.estimatedTransferSize;
194
+ const isFirstRun = !runsCache.has(json.outputPath || '');
195
+ for (const chunk of json.chunks) {
196
+ // During first build we want to display unchanged chunks
197
+ // but unchanged cached chunks are always marked as not rendered.
198
+ if (!isFirstRun && !chunk.rendered) {
199
+ continue;
200
+ }
201
+ const assets = (_b = json.assets) === null || _b === void 0 ? void 0 : _b.filter((asset) => { var _a; return (_a = chunk.files) === null || _a === void 0 ? void 0 : _a.includes(asset.name); });
202
+ let rawSize = 0;
203
+ let estimatedTransferSize;
204
+ if (assets) {
205
+ for (const asset of assets) {
206
+ if (asset.name.endsWith('.map')) {
207
+ continue;
208
+ }
209
+ rawSize += asset.size;
210
+ if (typeof asset.info.estimatedTransferSize === 'number') {
211
+ if (estimatedTransferSize === undefined) {
212
+ estimatedTransferSize = 0;
213
+ hasEstimatedTransferSizes = true;
183
214
  }
215
+ estimatedTransferSize += asset.info.estimatedTransferSize;
184
216
  }
185
217
  }
186
- changedChunksStats.push(generateBundleStats({ ...chunk, rawSize, estimatedTransferSize }));
187
218
  }
188
- unchangedChunkNumber = json.chunks.length - changedChunksStats.length;
189
- runsCache.add(json.outputPath || '');
219
+ changedChunksStats.push(generateBundleStats({ ...chunk, rawSize, estimatedTransferSize }));
190
220
  }
221
+ unchangedChunkNumber = json.chunks.length - changedChunksStats.length;
222
+ runsCache.add(json.outputPath || '');
191
223
  // Sort chunks by size in descending order
192
224
  changedChunksStats.sort((a, b) => {
193
225
  if (a.stats[2] > b.stats[2]) {
@@ -198,7 +230,7 @@ statsConfig, bundleState) {
198
230
  }
199
231
  return 0;
200
232
  });
201
- const statsTable = generateBuildStatsTable(changedChunksStats, colors, unchangedChunkNumber === 0, hasEstimatedTransferSizes);
233
+ const statsTable = generateBuildStatsTable(changedChunksStats, colors, unchangedChunkNumber === 0, hasEstimatedTransferSizes, budgetFailures);
202
234
  // In some cases we do things outside of webpack context
203
235
  // Such us index generation, service worker augmentation etc...
204
236
  // This will correct the time and include these.
@@ -320,8 +352,8 @@ function createWebpackLoggingCallback(options, logger) {
320
352
  };
321
353
  }
322
354
  exports.createWebpackLoggingCallback = createWebpackLoggingCallback;
323
- function webpackStatsLogger(logger, json, config, bundleStats) {
324
- logger.info(statsToString(json, config.stats, bundleStats));
355
+ function webpackStatsLogger(logger, json, config, budgetFailures) {
356
+ logger.info(statsToString(json, config.stats, budgetFailures));
325
357
  if (statsHasWarnings(json)) {
326
358
  logger.warn(statsWarningsToString(json, config.stats));
327
359
  }