@angular/build 18.0.0-rc.2 → 18.0.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.
- package/package.json +13 -13
- package/src/builders/application/build-action.js +2 -0
- package/src/builders/dev-server/vite-server.js +13 -7
- package/src/tools/babel/plugins/adjust-static-class-members.js +73 -74
- package/src/tools/esbuild/angular/compiler-plugin.js +7 -1
- package/src/tools/esbuild/commonjs-checker.js +1 -1
- package/src/tools/esbuild/external-packages-plugin.js +6 -1
- package/src/tools/esbuild/lmdb-cache-store.d.ts +18 -0
- package/src/tools/esbuild/lmdb-cache-store.js +54 -0
- package/src/tools/esbuild/stylesheets/css-inline-fonts-plugin.js +1 -1
- package/src/tools/esbuild/stylesheets/sass-language.js +1 -1
- package/src/tools/sass/rebasing-importer.js +5 -1
- package/src/tools/sass/sass-service.d.ts +7 -12
- package/src/tools/sass/sass-service.js +134 -109
- package/src/tools/sass/worker.d.ts +76 -0
- package/src/tools/sass/worker.js +16 -27
- package/src/utils/normalize-cache.js +1 -1
- package/src/utils/version.js +1 -1
- package/src/utils/index-file/style-nonce.d.ts +0 -12
- package/src/utils/index-file/style-nonce.js +0 -55
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular/build",
|
|
3
|
-
"version": "18.0.0
|
|
3
|
+
"version": "18.0.0",
|
|
4
4
|
"description": "Official build system for Angular",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Angular CLI",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"builders": "builders.json",
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@ampproject/remapping": "2.3.0",
|
|
26
|
-
"@angular-devkit/architect": "0.1800.0
|
|
26
|
+
"@angular-devkit/architect": "0.1800.0",
|
|
27
27
|
"@babel/core": "7.24.5",
|
|
28
28
|
"@babel/helper-annotate-as-pure": "7.22.5",
|
|
29
29
|
"@babel/helper-split-export-declaration": "7.24.5",
|
|
@@ -31,28 +31,28 @@
|
|
|
31
31
|
"ansi-colors": "4.1.3",
|
|
32
32
|
"browserslist": "^4.23.0",
|
|
33
33
|
"critters": "0.0.22",
|
|
34
|
-
"esbuild": "0.21.
|
|
34
|
+
"esbuild": "0.21.3",
|
|
35
35
|
"fast-glob": "3.3.2",
|
|
36
36
|
"https-proxy-agent": "7.0.4",
|
|
37
|
-
"inquirer": "9.2.
|
|
37
|
+
"inquirer": "9.2.22",
|
|
38
|
+
"lmdb": "3.0.8",
|
|
38
39
|
"magic-string": "0.30.10",
|
|
39
40
|
"mrmime": "2.0.0",
|
|
40
41
|
"ora": "5.4.1",
|
|
41
42
|
"picomatch": "4.0.2",
|
|
42
|
-
"piscina": "4.
|
|
43
|
+
"piscina": "4.5.0",
|
|
43
44
|
"parse5-html-rewriting-stream": "7.0.0",
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"undici": "6.16.0",
|
|
45
|
+
"sass": "1.77.2",
|
|
46
|
+
"semver": "7.6.2",
|
|
47
|
+
"undici": "6.18.0",
|
|
48
48
|
"vite": "5.2.11",
|
|
49
49
|
"watchpack": "2.4.1"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@angular/compiler-cli": "^18.0.0
|
|
53
|
-
"@angular/localize": "^18.0.0
|
|
54
|
-
"@angular/platform-server": "^18.0.0
|
|
55
|
-
"@angular/service-worker": "^18.0.0
|
|
52
|
+
"@angular/compiler-cli": "^18.0.0",
|
|
53
|
+
"@angular/localize": "^18.0.0",
|
|
54
|
+
"@angular/platform-server": "^18.0.0",
|
|
55
|
+
"@angular/service-worker": "^18.0.0",
|
|
56
56
|
"less": "^4.2.0",
|
|
57
57
|
"postcss": "^8.4.0",
|
|
58
58
|
"tailwindcss": "^2.0.0 || ^3.0.0",
|
|
@@ -136,6 +136,8 @@ async function* runEsBuildBuildAction(action, options) {
|
|
|
136
136
|
if (verbose) {
|
|
137
137
|
logger.info(changes.toDebugString());
|
|
138
138
|
}
|
|
139
|
+
// Clear removed files from current watch files
|
|
140
|
+
changes.removed.forEach((removedPath) => currentWatchFiles.delete(removedPath));
|
|
139
141
|
result = await withProgress('Changes detected. Rebuilding...', () => action(result.createRebuildState(changes)));
|
|
140
142
|
// Log all diagnostic (error/warning/logs) messages
|
|
141
143
|
await (0, utils_1.logMessages)(logger, result, colors, jsonLogs);
|
|
@@ -133,7 +133,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
133
133
|
// If server is active, send an error notification
|
|
134
134
|
if (result.errors?.length && server) {
|
|
135
135
|
hadError = true;
|
|
136
|
-
server.
|
|
136
|
+
server.hot.send({
|
|
137
137
|
type: 'error',
|
|
138
138
|
err: {
|
|
139
139
|
message: result.errors[0].text,
|
|
@@ -147,7 +147,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
147
147
|
else if (hadError && server) {
|
|
148
148
|
hadError = false;
|
|
149
149
|
// Send an empty update to clear the error overlay
|
|
150
|
-
server.
|
|
150
|
+
server.hot.send({
|
|
151
151
|
'type': 'update',
|
|
152
152
|
updates: [],
|
|
153
153
|
});
|
|
@@ -189,15 +189,13 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
189
189
|
if (!projectName) {
|
|
190
190
|
throw new Error('The builder requires a target.');
|
|
191
191
|
}
|
|
192
|
+
context.logger.info('NOTE: Raw file sizes do not reflect development server per-request transformations.');
|
|
192
193
|
const { root = '' } = await context.getProjectMetadata(projectName);
|
|
193
194
|
const projectRoot = (0, node_path_1.join)(context.workspaceRoot, root);
|
|
194
195
|
const browsers = (0, internal_1.getSupportedBrowsers)(projectRoot, context.logger);
|
|
195
196
|
const target = (0, internal_1.transformSupportedBrowsersToTargets)(browsers);
|
|
196
|
-
const polyfills = Array.isArray((browserOptions.polyfills ??= []))
|
|
197
|
-
? browserOptions.polyfills
|
|
198
|
-
: [browserOptions.polyfills];
|
|
199
197
|
// Setup server and start listening
|
|
200
|
-
const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, !!browserOptions.ssr, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
|
|
198
|
+
const serverConfiguration = await setupServer(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, !!browserOptions.ssr, prebundleTransformer, target, (0, internal_1.isZonelessApp)(browserOptions.polyfills), browserOptions.loader, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps);
|
|
201
199
|
server = await createServer(serverConfiguration);
|
|
202
200
|
await server.listen();
|
|
203
201
|
if (serverConfiguration.ssr?.optimizeDeps?.disabled === false) {
|
|
@@ -224,7 +222,7 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context
|
|
|
224
222
|
key: 'r',
|
|
225
223
|
description: 'force reload browser',
|
|
226
224
|
action(server) {
|
|
227
|
-
server.
|
|
225
|
+
server.hot.send({
|
|
228
226
|
type: 'full-reload',
|
|
229
227
|
path: '*',
|
|
230
228
|
});
|
|
@@ -354,6 +352,14 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
354
352
|
css: {
|
|
355
353
|
devSourcemap: true,
|
|
356
354
|
},
|
|
355
|
+
// Ensure custom 'file' loader build option entries are handled by Vite in application code that
|
|
356
|
+
// reference third-party libraries. Relative usage is handled directly by the build and not Vite.
|
|
357
|
+
// Only 'file' loader entries are currently supported directly by Vite.
|
|
358
|
+
assetsInclude: prebundleLoaderExtensions &&
|
|
359
|
+
Object.entries(prebundleLoaderExtensions)
|
|
360
|
+
.filter(([, value]) => value === 'file')
|
|
361
|
+
// Create a file extension glob for each key
|
|
362
|
+
.map(([key]) => '*' + key),
|
|
357
363
|
// Vite will normalize the `base` option by adding a leading slash.
|
|
358
364
|
base: serverOptions.servePath,
|
|
359
365
|
resolve: {
|
|
@@ -216,77 +216,10 @@ function default_1() {
|
|
|
216
216
|
const { wrapStatementPaths, hasPotentialSideEffects } = exportDefaultAnalysis.get(classNode) ??
|
|
217
217
|
analyzeClassSiblings(origin, classNode.id, wrapDecorators);
|
|
218
218
|
visitedClasses.add(classNode);
|
|
219
|
-
if (hasPotentialSideEffects) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
219
|
// If no statements to wrap, check for static class properties.
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
// not known at this stage whether the class needs to be downleveled, the transform
|
|
227
|
-
// wraps classes preemptively to allow for potential removal within the optimization
|
|
228
|
-
// stages.
|
|
229
|
-
if (wrapStatementPaths.length === 0) {
|
|
230
|
-
let shouldWrap = false;
|
|
231
|
-
for (const element of path.get('body').get('body')) {
|
|
232
|
-
if (element.isClassProperty()) {
|
|
233
|
-
// Only need to analyze static properties
|
|
234
|
-
if (!element.node.static) {
|
|
235
|
-
continue;
|
|
236
|
-
}
|
|
237
|
-
// Check for potential side effects.
|
|
238
|
-
// These checks are conservative and could potentially be expanded in the future.
|
|
239
|
-
const elementKey = element.get('key');
|
|
240
|
-
const elementValue = element.get('value');
|
|
241
|
-
if (elementKey.isIdentifier() &&
|
|
242
|
-
(!elementValue.isExpression() ||
|
|
243
|
-
canWrapProperty(elementKey.node.name, elementValue))) {
|
|
244
|
-
shouldWrap = true;
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
// Not safe to wrap
|
|
248
|
-
shouldWrap = false;
|
|
249
|
-
break;
|
|
250
|
-
}
|
|
251
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
252
|
-
}
|
|
253
|
-
else if (element.isStaticBlock()) {
|
|
254
|
-
// Only need to analyze static blocks
|
|
255
|
-
const body = element.get('body');
|
|
256
|
-
if (Array.isArray(body) && body.length > 1) {
|
|
257
|
-
// Not safe to wrap
|
|
258
|
-
shouldWrap = false;
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
const expression = body.find((n) => n.isExpressionStatement());
|
|
262
|
-
const assignmentExpression = expression?.get('expression');
|
|
263
|
-
if (assignmentExpression?.isAssignmentExpression()) {
|
|
264
|
-
const left = assignmentExpression.get('left');
|
|
265
|
-
if (!left.isMemberExpression()) {
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
if (!left.get('object').isThisExpression()) {
|
|
269
|
-
// Not safe to wrap
|
|
270
|
-
shouldWrap = false;
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
const element = left.get('property');
|
|
274
|
-
const right = assignmentExpression.get('right');
|
|
275
|
-
if (element.isIdentifier() &&
|
|
276
|
-
(!right.isExpression() || canWrapProperty(element.node.name, right))) {
|
|
277
|
-
shouldWrap = true;
|
|
278
|
-
}
|
|
279
|
-
else {
|
|
280
|
-
// Not safe to wrap
|
|
281
|
-
shouldWrap = false;
|
|
282
|
-
break;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
if (!shouldWrap) {
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
220
|
+
if (hasPotentialSideEffects ||
|
|
221
|
+
(wrapStatementPaths.length === 0 && !analyzeClassStaticProperties(path).shouldWrap)) {
|
|
222
|
+
return;
|
|
290
223
|
}
|
|
291
224
|
const wrapStatementNodes = [];
|
|
292
225
|
for (const statementPath of wrapStatementPaths) {
|
|
@@ -310,9 +243,7 @@ function default_1() {
|
|
|
310
243
|
ClassExpression(path, state) {
|
|
311
244
|
const { node: classNode, parentPath } = path;
|
|
312
245
|
const { wrapDecorators } = state.opts;
|
|
313
|
-
|
|
314
|
-
// If not wrapping decorators, they do not need to be processed.
|
|
315
|
-
if (!wrapDecorators || visitedClasses.has(classNode)) {
|
|
246
|
+
if (visitedClasses.has(classNode)) {
|
|
316
247
|
return;
|
|
317
248
|
}
|
|
318
249
|
if (!parentPath.isVariableDeclarator() || !core_1.types.isIdentifier(parentPath.node.id)) {
|
|
@@ -324,7 +255,9 @@ function default_1() {
|
|
|
324
255
|
}
|
|
325
256
|
const { wrapStatementPaths, hasPotentialSideEffects } = analyzeClassSiblings(origin, parentPath.node.id, wrapDecorators);
|
|
326
257
|
visitedClasses.add(classNode);
|
|
327
|
-
|
|
258
|
+
// If no statements to wrap, check for static class properties.
|
|
259
|
+
if (hasPotentialSideEffects ||
|
|
260
|
+
(wrapStatementPaths.length === 0 && !analyzeClassStaticProperties(path).shouldWrap)) {
|
|
328
261
|
return;
|
|
329
262
|
}
|
|
330
263
|
const wrapStatementNodes = [];
|
|
@@ -349,3 +282,69 @@ function default_1() {
|
|
|
349
282
|
};
|
|
350
283
|
}
|
|
351
284
|
exports.default = default_1;
|
|
285
|
+
/**
|
|
286
|
+
* Static class properties may be downleveled at later stages in the build pipeline
|
|
287
|
+
* which results in additional function calls outside the class body. These calls
|
|
288
|
+
* then cause the class to be referenced and not eligible for removal. Since it is
|
|
289
|
+
* not known at this stage whether the class needs to be downleveled, the transform
|
|
290
|
+
* wraps classes preemptively to allow for potential removal within the optimization stages.
|
|
291
|
+
*/
|
|
292
|
+
function analyzeClassStaticProperties(path) {
|
|
293
|
+
let shouldWrap = false;
|
|
294
|
+
for (const element of path.get('body').get('body')) {
|
|
295
|
+
if (element.isClassProperty()) {
|
|
296
|
+
// Only need to analyze static properties
|
|
297
|
+
if (!element.node.static) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
// Check for potential side effects.
|
|
301
|
+
// These checks are conservative and could potentially be expanded in the future.
|
|
302
|
+
const elementKey = element.get('key');
|
|
303
|
+
const elementValue = element.get('value');
|
|
304
|
+
if (elementKey.isIdentifier() &&
|
|
305
|
+
(!elementValue.isExpression() || canWrapProperty(elementKey.node.name, elementValue))) {
|
|
306
|
+
shouldWrap = true;
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
// Not safe to wrap
|
|
310
|
+
shouldWrap = false;
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
314
|
+
}
|
|
315
|
+
else if (element.isStaticBlock()) {
|
|
316
|
+
// Only need to analyze static blocks
|
|
317
|
+
const body = element.get('body');
|
|
318
|
+
if (Array.isArray(body) && body.length > 1) {
|
|
319
|
+
// Not safe to wrap
|
|
320
|
+
shouldWrap = false;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
const expression = body.find((n) => n.isExpressionStatement());
|
|
324
|
+
const assignmentExpression = expression?.get('expression');
|
|
325
|
+
if (assignmentExpression?.isAssignmentExpression()) {
|
|
326
|
+
const left = assignmentExpression.get('left');
|
|
327
|
+
if (!left.isMemberExpression()) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
if (!left.get('object').isThisExpression()) {
|
|
331
|
+
// Not safe to wrap
|
|
332
|
+
shouldWrap = false;
|
|
333
|
+
break;
|
|
334
|
+
}
|
|
335
|
+
const element = left.get('property');
|
|
336
|
+
const right = assignmentExpression.get('right');
|
|
337
|
+
if (element.isIdentifier() &&
|
|
338
|
+
(!right.isExpression() || canWrapProperty(element.node.name, right))) {
|
|
339
|
+
shouldWrap = true;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
// Not safe to wrap
|
|
343
|
+
shouldWrap = false;
|
|
344
|
+
break;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return { shouldWrap };
|
|
350
|
+
}
|
|
@@ -38,6 +38,7 @@ const node_assert_1 = __importDefault(require("node:assert"));
|
|
|
38
38
|
const path = __importStar(require("node:path"));
|
|
39
39
|
const environment_options_1 = require("../../../utils/environment-options");
|
|
40
40
|
const javascript_transformer_1 = require("../javascript-transformer");
|
|
41
|
+
const lmdb_cache_store_1 = require("../lmdb-cache-store");
|
|
41
42
|
const load_result_cache_1 = require("../load-result-cache");
|
|
42
43
|
const profiling_1 = require("../profiling");
|
|
43
44
|
const compilation_1 = require("./compilation");
|
|
@@ -54,7 +55,11 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
|
|
|
54
55
|
let setupWarnings = [];
|
|
55
56
|
const preserveSymlinks = build.initialOptions.preserveSymlinks;
|
|
56
57
|
// Initialize a worker pool for JavaScript transformations
|
|
57
|
-
|
|
58
|
+
let cacheStore;
|
|
59
|
+
if (pluginOptions.sourceFileCache?.persistentCachePath) {
|
|
60
|
+
cacheStore = new lmdb_cache_store_1.LmbdCacheStore(path.join(pluginOptions.sourceFileCache.persistentCachePath, 'angular-compiler.db'));
|
|
61
|
+
}
|
|
62
|
+
const javascriptTransformer = new javascript_transformer_1.JavaScriptTransformer(pluginOptions, environment_options_1.maxWorkers, cacheStore?.createCache('jstransformer'));
|
|
58
63
|
// Setup defines based on the values used by the Angular compiler-cli
|
|
59
64
|
build.initialOptions.define ??= {};
|
|
60
65
|
build.initialOptions.define['ngI18nClosureMode'] ??= 'false';
|
|
@@ -320,6 +325,7 @@ function createCompilerPlugin(pluginOptions, styleOptions) {
|
|
|
320
325
|
sharedTSCompilationState?.dispose();
|
|
321
326
|
void stylesheetBundler.dispose();
|
|
322
327
|
void compilation.close?.();
|
|
328
|
+
void cacheStore?.close();
|
|
323
329
|
});
|
|
324
330
|
/**
|
|
325
331
|
* Checks if the file has side-effects when `advancedOptimizations` is enabled.
|
|
@@ -143,7 +143,7 @@ function createCommonJSModuleError(request, importer) {
|
|
|
143
143
|
notes: [
|
|
144
144
|
{
|
|
145
145
|
text: 'CommonJS or AMD dependencies can cause optimization bailouts.\n' +
|
|
146
|
-
'For more information see: https://angular.
|
|
146
|
+
'For more information see: https://angular.dev/tools/cli/build#configuring-commonjs-dependencies',
|
|
147
147
|
},
|
|
148
148
|
],
|
|
149
149
|
};
|
|
@@ -22,7 +22,12 @@ function createExternalPackagesPlugin(options) {
|
|
|
22
22
|
return {
|
|
23
23
|
name: 'angular-external-packages',
|
|
24
24
|
setup(build) {
|
|
25
|
-
|
|
25
|
+
// Find all loader keys that are not using the 'file' loader.
|
|
26
|
+
// The 'file' loader is automatically handled by Vite and does not need exclusion.
|
|
27
|
+
const loaderOptionKeys = build.initialOptions.loader &&
|
|
28
|
+
Object.entries(build.initialOptions.loader)
|
|
29
|
+
.filter(([, value]) => value !== 'file')
|
|
30
|
+
.map(([key]) => key);
|
|
26
31
|
// Safe to use native packages external option if no loader options or exclusions present
|
|
27
32
|
if (!exclusions && !loaderOptionKeys?.length) {
|
|
28
33
|
build.initialOptions.packages = 'external';
|
|
@@ -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 { Cache, CacheStore } from './cache';
|
|
9
|
+
export declare class LmbdCacheStore implements CacheStore<unknown> {
|
|
10
|
+
#private;
|
|
11
|
+
readonly cachePath: string;
|
|
12
|
+
constructor(cachePath: string);
|
|
13
|
+
get(key: string): Promise<any>;
|
|
14
|
+
has(key: string): boolean;
|
|
15
|
+
set(key: string, value: unknown): Promise<this>;
|
|
16
|
+
createCache<V = unknown>(namespace: string): Cache<V>;
|
|
17
|
+
close(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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.LmbdCacheStore = void 0;
|
|
11
|
+
const lmdb_1 = require("lmdb");
|
|
12
|
+
const cache_1 = require("./cache");
|
|
13
|
+
class LmbdCacheStore {
|
|
14
|
+
cachePath;
|
|
15
|
+
#cacheFileUrl;
|
|
16
|
+
#db;
|
|
17
|
+
constructor(cachePath) {
|
|
18
|
+
this.cachePath = cachePath;
|
|
19
|
+
this.#cacheFileUrl = cachePath;
|
|
20
|
+
}
|
|
21
|
+
#ensureCacheFile() {
|
|
22
|
+
this.#db ??= (0, lmdb_1.open)({
|
|
23
|
+
path: this.#cacheFileUrl,
|
|
24
|
+
compression: true,
|
|
25
|
+
});
|
|
26
|
+
return this.#db;
|
|
27
|
+
}
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
async get(key) {
|
|
30
|
+
const db = this.#ensureCacheFile();
|
|
31
|
+
const value = db.get(key);
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
has(key) {
|
|
35
|
+
return this.#ensureCacheFile().doesExist(key);
|
|
36
|
+
}
|
|
37
|
+
async set(key, value) {
|
|
38
|
+
const db = this.#ensureCacheFile();
|
|
39
|
+
await db.put(key, value);
|
|
40
|
+
return this;
|
|
41
|
+
}
|
|
42
|
+
createCache(namespace) {
|
|
43
|
+
return new cache_1.Cache(this, namespace);
|
|
44
|
+
}
|
|
45
|
+
async close() {
|
|
46
|
+
try {
|
|
47
|
+
await this.#db?.close();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// Failure to close should not be fatal
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.LmbdCacheStore = LmbdCacheStore;
|
|
@@ -71,7 +71,11 @@ class UrlRebasingImporter {
|
|
|
71
71
|
continue;
|
|
72
72
|
}
|
|
73
73
|
// Skip if root-relative, absolute or protocol relative url
|
|
74
|
-
if (/^((?:\w+:)?\/\/|data:|chrome
|
|
74
|
+
if (/^((?:\w+:)?\/\/|data:|chrome:|\/)/.test(value)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Skip if a fragment identifier but not a Sass interpolation
|
|
78
|
+
if (value[0] === '#' && value[1] !== '{') {
|
|
75
79
|
continue;
|
|
76
80
|
}
|
|
77
81
|
// Sass variable usage either starts with a `$` or contains a namespace and a `.$`
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.io/license
|
|
7
7
|
*/
|
|
8
|
-
import { CompileResult, Deprecation, SourceSpan, StringOptions } from 'sass';
|
|
8
|
+
import type { CompileResult, Deprecation, SourceSpan, StringOptions } from 'sass';
|
|
9
9
|
export interface SerializableVersion {
|
|
10
10
|
major: number;
|
|
11
11
|
minor: number;
|
|
@@ -36,14 +36,10 @@ export type SerializableWarningMessage = ({
|
|
|
36
36
|
* the worker which can be up to two times faster than the asynchronous variant.
|
|
37
37
|
*/
|
|
38
38
|
export declare class SassWorkerImplementation {
|
|
39
|
-
private
|
|
40
|
-
private readonly
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
private readonly workerPath;
|
|
44
|
-
private idCounter;
|
|
45
|
-
private nextWorkerIndex;
|
|
46
|
-
constructor(rebase?: boolean);
|
|
39
|
+
#private;
|
|
40
|
+
private readonly rebase;
|
|
41
|
+
readonly maxThreads: number;
|
|
42
|
+
constructor(rebase?: boolean, maxThreads?: number);
|
|
47
43
|
/**
|
|
48
44
|
* Provides information about the Sass implementation.
|
|
49
45
|
* This mimics enough of the `dart-sass` value to be used with the `sass-loader`.
|
|
@@ -63,10 +59,9 @@ export declare class SassWorkerImplementation {
|
|
|
63
59
|
/**
|
|
64
60
|
* Shutdown the Sass render worker.
|
|
65
61
|
* Executing this method will stop any pending render requests.
|
|
62
|
+
* @returns A void promise that resolves when closing is complete.
|
|
66
63
|
*/
|
|
67
|
-
close(): void
|
|
68
|
-
private createWorker;
|
|
64
|
+
close(): Promise<void>;
|
|
69
65
|
private processImporters;
|
|
70
|
-
private createRequest;
|
|
71
66
|
private isFileImporter;
|
|
72
67
|
}
|
|
@@ -6,12 +6,64 @@
|
|
|
6
6
|
* Use of this source code is governed by an MIT-style license that can be
|
|
7
7
|
* found in the LICENSE file at https://angular.io/license
|
|
8
8
|
*/
|
|
9
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
10
|
+
if (value !== null && value !== void 0) {
|
|
11
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
12
|
+
var dispose;
|
|
13
|
+
if (async) {
|
|
14
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
15
|
+
dispose = value[Symbol.asyncDispose];
|
|
16
|
+
}
|
|
17
|
+
if (dispose === void 0) {
|
|
18
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
19
|
+
dispose = value[Symbol.dispose];
|
|
20
|
+
}
|
|
21
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
22
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
23
|
+
}
|
|
24
|
+
else if (async) {
|
|
25
|
+
env.stack.push({ async: true });
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
29
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
30
|
+
return function (env) {
|
|
31
|
+
function fail(e) {
|
|
32
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
33
|
+
env.hasError = true;
|
|
34
|
+
}
|
|
35
|
+
function next() {
|
|
36
|
+
while (env.stack.length) {
|
|
37
|
+
var rec = env.stack.pop();
|
|
38
|
+
try {
|
|
39
|
+
var result = rec.dispose && rec.dispose.call(rec.value);
|
|
40
|
+
if (rec.async) return Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
fail(e);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (env.hasError) throw env.error;
|
|
47
|
+
}
|
|
48
|
+
return next();
|
|
49
|
+
};
|
|
50
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
51
|
+
var e = new Error(message);
|
|
52
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
53
|
+
});
|
|
54
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
55
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
56
|
+
};
|
|
9
57
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
58
|
exports.SassWorkerImplementation = void 0;
|
|
11
|
-
const
|
|
59
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
12
60
|
const node_url_1 = require("node:url");
|
|
13
61
|
const node_worker_threads_1 = require("node:worker_threads");
|
|
62
|
+
const piscina_1 = require("piscina");
|
|
14
63
|
const environment_options_1 = require("../../utils/environment-options");
|
|
64
|
+
// Polyfill Symbol.dispose if not present
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
Symbol.dispose ??= Symbol('Symbol Dispose');
|
|
15
67
|
/**
|
|
16
68
|
* The maximum number of Workers that will be created to execute render requests.
|
|
17
69
|
*/
|
|
@@ -24,14 +76,22 @@ const MAX_RENDER_WORKERS = environment_options_1.maxWorkers;
|
|
|
24
76
|
*/
|
|
25
77
|
class SassWorkerImplementation {
|
|
26
78
|
rebase;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
workerPath = (0, node_path_1.join)(__dirname, './worker.js');
|
|
31
|
-
idCounter = 1;
|
|
32
|
-
nextWorkerIndex = 0;
|
|
33
|
-
constructor(rebase = false) {
|
|
79
|
+
maxThreads;
|
|
80
|
+
#workerPool;
|
|
81
|
+
constructor(rebase = false, maxThreads = MAX_RENDER_WORKERS) {
|
|
34
82
|
this.rebase = rebase;
|
|
83
|
+
this.maxThreads = maxThreads;
|
|
84
|
+
}
|
|
85
|
+
#ensureWorkerPool() {
|
|
86
|
+
this.#workerPool ??= new piscina_1.Piscina({
|
|
87
|
+
filename: require.resolve('./worker'),
|
|
88
|
+
minThreads: 1,
|
|
89
|
+
maxThreads: this.maxThreads,
|
|
90
|
+
// Shutdown idle threads after 1 second of inactivity
|
|
91
|
+
idleTimeout: 1000,
|
|
92
|
+
recordTiming: false,
|
|
93
|
+
});
|
|
94
|
+
return this.#workerPool;
|
|
35
95
|
}
|
|
36
96
|
/**
|
|
37
97
|
* Provides information about the Sass implementation.
|
|
@@ -52,49 +112,20 @@ class SassWorkerImplementation {
|
|
|
52
112
|
* @param source The contents to compile.
|
|
53
113
|
* @param options The `dart-sass` options to use when rendering the stylesheet.
|
|
54
114
|
*/
|
|
55
|
-
compileStringAsync(source, options) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
let workerIndex = this.availableWorkers.pop();
|
|
65
|
-
if (workerIndex === undefined) {
|
|
66
|
-
if (this.workers.length < MAX_RENDER_WORKERS) {
|
|
67
|
-
workerIndex = this.workers.length;
|
|
68
|
-
this.workers.push(this.createWorker());
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
workerIndex = this.nextWorkerIndex++;
|
|
72
|
-
if (this.nextWorkerIndex >= this.workers.length) {
|
|
73
|
-
this.nextWorkerIndex = 0;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
115
|
+
async compileStringAsync(source, options) {
|
|
116
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
117
|
+
try {
|
|
118
|
+
// The `functions`, `logger` and `importer` options are JavaScript functions that cannot be transferred.
|
|
119
|
+
// If any additional function options are added in the future, they must be excluded as well.
|
|
120
|
+
const { functions, importers, url, logger, ...serializableOptions } = options;
|
|
121
|
+
// The CLI's configuration does not use or expose the ability to define custom Sass functions
|
|
122
|
+
if (functions && Object.keys(functions).length > 0) {
|
|
123
|
+
throw new Error('Sass custom functions are not supported.');
|
|
76
124
|
}
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const url = error.span?.url;
|
|
80
|
-
if (url) {
|
|
81
|
-
error.span.url = (0, node_url_1.pathToFileURL)(url);
|
|
82
|
-
}
|
|
83
|
-
reject(error);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
if (!result) {
|
|
87
|
-
reject(new Error('No result.'));
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
resolve(result);
|
|
91
|
-
};
|
|
92
|
-
const request = this.createRequest(workerIndex, callback, logger, importers);
|
|
93
|
-
this.requests.set(request.id, request);
|
|
94
|
-
this.workers[workerIndex].postMessage({
|
|
95
|
-
id: request.id,
|
|
125
|
+
const importerChannel = __addDisposableResource(env_1, importers?.length ? this.#createImporterChannel(importers) : undefined, false);
|
|
126
|
+
const response = (await this.#ensureWorkerPool().run({
|
|
96
127
|
source,
|
|
97
|
-
|
|
128
|
+
importerChannel,
|
|
98
129
|
hasLogger: !!logger,
|
|
99
130
|
rebase: this.rebase,
|
|
100
131
|
options: {
|
|
@@ -102,39 +133,13 @@ class SassWorkerImplementation {
|
|
|
102
133
|
// URL is not serializable so to convert to string here and back to URL in the worker.
|
|
103
134
|
url: url ? (0, node_url_1.fileURLToPath)(url) : undefined,
|
|
104
135
|
},
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
close() {
|
|
113
|
-
for (const worker of this.workers) {
|
|
114
|
-
try {
|
|
115
|
-
void worker.terminate();
|
|
116
|
-
}
|
|
117
|
-
catch { }
|
|
118
|
-
}
|
|
119
|
-
this.requests.clear();
|
|
120
|
-
}
|
|
121
|
-
createWorker() {
|
|
122
|
-
const { port1: mainImporterPort, port2: workerImporterPort } = new node_worker_threads_1.MessageChannel();
|
|
123
|
-
const importerSignal = new Int32Array(new SharedArrayBuffer(4));
|
|
124
|
-
const worker = new node_worker_threads_1.Worker(this.workerPath, {
|
|
125
|
-
workerData: { workerImporterPort, importerSignal },
|
|
126
|
-
transferList: [workerImporterPort],
|
|
127
|
-
});
|
|
128
|
-
worker.on('message', (response) => {
|
|
129
|
-
const request = this.requests.get(response.id);
|
|
130
|
-
if (!request) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
this.requests.delete(response.id);
|
|
134
|
-
this.availableWorkers.push(request.workerIndex);
|
|
135
|
-
if (response.warnings && request.logger?.warn) {
|
|
136
|
-
for (const { message, span, ...options } of response.warnings) {
|
|
137
|
-
request.logger.warn(message, {
|
|
136
|
+
}, {
|
|
137
|
+
transferList: importerChannel ? [importerChannel.port] : undefined,
|
|
138
|
+
}));
|
|
139
|
+
const { result, error, warnings } = response;
|
|
140
|
+
if (warnings && logger?.warn) {
|
|
141
|
+
for (const { message, span, ...options } of warnings) {
|
|
142
|
+
logger.warn(message, {
|
|
138
143
|
...options,
|
|
139
144
|
span: span && {
|
|
140
145
|
...span,
|
|
@@ -143,26 +148,49 @@ class SassWorkerImplementation {
|
|
|
143
148
|
});
|
|
144
149
|
}
|
|
145
150
|
}
|
|
146
|
-
if (
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
151
|
+
if (error) {
|
|
152
|
+
// Convert stringified url value required for cloning back to a URL object
|
|
153
|
+
const url = error.span?.url;
|
|
154
|
+
if (url) {
|
|
155
|
+
error.span.url = (0, node_url_1.pathToFileURL)(url);
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
152
158
|
}
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
(0, node_assert_1.default)(result, 'Sass render worker should always return a result or an error');
|
|
160
|
+
return {
|
|
161
|
+
...result,
|
|
162
|
+
// URL is not serializable so in the worker we convert to string and here back to URL.
|
|
163
|
+
loadedUrls: result.loadedUrls.map((p) => (0, node_url_1.pathToFileURL)(p)),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
catch (e_1) {
|
|
167
|
+
env_1.error = e_1;
|
|
168
|
+
env_1.hasError = true;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
__disposeResources(env_1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Shutdown the Sass render worker.
|
|
176
|
+
* Executing this method will stop any pending render requests.
|
|
177
|
+
* @returns A void promise that resolves when closing is complete.
|
|
178
|
+
*/
|
|
179
|
+
async close() {
|
|
180
|
+
if (this.#workerPool) {
|
|
181
|
+
try {
|
|
182
|
+
await this.#workerPool.destroy();
|
|
155
183
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const request = this.requests.get(id);
|
|
159
|
-
if (!request?.importers) {
|
|
160
|
-
mainImporterPort.postMessage(null);
|
|
161
|
-
Atomics.store(importerSignal, 0, 1);
|
|
162
|
-
Atomics.notify(importerSignal, 0);
|
|
163
|
-
return;
|
|
184
|
+
finally {
|
|
185
|
+
this.#workerPool = undefined;
|
|
164
186
|
}
|
|
165
|
-
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
#createImporterChannel(importers) {
|
|
190
|
+
const { port1: mainImporterPort, port2: workerImporterPort } = new node_worker_threads_1.MessageChannel();
|
|
191
|
+
const importerSignal = new Int32Array(new SharedArrayBuffer(4));
|
|
192
|
+
mainImporterPort.on('message', ({ url, options }) => {
|
|
193
|
+
this.processImporters(importers, url, {
|
|
166
194
|
...options,
|
|
167
195
|
// URL is not serializable so in the worker we convert to string and here back to URL.
|
|
168
196
|
containingUrl: options.containingUrl
|
|
@@ -181,7 +209,13 @@ class SassWorkerImplementation {
|
|
|
181
209
|
});
|
|
182
210
|
});
|
|
183
211
|
mainImporterPort.unref();
|
|
184
|
-
return
|
|
212
|
+
return {
|
|
213
|
+
port: workerImporterPort,
|
|
214
|
+
signal: importerSignal,
|
|
215
|
+
[Symbol.dispose]() {
|
|
216
|
+
mainImporterPort.close();
|
|
217
|
+
},
|
|
218
|
+
};
|
|
185
219
|
}
|
|
186
220
|
async processImporters(importers, url, options) {
|
|
187
221
|
for (const importer of importers) {
|
|
@@ -197,15 +231,6 @@ class SassWorkerImplementation {
|
|
|
197
231
|
}
|
|
198
232
|
return null;
|
|
199
233
|
}
|
|
200
|
-
createRequest(workerIndex, callback, logger, importers) {
|
|
201
|
-
return {
|
|
202
|
-
id: this.idCounter++,
|
|
203
|
-
workerIndex,
|
|
204
|
-
callback,
|
|
205
|
-
logger,
|
|
206
|
-
importers,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
234
|
isFileImporter(value) {
|
|
210
235
|
return 'findFileUrl' in value;
|
|
211
236
|
}
|
|
@@ -5,4 +5,80 @@
|
|
|
5
5
|
* Use of this source code is governed by an MIT-style license that can be
|
|
6
6
|
* found in the LICENSE file at https://angular.io/license
|
|
7
7
|
*/
|
|
8
|
+
/// <reference types="node" />
|
|
9
|
+
/// <reference types="source-map-js/source-map" />
|
|
10
|
+
import { MessagePort } from 'node:worker_threads';
|
|
11
|
+
import { SourceSpan, StringOptions } from 'sass';
|
|
12
|
+
import type { SerializableWarningMessage } from './sass-service';
|
|
13
|
+
/**
|
|
14
|
+
* A request to render a Sass stylesheet using the supplied options.
|
|
15
|
+
*/
|
|
16
|
+
interface RenderRequestMessage {
|
|
17
|
+
/**
|
|
18
|
+
* The contents to compile.
|
|
19
|
+
*/
|
|
20
|
+
source: string;
|
|
21
|
+
/**
|
|
22
|
+
* The Sass options to provide to the `dart-sass` compile function.
|
|
23
|
+
*/
|
|
24
|
+
options: Omit<StringOptions<'sync'>, 'url'> & {
|
|
25
|
+
url: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Indicates the request has a custom importer function on the main thread.
|
|
29
|
+
*/
|
|
30
|
+
importerChannel?: {
|
|
31
|
+
port: MessagePort;
|
|
32
|
+
signal: Int32Array;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Indicates the request has a custom logger for warning messages.
|
|
36
|
+
*/
|
|
37
|
+
hasLogger: boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Indicates paths within url() CSS functions should be rebased.
|
|
40
|
+
*/
|
|
41
|
+
rebase: boolean;
|
|
42
|
+
}
|
|
43
|
+
export default function renderSassStylesheet(request: RenderRequestMessage): Promise<{
|
|
44
|
+
warnings: SerializableWarningMessage[] | undefined;
|
|
45
|
+
result: {
|
|
46
|
+
loadedUrls: string[];
|
|
47
|
+
css: string;
|
|
48
|
+
sourceMap?: import("source-map-js").RawSourceMap | undefined;
|
|
49
|
+
};
|
|
50
|
+
error?: undefined;
|
|
51
|
+
} | {
|
|
52
|
+
warnings: SerializableWarningMessage[] | undefined;
|
|
53
|
+
error: {
|
|
54
|
+
span: Omit<SourceSpan, "url"> & {
|
|
55
|
+
url?: string | undefined;
|
|
56
|
+
};
|
|
57
|
+
message: string;
|
|
58
|
+
stack: string | undefined;
|
|
59
|
+
sassMessage: string;
|
|
60
|
+
sassStack: string;
|
|
61
|
+
};
|
|
62
|
+
result?: undefined;
|
|
63
|
+
} | {
|
|
64
|
+
warnings: SerializableWarningMessage[] | undefined;
|
|
65
|
+
error: {
|
|
66
|
+
message: string;
|
|
67
|
+
stack: string | undefined;
|
|
68
|
+
span?: undefined;
|
|
69
|
+
sassMessage?: undefined;
|
|
70
|
+
sassStack?: undefined;
|
|
71
|
+
};
|
|
72
|
+
result?: undefined;
|
|
73
|
+
} | {
|
|
74
|
+
warnings: SerializableWarningMessage[] | undefined;
|
|
75
|
+
error: {
|
|
76
|
+
message: string;
|
|
77
|
+
span?: undefined;
|
|
78
|
+
stack?: undefined;
|
|
79
|
+
sassMessage?: undefined;
|
|
80
|
+
sassStack?: undefined;
|
|
81
|
+
};
|
|
82
|
+
result?: undefined;
|
|
83
|
+
}>;
|
|
8
84
|
export {};
|
package/src/tools/sass/worker.js
CHANGED
|
@@ -16,22 +16,14 @@ const node_url_1 = require("node:url");
|
|
|
16
16
|
const node_worker_threads_1 = require("node:worker_threads");
|
|
17
17
|
const sass_1 = require("sass");
|
|
18
18
|
const rebasing_importer_1 = require("./rebasing-importer");
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
22
|
-
// The importer variables are used to proxy import requests to the main thread
|
|
23
|
-
const { workerImporterPort, importerSignal } = node_worker_threads_1.workerData;
|
|
24
|
-
node_worker_threads_1.parentPort.on('message', (message) => {
|
|
25
|
-
if (!node_worker_threads_1.parentPort) {
|
|
26
|
-
throw new Error('"parentPort" is not defined. Sass worker must be executed as a Worker.');
|
|
27
|
-
}
|
|
28
|
-
const { id, hasImporter, hasLogger, source, options, rebase } = message;
|
|
19
|
+
async function renderSassStylesheet(request) {
|
|
20
|
+
const { importerChannel, hasLogger, source, options, rebase } = request;
|
|
29
21
|
const entryDirectory = (0, node_path_1.dirname)(options.url);
|
|
30
22
|
let warnings;
|
|
31
23
|
try {
|
|
32
24
|
const directoryCache = new Map();
|
|
33
25
|
const rebaseSourceMaps = options.sourceMap ? new Map() : undefined;
|
|
34
|
-
if (
|
|
26
|
+
if (importerChannel) {
|
|
35
27
|
// When a custom importer function is present, the importer request must be proxied
|
|
36
28
|
// back to the main thread where it can be executed.
|
|
37
29
|
// This process must be synchronous from the perspective of dart-sass. The `Atomics`
|
|
@@ -39,17 +31,16 @@ node_worker_threads_1.parentPort.on('message', (message) => {
|
|
|
39
31
|
// `receiveMessageOnPort` function are used to ensure synchronous behavior.
|
|
40
32
|
const proxyImporter = {
|
|
41
33
|
findFileUrl: (url, { fromImport, containingUrl }) => {
|
|
42
|
-
Atomics.store(
|
|
43
|
-
|
|
44
|
-
id,
|
|
34
|
+
Atomics.store(importerChannel.signal, 0, 0);
|
|
35
|
+
importerChannel.port.postMessage({
|
|
45
36
|
url,
|
|
46
37
|
options: {
|
|
47
38
|
fromImport,
|
|
48
39
|
containingUrl: containingUrl ? (0, node_url_1.fileURLToPath)(containingUrl) : null,
|
|
49
40
|
},
|
|
50
41
|
});
|
|
51
|
-
Atomics.wait(
|
|
52
|
-
const result = (0, node_worker_threads_1.receiveMessageOnPort)(
|
|
42
|
+
Atomics.wait(importerChannel.signal, 0, 0);
|
|
43
|
+
const result = (0, node_worker_threads_1.receiveMessageOnPort)(importerChannel.port)?.message;
|
|
53
44
|
return result ? (0, node_url_1.pathToFileURL)(result) : null;
|
|
54
45
|
},
|
|
55
46
|
};
|
|
@@ -99,22 +90,20 @@ node_worker_threads_1.parentPort.on('message', (message) => {
|
|
|
99
90
|
// is referencing its original self.
|
|
100
91
|
(file, context) => (file !== context.importer ? rebaseSourceMaps.get(file) : null));
|
|
101
92
|
}
|
|
102
|
-
|
|
103
|
-
id,
|
|
93
|
+
return {
|
|
104
94
|
warnings,
|
|
105
95
|
result: {
|
|
106
96
|
...result,
|
|
107
97
|
// URL is not serializable so to convert to string here and back to URL in the parent.
|
|
108
98
|
loadedUrls: result.loadedUrls.map((p) => (0, node_url_1.fileURLToPath)(p)),
|
|
109
99
|
},
|
|
110
|
-
}
|
|
100
|
+
};
|
|
111
101
|
}
|
|
112
102
|
catch (error) {
|
|
113
103
|
// Needed because V8 will only serialize the message and stack properties of an Error instance.
|
|
114
104
|
if (error instanceof sass_1.Exception) {
|
|
115
105
|
const { span, message, stack, sassMessage, sassStack } = error;
|
|
116
|
-
|
|
117
|
-
id,
|
|
106
|
+
return {
|
|
118
107
|
warnings,
|
|
119
108
|
error: {
|
|
120
109
|
span: convertSourceSpan(span),
|
|
@@ -123,21 +112,21 @@ node_worker_threads_1.parentPort.on('message', (message) => {
|
|
|
123
112
|
sassMessage,
|
|
124
113
|
sassStack,
|
|
125
114
|
},
|
|
126
|
-
}
|
|
115
|
+
};
|
|
127
116
|
}
|
|
128
117
|
else if (error instanceof Error) {
|
|
129
118
|
const { message, stack } = error;
|
|
130
|
-
|
|
119
|
+
return { warnings, error: { message, stack } };
|
|
131
120
|
}
|
|
132
121
|
else {
|
|
133
|
-
|
|
134
|
-
id,
|
|
122
|
+
return {
|
|
135
123
|
warnings,
|
|
136
124
|
error: { message: 'An unknown error has occurred.' },
|
|
137
|
-
}
|
|
125
|
+
};
|
|
138
126
|
}
|
|
139
127
|
}
|
|
140
|
-
}
|
|
128
|
+
}
|
|
129
|
+
exports.default = renderSassStylesheet;
|
|
141
130
|
/**
|
|
142
131
|
* Converts a Sass SourceSpan object into a serializable form.
|
|
143
132
|
* The SourceSpan object contains a URL property which must be converted into a string.
|
|
@@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
10
10
|
exports.normalizeCacheOptions = void 0;
|
|
11
11
|
const node_path_1 = require("node:path");
|
|
12
12
|
/** Version placeholder is replaced during the build process with actual package version */
|
|
13
|
-
const VERSION = '18.0.0
|
|
13
|
+
const VERSION = '18.0.0';
|
|
14
14
|
function hasCacheMetadata(value) {
|
|
15
15
|
return (!!value &&
|
|
16
16
|
typeof value === 'object' &&
|
package/src/utils/version.js
CHANGED
|
@@ -58,7 +58,7 @@ function assertCompatibleAngularVersion(projectRoot) {
|
|
|
58
58
|
if (!(0, semver_1.satisfies)(angularVersion, supportedAngularSemver, { includePrerelease: true })) {
|
|
59
59
|
console.error(`This version of CLI is only compatible with Angular versions ${supportedAngularSemver},\n` +
|
|
60
60
|
`but Angular version ${angularVersion} was found instead.\n` +
|
|
61
|
-
'Please visit the link below to find instructions on how to update Angular.\nhttps://update.angular.
|
|
61
|
+
'Please visit the link below to find instructions on how to update Angular.\nhttps://update.angular.dev/');
|
|
62
62
|
process.exit(3);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
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
|
-
/**
|
|
9
|
-
* Finds the `ngCspNonce` value and copies it to all inline `<style>` and `<script> `tags.
|
|
10
|
-
* @param html Markup that should be processed.
|
|
11
|
-
*/
|
|
12
|
-
export declare function addNonce(html: string): Promise<string>;
|
|
@@ -1,55 +0,0 @@
|
|
|
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.addNonce = void 0;
|
|
11
|
-
const html_rewriting_stream_1 = require("./html-rewriting-stream");
|
|
12
|
-
/**
|
|
13
|
-
* Pattern matching the name of the Angular nonce attribute. Note that this is
|
|
14
|
-
* case-insensitive, because HTML attribute names are case-insensitive as well.
|
|
15
|
-
*/
|
|
16
|
-
const NONCE_ATTR_PATTERN = /ngCspNonce/i;
|
|
17
|
-
/**
|
|
18
|
-
* Finds the `ngCspNonce` value and copies it to all inline `<style>` and `<script> `tags.
|
|
19
|
-
* @param html Markup that should be processed.
|
|
20
|
-
*/
|
|
21
|
-
async function addNonce(html) {
|
|
22
|
-
const nonce = await findNonce(html);
|
|
23
|
-
if (!nonce) {
|
|
24
|
-
return html;
|
|
25
|
-
}
|
|
26
|
-
const { rewriter, transformedContent } = await (0, html_rewriting_stream_1.htmlRewritingStream)(html);
|
|
27
|
-
rewriter.on('startTag', (tag) => {
|
|
28
|
-
if ((tag.tagName === 'style' ||
|
|
29
|
-
(tag.tagName === 'script' && !tag.attrs.some((attr) => attr.name === 'src'))) &&
|
|
30
|
-
!tag.attrs.some((attr) => attr.name === 'nonce')) {
|
|
31
|
-
tag.attrs.push({ name: 'nonce', value: nonce });
|
|
32
|
-
}
|
|
33
|
-
rewriter.emitStartTag(tag);
|
|
34
|
-
});
|
|
35
|
-
return transformedContent();
|
|
36
|
-
}
|
|
37
|
-
exports.addNonce = addNonce;
|
|
38
|
-
/** Finds the Angular nonce in an HTML string. */
|
|
39
|
-
async function findNonce(html) {
|
|
40
|
-
// Inexpensive check to avoid parsing the HTML when we're sure there's no nonce.
|
|
41
|
-
if (!NONCE_ATTR_PATTERN.test(html)) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
const { rewriter, transformedContent } = await (0, html_rewriting_stream_1.htmlRewritingStream)(html);
|
|
45
|
-
let nonce = null;
|
|
46
|
-
rewriter.on('startTag', (tag) => {
|
|
47
|
-
const nonceAttr = tag.attrs.find((attr) => NONCE_ATTR_PATTERN.test(attr.name));
|
|
48
|
-
if (nonceAttr?.value) {
|
|
49
|
-
nonce = nonceAttr.value;
|
|
50
|
-
rewriter.stop(); // Stop parsing since we've found the nonce.
|
|
51
|
-
}
|
|
52
|
-
});
|
|
53
|
-
await transformedContent();
|
|
54
|
-
return nonce;
|
|
55
|
-
}
|