@angular-devkit/build-angular 17.1.0-next.0 → 17.1.0-next.1

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": "17.1.0-next.0",
3
+ "version": "17.1.0-next.1",
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
9
  "@ampproject/remapping": "2.2.1",
10
- "@angular-devkit/architect": "0.1701.0-next.0",
11
- "@angular-devkit/build-webpack": "0.1701.0-next.0",
12
- "@angular-devkit/core": "17.1.0-next.0",
10
+ "@angular-devkit/architect": "0.1701.0-next.1",
11
+ "@angular-devkit/build-webpack": "0.1701.0-next.1",
12
+ "@angular-devkit/core": "17.1.0-next.1",
13
13
  "@babel/core": "7.23.5",
14
14
  "@babel/generator": "7.23.5",
15
15
  "@babel/helper-annotate-as-pure": "7.22.5",
@@ -20,14 +20,13 @@
20
20
  "@babel/preset-env": "7.23.5",
21
21
  "@babel/runtime": "7.23.5",
22
22
  "@discoveryjs/json-ext": "0.5.7",
23
- "@ngtools/webpack": "17.1.0-next.0",
23
+ "@ngtools/webpack": "17.1.0-next.1",
24
24
  "@vitejs/plugin-basic-ssl": "1.0.2",
25
25
  "ansi-colors": "4.1.3",
26
26
  "autoprefixer": "10.4.16",
27
27
  "babel-loader": "9.1.3",
28
28
  "babel-plugin-istanbul": "6.1.1",
29
29
  "browserslist": "^4.21.5",
30
- "browser-sync": "2.29.3",
31
30
  "chokidar": "3.5.3",
32
31
  "copy-webpack-plugin": "11.0.0",
33
32
  "critters": "0.0.20",
@@ -51,7 +50,7 @@
51
50
  "parse5-html-rewriting-stream": "7.0.0",
52
51
  "picomatch": "3.0.1",
53
52
  "piscina": "4.2.0",
54
- "postcss": "8.4.31",
53
+ "postcss": "8.4.32",
55
54
  "postcss-loader": "7.3.3",
56
55
  "resolve-url-loader": "5.0.0",
57
56
  "rxjs": "7.8.1",
@@ -60,12 +59,12 @@
60
59
  "semver": "7.5.4",
61
60
  "source-map-loader": "4.0.1",
62
61
  "source-map-support": "0.5.21",
63
- "terser": "5.24.0",
62
+ "terser": "5.25.0",
64
63
  "text-table": "0.2.0",
65
64
  "tree-kill": "1.2.2",
66
65
  "tslib": "2.6.2",
67
- "undici": "5.28.1",
68
- "vite": "5.0.4",
66
+ "undici": "6.0.1",
67
+ "vite": "5.0.6",
69
68
  "webpack": "5.89.0",
70
69
  "webpack-dev-middleware": "6.1.1",
71
70
  "webpack-dev-server": "4.15.1",
@@ -80,6 +79,7 @@
80
79
  "@angular/localize": "^17.0.0 || ^17.1.0-next.0",
81
80
  "@angular/platform-server": "^17.0.0 || ^17.1.0-next.0",
82
81
  "@angular/service-worker": "^17.0.0 || ^17.1.0-next.0",
82
+ "browser-sync": "^2.29.3",
83
83
  "jest": "^29.5.0",
84
84
  "jest-environment-jsdom": "^29.5.0",
85
85
  "karma": "^6.3.0",
@@ -98,6 +98,9 @@
98
98
  "@angular/service-worker": {
99
99
  "optional": true
100
100
  },
101
+ "browser-sync": {
102
+ "optional": true
103
+ },
101
104
  "jest": {
102
105
  "optional": true
103
106
  },
@@ -24,4 +24,5 @@ export declare function runEsBuildBuildAction(action: (rebuildState?: RebuildSta
24
24
  deleteOutputPath?: boolean;
25
25
  poll?: number;
26
26
  signal?: AbortSignal;
27
+ preserveSymlinks?: boolean;
27
28
  }): AsyncIterable<(ExecutionResult['outputWithFiles'] | ExecutionResult['output']) & BuilderOutput>;
@@ -40,7 +40,7 @@ const utils_1 = require("../../tools/esbuild/utils");
40
40
  const delete_output_dir_1 = require("../../utils/delete-output-dir");
