diff --git a/LICENSE b/LICENSE index 8876c32c..9017f114 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2017 Google, Inc. +Copyright (c) 2010-2024 Google LLC. https://angular.dev/license Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/package.json b/package.json index 0fe14313..24ce5866 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@angular/build", - "version": "18.2.0-next.3+sha-6419c2a", + "version": "18.2.20+sha-5d82d44", "description": "Official build system for Angular", "keywords": [ "Angular CLI", @@ -23,7 +23,7 @@ "builders": "builders.json", "dependencies": { "@ampproject/remapping": "2.3.0", - "@angular-devkit/architect": "github:angular/angular-devkit-architect-builds#6419c2a", + "@angular-devkit/architect": "github:angular/angular-devkit-architect-builds#5d82d44", "@babel/core": "7.25.2", "@babel/helper-annotate-as-pure": "7.24.7", "@babel/helper-split-export-declaration": "7.24.7", @@ -42,17 +42,17 @@ "parse5-html-rewriting-stream": "7.0.0", "picomatch": "4.0.2", "piscina": "4.6.1", - "rollup": "4.20.0", - "sass": "1.77.8", + "rollup": "4.22.4", + "sass": "1.77.6", "semver": "7.6.3", - "vite": "5.3.5", + "vite": "~5.4.17", "watchpack": "2.4.1" }, "peerDependencies": { - "@angular/compiler-cli": "^18.0.0 || ^18.2.0-next.0", - "@angular/localize": "^18.0.0 || ^18.2.0-next.0", - "@angular/platform-server": "^18.0.0 || ^18.2.0-next.0", - "@angular/service-worker": "^18.0.0 || ^18.2.0-next.0", + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", "less": "^4.2.0", "postcss": "^8.4.0", "tailwindcss": "^2.0.0 || ^3.0.0", diff --git a/src/builders/application/chunk-optimizer.js b/src/builders/application/chunk-optimizer.js index 2412495f..6f93c798 100644 --- a/src/builders/application/chunk-optimizer.js +++ b/src/builders/application/chunk-optimizer.js @@ -83,10 +83,7 @@ async function optimizeChunks(original, sourcemap) { const result = await bundle.generate({ compact: true, sourcemap, - chunkFileNames(chunkInfo) { - // Do not add hash to file name if already present - return /-[a-zA-Z0-9]{8}$/.test(chunkInfo.name) ? '[name].js' : '[name]-[hash].js'; - }, + chunkFileNames: (chunkInfo) => `${chunkInfo.name.replace(/-[a-zA-Z0-9]{8}$/, '')}-[hash].js`, }); optimizedOutput = result.output; } diff --git a/src/builders/dev-server/options.d.ts b/src/builders/dev-server/options.d.ts index 089034bb..a601907c 100644 --- a/src/builders/dev-server/options.d.ts +++ b/src/builders/dev-server/options.d.ts @@ -44,4 +44,5 @@ export declare function normalizeOptions(context: BuilderContext, projectName: s host?: string; port?: number; }; + allowedHosts: true | string[]; }>; diff --git a/src/builders/dev-server/options.js b/src/builders/dev-server/options.js index 70d8aa6e..178cb590 100644 --- a/src/builders/dev-server/options.js +++ b/src/builders/dev-server/options.js @@ -73,7 +73,7 @@ async function normalizeOptions(context, projectName, options) { } } // Initial options to keep - const { host, port, poll, open, verbose, watch, liveReload, hmr, headers, proxyConfig, servePath, ssl, sslCert, sslKey, prebundle, } = options; + const { host, port, poll, open, verbose, watch, liveReload, hmr, headers, proxyConfig, servePath, ssl, sslCert, sslKey, prebundle, allowedHosts, } = options; // Return all the normalized options return { buildTarget, @@ -97,5 +97,6 @@ async function normalizeOptions(context, projectName, options) { // Prebundling defaults to true but requires caching to function prebundle: cacheOptions.enabled && !optimization.scripts && prebundle, inspect, + allowedHosts: allowedHosts ? allowedHosts : [], }; } diff --git a/src/builders/dev-server/schema.d.ts b/src/builders/dev-server/schema.d.ts index cb7b93d8..e0bcba95 100644 --- a/src/builders/dev-server/schema.d.ts +++ b/src/builders/dev-server/schema.d.ts @@ -2,6 +2,12 @@ * Dev Server target options for Build Facade. */ export interface Schema { + /** + * The hosts that can access the development server. This option sets the Vite option of the + * same name. For further details: + * https://vite.dev/config/server-options.html#server-allowedhosts + */ + allowedHosts?: AllowedHosts; /** * A build builder target to serve in the format of `project:target[:configuration]`. You * can also pass in more than one configuration name as a comma-separated list. Example: @@ -78,6 +84,12 @@ export interface Schema { */ watch?: boolean; } +/** + * The hosts that can access the development server. This option sets the Vite option of the + * same name. For further details: + * https://vite.dev/config/server-options.html#server-allowedhosts + */ +export type AllowedHosts = string[] | boolean; /** * Activate debugging inspector. This option only has an effect when 'SSR' or 'SSG' are * enabled. diff --git a/src/builders/dev-server/schema.json b/src/builders/dev-server/schema.json index 3adce45e..775ce72e 100644 --- a/src/builders/dev-server/schema.json +++ b/src/builders/dev-server/schema.json @@ -36,6 +36,23 @@ "type": "string", "description": "SSL certificate to use for serving HTTPS." }, + "allowedHosts": { + "description": "The hosts that can access the development server. This option sets the Vite option of the same name. For further details: https://vite.dev/config/server-options.html#server-allowedhosts", + "default": [], + "oneOf": [ + { + "type": "array", + "description": "List of hosts that are allowed to access the development server.", + "items": { + "type": "string" + } + }, + { + "type": "boolean", + "description": "Indicates that all hosts are allowed. This is not recommended and a security risk." + } + ] + }, "headers": { "type": "object", "description": "Custom HTTP headers to be added to all responses.", diff --git a/src/builders/dev-server/vite-server.d.ts b/src/builders/dev-server/vite-server.d.ts index e1f844d3..e0ed7925 100644 --- a/src/builders/dev-server/vite-server.d.ts +++ b/src/builders/dev-server/vite-server.d.ts @@ -19,6 +19,10 @@ interface OutputFileRecord { updated: boolean; servable: boolean; } +interface DevServerExternalResultMetadata extends Omit { + explicitBrowser: string[]; + explicitServer: string[]; +} export type BuilderAction = (options: ApplicationBuilderInternalOptions, context: BuilderContext, plugins?: Plugin[]) => AsyncIterable; export declare function serveWithVite(serverOptions: NormalizedDevServerOptions, builderName: string, builderAction: BuilderAction, context: BuilderContext, transformers?: { indexHtml?: (content: string) => Promise; @@ -26,6 +30,6 @@ export declare function serveWithVite(serverOptions: NormalizedDevServerOptions, middleware?: Connect.NextHandleFunction[]; buildPlugins?: Plugin[]; }): AsyncIterableIterator; -export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map, assets: Map, preserveSymlinks: boolean | undefined, externalMetadata: ExternalResultMetadata, ssr: boolean, prebundleTransformer: JavaScriptTransformer, target: string[], zoneless: boolean, prebundleLoaderExtensions: EsbuildLoaderOption | undefined, extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise, thirdPartySourcemaps?: boolean): Promise; +export declare function setupServer(serverOptions: NormalizedDevServerOptions, outputFiles: Map, assets: Map, preserveSymlinks: boolean | undefined, externalMetadata: DevServerExternalResultMetadata, ssr: boolean, prebundleTransformer: JavaScriptTransformer, target: string[], zoneless: boolean, prebundleLoaderExtensions: EsbuildLoaderOption | undefined, extensionMiddleware?: Connect.NextHandleFunction[], indexHtmlTransformer?: (content: string) => Promise, thirdPartySourcemaps?: boolean): Promise; type EsbuildLoaderOption = Exclude['loader']; export {}; diff --git a/src/builders/dev-server/vite-server.js b/src/builders/dev-server/vite-server.js index ce463cbd..bff7ea80 100644 --- a/src/builders/dev-server/vite-server.js +++ b/src/builders/dev-server/vite-server.js @@ -74,6 +74,12 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context browserOptions.ssr = true; // https://nodejs.org/api/process.html#processsetsourcemapsenabledval process.setSourceMapsEnabled(true); + if (browserOptions.progress !== false) { + // This is a workaround for https://github.com/angular/angular-cli/issues/28336, which is caused by the interaction between `zone.js` and `listr2`. + process.once('SIGINT', () => { + process.kill(process.pid); + }); + } } // Set all packages as external to support Vite's prebundle caching browserOptions.externalPackages = serverOptions.prebundle; @@ -107,7 +113,8 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context const externalMetadata = { implicitBrowser: [], implicitServer: [], - explicit: [], + explicitBrowser: [], + explicitServer: [], }; // Add cleanup logic via a builder teardown. let deferred; @@ -187,15 +194,18 @@ async function* serveWithVite(serverOptions, builderName, builderAction, context requiresServerRestart = implicitServerFiltered.some((dep) => !previousImplicitServer.has(dep)); } // Empty Arrays to avoid growing unlimited with every re-build. - externalMetadata.explicit.length = 0; + externalMetadata.explicitBrowser.length = 0; + externalMetadata.explicitServer.length = 0; externalMetadata.implicitServer.length = 0; externalMetadata.implicitBrowser.length = 0; - externalMetadata.explicit.push(...explicit); + externalMetadata.explicitBrowser.push(...explicit); + externalMetadata.explicitServer.push(...explicit, ...nodeJsBuiltinModules); externalMetadata.implicitServer.push(...implicitServerFiltered); externalMetadata.implicitBrowser.push(...implicitBrowserFiltered); // The below needs to be sorted as Vite uses these options are part of the hashing invalidation algorithm. // See: https://github.com/vitejs/vite/blob/0873bae0cfe0f0718ad2f5743dd34a17e4ab563d/packages/vite/src/node/optimizer/index.ts#L1203-L1239 - externalMetadata.explicit.sort(); + externalMetadata.explicitBrowser.sort(); + externalMetadata.explicitServer.sort(); externalMetadata.implicitServer.sort(); externalMetadata.implicitBrowser.sort(); } @@ -368,7 +378,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, const { normalizePath } = await (0, load_esm_1.loadEsmModule)('vite'); // Path will not exist on disk and only used to provide separate path for Vite requests const virtualProjectRoot = normalizePath((0, node_path_1.join)(serverOptions.workspaceRoot, `.angular/vite-root`, serverOptions.buildTarget.project)); - const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, 'vite'); + const cacheDir = (0, node_path_1.join)(serverOptions.cacheOptions.path, serverOptions.buildTarget.project, 'vite'); const configuration = { configFile: false, envFile: false, @@ -401,6 +411,7 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, strictPort: true, host: serverOptions.host, open: serverOptions.open, + allowedHosts: serverOptions.allowedHosts, headers: serverOptions.headers, proxy, cors: { @@ -422,18 +433,18 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, }, // This is needed when `externalDependencies` is used to prevent Vite load errors. // NOTE: If Vite adds direct support for externals, this can be removed. - preTransformRequests: externalMetadata.explicit.length === 0, + preTransformRequests: externalMetadata.explicitBrowser.length === 0, }, ssr: { // Note: `true` and `/.*/` have different sematics. When true, the `external` option is ignored. noExternal: /.*/, // Exclude any Node.js built in module and provided dependencies (currently build defined externals) - external: externalMetadata.explicit, + external: externalMetadata.explicitServer, optimizeDeps: getDepOptimizationConfig({ // Only enable with caching since it causes prebundle dependencies to be cached disabled: serverOptions.prebundle === false, // Exclude any explicitly defined dependencies (currently build defined externals and node.js built-ins) - exclude: externalMetadata.explicit, + exclude: externalMetadata.explicitServer, // Include all implict dependencies from the external packages internal option include: externalMetadata.implicitServer, ssr: true, @@ -452,19 +463,19 @@ async function setupServer(serverOptions, outputFiles, assets, preserveSymlinks, outputFiles, assets, ssr, - external: externalMetadata.explicit, + external: externalMetadata.explicitBrowser, indexHtmlTransformer, extensionMiddleware, normalizePath, }), - (0, id_prefix_plugin_1.createRemoveIdPrefixPlugin)(externalMetadata.explicit), + (0, id_prefix_plugin_1.createRemoveIdPrefixPlugin)(externalMetadata.explicitBrowser), ], // Browser only optimizeDeps. (This does not run for SSR dependencies). optimizeDeps: getDepOptimizationConfig({ // Only enable with caching since it causes prebundle dependencies to be cached disabled: serverOptions.prebundle === false, // Exclude any explicitly defined dependencies (currently build defined externals) - exclude: externalMetadata.explicit, + exclude: externalMetadata.explicitBrowser, // Include all implict dependencies from the external packages internal option include: externalMetadata.implicitBrowser, ssr: false, diff --git a/src/builders/extract-i18n/application-extraction.js b/src/builders/extract-i18n/application-extraction.js index 866f8358..879c7c28 100644 --- a/src/builders/extract-i18n/application-extraction.js +++ b/src/builders/extract-i18n/application-extraction.js @@ -11,6 +11,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractMessages = extractMessages; +const node_fs_1 = require("node:fs"); const node_path_1 = __importDefault(require("node:path")); const application_1 = require("../application"); const results_1 = require("../application/results"); @@ -72,6 +73,9 @@ function setupLocalizeExtractor(extractorConstructor, files, context) { if (file?.origin === 'memory') { content = textDecoder.decode(file.contents); } + else if (file?.origin === 'disk') { + content = (0, node_fs_1.readFileSync)(file.inputPath, 'utf-8'); + } if (content === undefined) { throw new Error('Unknown file requested: ' + requestedPath); } diff --git a/src/tools/esbuild/angular/compiler-plugin.js b/src/tools/esbuild/angular/compiler-plugin.js index 20616561..b03875cb 100644 --- a/src/tools/esbuild/angular/compiler-plugin.js +++ b/src/tools/esbuild/angular/compiler-plugin.js @@ -314,18 +314,39 @@ function createCompilerPlugin(pluginOptions, styleOptions) { // Store as the returned Uint8Array to allow caching the fully transformed code typeScriptFileCache.set(request, contents); } + let loader; + if (useTypeScriptTranspilation || isJS) { + // TypeScript has transpiled to JS or is already JS + loader = 'js'; + } + else if (request.at(-1) === 'x') { + // TSX and TS have different syntax rules. Only set if input is a TSX file. + loader = 'tsx'; + } + else { + // Otherwise, directly bundle TS + loader = 'ts'; + } return { contents, - loader: useTypeScriptTranspilation || isJS ? 'js' : 'ts', + loader, }; }); build.onLoad({ filter: /\.[cm]?js$/ }, (0, load_result_cache_1.createCachedLoad)(pluginOptions.loadResultCache, async (args) => { + let request = args.path; + if (pluginOptions.fileReplacements) { + const replacement = pluginOptions.fileReplacements[path.normalize(args.path)]; + if (replacement) { + request = path.normalize(replacement); + } + } return (0, profiling_1.profileAsync)('NG_EMIT_JS*', async () => { - const sideEffects = await hasSideEffects(args.path); - const contents = await javascriptTransformer.transformFile(args.path, pluginOptions.jit, sideEffects); + const sideEffects = await hasSideEffects(request); + const contents = await javascriptTransformer.transformFile(request, pluginOptions.jit, sideEffects); return { contents, loader: 'js', + watchFiles: request !== args.path ? [request] : undefined, }; }, true); })); diff --git a/src/tools/esbuild/javascript-transformer.js b/src/tools/esbuild/javascript-transformer.js index b22d163d..66a8029b 100644 --- a/src/tools/esbuild/javascript-transformer.js +++ b/src/tools/esbuild/javascript-transformer.js @@ -48,6 +48,8 @@ class JavaScriptTransformer { // Shutdown idle threads after 1 second of inactivity idleTimeout: 1000, recordTiming: false, + // Prevent passing `--import` (loader-hooks) from parent to child worker. + execArgv: [], }); return this.#workerPool; } diff --git a/src/tools/sass/rebasing-importer.js b/src/tools/sass/rebasing-importer.js index ab31b03f..51c91850 100644 --- a/src/tools/sass/rebasing-importer.js +++ b/src/tools/sass/rebasing-importer.js @@ -65,7 +65,7 @@ class UrlRebasingImporter { continue; } // Sass variable usage either starts with a `$` or contains a namespace and a `.$` - const valueNormalized = value[0] === '$' || /^\w+\.\$/.test(value) ? `#{${value}}` : value; + const valueNormalized = value[0] === '$' || /^\w[\w_-]*\.\$/.test(value) ? `#{${value}}` : value; const rebasedPath = (0, node_path_1.relative)(this.entryDirectory, stylesheetDirectory); // Normalize path separators and escape characters // https://developer.mozilla.org/en-US/docs/Web/CSS/url#syntax diff --git a/src/tools/sass/worker.js b/src/tools/sass/worker.js index 3e038f39..bc85aab4 100644 --- a/src/tools/sass/worker.js +++ b/src/tools/sass/worker.js @@ -40,6 +40,25 @@ async function renderSassStylesheet(request) { containingUrl: containingUrl ? (0, node_url_1.fileURLToPath)(containingUrl) : null, }, }); + // Wait for the main thread to set the signal to 1 and notify, which tells + // us that a message can be received on the port. + // If the main thread is fast, the signal will already be set to 1, and no + // sleep/notify is necessary. + // However, there can be a race condition here: + // - the main thread sets the signal to 1, but does not get to the notify instruction yet + // - the worker does not pause because the signal is set to 1 + // - the worker very soon enters this method again + // - this method sets the signal to 0 and sends the message + // - the signal is 0 and so the `Atomics.wait` call blocks + // - only now the main thread runs the `notify` from the first invocation, so the + // worker continues. + // - but there is no message yet in the port, because the thread should not have been + // waken up yet. + // To combat this, wait for a non-0 value _twice_. + // Almost every time, this immediately continues with "not-equal", because + // the signal is still set to 1, except during the race condition, when the second + // wait will wait for the correct notify. + Atomics.wait(importerChannel.signal, 0, 0); Atomics.wait(importerChannel.signal, 0, 0); const result = (0, node_worker_threads_1.receiveMessageOnPort)(importerChannel.port)?.message; return result ? (0, node_url_1.pathToFileURL)(result) : null; diff --git a/src/tools/vite/middlewares/assets-middleware.js b/src/tools/vite/middlewares/assets-middleware.js index b9da48c1..aa2638c6 100644 --- a/src/tools/vite/middlewares/assets-middleware.js +++ b/src/tools/vite/middlewares/assets-middleware.js @@ -47,6 +47,11 @@ function createAngularAssetsMiddleware(server, assets, outputFiles) { next(); return; } + // Support HTTP HEAD requests for the virtual output files from the Angular build + if (req.method === 'HEAD' && outputFiles.get(pathname)?.servable) { + // While a GET will also generate content, the rest of the response is equivalent + req.method = 'GET'; + } // Resource files are handled directly. // Global stylesheets (CSS files) are currently considered resources to workaround // dev server sourcemap issues with stylesheets. diff --git a/src/tools/vite/middlewares/html-fallback-middleware.js b/src/tools/vite/middlewares/html-fallback-middleware.js index f950e500..b227892e 100644 --- a/src/tools/vite/middlewares/html-fallback-middleware.js +++ b/src/tools/vite/middlewares/html-fallback-middleware.js @@ -9,15 +9,31 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.angularHtmlFallbackMiddleware = angularHtmlFallbackMiddleware; const utils_1 = require("../utils"); +const ALLOWED_FALLBACK_METHODS = Object.freeze(['GET', 'HEAD']); function angularHtmlFallbackMiddleware(req, res, next) { // Similar to how it is handled in vite // https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/htmlFallback.ts#L15C19-L15C45 - if ((req.method === 'GET' || req.method === 'HEAD') && - (!req.url || !(0, utils_1.lookupMimeTypeFromRequest)(req.url)) && - (!req.headers.accept || - req.headers.accept.includes('text/html') || - req.headers.accept.includes('text/*') || - req.headers.accept.includes('*/*'))) { + if (!req.method || !ALLOWED_FALLBACK_METHODS.includes(req.method)) { + // No fallback for unsupported request methods + next(); + return; + } + if (req.url) { + const mimeType = (0, utils_1.lookupMimeTypeFromRequest)(req.url); + if (mimeType === 'text/html' || mimeType === 'application/xhtml+xml') { + // eslint-disable-next-line no-console + console.warn(`Request for HTML file "${req.url}" was received but no asset found. Asset may be missing from build.`); + } + else if (mimeType) { + // No fallback for request of asset-like files + next(); + return; + } + } + if (!req.headers.accept || + req.headers.accept.includes('text/html') || + req.headers.accept.includes('text/*') || + req.headers.accept.includes('*/*')) { req.url = '/index.html'; } next(); diff --git a/src/utils/index-file/valid-self-closing-tags.js b/src/utils/index-file/valid-self-closing-tags.js index 91a9c592..6d4e0dd7 100644 --- a/src/utils/index-file/valid-self-closing-tags.js +++ b/src/utils/index-file/valid-self-closing-tags.js @@ -25,8 +25,36 @@ exports.VALID_SELF_CLOSING_TAGS = new Set([ 'track', 'wbr', /** SVG tags */ + 'animate', + 'animateMotion', + 'animateTransform', 'circle', 'ellipse', + 'feBlend', + 'feColorMatrix', + 'feComponentTransfer', + 'feComposite', + 'feConvolveMatrix', + 'feDiffuseLighting', + 'feDisplacementMap', + 'feDistantLight', + 'feDropShadow', + 'feFlood', + 'feFuncA', + 'feFuncB', + 'feFuncG', + 'feFuncR', + 'feGaussianBlur', + 'feImage', + 'feMerge', + 'feMergeNode', + 'feMorphology', + 'feOffset', + 'fePointLight', + 'feSpecularLighting', + 'feSpotLight', + 'feTile', + 'feTurbulence', 'line', 'path', 'polygon', diff --git a/src/utils/normalize-cache.js b/src/utils/normalize-cache.js index fe53695b..531c5501 100644 --- a/src/utils/normalize-cache.js +++ b/src/utils/normalize-cache.js @@ -10,7 +10,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.normalizeCacheOptions = normalizeCacheOptions; const node_path_1 = require("node:path"); /** Version placeholder is replaced during the build process with actual package version */ -const VERSION = '18.2.0-next.3+sha-6419c2a'; +const VERSION = '18.2.20+sha-5d82d44'; function hasCacheMetadata(value) { return (!!value && typeof value === 'object' && diff --git a/src/utils/server-rendering/prerender.js b/src/utils/server-rendering/prerender.js index 9902e127..cfc6a884 100644 --- a/src/utils/server-rendering/prerender.js +++ b/src/utils/server-rendering/prerender.js @@ -49,11 +49,14 @@ async function prerenderPages(workspaceRoot, appShellOptions = {}, prerenderOpti assetsReversed[addLeadingSlash(destination.replace(/\\/g, node_path_1.posix.sep))] = source; } // Get routes to prerender - const { routes: allRoutes, warnings: routesWarnings } = await getAllRoutes(workspaceRoot, outputFilesForWorker, assetsReversed, document, appShellOptions, prerenderOptions, sourcemap, verbose); + const { routes: allRoutes, warnings: routesWarnings, errors: routesErrors, } = await getAllRoutes(workspaceRoot, outputFilesForWorker, assetsReversed, document, appShellOptions, prerenderOptions, sourcemap, verbose); + if (routesErrors?.length) { + errors.push(...routesErrors); + } if (routesWarnings?.length) { warnings.push(...routesWarnings); } - if (allRoutes.size < 1) { + if (allRoutes.size < 1 || errors.length > 0) { return { errors, warnings, @@ -109,7 +112,8 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp const isAppShellRoute = appShellRoute === route; const serverContext = isAppShellRoute ? 'app-shell' : 'ssg'; const render = renderWorker.run({ route, serverContext }); - const renderResult = render.then(({ content, warnings, errors }) => { + const renderResult = render + .then(({ content, warnings, errors }) => { if (content !== undefined) { const outPath = isAppShellRoute ? 'index.html' @@ -122,6 +126,10 @@ async function renderPages(sourcemap, allRoutes, maxThreads, workspaceRoot, outp if (errors) { errors.push(...errors); } + }) + .catch((err) => { + errors.push(`An error occurred while prerendering route '${route}'.\n\n${err.stack ?? err.message ?? err.code ?? err}`); + void renderWorker.destroy(); }); renderingPromises.push(renderResult); } @@ -173,15 +181,19 @@ async function getAllRoutes(workspaceRoot, outputFilesForWorker, assetFilesForWo execArgv: workerExecArgv, recordTiming: false, }); + const errors = []; const { routes: extractedRoutes, warnings } = await renderWorker .run({}) + .catch((err) => { + errors.push(`An error occurred while extracting routes.\n\n${err.stack ?? err.message ?? err.code ?? err}`); + }) .finally(() => { void renderWorker.destroy(); }); for (const route of extractedRoutes) { routes.add(route); } - return { routes, warnings }; + return { routes, warnings, errors }; } function addLeadingSlash(value) { return value.charAt(0) === '/' ? value : '/' + value; diff --git a/src/utils/supported-browsers.js b/src/utils/supported-browsers.js index cde7df57..24df2dc1 100644 --- a/src/utils/supported-browsers.js +++ b/src/utils/supported-browsers.js @@ -19,6 +19,7 @@ function getSupportedBrowsers(projectRoot, logger) { 'last 2 Edge major versions', 'last 2 Safari major versions', 'last 2 iOS major versions', + 'last 2 Android major versions', 'Firefox ESR', ]; // Get browsers from config or default. diff --git a/uniqueId b/uniqueId index b7bb4733..63cbb9ef 100644 --- a/uniqueId +++ b/uniqueId @@ -1 +1 @@ -Wed Aug 07 2024 20:40:30 GMT+0000 (Coordinated Universal Time) \ No newline at end of file +Wed Jun 11 2025 13:32:21 GMT+0000 (Coordinated Universal Time) \ No newline at end of file