@angular-devkit/build-angular 15.0.0 → 15.1.0-next.0

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.
@@ -15,11 +15,6 @@ const magic_string_1 = __importDefault(require("magic-string"));
15
15
  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
- /**
19
- * A Regular expression used to find all `url()` functions within a stylesheet.
20
- * From packages/angular_devkit/build_angular/src/webpack/plugins/postcss-cli-resources.ts
21
- */
22
- const URL_REGEXP = /url(?:\(\s*(['"]?))(.*?)(?:\1\s*\))/g;
23
18
  /**
24
19
  * A Sass Importer base class that provides the load logic to rebase all `url()` functions
25
20
  * within a stylesheet. The rebasing will ensure that the URLs in the output of the Sass compiler
@@ -43,37 +38,36 @@ class UrlRebasingImporter {
43
38
  }
44
39
  load(canonicalUrl) {
45
40
  const stylesheetPath = (0, node_url_1.fileURLToPath)(canonicalUrl);
41
+ const stylesheetDirectory = (0, node_path_1.dirname)(stylesheetPath);
46
42
  let contents = (0, node_fs_1.readFileSync)(stylesheetPath, 'utf-8');
47
43
  // Rebase any URLs that are found
48
- if (contents.includes('url(')) {
49
- const stylesheetDirectory = (0, node_path_1.dirname)(stylesheetPath);
50
- let match;
51
- URL_REGEXP.lastIndex = 0;
52
- let updatedContents;
53
- while ((match = URL_REGEXP.exec(contents))) {
54
- const originalUrl = match[2];
55
- // If root-relative, absolute or protocol relative url, leave as-is
56
- if (/^((?:\w+:)?\/\/|data:|chrome:|#|\/)/.test(originalUrl)) {
57
- continue;
58
- }
59
- const rebasedPath = (0, node_path_1.relative)(this.entryDirectory, (0, node_path_1.join)(stylesheetDirectory, originalUrl));
60
- // Normalize path separators and escape characters
61
- // https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax
62
- const rebasedUrl = './' + rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&');
63
- updatedContents !== null && updatedContents !== void 0 ? updatedContents : (updatedContents = new magic_string_1.default(contents));
64
- updatedContents.update(match.index, match.index + match[0].length, `url(${rebasedUrl})`);
44
+ let updatedContents;
45
+ for (const { start, end, value } of findUrls(contents)) {
46
+ // Skip if value is empty or a Sass variable
47
+ if (value.length === 0 || value.startsWith('$')) {
48
+ continue;
65
49
  }
66
- if (updatedContents) {
67
- contents = updatedContents.toString();
68
- if (this.rebaseSourceMaps) {
69
- // Generate an intermediate source map for the rebasing changes
70
- const map = updatedContents.generateMap({
71
- hires: true,
72
- includeContent: true,
73
- source: canonicalUrl.href,
74
- });
75
- this.rebaseSourceMaps.set(canonicalUrl.href, map);
76
- }
50
+ // Skip if root-relative, absolute or protocol relative url
51
+ if (/^((?:\w+:)?\/\/|data:|chrome:|#|\/)/.test(value)) {
52
+ continue;
53
+ }
54
+ const rebasedPath = (0, node_path_1.relative)(this.entryDirectory, (0, node_path_1.join)(stylesheetDirectory, value));
55
+ // Normalize path separators and escape characters
56
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax
57
+ const rebasedUrl = './' + rebasedPath.replace(/\\/g, '/').replace(/[()\s'"]/g, '\\$&');
58
+ updatedContents !== null && updatedContents !== void 0 ? updatedContents : (updatedContents = new magic_string_1.default(contents));
59
+ updatedContents.update(start, end, rebasedUrl);
60
+ }
61
+ if (updatedContents) {
62
+ contents = updatedContents.toString();
63
+ if (this.rebaseSourceMaps) {
64
+ // Generate an intermediate source map for the rebasing changes
65
+ const map = updatedContents.generateMap({
66
+ hires: true,
67
+ includeContent: true,
68
+ source: canonicalUrl.href,
69
+ });
70
+ this.rebaseSourceMaps.set(canonicalUrl.href, map);
77
71
  }
78
72
  }
79
73
  let syntax;
@@ -95,6 +89,156 @@ class UrlRebasingImporter {
95
89
  };
96
90
  }
97
91
  }
92
+ /**
93
+ * Determines if a unicode code point is a CSS whitespace character.
94
+ * @param code The unicode code point to test.
95
+ * @returns true, if the code point is CSS whitespace; false, otherwise.
96
+ */
97
+ function isWhitespace(code) {
98
+ // Based on https://www.w3.org/TR/css-syntax-3/#whitespace
99
+ switch (code) {
100
+ case 0x0009: // tab
101
+ case 0x0020: // space
102
+ case 0x000a: // line feed
103
+ case 0x000c: // form feed
104
+ case 0x000d: // carriage return
105
+ return true;
106
+ default:
107
+ return false;
108
+ }
109
+ }
110
+ /**
111
+ * Scans a CSS or Sass file and locates all valid url function values as defined by the CSS
112
+ * syntax specification.
113
+ * @param contents A string containing a CSS or Sass file to scan.
114
+ * @returns An iterable that yields each CSS url function value found.
115
+ */
116
+ function* findUrls(contents) {
117
+ let pos = 0;
118
+ let width = 1;
119
+ let current = -1;
120
+ const next = () => {
121
+ var _a;
122
+ pos += width;
123
+ current = (_a = contents.codePointAt(pos)) !== null && _a !== void 0 ? _a : -1;
124
+ width = current > 0xffff ? 2 : 1;
125
+ return current;
126
+ };
127
+ // Based on https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token
128
+ while ((pos = contents.indexOf('url(', pos)) !== -1) {
129
+ // Set to position of the (
130
+ pos += 3;
131
+ width = 1;
132
+ // Consume all leading whitespace
133
+ while (isWhitespace(next())) {
134
+ /* empty */
135
+ }
136
+ // Initialize URL state
137
+ const url = { start: pos, end: -1, value: '' };
138
+ let complete = false;
139
+ // If " or ', then consume the value as a string
140
+ if (current === 0x0022 || current === 0x0027) {
141
+ const ending = current;
142
+ // Based on https://www.w3.org/TR/css-syntax-3/#consume-string-token
143
+ while (!complete) {
144
+ switch (next()) {
145
+ case -1: // EOF
146
+ return;
147
+ case 0x000a: // line feed
148
+ case 0x000c: // form feed
149
+ case 0x000d: // carriage return
150
+ // Invalid
151
+ complete = true;
152
+ break;
153
+ case 0x005c: // \ -- character escape
154
+ // If not EOF or newline, add the character after the escape
155
+ switch (next()) {
156
+ case -1:
157
+ return;
158
+ case 0x000a: // line feed
159
+ case 0x000c: // form feed
160
+ case 0x000d: // carriage return
161
+ // Skip when inside a string
162
+ break;
163
+ default:
164
+ // TODO: Handle hex escape codes
165
+ url.value += String.fromCodePoint(current);
166
+ break;
167
+ }
168
+ break;
169
+ case ending:
170
+ // Full string position should include the quotes for replacement
171
+ url.end = pos + 1;
172
+ complete = true;
173
+ yield url;
174
+ break;
175
+ default:
176
+ url.value += String.fromCodePoint(current);
177
+ break;
178
+ }
179
+ }
180
+ next();
181
+ continue;
182
+ }
183
+ // Based on https://www.w3.org/TR/css-syntax-3/#consume-url-token
184
+ while (!complete) {
185
+ switch (current) {
186
+ case -1: // EOF
187
+ return;
188
+ case 0x0022: // "
189
+ case 0x0027: // '
190
+ case 0x0028: // (
191
+ // Invalid
192
+ complete = true;
193
+ break;
194
+ case 0x0029: // )
195
+ // URL is valid and complete
196
+ url.end = pos;
197
+ complete = true;
198
+ break;
199
+ case 0x005c: // \ -- character escape
200
+ // If not EOF or newline, add the character after the escape
201
+ switch (next()) {
202
+ case -1: // EOF
203
+ return;
204
+ case 0x000a: // line feed
205
+ case 0x000c: // form feed
206
+ case 0x000d: // carriage return
207
+ // Invalid
208
+ complete = true;
209
+ break;
210
+ default:
211
+ // TODO: Handle hex escape codes
212
+ url.value += String.fromCodePoint(current);
213
+ break;
214
+ }
215
+ break;
216
+ default:
217
+ if (isWhitespace(current)) {
218
+ while (isWhitespace(next())) {
219
+ /* empty */
220
+ }
221
+ // Unescaped whitespace is only valid before the closing )
222
+ if (current === 0x0029) {
223
+ // URL is valid
224
+ url.end = pos;
225
+ }
226
+ complete = true;
227
+ }
228
+ else {
229
+ // Add the character to the url value
230
+ url.value += String.fromCodePoint(current);
231
+ }
232
+ break;
233
+ }
234
+ next();
235
+ }
236
+ // An end position indicates a URL was found
237
+ if (url.end !== -1) {
238
+ yield url;
239
+ }
240
+ }
241
+ }
98
242
  /**
99
243
  * Provides the Sass importer logic to resolve relative stylesheet imports via both import and use rules
100
244
  * and also rebase any `url()` function usage within those stylesheets. The rebasing will ensure that
@@ -131,17 +275,6 @@ class RelativeUrlRebasingImporter extends UrlRebasingImporter {
131
275
  const hasStyleExtension = extension === '.scss' || extension === '.sass' || extension === '.css';
132
276
  // Remove the style extension if present to allow adding the `.import` suffix
133
277
  const filename = (0, node_path_1.basename)(stylesheetPath, hasStyleExtension ? extension : undefined);
134
- let entries;
135
- try {
136
- entries = this.directoryCache.get(directory);
137
- if (!entries) {
138
- entries = (0, node_fs_1.readdirSync)(directory, { withFileTypes: true });
139
- this.directoryCache.set(directory, entries);
140
- }
141
- }
142
- catch {
143
- return null;
144
- }
145
278
  const importPotentials = new Set();
146
279
  const defaultPotentials = new Set();
147
280
  if (hasStyleExtension) {
@@ -168,37 +301,69 @@ class RelativeUrlRebasingImporter extends UrlRebasingImporter {
168
301
  defaultPotentials.add('_' + filename + '.sass');
169
302
  defaultPotentials.add('_' + filename + '.css');
170
303
  }
171
- const foundDefaults = [];
172
- const foundImports = [];
304
+ let foundDefaults;
305
+ let foundImports;
173
306
  let hasPotentialIndex = false;
174
- for (const entry of entries) {
175
- // Record if the name should be checked as a directory with an index file
176
- if (checkDirectory && !hasStyleExtension && entry.name === filename && entry.isDirectory()) {
177
- hasPotentialIndex = true;
178
- }
179
- if (!entry.isFile()) {
180
- continue;
307
+ let cachedEntries = this.directoryCache.get(directory);
308
+ if (cachedEntries) {
309
+ // If there is a preprocessed cache of the directory, perform an intersection of the potentials
310
+ // and the directory files.
311
+ const { files, directories } = cachedEntries;
312
+ foundDefaults = [...defaultPotentials].filter((potential) => files.has(potential));
313
+ foundImports = [...importPotentials].filter((potential) => files.has(potential));
314
+ hasPotentialIndex = checkDirectory && !hasStyleExtension && directories.has(filename);
315
+ }
316
+ else {
317
+ // If no preprocessed cache exists, get the entries from the file system and, while searching,
318
+ // generate the cache for later requests.
319
+ let entries;
320
+ try {
321
+ entries = (0, node_fs_1.readdirSync)(directory, { withFileTypes: true });
181
322
  }
182
- if (importPotentials.has(entry.name)) {
183
- foundImports.push((0, node_path_1.join)(directory, entry.name));
323
+ catch {
324
+ return null;
184
325
  }
185
- if (defaultPotentials.has(entry.name)) {
186
- foundDefaults.push((0, node_path_1.join)(directory, entry.name));
326
+ foundDefaults = [];
327
+ foundImports = [];
328
+ cachedEntries = { files: new Set(), directories: new Set() };
329
+ for (const entry of entries) {
330
+ const isDirectory = entry.isDirectory();
331
+ if (isDirectory) {
332
+ cachedEntries.directories.add(entry.name);
333
+ }
334
+ // Record if the name should be checked as a directory with an index file
335
+ if (checkDirectory && !hasStyleExtension && entry.name === filename && isDirectory) {
336
+ hasPotentialIndex = true;
337
+ }
338
+ if (!entry.isFile()) {
339
+ continue;
340
+ }
341
+ cachedEntries.files.add(entry.name);
342
+ if (importPotentials.has(entry.name)) {
343
+ foundImports.push(entry.name);
344
+ }
345
+ if (defaultPotentials.has(entry.name)) {
346
+ foundDefaults.push(entry.name);
347
+ }
187
348
  }
349
+ this.directoryCache.set(directory, cachedEntries);
188
350
  }
189
351
  // `foundImports` will only contain elements if `options.fromImport` is true
190
352
  const result = (_a = this.checkFound(foundImports)) !== null && _a !== void 0 ? _a : this.checkFound(foundDefaults);
191
- if (result === null && hasPotentialIndex) {
353
+ if (result !== null) {
354
+ return (0, node_url_1.pathToFileURL)((0, node_path_1.join)(directory, result));
355
+ }
356
+ if (hasPotentialIndex) {
192
357
  // Check for index files using filename as a directory
193
358
  return this.resolveImport(url + '/index', fromImport, false);
194
359
  }
195
- return result;
360
+ return null;
196
361
  }
197
362
  /**
198
363
  * Checks an array of potential stylesheet files to determine if there is a valid
199
364
  * stylesheet file. More than one discovered file may indicate an error.
200
365
  * @param found An array of discovered stylesheet files.
201
- * @returns A fully resolved URL for a stylesheet file or `null` if not found.
366
+ * @returns A fully resolved path for a stylesheet file or `null` if not found.
202
367
  * @throws If there are ambiguous files discovered.
203
368
  */
204
369
  checkFound(found) {
@@ -217,9 +382,9 @@ class RelativeUrlRebasingImporter extends UrlRebasingImporter {
217
382
  }
218
383
  // Return the non-CSS file (sass/scss files have priority)
219
384
  // https://github.com/sass/dart-sass/blob/44d6bb6ac72fe6b93f5bfec371a1fffb18e6b76d/lib/src/importer/utils.dart#L44-L47
220
- return (0, node_url_1.pathToFileURL)(foundWithoutCss[0]);
385
+ return foundWithoutCss[0];
221
386
  }
222
- return (0, node_url_1.pathToFileURL)(found[0]);
387
+ return found[0];
223
388
  }
224
389
  }
225
390
  exports.RelativeUrlRebasingImporter = RelativeUrlRebasingImporter;
@@ -78,13 +78,12 @@ function normalizeAssetPatterns(assetPatterns, workspaceRoot, projectRoot, proje
78
78
  }
79
79
  // Output directory for both is the relative path from source root to input.
80
80
  const output = path.relative(resolvedSourceRoot, path.resolve(workspaceRoot, input));
81
- // Return the asset pattern in object format.
82
- return { glob, input, output };
81
+ assetPattern = { glob, input, output };
83
82
  }
84
- else {
85
- // It's already an AssetPatternObject, no need to convert.
86
- return assetPattern;
83
+ if (assetPattern.output.startsWith('..')) {
84
+ throw new Error('An asset cannot be written to a location outside of the output path.');
87
85
  }
86
+ return assetPattern;
88
87
  });
89
88
  }
90
89
  exports.normalizeAssetPatterns = normalizeAssetPatterns;
@@ -279,7 +279,16 @@ function statsWarningsToString(json, statsConfig) {
279
279
  output += yb(`Warning: ${warning}\n\n`);
280
280
  }
281
281
  else {
282
- const file = warning.file || warning.moduleName;
282
+ let file = warning.file || warning.moduleName;
283
+ // Clean up warning paths
284
+ // Ex: ./src/app/styles.scss.webpack[javascript/auto]!=!./node_modules/css-loader/dist/cjs.js....
285
+ // to ./src/app/styles.scss.webpack
286
+ if (file && !statsConfig.errorDetails) {
287
+ const webpackPathIndex = file.indexOf('.webpack[');
288
+ if (webpackPathIndex !== -1) {
289
+ file = file.substring(0, webpackPathIndex);
290
+ }
291
+ }
283
292
  if (file) {
284
293
  output += c(file);
285
294
  if (warning.loc) {