@angular-devkit/build-angular 17.1.0 → 17.1.2
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 +6 -6
- package/src/builders/application/options.js +1 -1
- package/src/builders/dev-server/vite-server.js +14 -213
- package/src/builders/extract-i18n/application-extraction.js +3 -1
- package/src/builders/prerender/routes-extractor-worker.js +1 -1
- package/src/tools/esbuild/angular/component-stylesheets.js +10 -8
- package/src/tools/esbuild/application-code-bundle.js +12 -3
- package/src/tools/esbuild/bundler-context.js +15 -9
- package/src/tools/esbuild/external-packages-plugin.d.ts +16 -0
- package/src/tools/esbuild/external-packages-plugin.js +66 -0
- package/src/tools/esbuild/stylesheets/css-resource-plugin.js +3 -2
- package/src/tools/vite/angular-memory-plugin.d.ts +24 -0
- package/src/tools/vite/angular-memory-plugin.js +251 -0
- package/src/utils/index-file/inline-critical-css.js +28 -20
- package/src/utils/load-proxy-config.js +3 -3
- package/src/utils/server-rendering/render-page.js +5 -0
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angular-devkit/build-angular",
|
|
3
|
-
"version": "17.1.
|
|
3
|
+
"version": "17.1.2",
|
|
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.
|
|
11
|
-
"@angular-devkit/build-webpack": "0.1701.
|
|
12
|
-
"@angular-devkit/core": "17.1.
|
|
10
|
+
"@angular-devkit/architect": "0.1701.2",
|
|
11
|
+
"@angular-devkit/build-webpack": "0.1701.2",
|
|
12
|
+
"@angular-devkit/core": "17.1.2",
|
|
13
13
|
"@babel/core": "7.23.7",
|
|
14
14
|
"@babel/generator": "7.23.6",
|
|
15
15
|
"@babel/helper-annotate-as-pure": "7.22.5",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"@babel/preset-env": "7.23.7",
|
|
21
21
|
"@babel/runtime": "7.23.7",
|
|
22
22
|
"@discoveryjs/json-ext": "0.5.7",
|
|
23
|
-
"@ngtools/webpack": "17.1.
|
|
23
|
+
"@ngtools/webpack": "17.1.2",
|
|
24
24
|
"@vitejs/plugin-basic-ssl": "1.0.2",
|
|
25
25
|
"ansi-colors": "4.1.3",
|
|
26
26
|
"autoprefixer": "10.4.16",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"tree-kill": "1.2.2",
|
|
64
64
|
"tslib": "2.6.2",
|
|
65
65
|
"undici": "6.2.1",
|
|
66
|
-
"vite": "5.0.
|
|
66
|
+
"vite": "5.0.12",
|
|
67
67
|
"watchpack": "2.4.0",
|
|
68
68
|
"webpack": "5.89.0",
|
|
69
69
|
"webpack-dev-middleware": "6.1.1",
|
|
@@ -70,7 +70,7 @@ async function normalizeOptions(context, projectName, options, extensions) {
|
|
|
70
70
|
server: 'server',
|
|
71
71
|
media: 'media',
|
|
72
72
|
...(typeof outputPath === 'string' ? undefined : outputPath),
|
|
73
|
-
base: normalizeDirectoryPath(node_path_1.default.
|
|
73
|
+
base: normalizeDirectoryPath(node_path_1.default.resolve(workspaceRoot, typeof outputPath === 'string' ? outputPath : outputPath.base)),
|
|
74
74
|
};
|
|
75
75
|
const outputNames = {
|
|
76
76
|
bundles: options.outputHashing === schema_1.OutputHashing.All || options.outputHashing === schema_1.OutputHashing.Bundles
|
|
@@ -34,8 +34,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
34
34
|
};
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.setupServer = exports.serveWithVite = void 0;
|
|
37
|
-
const remapping_1 = __importDefault(require("@ampproject/remapping"));
|
|
38
|
-
const mrmime_1 = require("mrmime");
|
|
39
37
|
const node_assert_1 = __importDefault(require("node:assert"));
|
|
40
38
|
const promises_1 = require("node:fs/promises");
|
|
41
39
|
const node_path_1 = require("node:path");
|
|
@@ -43,10 +41,10 @@ const bundler_context_1 = require("../../tools/esbuild/bundler-context");
|
|
|
43
41
|
const javascript_transformer_1 = require("../../tools/esbuild/javascript-transformer");
|
|
44
42
|
const rxjs_esm_resolution_plugin_1 = require("../../tools/esbuild/rxjs-esm-resolution-plugin");
|
|
45
43
|
const utils_1 = require("../../tools/esbuild/utils");
|
|
44
|
+
const angular_memory_plugin_1 = require("../../tools/vite/angular-memory-plugin");
|
|
46
45
|
const i18n_locale_plugin_1 = require("../../tools/vite/i18n-locale-plugin");
|
|
47
46
|
const utils_2 = require("../../utils");
|
|
48
47
|
const load_esm_1 = require("../../utils/load-esm");
|
|
49
|
-
const render_page_1 = require("../../utils/server-rendering/render-page");
|
|
50
48
|
const supported_browsers_1 = require("../../utils/supported-browsers");
|
|
51
49
|
const webpack_browser_config_1 = require("../../utils/webpack-browser-config");
|
|
52
50
|
const application_1 = require("../application");
|
|
@@ -324,7 +322,6 @@ function analyzeResultFiles(normalizePath, htmlIndexPath, resultFiles, generated
|
|
|
324
322
|
}
|
|
325
323
|
}
|
|
326
324
|
}
|
|
327
|
-
// eslint-disable-next-line max-lines-per-function
|
|
328
325
|
async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, externalMetadata, ssr, prebundleTransformer, target, prebundleLoaderExtensions, extensionMiddleware, indexHtmlTransformer, thirdPartySourcemaps = false) {
|
|
329
326
|
const proxy = await (0, utils_2.loadProxyConfiguration)(serverOptions.workspaceRoot, serverOptions.proxyConfig, true);
|
|
330
327
|
// dynamically import Vite for ESM compatibility
|
|
@@ -344,7 +341,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
344
341
|
publicDir: false,
|
|
345
342
|
esbuild: false,
|
|
346
343
|
mode: 'development',
|
|
347
|
-
appType: '
|
|
344
|
+
appType: 'mpa',
|
|
348
345
|
css: {
|
|
349
346
|
devSourcemap: true,
|
|
350
347
|
},
|
|
@@ -409,189 +406,18 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
409
406
|
},
|
|
410
407
|
plugins: [
|
|
411
408
|
(0, i18n_locale_plugin_1.createAngularLocaleDataPlugin)(),
|
|
412
|
-
{
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
// Remove query if present
|
|
425
|
-
const [importerFile] = importer.split('?', 1);
|
|
426
|
-
source =
|
|
427
|
-
'/' +
|
|
428
|
-
normalizePath((0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source));
|
|
429
|
-
}
|
|
430
|
-
const [file] = source.split('?', 1);
|
|
431
|
-
if (outputFiles.has(file)) {
|
|
432
|
-
return (0, node_path_1.join)(virtualProjectRoot, source);
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
load(id) {
|
|
436
|
-
const [file] = id.split('?', 1);
|
|
437
|
-
const relativeFile = '/' + normalizePath((0, node_path_1.relative)(virtualProjectRoot, file));
|
|
438
|
-
const codeContents = outputFiles.get(relativeFile)?.contents;
|
|
439
|
-
if (codeContents === undefined) {
|
|
440
|
-
if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) {
|
|
441
|
-
return loadViteClientCode(file);
|
|
442
|
-
}
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const code = Buffer.from(codeContents).toString('utf-8');
|
|
446
|
-
const mapContents = outputFiles.get(relativeFile + '.map')?.contents;
|
|
447
|
-
return {
|
|
448
|
-
// Remove source map URL comments from the code if a sourcemap is present.
|
|
449
|
-
// Vite will inline and add an additional sourcemap URL for the sourcemap.
|
|
450
|
-
code: mapContents ? code.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '') : code,
|
|
451
|
-
map: mapContents && Buffer.from(mapContents).toString('utf-8'),
|
|
452
|
-
};
|
|
453
|
-
},
|
|
454
|
-
configureServer(server) {
|
|
455
|
-
const originalssrTransform = server.ssrTransform;
|
|
456
|
-
server.ssrTransform = async (code, map, url, originalCode) => {
|
|
457
|
-
const result = await originalssrTransform(code, null, url, originalCode);
|
|
458
|
-
if (!result || !result.map || !map) {
|
|
459
|
-
return result;
|
|
460
|
-
}
|
|
461
|
-
const remappedMap = (0, remapping_1.default)([result.map, map], () => null);
|
|
462
|
-
// Set the sourcemap root to the workspace root. This is needed since we set a virtual path as root.
|
|
463
|
-
remappedMap.sourceRoot = normalizePath(serverOptions.workspaceRoot) + '/';
|
|
464
|
-
return {
|
|
465
|
-
...result,
|
|
466
|
-
map: remappedMap,
|
|
467
|
-
};
|
|
468
|
-
};
|
|
469
|
-
// Assets and resources get handled first
|
|
470
|
-
server.middlewares.use(function angularAssetsMiddleware(req, res, next) {
|
|
471
|
-
if (req.url === undefined || res.writableEnded) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
// Parse the incoming request.
|
|
475
|
-
// The base of the URL is unused but required to parse the URL.
|
|
476
|
-
const pathname = pathnameWithoutBasePath(req.url, server.config.base);
|
|
477
|
-
const extension = (0, node_path_1.extname)(pathname);
|
|
478
|
-
// Rewrite all build assets to a vite raw fs URL
|
|
479
|
-
const assetSourcePath = assets.get(pathname);
|
|
480
|
-
if (assetSourcePath !== undefined) {
|
|
481
|
-
// Workaround to disable Vite transformer middleware.
|
|
482
|
-
// See: https://github.com/vitejs/vite/blob/746a1daab0395f98f0afbdee8f364cb6cf2f3b3f/packages/vite/src/node/server/middlewares/transform.ts#L201 and
|
|
483
|
-
// https://github.com/vitejs/vite/blob/746a1daab0395f98f0afbdee8f364cb6cf2f3b3f/packages/vite/src/node/server/transformRequest.ts#L204-L206
|
|
484
|
-
req.headers.accept = 'text/html';
|
|
485
|
-
// The encoding needs to match what happens in the vite static middleware.
|
|
486
|
-
// ref: https://github.com/vitejs/vite/blob/d4f13bd81468961c8c926438e815ab6b1c82735e/packages/vite/src/node/server/middlewares/static.ts#L163
|
|
487
|
-
req.url = `${server.config.base}@fs/${encodeURI(assetSourcePath)}`;
|
|
488
|
-
next();
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
// Resource files are handled directly.
|
|
492
|
-
// Global stylesheets (CSS files) are currently considered resources to workaround
|
|
493
|
-
// dev server sourcemap issues with stylesheets.
|
|
494
|
-
if (extension !== '.js' && extension !== '.html') {
|
|
495
|
-
const outputFile = outputFiles.get(pathname);
|
|
496
|
-
if (outputFile?.servable) {
|
|
497
|
-
const mimeType = (0, mrmime_1.lookup)(extension);
|
|
498
|
-
if (mimeType) {
|
|
499
|
-
res.setHeader('Content-Type', mimeType);
|
|
500
|
-
}
|
|
501
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
502
|
-
if (serverOptions.headers) {
|
|
503
|
-
Object.entries(serverOptions.headers).forEach(([name, value]) => res.setHeader(name, value));
|
|
504
|
-
}
|
|
505
|
-
res.end(outputFile.contents);
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
next();
|
|
510
|
-
});
|
|
511
|
-
if (extensionMiddleware?.length) {
|
|
512
|
-
extensionMiddleware.forEach((middleware) => server.middlewares.use(middleware));
|
|
513
|
-
}
|
|
514
|
-
// Returning a function, installs middleware after the main transform middleware but
|
|
515
|
-
// before the built-in HTML middleware
|
|
516
|
-
return () => {
|
|
517
|
-
function angularSSRMiddleware(req, res, next) {
|
|
518
|
-
const url = req.originalUrl;
|
|
519
|
-
if (
|
|
520
|
-
// Skip if path is not defined.
|
|
521
|
-
!url ||
|
|
522
|
-
// Skip if path is like a file.
|
|
523
|
-
// NOTE: We use a regexp to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
|
|
524
|
-
/^\.[a-z]{2,4}$/i.test((0, node_path_1.extname)(url.split('?')[0]))) {
|
|
525
|
-
next();
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
const rawHtml = outputFiles.get('/index.server.html')?.contents;
|
|
529
|
-
if (!rawHtml) {
|
|
530
|
-
next();
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
transformIndexHtmlAndAddHeaders(url, rawHtml, res, next, async (html) => {
|
|
534
|
-
const { content } = await (0, render_page_1.renderPage)({
|
|
535
|
-
document: html,
|
|
536
|
-
route: new URL(req.originalUrl ?? '/', server.resolvedUrls?.local[0]).toString(),
|
|
537
|
-
serverContext: 'ssr',
|
|
538
|
-
loadBundle: (uri) =>
|
|
539
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
540
|
-
server.ssrLoadModule(uri.slice(1)),
|
|
541
|
-
// Files here are only needed for critical CSS inlining.
|
|
542
|
-
outputFiles: {},
|
|
543
|
-
// TODO: add support for critical css inlining.
|
|
544
|
-
inlineCriticalCss: false,
|
|
545
|
-
});
|
|
546
|
-
return indexHtmlTransformer && content
|
|
547
|
-
? await indexHtmlTransformer(content)
|
|
548
|
-
: content;
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
if (ssr) {
|
|
552
|
-
server.middlewares.use(angularSSRMiddleware);
|
|
553
|
-
}
|
|
554
|
-
server.middlewares.use(function angularIndexMiddleware(req, res, next) {
|
|
555
|
-
if (!req.url) {
|
|
556
|
-
next();
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
// Parse the incoming request.
|
|
560
|
-
// The base of the URL is unused but required to parse the URL.
|
|
561
|
-
const pathname = pathnameWithoutBasePath(req.url, server.config.base);
|
|
562
|
-
if (pathname === '/' || pathname === `/index.html`) {
|
|
563
|
-
const rawHtml = outputFiles.get('/index.html')?.contents;
|
|
564
|
-
if (rawHtml) {
|
|
565
|
-
transformIndexHtmlAndAddHeaders(req.url, rawHtml, res, next, indexHtmlTransformer);
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
next();
|
|
570
|
-
});
|
|
571
|
-
};
|
|
572
|
-
function transformIndexHtmlAndAddHeaders(url, rawHtml, res, next, additionalTransformer) {
|
|
573
|
-
server
|
|
574
|
-
.transformIndexHtml(url, Buffer.from(rawHtml).toString('utf-8'))
|
|
575
|
-
.then(async (processedHtml) => {
|
|
576
|
-
if (additionalTransformer) {
|
|
577
|
-
const content = await additionalTransformer(processedHtml);
|
|
578
|
-
if (!content) {
|
|
579
|
-
next();
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
processedHtml = content;
|
|
583
|
-
}
|
|
584
|
-
res.setHeader('Content-Type', 'text/html');
|
|
585
|
-
res.setHeader('Cache-Control', 'no-cache');
|
|
586
|
-
if (serverOptions.headers) {
|
|
587
|
-
Object.entries(serverOptions.headers).forEach(([name, value]) => res.setHeader(name, value));
|
|
588
|
-
}
|
|
589
|
-
res.end(processedHtml);
|
|
590
|
-
})
|
|
591
|
-
.catch((error) => next(error));
|
|
592
|
-
}
|
|
593
|
-
},
|
|
594
|
-
},
|
|
409
|
+
(0, angular_memory_plugin_1.createAngularMemoryPlugin)({
|
|
410
|
+
workspaceRoot: serverOptions.workspaceRoot,
|
|
411
|
+
virtualProjectRoot,
|
|
412
|
+
outputFiles,
|
|
413
|
+
assets,
|
|
414
|
+
ssr,
|
|
415
|
+
external: externalMetadata.explicit,
|
|
416
|
+
indexHtmlTransformer,
|
|
417
|
+
extensionMiddleware,
|
|
418
|
+
extraHeaders: serverOptions.headers,
|
|
419
|
+
normalizePath,
|
|
420
|
+
}),
|
|
595
421
|
],
|
|
596
422
|
// Browser only optimizeDeps. (This does not run for SSR dependencies).
|
|
597
423
|
optimizeDeps: getDepOptimizationConfig({
|
|
@@ -627,31 +453,6 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks,
|
|
|
627
453
|
return configuration;
|
|
628
454
|
}
|
|
629
455
|
exports.setupServer = setupServer;
|
|
630
|
-
/**
|
|
631
|
-
* Reads the resolved Vite client code from disk and updates the content to remove
|
|
632
|
-
* an unactionable suggestion to update the Vite configuration file to disable the
|
|
633
|
-
* error overlay. The Vite configuration file is not present when used in the Angular
|
|
634
|
-
* CLI.
|
|
635
|
-
* @param file The absolute path to the Vite client code.
|
|
636
|
-
* @returns
|
|
637
|
-
*/
|
|
638
|
-
async function loadViteClientCode(file) {
|
|
639
|
-
const originalContents = await (0, promises_1.readFile)(file, 'utf-8');
|
|
640
|
-
let contents = originalContents.replace('You can also disable this overlay by setting', '');
|
|
641
|
-
contents = contents.replace(
|
|
642
|
-
// eslint-disable-next-line max-len
|
|
643
|
-
'<code part="config-option-name">server.hmr.overlay</code> to <code part="config-option-value">false</code> in <code part="config-file-name">vite.config.js.</code>', '');
|
|
644
|
-
(0, node_assert_1.default)(originalContents !== contents, 'Failed to update Vite client error overlay text.');
|
|
645
|
-
return contents;
|
|
646
|
-
}
|
|
647
|
-
function pathnameWithoutBasePath(url, basePath) {
|
|
648
|
-
const parsedUrl = new URL(url, 'http://localhost');
|
|
649
|
-
const pathname = decodeURIComponent(parsedUrl.pathname);
|
|
650
|
-
// slice(basePath.length - 1) to retain the trailing slash
|
|
651
|
-
return basePath !== '/' && pathname.startsWith(basePath)
|
|
652
|
-
? pathname.slice(basePath.length - 1)
|
|
653
|
-
: pathname;
|
|
654
|
-
}
|
|
655
456
|
function getDepOptimizationConfig({ disabled, exclude, include, target, prebundleTransformer, ssr, loader, thirdPartySourcemaps, }) {
|
|
656
457
|
const plugins = [
|
|
657
458
|
{
|
|
@@ -20,9 +20,11 @@ async function extractMessages(options, builderName, context, extractorConstruct
|
|
|
20
20
|
// Setup the build options for the application based on the buildTarget option
|
|
21
21
|
const buildOptions = (await context.validateOptions(await context.getTargetOptions(options.buildTarget), builderName));
|
|
22
22
|
buildOptions.optimization = false;
|
|
23
|
-
buildOptions.sourceMap = { scripts: true, vendor: true };
|
|
23
|
+
buildOptions.sourceMap = { scripts: true, vendor: true, styles: false };
|
|
24
24
|
buildOptions.localize = false;
|
|
25
25
|
buildOptions.budgets = undefined;
|
|
26
|
+
buildOptions.index = false;
|
|
27
|
+
buildOptions.serviceWorker = false;
|
|
26
28
|
let build;
|
|
27
29
|
if (builderName === '@angular-devkit/build-angular:application') {
|
|
28
30
|
build = application_1.buildApplicationInternal;
|
|
@@ -43,7 +43,7 @@ async function extract() {
|
|
|
43
43
|
const browserIndexInputPath = path.join(outputPath, indexFile);
|
|
44
44
|
const document = await fs.promises.readFile(browserIndexInputPath, 'utf8');
|
|
45
45
|
const bootstrapAppFnOrModule = bootstrapAppFn || AppServerModule;
|
|
46
|
-
(0, node_assert_1.default)(bootstrapAppFnOrModule, `
|
|
46
|
+
(0, node_assert_1.default)(bootstrapAppFnOrModule, `The file "${serverBundlePath}" does not have a default export for an AppServerModule or a bootstrapping function.`);
|
|
47
47
|
const routes = [];
|
|
48
48
|
for await (const { route, success } of extractRoutes(bootstrapAppFnOrModule, document)) {
|
|
49
49
|
if (success) {
|
|
@@ -110,18 +110,20 @@ class ComponentStylesheetBundler {
|
|
|
110
110
|
if (!result.errors) {
|
|
111
111
|
for (const outputFile of result.outputFiles) {
|
|
112
112
|
const filename = node_path_1.default.basename(outputFile.path);
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
113
|
+
if (outputFile.type === bundler_context_1.BuildOutputFileType.Media || filename.endsWith('.css.map')) {
|
|
114
|
+
// The output files could also contain resources (images/fonts/etc.) that were referenced and the map files.
|
|
115
|
+
// Clone the output file to avoid amending the original path which would causes problems during rebuild.
|
|
116
|
+
const clonedOutputFile = outputFile.clone();
|
|
117
|
+
// Needed for Bazel as otherwise the files will not be written in the correct place,
|
|
118
|
+
// this is because esbuild will resolve the output file from the outdir which is currently set to `workspaceRoot` twice,
|
|
119
|
+
// once in the stylesheet and the other in the application code bundler.
|
|
120
|
+
// Ex: `../../../../../app.component.css.map`.
|
|
121
|
+
clonedOutputFile.path = node_path_1.default.join(this.options.workspaceRoot, outputFile.path);
|
|
122
|
+
outputFiles.push(clonedOutputFile);
|
|
118
123
|
}
|
|
119
124
|
else if (filename.endsWith('.css')) {
|
|
120
125
|
contents = outputFile.text;
|
|
121
126
|
}
|
|
122
|
-
else if (filename.endsWith('.css.map')) {
|
|
123
|
-
outputFiles.push(outputFile);
|
|
124
|
-
}
|
|
125
127
|
else {
|
|
126
128
|
throw new Error(`Unexpected non CSS/Media file "${filename}" outputted during component stylesheet processing.`);
|
|
127
129
|
}
|
|
@@ -18,6 +18,7 @@ const node_path_1 = require("node:path");
|
|
|
18
18
|
const environment_options_1 = require("../../utils/environment-options");
|
|
19
19
|
const compiler_plugin_1 = require("./angular/compiler-plugin");
|
|
20
20
|
const compiler_plugin_options_1 = require("./compiler-plugin-options");
|
|
21
|
+
const external_packages_plugin_1 = require("./external-packages-plugin");
|
|
21
22
|
const i18n_locale_plugin_1 = require("./i18n-locale-plugin");
|
|
22
23
|
const rxjs_esm_resolution_plugin_1 = require("./rxjs-esm-resolution-plugin");
|
|
23
24
|
const sourcemap_ignorelist_plugin_1 = require("./sourcemap-ignorelist-plugin");
|
|
@@ -47,12 +48,20 @@ function createBrowserCodeBundleOptions(options, target, sourceFileCache) {
|
|
|
47
48
|
styleOptions),
|
|
48
49
|
],
|
|
49
50
|
};
|
|
50
|
-
if (options.externalPackages) {
|
|
51
|
-
buildOptions.packages = 'external';
|
|
52
|
-
}
|
|
53
51
|
if (options.plugins) {
|
|
54
52
|
buildOptions.plugins?.push(...options.plugins);
|
|
55
53
|
}
|
|
54
|
+
if (options.externalPackages) {
|
|
55
|
+
// Package files affected by a customized loader should not be implicitly marked as external
|
|
56
|
+
if (options.loaderExtensions || options.plugins) {
|
|
57
|
+
// Plugin must be added after custom plugins to ensure any added loader options are considered
|
|
58
|
+
buildOptions.plugins?.push((0, external_packages_plugin_1.createExternalPackagesPlugin)());
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Safe to use the packages external option directly
|
|
62
|
+
buildOptions.packages = 'external';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
56
65
|
return buildOptions;
|
|
57
66
|
}
|
|
58
67
|
exports.createBrowserCodeBundleOptions = createBrowserCodeBundleOptions;
|
|
@@ -177,10 +177,12 @@ class BundlerContext {
|
|
|
177
177
|
if (this.incremental) {
|
|
178
178
|
// When incremental always add any files from the load result cache
|
|
179
179
|
if (this.#loadCache) {
|
|
180
|
-
this.#loadCache.watchFiles
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
180
|
+
for (const file of this.#loadCache.watchFiles) {
|
|
181
|
+
if (!isInternalAngularFile(file)) {
|
|
182
|
+
// watch files are fully resolved paths
|
|
183
|
+
this.watchFiles.add(file);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
184
186
|
}
|
|
185
187
|
}
|
|
186
188
|
}
|
|
@@ -189,10 +191,12 @@ class BundlerContext {
|
|
|
189
191
|
// currently enabled with watch mode where watch files are needed.
|
|
190
192
|
if (this.incremental) {
|
|
191
193
|
// Add input files except virtual angular files which do not exist on disk
|
|
192
|
-
Object.keys(result.metafile.inputs)
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
194
|
+
for (const input of Object.keys(result.metafile.inputs)) {
|
|
195
|
+
if (!isInternalAngularFile(input)) {
|
|
196
|
+
// input file paths are always relative to the workspace root
|
|
197
|
+
this.watchFiles.add((0, node_path_1.join)(this.workspaceRoot, input));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
196
200
|
}
|
|
197
201
|
// Return if the build encountered any errors
|
|
198
202
|
if (result.errors.length) {
|
|
@@ -256,7 +260,9 @@ class BundlerContext {
|
|
|
256
260
|
for (const { imports } of Object.values(result.metafile.outputs)) {
|
|
257
261
|
for (const importData of imports) {
|
|
258
262
|
if (!importData.external ||
|
|
259
|
-
(importData.kind !== 'import-statement' &&
|
|
263
|
+
(importData.kind !== 'import-statement' &&
|
|
264
|
+
importData.kind !== 'dynamic-import' &&
|
|
265
|
+
importData.kind !== 'require-call')) {
|
|
260
266
|
continue;
|
|
261
267
|
}
|
|
262
268
|
externalImports.add(importData.path);
|
|
@@ -0,0 +1,16 @@
|
|
|
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 type { Plugin } from 'esbuild';
|
|
9
|
+
/**
|
|
10
|
+
* Creates a plugin that marks any resolved path as external if it is within a node modules directory.
|
|
11
|
+
* This is used instead of the esbuild `packages` option to avoid marking files that should be loaded
|
|
12
|
+
* via customized loaders. This is necessary to prevent Vite development server pre-bundling errors.
|
|
13
|
+
*
|
|
14
|
+
* @returns An esbuild plugin.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createExternalPackagesPlugin(): Plugin;
|
|
@@ -0,0 +1,66 @@
|
|
|
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.createExternalPackagesPlugin = void 0;
|
|
11
|
+
const node_path_1 = require("node:path");
|
|
12
|
+
const EXTERNAL_PACKAGE_RESOLUTION = Symbol('EXTERNAL_PACKAGE_RESOLUTION');
|
|
13
|
+
/**
|
|
14
|
+
* Creates a plugin that marks any resolved path as external if it is within a node modules directory.
|
|
15
|
+
* This is used instead of the esbuild `packages` option to avoid marking files that should be loaded
|
|
16
|
+
* via customized loaders. This is necessary to prevent Vite development server pre-bundling errors.
|
|
17
|
+
*
|
|
18
|
+
* @returns An esbuild plugin.
|
|
19
|
+
*/
|
|
20
|
+
function createExternalPackagesPlugin() {
|
|
21
|
+
return {
|
|
22
|
+
name: 'angular-external-packages',
|
|
23
|
+
setup(build) {
|
|
24
|
+
// Safe to use native packages external option if no loader options present
|
|
25
|
+
if (build.initialOptions.loader === undefined ||
|
|
26
|
+
Object.keys(build.initialOptions.loader).length === 0) {
|
|
27
|
+
build.initialOptions.packages = 'external';
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const loaderFileExtensions = new Set(Object.keys(build.initialOptions.loader));
|
|
31
|
+
// Only attempt resolve of non-relative and non-absolute paths
|
|
32
|
+
build.onResolve({ filter: /^[^./]/ }, async (args) => {
|
|
33
|
+
if (args.pluginData?.[EXTERNAL_PACKAGE_RESOLUTION]) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
|
|
37
|
+
pluginData[EXTERNAL_PACKAGE_RESOLUTION] = true;
|
|
38
|
+
const result = await build.resolve(args.path, {
|
|
39
|
+
importer,
|
|
40
|
+
kind,
|
|
41
|
+
namespace,
|
|
42
|
+
pluginData,
|
|
43
|
+
resolveDir,
|
|
44
|
+
});
|
|
45
|
+
// Return result if unable to resolve or explicitly marked external (externalDependencies option)
|
|
46
|
+
if (!result.path || result.external) {
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
// Allow customized loaders to run against configured paths regardless of location
|
|
50
|
+
if (loaderFileExtensions.has((0, node_path_1.extname)(result.path))) {
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
// Mark paths from a node modules directory as external
|
|
54
|
+
if (/[\\/]node_modules[\\/]/.test(result.path)) {
|
|
55
|
+
return {
|
|
56
|
+
path: args.path,
|
|
57
|
+
external: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Otherwise return original result
|
|
61
|
+
return result;
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
exports.createExternalPackagesPlugin = createExternalPackagesPlugin;
|
|
@@ -11,6 +11,7 @@ exports.createCssResourcePlugin = void 0;
|
|
|
11
11
|
const promises_1 = require("node:fs/promises");
|
|
12
12
|
const node_path_1 = require("node:path");
|
|
13
13
|
const load_result_cache_1 = require("../load-result-cache");
|
|
14
|
+
const CSS_RESOURCE_NAMESPACE = 'angular:css-resource';
|
|
14
15
|
/**
|
|
15
16
|
* Symbol marker used to indicate CSS resource resolution is being attempted.
|
|
16
17
|
* This is used to prevent an infinite loop within the plugin's resolve hook.
|
|
@@ -89,10 +90,10 @@ function createCssResourcePlugin(cache) {
|
|
|
89
90
|
// Use a relative path to prevent fully resolved paths in the metafile (JSON stats file).
|
|
90
91
|
// This is only necessary for custom namespaces. esbuild will handle the file namespace.
|
|
91
92
|
path: (0, node_path_1.relative)(build.initialOptions.absWorkingDir ?? '', result.path),
|
|
92
|
-
namespace:
|
|
93
|
+
namespace: CSS_RESOURCE_NAMESPACE,
|
|
93
94
|
};
|
|
94
95
|
});
|
|
95
|
-
build.onLoad({ filter: /./, namespace:
|
|
96
|
+
build.onLoad({ filter: /./, namespace: CSS_RESOURCE_NAMESPACE }, (0, load_result_cache_1.createCachedLoad)(cache, async (args) => {
|
|
96
97
|
const resourcePath = (0, node_path_1.join)(build.initialOptions.absWorkingDir ?? '', args.path);
|
|
97
98
|
return {
|
|
98
99
|
contents: await (0, promises_1.readFile)(resourcePath),
|
|
@@ -0,0 +1,24 @@
|
|
|
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 type { Connect, Plugin } from 'vite';
|
|
9
|
+
export interface AngularMemoryPluginOptions {
|
|
10
|
+
workspaceRoot: string;
|
|
11
|
+
virtualProjectRoot: string;
|
|
12
|
+
outputFiles: Map<string, {
|
|
13
|
+
contents: Uint8Array;
|
|
14
|
+
servable: boolean;
|
|
15
|
+
}>;
|
|
16
|
+
assets: Map<string, string>;
|
|
17
|
+
ssr: boolean;
|
|
18
|
+
external?: string[];
|
|
19
|
+
extensionMiddleware?: Connect.NextHandleFunction[];
|
|
20
|
+
extraHeaders?: Record<string, string>;
|
|
21
|
+
indexHtmlTransformer?: (content: string) => Promise<string>;
|
|
22
|
+
normalizePath: (path: string) => string;
|
|
23
|
+
}
|
|
24
|
+
export declare function createAngularMemoryPlugin(options: AngularMemoryPluginOptions): Plugin;
|
|
@@ -0,0 +1,251 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
10
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
11
|
+
};
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.createAngularMemoryPlugin = void 0;
|
|
14
|
+
const remapping_1 = __importDefault(require("@ampproject/remapping"));
|
|
15
|
+
const mrmime_1 = require("mrmime");
|
|
16
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
17
|
+
const promises_1 = require("node:fs/promises");
|
|
18
|
+
const node_path_1 = require("node:path");
|
|
19
|
+
const render_page_1 = require("../../utils/server-rendering/render-page");
|
|
20
|
+
// eslint-disable-next-line max-lines-per-function
|
|
21
|
+
function createAngularMemoryPlugin(options) {
|
|
22
|
+
const { workspaceRoot, virtualProjectRoot, outputFiles, assets, external, ssr, extensionMiddleware, extraHeaders, indexHtmlTransformer, normalizePath, } = options;
|
|
23
|
+
return {
|
|
24
|
+
name: 'vite:angular-memory',
|
|
25
|
+
// Ensures plugin hooks run before built-in Vite hooks
|
|
26
|
+
enforce: 'pre',
|
|
27
|
+
async resolveId(source, importer) {
|
|
28
|
+
// Prevent vite from resolving an explicit external dependency (`externalDependencies` option)
|
|
29
|
+
if (external?.includes(source)) {
|
|
30
|
+
// This is still not ideal since Vite will still transform the import specifier to
|
|
31
|
+
// `/@id/${source}` but is currently closer to a raw external than a resolved file path.
|
|
32
|
+
return source;
|
|
33
|
+
}
|
|
34
|
+
if (importer && source[0] === '.' && importer.startsWith(virtualProjectRoot)) {
|
|
35
|
+
// Remove query if present
|
|
36
|
+
const [importerFile] = importer.split('?', 1);
|
|
37
|
+
source =
|
|
38
|
+
'/' + normalizePath((0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.relative)(virtualProjectRoot, importerFile)), source));
|
|
39
|
+
}
|
|
40
|
+
const [file] = source.split('?', 1);
|
|
41
|
+
if (outputFiles.has(file)) {
|
|
42
|
+
return (0, node_path_1.join)(virtualProjectRoot, source);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
load(id) {
|
|
46
|
+
const [file] = id.split('?', 1);
|
|
47
|
+
const relativeFile = '/' + normalizePath((0, node_path_1.relative)(virtualProjectRoot, file));
|
|
48
|
+
const codeContents = outputFiles.get(relativeFile)?.contents;
|
|
49
|
+
if (codeContents === undefined) {
|
|
50
|
+
if (relativeFile.endsWith('/node_modules/vite/dist/client/client.mjs')) {
|
|
51
|
+
return loadViteClientCode(file);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const code = Buffer.from(codeContents).toString('utf-8');
|
|
56
|
+
const mapContents = outputFiles.get(relativeFile + '.map')?.contents;
|
|
57
|
+
return {
|
|
58
|
+
// Remove source map URL comments from the code if a sourcemap is present.
|
|
59
|
+
// Vite will inline and add an additional sourcemap URL for the sourcemap.
|
|
60
|
+
code: mapContents ? code.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '') : code,
|
|
61
|
+
map: mapContents && Buffer.from(mapContents).toString('utf-8'),
|
|
62
|
+
};
|
|
63
|
+
},
|
|
64
|
+
// eslint-disable-next-line max-lines-per-function
|
|
65
|
+
configureServer(server) {
|
|
66
|
+
const originalssrTransform = server.ssrTransform;
|
|
67
|
+
server.ssrTransform = async (code, map, url, originalCode) => {
|
|
68
|
+
const result = await originalssrTransform(code, null, url, originalCode);
|
|
69
|
+
if (!result || !result.map || !map) {
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
const remappedMap = (0, remapping_1.default)([result.map, map], () => null);
|
|
73
|
+
// Set the sourcemap root to the workspace root. This is needed since we set a virtual path as root.
|
|
74
|
+
remappedMap.sourceRoot = normalizePath(workspaceRoot) + '/';
|
|
75
|
+
return {
|
|
76
|
+
...result,
|
|
77
|
+
map: remappedMap,
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
// Assets and resources get handled first
|
|
81
|
+
server.middlewares.use(function angularAssetsMiddleware(req, res, next) {
|
|
82
|
+
if (req.url === undefined || res.writableEnded) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
// Parse the incoming request.
|
|
86
|
+
// The base of the URL is unused but required to parse the URL.
|
|
87
|
+
const pathname = pathnameWithoutBasePath(req.url, server.config.base);
|
|
88
|
+
const extension = (0, node_path_1.extname)(pathname);
|
|
89
|
+
// Rewrite all build assets to a vite raw fs URL
|
|
90
|
+
const assetSourcePath = assets.get(pathname);
|
|
91
|
+
if (assetSourcePath !== undefined) {
|
|
92
|
+
// Workaround to disable Vite transformer middleware.
|
|
93
|
+
// See: https://github.com/vitejs/vite/blob/746a1daab0395f98f0afbdee8f364cb6cf2f3b3f/packages/vite/src/node/server/middlewares/transform.ts#L201 and
|
|
94
|
+
// https://github.com/vitejs/vite/blob/746a1daab0395f98f0afbdee8f364cb6cf2f3b3f/packages/vite/src/node/server/transformRequest.ts#L204-L206
|
|
95
|
+
req.headers.accept = 'text/html';
|
|
96
|
+
// The encoding needs to match what happens in the vite static middleware.
|
|
97
|
+
// ref: https://github.com/vitejs/vite/blob/d4f13bd81468961c8c926438e815ab6b1c82735e/packages/vite/src/node/server/middlewares/static.ts#L163
|
|
98
|
+
req.url = `${server.config.base}@fs/${encodeURI(assetSourcePath)}`;
|
|
99
|
+
next();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
// Resource files are handled directly.
|
|
103
|
+
// Global stylesheets (CSS files) are currently considered resources to workaround
|
|
104
|
+
// dev server sourcemap issues with stylesheets.
|
|
105
|
+
if (extension !== '.js' && extension !== '.html') {
|
|
106
|
+
const outputFile = outputFiles.get(pathname);
|
|
107
|
+
if (outputFile?.servable) {
|
|
108
|
+
const mimeType = (0, mrmime_1.lookup)(extension);
|
|
109
|
+
if (mimeType) {
|
|
110
|
+
res.setHeader('Content-Type', mimeType);
|
|
111
|
+
}
|
|
112
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
113
|
+
if (extraHeaders) {
|
|
114
|
+
Object.entries(extraHeaders).forEach(([name, value]) => res.setHeader(name, value));
|
|
115
|
+
}
|
|
116
|
+
res.end(outputFile.contents);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
next();
|
|
121
|
+
});
|
|
122
|
+
if (extensionMiddleware?.length) {
|
|
123
|
+
extensionMiddleware.forEach((middleware) => server.middlewares.use(middleware));
|
|
124
|
+
}
|
|
125
|
+
// Returning a function, installs middleware after the main transform middleware but
|
|
126
|
+
// before the built-in HTML middleware
|
|
127
|
+
return () => {
|
|
128
|
+
server.middlewares.use(angularHtmlFallbackMiddleware);
|
|
129
|
+
function angularSSRMiddleware(req, res, next) {
|
|
130
|
+
const url = req.originalUrl;
|
|
131
|
+
if (!req.url ||
|
|
132
|
+
// Skip if path is not defined.
|
|
133
|
+
!url ||
|
|
134
|
+
// Skip if path is like a file.
|
|
135
|
+
// NOTE: We use a mime type lookup to mitigate against matching requests like: /browse/pl.0ef59752c0cd457dbf1391f08cbd936f
|
|
136
|
+
lookupMimeTypeFromRequest(url)) {
|
|
137
|
+
next();
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const rawHtml = outputFiles.get('/index.server.html')?.contents;
|
|
141
|
+
if (!rawHtml) {
|
|
142
|
+
next();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
transformIndexHtmlAndAddHeaders(req.url, rawHtml, res, next, async (html) => {
|
|
146
|
+
const { content } = await (0, render_page_1.renderPage)({
|
|
147
|
+
document: html,
|
|
148
|
+
route: new URL(req.originalUrl ?? '/', server.resolvedUrls?.local[0]).toString(),
|
|
149
|
+
serverContext: 'ssr',
|
|
150
|
+
loadBundle: (uri) =>
|
|
151
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
152
|
+
server.ssrLoadModule(uri.slice(1)),
|
|
153
|
+
// Files here are only needed for critical CSS inlining.
|
|
154
|
+
outputFiles: {},
|
|
155
|
+
// TODO: add support for critical css inlining.
|
|
156
|
+
inlineCriticalCss: false,
|
|
157
|
+
});
|
|
158
|
+
return indexHtmlTransformer && content ? await indexHtmlTransformer(content) : content;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
if (ssr) {
|
|
162
|
+
server.middlewares.use(angularSSRMiddleware);
|
|
163
|
+
}
|
|
164
|
+
server.middlewares.use(function angularIndexMiddleware(req, res, next) {
|
|
165
|
+
if (!req.url) {
|
|
166
|
+
next();
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
// Parse the incoming request.
|
|
170
|
+
// The base of the URL is unused but required to parse the URL.
|
|
171
|
+
const pathname = pathnameWithoutBasePath(req.url, server.config.base);
|
|
172
|
+
if (pathname === '/' || pathname === `/index.html`) {
|
|
173
|
+
const rawHtml = outputFiles.get('/index.html')?.contents;
|
|
174
|
+
if (rawHtml) {
|
|
175
|
+
transformIndexHtmlAndAddHeaders(req.url, rawHtml, res, next, indexHtmlTransformer);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
next();
|
|
180
|
+
});
|
|
181
|
+
};
|
|
182
|
+
function transformIndexHtmlAndAddHeaders(url, rawHtml, res, next, additionalTransformer) {
|
|
183
|
+
server
|
|
184
|
+
.transformIndexHtml(url, Buffer.from(rawHtml).toString('utf-8'))
|
|
185
|
+
.then(async (processedHtml) => {
|
|
186
|
+
if (additionalTransformer) {
|
|
187
|
+
const content = await additionalTransformer(processedHtml);
|
|
188
|
+
if (!content) {
|
|
189
|
+
next();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
processedHtml = content;
|
|
193
|
+
}
|
|
194
|
+
res.setHeader('Content-Type', 'text/html');
|
|
195
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
196
|
+
if (extraHeaders) {
|
|
197
|
+
Object.entries(extraHeaders).forEach(([name, value]) => res.setHeader(name, value));
|
|
198
|
+
}
|
|
199
|
+
res.end(processedHtml);
|
|
200
|
+
})
|
|
201
|
+
.catch((error) => next(error));
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
exports.createAngularMemoryPlugin = createAngularMemoryPlugin;
|
|
207
|
+
/**
|
|
208
|
+
* Reads the resolved Vite client code from disk and updates the content to remove
|
|
209
|
+
* an unactionable suggestion to update the Vite configuration file to disable the
|
|
210
|
+
* error overlay. The Vite configuration file is not present when used in the Angular
|
|
211
|
+
* CLI.
|
|
212
|
+
* @param file The absolute path to the Vite client code.
|
|
213
|
+
* @returns
|
|
214
|
+
*/
|
|
215
|
+
async function loadViteClientCode(file) {
|
|
216
|
+
const originalContents = await (0, promises_1.readFile)(file, 'utf-8');
|
|
217
|
+
let contents = originalContents.replace('You can also disable this overlay by setting', '');
|
|
218
|
+
contents = contents.replace(
|
|
219
|
+
// eslint-disable-next-line max-len
|
|
220
|
+
'<code part="config-option-name">server.hmr.overlay</code> to <code part="config-option-value">false</code> in <code part="config-file-name">vite.config.js.</code>', '');
|
|
221
|
+
(0, node_assert_1.default)(originalContents !== contents, 'Failed to update Vite client error overlay text.');
|
|
222
|
+
return contents;
|
|
223
|
+
}
|
|
224
|
+
function pathnameWithoutBasePath(url, basePath) {
|
|
225
|
+
const parsedUrl = new URL(url, 'http://localhost');
|
|
226
|
+
const pathname = decodeURIComponent(parsedUrl.pathname);
|
|
227
|
+
// slice(basePath.length - 1) to retain the trailing slash
|
|
228
|
+
return basePath !== '/' && pathname.startsWith(basePath)
|
|
229
|
+
? pathname.slice(basePath.length - 1)
|
|
230
|
+
: pathname;
|
|
231
|
+
}
|
|
232
|
+
function angularHtmlFallbackMiddleware(req, res, next) {
|
|
233
|
+
// Similar to how it is handled in vite
|
|
234
|
+
// https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L15C19-L15C45
|
|
235
|
+
if ((req.method === 'GET' || req.method === 'HEAD') &&
|
|
236
|
+
(!req.url || !lookupMimeTypeFromRequest(req.url)) &&
|
|
237
|
+
(!req.headers.accept ||
|
|
238
|
+
req.headers.accept.includes('text/html') ||
|
|
239
|
+
req.headers.accept.includes('text/*') ||
|
|
240
|
+
req.headers.accept.includes('*/*'))) {
|
|
241
|
+
req.url = '/index.html';
|
|
242
|
+
}
|
|
243
|
+
next();
|
|
244
|
+
}
|
|
245
|
+
function lookupMimeTypeFromRequest(url) {
|
|
246
|
+
const extension = (0, node_path_1.extname)(url.split('?')[0]);
|
|
247
|
+
if (extension === '.ico') {
|
|
248
|
+
return 'image/x-icon';
|
|
249
|
+
}
|
|
250
|
+
return extension && (0, mrmime_1.lookup)(extension);
|
|
251
|
+
}
|
|
@@ -23,23 +23,31 @@ const MEDIA_SET_HANDLER_PATTERN = /^this\.media=["'](.*)["'];?$/;
|
|
|
23
23
|
const CSP_MEDIA_ATTR = 'ngCspMedia';
|
|
24
24
|
/**
|
|
25
25
|
* Script text used to change the media value of the link tags.
|
|
26
|
+
*
|
|
27
|
+
* NOTE:
|
|
28
|
+
* We do not use `document.querySelectorAll('link').forEach((s) => s.addEventListener('load', ...)`
|
|
29
|
+
* because this does not always fire on Chome.
|
|
30
|
+
* See: https://github.com/angular/angular-cli/issues/26932 and https://crbug.com/1521256
|
|
26
31
|
*/
|
|
27
32
|
const LINK_LOAD_SCRIPT_CONTENT = [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`
|
|
41
|
-
|
|
42
|
-
|
|
33
|
+
'(() => {',
|
|
34
|
+
` const CSP_MEDIA_ATTR = '${CSP_MEDIA_ATTR}';`,
|
|
35
|
+
' const documentElement = document.documentElement;',
|
|
36
|
+
' const listener = (e) => {',
|
|
37
|
+
' const target = e.target;',
|
|
38
|
+
` if (!target || target.tagName !== 'LINK' || !target.hasAttribute(CSP_MEDIA_ATTR)) {`,
|
|
39
|
+
' return;',
|
|
40
|
+
' }',
|
|
41
|
+
' target.media = target.getAttribute(CSP_MEDIA_ATTR);',
|
|
42
|
+
' target.removeAttribute(CSP_MEDIA_ATTR);',
|
|
43
|
+
// Remove onload listener when there are no longer styles that need to be loaded.
|
|
44
|
+
' if (!document.head.querySelector(`link[${CSP_MEDIA_ATTR}]`)) {',
|
|
45
|
+
` documentElement.removeEventListener('load', listener);`,
|
|
46
|
+
' }',
|
|
47
|
+
' };',
|
|
48
|
+
// We use an event with capturing (the true parameter) because load events don't bubble.
|
|
49
|
+
` documentElement.addEventListener('load', listener, true);`,
|
|
50
|
+
'})();',
|
|
43
51
|
].join('\n');
|
|
44
52
|
class CrittersExtended extends critters_1.default {
|
|
45
53
|
optionsExtended;
|
|
@@ -105,7 +113,7 @@ class CrittersExtended extends critters_1.default {
|
|
|
105
113
|
// `addEventListener` to apply the media query instead.
|
|
106
114
|
link.removeAttribute('onload');
|
|
107
115
|
link.setAttribute(CSP_MEDIA_ATTR, crittersMedia[1]);
|
|
108
|
-
this.conditionallyInsertCspLoadingScript(document, cspNonce);
|
|
116
|
+
this.conditionallyInsertCspLoadingScript(document, cspNonce, link);
|
|
109
117
|
}
|
|
110
118
|
// Ideally we would hook in at the time Critters inserts the `style` tags, but there isn't
|
|
111
119
|
// a way of doing that at the moment so we fall back to doing it any time a `link` tag is
|
|
@@ -137,16 +145,16 @@ class CrittersExtended extends critters_1.default {
|
|
|
137
145
|
* Inserts the `script` tag that swaps the critical CSS at runtime,
|
|
138
146
|
* if one hasn't been inserted into the document already.
|
|
139
147
|
*/
|
|
140
|
-
conditionallyInsertCspLoadingScript(document, nonce) {
|
|
148
|
+
conditionallyInsertCspLoadingScript(document, nonce, link) {
|
|
141
149
|
if (this.addedCspScriptsDocuments.has(document)) {
|
|
142
150
|
return;
|
|
143
151
|
}
|
|
144
152
|
const script = document.createElement('script');
|
|
145
153
|
script.setAttribute('nonce', nonce);
|
|
146
154
|
script.textContent = LINK_LOAD_SCRIPT_CONTENT;
|
|
147
|
-
//
|
|
148
|
-
// run as early as possible,
|
|
149
|
-
document.head.
|
|
155
|
+
// Prepend the script to the head since it needs to
|
|
156
|
+
// run as early as possible, before the `link` tags.
|
|
157
|
+
document.head.insertBefore(script, link);
|
|
150
158
|
this.addedCspScriptsDocuments.add(document);
|
|
151
159
|
}
|
|
152
160
|
}
|
|
@@ -135,9 +135,9 @@ function normalizeProxyConfiguration(proxy) {
|
|
|
135
135
|
}
|
|
136
136
|
// TODO: Consider upstreaming glob support
|
|
137
137
|
for (const key of Object.keys(normalizedProxy)) {
|
|
138
|
-
if ((0, fast_glob_1.isDynamicPattern)(key)) {
|
|
139
|
-
const
|
|
140
|
-
normalizedProxy[
|
|
138
|
+
if (key[0] !== '^' && (0, fast_glob_1.isDynamicPattern)(key)) {
|
|
139
|
+
const pattern = (0, picomatch_1.makeRe)(key).source;
|
|
140
|
+
normalizedProxy[pattern] = normalizedProxy[key];
|
|
141
141
|
delete normalizedProxy[key];
|
|
142
142
|
}
|
|
143
143
|
}
|
|
@@ -29,8 +29,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
29
29
|
__setModuleDefault(result, mod);
|
|
30
30
|
return result;
|
|
31
31
|
};
|
|
32
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
33
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
34
|
+
};
|
|
32
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
36
|
exports.renderPage = void 0;
|
|
37
|
+
const node_assert_1 = __importDefault(require("node:assert"));
|
|
34
38
|
const node_path_1 = require("node:path");
|
|
35
39
|
const load_esm_1 = require("../load-esm");
|
|
36
40
|
/**
|
|
@@ -65,6 +69,7 @@ async function renderPage({ route, serverContext, document, inlineCriticalCss, o
|
|
|
65
69
|
},
|
|
66
70
|
];
|
|
67
71
|
let html;
|
|
72
|
+
(0, node_assert_1.default)(bootstrapAppFnOrModule, 'The file "./main.server.mjs" does not have a default export for an AppServerModule or a bootstrapping function.');
|
|
68
73
|
if (isBootstrapFn(bootstrapAppFnOrModule)) {
|
|
69
74
|
html = await renderApplication(bootstrapAppFnOrModule, {
|
|
70
75
|
document,
|