41
41
  const environment_options_1 = require("../../utils/environment-options");
42
42
  async function* runEsBuildBuildAction(action, options) {
43
- const { writeToFileSystemFilter, writeToFileSystem = true, watch, poll, logger, deleteOutputPath, cacheOptions, outputPath, verbose, projectRoot, workspaceRoot, progress, } = options;
43
+ const { writeToFileSystemFilter, writeToFileSystem = true, watch, poll, logger, deleteOutputPath, cacheOptions, outputPath, verbose, projectRoot, workspaceRoot, progress, preserveSymlinks, } = options;
44
44
  if (deleteOutputPath && writeToFileSystem) {
45
45
  await (0, delete_output_dir_1.deleteOutputDir)(workspaceRoot, outputPath);
46
46
  }
@@ -67,6 +67,7 @@ async function* runEsBuildBuildAction(action, options) {
67
67
  watcher = createWatcher({
68
68
  polling: typeof poll === 'number',
69
69
  interval: poll,
70
+ followSymlinks: preserveSymlinks,
70
71
  ignored: [
71
72
  // Ignore the output and cache paths to avoid infinite rebuild cycles
72
73
  outputPath,
@@ -39,9 +39,11 @@ async function executePostBundleSteps(options, outputFiles, assetFiles, initialF
39
39
  * NOTE: we don't perform critical CSS inlining as this will be done during server rendering.
40
40
  */
41
41
  let indexContentOutputNoCssInlining;
42
+ // When using prerender/app-shell the index HTML file can be regenerated.
43
+ // Thus, we use a Map so that we do not generate 2 files with the same filename.
44
+ const additionalHtmlOutputFiles = new Map();
42
45
  // Generate index HTML file
43
46
  // If localization is enabled, index generation is handled in the inlining process.
44
- // NOTE: Localization with SSR is not currently supported.
45
47
  if (indexHtmlOptions) {
46
48
  const { content, contentWithoutCriticalCssInlined, errors, warnings } = await (0, index_html_generator_1.generateIndexHtml)(initialFiles, outputFiles, {
47
49
  ...options,
@@ -50,9 +52,10 @@ async function executePostBundleSteps(options, outputFiles, assetFiles, initialF
50
52
  indexContentOutputNoCssInlining = contentWithoutCriticalCssInlined;
51
53
  allErrors.push(...errors);
52
54
  allWarnings.push(...warnings);
53
- additionalOutputFiles.push((0, utils_1.createOutputFileFromText)(indexHtmlOptions.output, content, bundler_context_1.BuildOutputFileType.Browser));
55
+ additionalHtmlOutputFiles.set(indexHtmlOptions.output, (0, utils_1.createOutputFileFromText)(indexHtmlOptions.output, content, bundler_context_1.BuildOutputFileType.Browser));
54
56
  if (ssrOptions) {
55
- additionalOutputFiles.push((0, utils_1.createOutputFileFromText)('index.server.html', contentWithoutCriticalCssInlined, bundler_context_1.BuildOutputFileType.Server));
57
+ const serverIndexHtmlFilename = 'index.server.html';
58
+ additionalHtmlOutputFiles.set(serverIndexHtmlFilename, (0, utils_1.createOutputFileFromText)(serverIndexHtmlFilename, contentWithoutCriticalCssInlined, bundler_context_1.BuildOutputFileType.Server));
56
59
  }
57
60
  }
58
61
  // Pre-render (SSG) and App-shell
@@ -64,9 +67,10 @@ async function executePostBundleSteps(options, outputFiles, assetFiles, initialF
64
67
  allWarnings.push(...warnings);
65
68
  prerenderedRoutes.push(...Array.from(generatedRoutes));
66
69
  for (const [path, content] of Object.entries(output)) {
67
- additionalOutputFiles.push((0, utils_1.createOutputFileFromText)(path, content, bundler_context_1.BuildOutputFileType.Browser));
70
+ additionalHtmlOutputFiles.set(path, (0, utils_1.createOutputFileFromText)(path, content, bundler_context_1.BuildOutputFileType.Browser));
68
71
  }
69
72
  }
73
+ additionalOutputFiles.push(...additionalHtmlOutputFiles.values());
70
74
  // Augment the application with service worker support
71
75
  // If localization is enabled, service worker is handled in the inlining process.
72
76
  if (serviceWorker) {
@@ -29,6 +29,13 @@ context, infrastructureSettings, plugins) {
29
29
  return;
30
30
  }
31
31
  const normalizedOptions = await (0, options_1.normalizeOptions)(context, projectName, options, plugins);
32
+ // Setup an abort controller with a builder teardown if no signal is present
33
+ let signal = context.signal;
34
+ if (!signal) {
35
+ const controller = new AbortController();
36
+ signal = controller.signal;
37
+ context.addTeardown(() => controller.abort('builder-teardown'));
38
+ }
32
39
  yield* (0, build_action_1.runEsBuildBuildAction)(async (rebuildState) => {
33
40
  const startTime = process.hrtime.bigint();
34
41
  const result = await (0, execute_build_1.executeBuild)(normalizedOptions, context, rebuildState);
@@ -38,6 +45,7 @@ context, infrastructureSettings, plugins) {
38
45
  return result;
39
46
  }, {
40
47
  watch: normalizedOptions.watch,
48
+ preserveSymlinks: normalizedOptions.preserveSymlinks,
41
49
  poll: normalizedOptions.poll,
42
50
  deleteOutputPath: normalizedOptions.deleteOutputPath,
43
51
  cacheOptions: normalizedOptions.cacheOptions,
@@ -53,7 +61,7 @@ context, infrastructureSettings, plugins) {
53
61
  ? undefined
54
62
  : (file) => file.type !== bundler_context_1.BuildOutputFileType.Server,
55
63
  logger: context.logger,
56
- signal: context.signal,
64
+ signal,
57
65
  });
58
66
  }
59
67
  exports.buildApplicationInternal = buildApplicationInternal;
@@ -181,7 +181,7 @@ async function normalizeOptions(context, projectName, options, plugins) {
181
181
  const { allowedCommonJsDependencies, aot, baseHref, crossOrigin, externalDependencies, extractLicenses, inlineStyleLanguage = 'css', outExtension, serviceWorker, poll, polyfills, statsJson, stylePreprocessorOptions, subresourceIntegrity, verbose, watch, progress = true, externalPackages, deleteOutputPath, namedChunks, budgets, deployUrl, } = options;
182
182
  // Return all the normalized options
183
183
  return {
184
- advancedOptimizations: !!aot,
184
+ advancedOptimizations: !!aot && optimizationOptions.scripts,
185
185
  allowedCommonJsDependencies,
186
186
  baseHref,
187
187
  cacheOptions,
@@ -157,7 +157,7 @@ async function* serveWithVite(serverOptions, builderName, context, transformers,
157
157
  assetFiles.clear();
158
158
  if (result.assetFiles) {
159
159
  for (const asset of result.assetFiles) {
160
- assetFiles.set('/' + normalizePath(asset.destination), asset.source);
160
+ assetFiles.set('/' + normalizePath(asset.destination), normalizePath(asset.source));
161
161
  }
162
162
  }
163
163
  // To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced.
@@ -35,7 +35,6 @@ const architect_1 = require("@angular-devkit/architect");
35
35
  const core_1 = require("@angular-devkit/core");
36
36
  const path_1 = require("path");
37
37
  const rxjs_1 = require("rxjs");
38
- const operators_1 = require("rxjs/operators");
39
38
  const url = __importStar(require("url"));
40
39
  const error_1 = require("../../utils/error");
41
40
  const utils_1 = require("./utils");
@@ -60,7 +59,17 @@ function execute(options, context) {
60
59
  progress: options.progress,
61
60
  verbose: options.verbose,
62
61
  });
63
- const bsInstance = require('browser-sync').create();
62
+ let browserSync;
63
+ try {
64
+ browserSync = require('browser-sync');
65
+ }
66
+ catch {
67
+ return (0, rxjs_1.of)({
68
+ success: false,
69
+ error: '"browser-sync" is not installed, most likely you need to run `npm install browser-sync --save-dev` in your project.',
70
+ });
71
+ }
72
+ const bsInstance = browserSync.create();
64
73
  context.logger.error(core_1.tags.stripIndents `
65
74
  ****************************************************************************************
66
75
  This is a simple server for use in testing or debugging Angular applications locally.
@@ -69,35 +78,35 @@ function execute(options, context) {
69
78
  DON'T USE IT FOR PRODUCTION!
70
79
  ****************************************************************************************
71
80
  `);
72
- return (0, rxjs_1.zip)(browserTargetRun, serverTargetRun, (0, utils_1.getAvailablePort)()).pipe((0, operators_1.switchMap)(([br, sr, nodeServerPort]) => {
81
+ return (0, rxjs_1.zip)(browserTargetRun, serverTargetRun, (0, utils_1.getAvailablePort)()).pipe((0, rxjs_1.switchMap)(([br, sr, nodeServerPort]) => {
73
82
  return (0, rxjs_1.combineLatest)([br.output, sr.output]).pipe(
74
83
  // This is needed so that if both server and browser emit close to each other
75
84
  // we only emit once. This typically happens on the first build.
76
- (0, operators_1.debounceTime)(120), (0, operators_1.switchMap)(([b, s]) => {
85
+ (0, rxjs_1.debounceTime)(120), (0, rxjs_1.switchMap)(([b, s]) => {
77
86
  if (!s.success || !b.success) {
78
87
  return (0, rxjs_1.of)([b, s]);
79
88
  }
80
- return startNodeServer(s, nodeServerPort, context.logger, !!options.inspect).pipe((0, operators_1.mapTo)([b, s]), (0, operators_1.catchError)((err) => {
89
+ return startNodeServer(s, nodeServerPort, context.logger, !!options.inspect).pipe((0, rxjs_1.map)(() => [b, s]), (0, rxjs_1.catchError)((err) => {
81
90
  context.logger.error(`A server error has occurred.\n${mapErrorToMessage(err)}`);
82
91
  return rxjs_1.EMPTY;
83
92
  }));
84
- }), (0, operators_1.map)(([b, s]) => [
93
+ }), (0, rxjs_1.map)(([b, s]) => [
85
94
  {
86
95
  success: b.success && s.success,
87
96
  error: b.error || s.error,
88
97
  },
89
98
  nodeServerPort,
90
- ]), (0, operators_1.tap)(([builderOutput]) => {
99
+ ]), (0, rxjs_1.tap)(([builderOutput]) => {
91
100
  if (builderOutput.success) {
92
101
  context.logger.info('\nCompiled successfully.');
93
102
  }
94
- }), (0, operators_1.debounce)(([builderOutput]) => builderOutput.success && !options.inspect
103
+ }), (0, rxjs_1.debounce)(([builderOutput]) => builderOutput.success && !options.inspect
95
104
  ? (0, utils_1.waitUntilServerIsListening)(nodeServerPort)
96
- : rxjs_1.EMPTY), (0, operators_1.finalize)(() => {
105
+ : rxjs_1.EMPTY), (0, rxjs_1.finalize)(() => {
97
106
  void br.stop();
98
107
  void sr.stop();
99
108
  }));
100
- }), (0, operators_1.concatMap)(([builderOutput, nodeServerPort]) => {
109
+ }), (0, rxjs_1.concatMap)(([builderOutput, nodeServerPort]) => {
101
110
  if (!builderOutput.success) {
102
111
  return (0, rxjs_1.of)(builderOutput);
103
112
  }
@@ -106,7 +115,7 @@ function execute(options, context) {
106
115
  return (0, rxjs_1.of)(builderOutput);
107
116
  }
108
117
  else {
109
- return (0, rxjs_1.from)(initBrowserSync(bsInstance, nodeServerPort, options, context)).pipe((0, operators_1.tap)((bs) => {
118
+ return (0, rxjs_1.from)(initBrowserSync(bsInstance, nodeServerPort, options, context)).pipe((0, rxjs_1.tap)((bs) => {
110
119
  const baseUrl = getBaseUrl(bs);
111
120
  context.logger.info(core_1.tags.oneLine `
112
121
  **
@@ -114,19 +123,19 @@ function execute(options, context) {
114
123
  open your browser on ${baseUrl}
115
124
  **
116
125
  `);
117
- }), (0, operators_1.mapTo)(builderOutput));
126
+ }), (0, rxjs_1.map)(() => builderOutput));
118
127
  }
119
- }), (0, operators_1.map)((builderOutput) => ({
128
+ }), (0, rxjs_1.map)((builderOutput) => ({
120
129
  success: builderOutput.success,
121
130
  error: builderOutput.error,
122
131
  baseUrl: getBaseUrl(bsInstance),
123
132
  port: bsInstance.getOption('port'),
124
- })), (0, operators_1.finalize)(() => {
133
+ })), (0, rxjs_1.finalize)(() => {
125
134
  if (bsInstance) {
126
135
  bsInstance.exit();
127
136
  bsInstance.cleanup();
128
137
  }
129
- }), (0, operators_1.catchError)((error) => (0, rxjs_1.of)({
138
+ }), (0, rxjs_1.catchError)((error) => (0, rxjs_1.of)({
130
139
  success: false,
131
140
  error: mapErrorToMessage(error),
132
141
  })));
@@ -152,10 +161,10 @@ function startNodeServer(serverOutput, port, logger, inspectMode = false) {
152
161
  if (inspectMode) {
153
162
  args.unshift('--inspect-brk');
154
163
  }
155
- return (0, rxjs_1.of)(null).pipe((0, operators_1.delay)(0), // Avoid EADDRINUSE error since it will cause the kill event to be finish.
156
- (0, operators_1.switchMap)(() => (0, utils_1.spawnAsObservable)('node', args, { env, shell: true })), (0, operators_1.tap)((res) => log({ stderr: res.stderr, stdout: res.stdout }, logger)), (0, operators_1.ignoreElements)(),
164
+ return (0, rxjs_1.of)(null).pipe((0, rxjs_1.delay)(0), // Avoid EADDRINUSE error since it will cause the kill event to be finish.
165
+ (0, rxjs_1.switchMap)(() => (0, utils_1.spawnAsObservable)('node', args, { env, shell: true })), (0, rxjs_1.tap)((res) => log({ stderr: res.stderr, stdout: res.stdout }, logger)), (0, rxjs_1.ignoreElements)(),
157
166
  // Emit a signal after the process has been started
158
- (0, operators_1.startWith)(undefined));
167
+ (0, rxjs_1.startWith)(undefined));
159
168
  }
160
169
  async function initBrowserSync(browserSyncInstance, nodeServerPort, options, context) {
161
170
  if (browserSyncInstance.active) {
@@ -6,6 +6,10 @@
6
6
  * found in the LICENSE file at https://angular.io/license
7
7
  */
8
8
  import type { Plugin } from 'esbuild';
9
+ /**
10
+ * The internal namespace used by generated locale import statements and Angular locale data plugin.
11
+ */
12
+ export declare const LOCALE_DATA_NAMESPACE = "angular:locale/data";
9
13
  /**
10
14
  * The base module location used to search for locale specific data.
11
15
  */
@@ -7,7 +7,11 @@
7
7
  * found in the LICENSE file at https://angular.io/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.createAngularLocaleDataPlugin = exports.LOCALE_DATA_BASE_MODULE = void 0;
10
+ exports.createAngularLocaleDataPlugin = exports.LOCALE_DATA_BASE_MODULE = exports.LOCALE_DATA_NAMESPACE = void 0;
11
+ /**
12
+ * The internal namespace used by generated locale import statements and Angular locale data plugin.
13
+ */
14
+ exports.LOCALE_DATA_NAMESPACE = 'angular:locale/data';
11
15
  /**
12
16
  * The base module location used to search for locale specific data.
13
17
  */
@@ -33,12 +37,37 @@ function createAngularLocaleDataPlugin() {
33
37
  }
34
38
  build.onResolve({ filter: /^angular:locale\/data:/ }, async ({ path }) => {
35
39
  // Extract the locale from the path
36
- const originalLocale = path.split(':', 3)[2];
37
- // Remove any private subtags since these will never match
38
- let partialLocale = originalLocale.replace(/-x(-[a-zA-Z0-9]{1,8})+$/, '');
40
+ const rawLocaleTag = path.split(':', 3)[2];
41
+ // Extract and normalize the base name of the raw locale tag
42
+ let partialLocaleTag;
43
+ try {
44
+ const locale = new Intl.Locale(rawLocaleTag);
45
+ partialLocaleTag = locale.baseName;
46
+ }
47
+ catch {
48
+ return {
49
+ path: rawLocaleTag,
50
+ namespace: exports.LOCALE_DATA_NAMESPACE,
51
+ errors: [
52
+ {
53
+ text: `Invalid or unsupported locale provided in configuration: "${rawLocaleTag}"`,
54
+ },
55
+ ],
56
+ };
57
+ }
39
58
  let exact = true;
40
- while (partialLocale) {
41
- const potentialPath = `${exports.LOCALE_DATA_BASE_MODULE}/${partialLocale}`;
59
+ while (partialLocaleTag) {
60
+ // Angular embeds the `en`/`en-US` locale into the framework and it does not need to be included again here.
61
+ // The onLoad hook below for the locale data namespace has an `empty` loader that will prevent inclusion.
62
+ // Angular does not contain exact locale data for `en-US` but `en` is equivalent.
63
+ if (partialLocaleTag === 'en' || partialLocaleTag === 'en-US') {
64
+ return {
65
+ path: rawLocaleTag,
66
+ namespace: exports.LOCALE_DATA_NAMESPACE,
67
+ };
68
+ }
69
+ // Attempt to resolve the locale tag data within the Angular base module location
70
+ const potentialPath = `${exports.LOCALE_DATA_BASE_MODULE}/${partialLocaleTag}`;
42
71
  const result = await build.resolve(potentialPath, {
43
72
  kind: 'import-statement',
44
73
  resolveDir: build.initialOptions.absWorkingDir,
@@ -54,36 +83,37 @@ function createAngularLocaleDataPlugin() {
54
83
  ...result.warnings,
55
84
  {
56
85
  location: null,
57
- text: `Locale data for '${originalLocale}' cannot be found. Using locale data for '${partialLocale}'.`,
86
+ text: `Locale data for '${rawLocaleTag}' cannot be found. Using locale data for '${partialLocaleTag}'.`,
58
87
  },
59
88
  ],
60
89
  };
61
90
  }
62
91
  }
63
- // Remove the last subtag and try again with a less specific locale
64
- const parts = partialLocale.split('-');
65
- partialLocale = parts.slice(0, -1).join('-');
92
+ // Remove the last subtag and try again with a less specific locale.
93
+ // Usually the match is exact so the string splitting here is not done until actually needed after the exact
94
+ // match fails to resolve.
95
+ const parts = partialLocaleTag.split('-');
96
+ partialLocaleTag = parts.slice(0, -1).join('-');
66
97
  exact = false;
67
- // The locales "en" and "en-US" are considered exact to retain existing behavior
68
- if (originalLocale === 'en-US' && partialLocale === 'en') {
69
- exact = true;
70
- }
71
98
  }
72
99
  // Not found so issue a warning and use an empty loader. Framework built-in `en-US` data will be used.
73
100
  // This retains existing behavior as in the Webpack-based builder.
74
101
  return {
75
- path: originalLocale,
76
- namespace: 'angular:locale/data',
102
+ path: rawLocaleTag,
103
+ namespace: exports.LOCALE_DATA_NAMESPACE,
77
104
  warnings: [
78
105
  {
79
106
  location: null,
80
- text: `Locale data for '${originalLocale}' cannot be found. No locale data will be included for this locale.`,
107
+ text: `Locale data for '${rawLocaleTag}' cannot be found. No locale data will be included for this locale.`,
81
108
  },
82
109
  ],
83
110
  };
84
111
  });
85
112
  // Locales that cannot be found will be loaded as empty content with a warning from the resolve step
86
- build.onLoad({ filter: /./, namespace: 'angular:locale/data' }, () => ({ loader: 'empty' }));
113
+ build.onLoad({ filter: /./, namespace: exports.LOCALE_DATA_NAMESPACE }, () => ({
114
+ contents: '',
115
+ loader: 'empty',
116
+ }));
87
117
  },
88
118
  };
89
119
  }
@@ -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 declare function normalizePath(path: string): string;
@@ -0,0 +1,22 @@
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.normalizePath = void 0;
11
+ const node_os_1 = require("node:os");
12
+ const node_path_1 = require("node:path");
13
+ const USING_WINDOWS = (0, node_os_1.platform)() === 'win32';
14
+ function normalizePath(path) {
15
+ if (USING_WINDOWS) {
16
+ return (0, node_path_1.normalize)(path).toLowerCase();
17
+ }
18
+ else {
19
+ return (0, node_path_1.normalize)(path);
20
+ }
21
+ }
22
+ exports.normalizePath = normalizePath;
@@ -21,4 +21,5 @@ export declare function createWatcher(options?: {
21
21
  polling?: boolean;
22
22
  interval?: number;
23
23
  ignored?: string[];
24
+ followSymlinks?: boolean;
24
25
  }): BuildWatcher;
@@ -32,6 +32,7 @@ function createWatcher(options) {
32
32
  usePolling: options?.polling,
33
33
  interval: options?.interval,
34
34
  ignored: options?.ignored,
35
+ followSymlinks: options?.followSymlinks,
35
36
  disableGlobbing: true,
36
37
  ignoreInitial: true,
37
38
  });
@@ -83,52 +84,23 @@ function createWatcher(options) {
83
84
  * ... (Nothing)
84
85
  * ```
85
86
  */
86
- watcher.on('raw', (event, path, { watchedPath }) => {
87
+ watcher
88
+ .on('raw', (event, path, { watchedPath }) => {
89
+ if (watchedPath && !(0, node_path_1.extname)(watchedPath)) {
90
+ // Ignore directories, file changes in directories will be fired seperatly.
91
+ return;
92
+ }
87
93
  switch (event) {
88
- case 'add':
89
- case 'change':
90
- // When using Visual Studio the rename event is fired before a change event when the contents of the file changed
91
- // or instead of `unlink` when the file has been renamed.
92
- case 'unlink':
93
94
  case 'rename':
95
+ case 'change':
94
96
  // When polling is enabled `watchedPath` can be undefined.
95
97
  // `path` is always normalized unlike `watchedPath`.
96
98
  const changedPath = watchedPath ? (0, node_path_1.normalize)(watchedPath) : path;
97
- currentEvents ??= new Map();
98
- currentEvents.set(changedPath, event);
99
+ handleFileChange(event, changedPath);
99
100
  break;
100
- default:
101
- return;
102
- }
103
- // Wait 250ms from next change to better capture groups of file save operations.
104
- if (!nextWaitTimeout) {
105
- nextWaitTimeout = setTimeout(() => {
106
- nextWaitTimeout = undefined;
107
- const next = nextQueue.shift();
108
- if (next && currentEvents) {
109
- const events = currentEvents;
110
- currentEvents = undefined;
111
- const currentChanges = new ChangedFiles();
112
- for (const [path, event] of events) {
113
- switch (event) {
114
- case 'add':
115
- currentChanges.added.add(path);
116
- break;
117
- case 'change':
118
- currentChanges.modified.add(path);
119
- break;
120
- case 'unlink':
121
- case 'rename':
122
- currentChanges.removed.add(path);
123
- break;
124
- }
125
- }
126
- next(currentChanges);
127
- }
128
- }, 250);
129
- nextWaitTimeout?.unref();
130
101
  }
131
- });
102
+ })
103
+ .on('all', handleFileChange);
132
104
  return {
133
105
  [Symbol.asyncIterator]() {
134
106
  return this;
@@ -164,5 +136,48 @@ function createWatcher(options) {
164
136
  }
165
137
  },
166
138
  };
139
+ function handleFileChange(event, path) {
140
+ switch (event) {
141
+ case 'add':
142
+ case 'change':
143
+ // When using Visual Studio the rename event is fired before a change event when the contents of the file changed
144
+ // or instead of `unlink` when the file has been renamed.
145
+ case 'unlink':
146
+ case 'rename':
147
+ currentEvents ??= new Map();
148
+ currentEvents.set(path, event);
149
+ break;
150
+ default:
151
+ return;
152
+ }
153
+ // Wait 250ms from next change to better capture groups of file save operations.
154
+ if (!nextWaitTimeout) {
155
+ nextWaitTimeout = setTimeout(() => {
156
+ nextWaitTimeout = undefined;
157
+ const next = nextQueue.shift();
158
+ if (next && currentEvents) {
159
+ const events = currentEvents;
160
+ currentEvents = undefined;
161
+ const currentChanges = new ChangedFiles();
162
+ for (const [path, event] of events) {
163
+ switch (event) {
164
+ case 'add':
165
+ currentChanges.added.add(path);
166
+ break;
167
+ case 'change':
168
+ currentChanges.modified.add(path);
169
+ break;
170
+ case 'unlink':
171
+ case 'rename':
172
+ currentChanges.removed.add(path);
173
+ break;
174
+ }
175
+ }
176
+ next(currentChanges);
177
+ }
178
+ }, 250);
179
+ nextWaitTimeout?.unref();
180
+ }
181
+ }
167
182
  }
168
183
  exports.createWatcher = createWatcher;
@@ -16,6 +16,25 @@ const node_fs_1 = require("node:fs");
16
16
  const node_path_1 = require("node:path");
17
17
  const node_url_1 = require("node:url");
18
18
  const lexer_1 = require("./lexer");
19
+ /**
20
+ * Ensures that a bare specifier URL path that is intended to be treated as
21
+ * a relative path has a leading `./` or `../` prefix.
22
+ *
23
+ * @param url A bare specifier URL path that should be considered relative.
24
+ * @returns
25
+ */
26
+ function ensureRelative(url) {
27
+ // Empty
28
+ if (!url) {
29
+ return url;
30
+ }
31
+ // Already relative
32
+ if (url[0] === '.' && (url[1] === '/' || (url[1] === '.' && url[2] === '/'))) {
33
+ return url;
34
+ }
35
+ // Needs prefix
36
+ return './' + url;
37
+ }
19
38
  /**
20
39
  * A Sass Importer base class that provides the load logic to rebase all `url()` functions
21
40
  * within a stylesheet. The rebasing will ensure that the URLs in the output of the Sass compiler
@@ -46,8 +65,13 @@ class UrlRebasingImporter {
46
65
  // Rebase any URLs that are found
47
66
  let updatedContents;
48
67
  for (const { start, end, value } of (0, lexer_1.findUrls)(contents)) {
49
- // Skip if value is empty, a Sass variable, or Webpack-specific prefix
50
- if (value.length === 0 || value[0] === '$' || value[0] === '~' || value[0] === '^') {
68
+ // Skip if value is empty or Webpack-specific prefix
69
+ if (value.length === 0 || value[0] === '~' || value[0] === '^') {
70
+ continue;
71
+ }
72
+ // Skip if value is a Sass variable.
73
+ // Sass variable usage either starts with a `$` or contains a namespace and a `.$`
74
+ if (value[0] === '$' || /^\w+\.\$/.test(value)) {
51
75
  continue;
52
76
  }
53
77
  // Skip if root-relative, absolute or protocol relative url
@@ -57,9 +81,10 @@ class UrlRebasingImporter {
57
81
  const rebasedPath = (0, node_path_1.relative)(this.entryDirectory, (0, node_path_1.join)(stylesheetDirectory, value));
58
82
  // Normalize path separators and escape characters
59
83
  // https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax
60
- const rebasedUrl = './' + rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&');
84
+ const rebasedUrl = ensureRelative(rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&'));
61
85
  updatedContents ??= new magic_string_1.default(contents);
62
- updatedContents.update(start, end, rebasedUrl);
86
+ // Always quote the URL to avoid potential downstream parsing problems
87
+ updatedContents.update(start, end, `"${rebasedUrl}"`);
63
88
  }
64
89
  if (updatedContents) {
65
90
  contents = updatedContents.toString();
@@ -7,9 +7,7 @@
7
7
  * found in the LICENSE file at https://angular.io/license
8
8
  */
9
9
  Object.defineProperty(exports, "__esModule", { value: true });
10
- // TODO: remove the below once @types/node are version 20.x.x
11
- // @ts-expect-error "node:module"' has no exported member 'register'.ts(2305)
12
10
  const node_module_1 = require("node:module");
13
11
  const node_url_1 = require("node:url");
14
12
  const node_worker_threads_1 = require("node:worker_threads");
15
- (0, node_module_1.register)('./loader-hooks.js', (0, node_url_1.pathToFileURL)(__filename), { data: node_worker_threads_1.workerData });
13
+ (0, node_module_1.register)('./loader-hooks.js', { parentURL: (0, node_url_1.pathToFileURL)(__filename), data: node_worker_threads_1.workerData });
@@ -21,7 +21,7 @@ class Spinner {
21
21
  #isTTY = (0, tty_1.isTTY)();
22
22
  constructor(text) {
23
23
  this.spinner = (0, ora_1.default)({
24
- text,
24
+ text: text === undefined ? undefined : text + '\n',
25
25
  // The below 2 options are needed because otherwise CTRL+C will be delayed
26
26
  // when the underlying process is sync.
27
27
  hideCursor: false,