UNPKG

@angular/build

Version:

Official build system for Angular

382 lines 19.8 kB
"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.serveWithVite = serveWithVite; const node_assert_1 = __importDefault(require("node:assert")); const node_path_1 = require("node:path"); const plugins_1 = require("../../../tools/vite/plugins"); const utils_1 = require("../../../tools/vite/utils"); const utils_2 = require("../../../utils"); const environment_options_1 = require("../../../utils/environment-options"); const results_1 = require("../../application/results"); const schema_1 = require("../../application/schema"); const internal_1 = require("../internal"); const hmr_1 = require("./hmr"); const server_1 = require("./server"); const utils_3 = require("./utils"); /** * Build options that are also present on the dev server but are only passed * to the build. */ const CONVENIENCE_BUILD_OPTIONS = ['watch', 'poll', 'verbose', 'define']; // eslint-disable-next-line max-lines-per-function async function* serveWithVite(serverOptions, builderName, builderAction, context, transformers, extensions) { // Get the browser configuration from the target name. const rawBrowserOptions = await context.getTargetOptions(serverOptions.buildTarget); // Deploy url is not used in the dev-server. delete rawBrowserOptions.deployUrl; // Copy convenience options to build for (const optionName of CONVENIENCE_BUILD_OPTIONS) { const optionValue = serverOptions[optionName]; if (optionValue !== undefined) { if (optionName === 'define' && rawBrowserOptions[optionName]) { // Define has merging behavior within the application for (const [key, value] of Object.entries(optionValue)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any rawBrowserOptions[optionName][key] = value; } } else { rawBrowserOptions[optionName] = optionValue; } } } // TODO: Adjust architect to not force a JsonObject derived return type const browserOptions = (await context.validateOptions(rawBrowserOptions, builderName)); if (browserOptions.prerender || (browserOptions.outputMode && browserOptions.server)) { // Disable prerendering if enabled and force SSR. // This is so instead of prerendering all the routes for every change, the page is "prerendered" when it is requested. browserOptions.prerender = undefined; browserOptions.ssr ||= true; } // Disable auto CSP. browserOptions.security = { autoCsp: false, }; // Disable JSON build stats. // These are not accessible with the dev server and can cause HMR fallbacks. if (browserOptions.statsJson === true) { context.logger.warn('Build JSON statistics output (`statsJson` option) has been disabled.' + ' The development server does not support this option.'); } browserOptions.statsJson = false; // Set all packages as external to support Vite's prebundle caching browserOptions.externalPackages = serverOptions.prebundle; // Disable generating a full manifest with routes. // This is done during runtime when using the dev-server. browserOptions.partialSSRBuild = true; // The development server currently only supports a single locale when localizing. // This matches the behavior of the Webpack-based development server but could be expanded in the future. if (browserOptions.localize === true || (Array.isArray(browserOptions.localize) && browserOptions.localize.length > 1)) { context.logger.warn('Localization (`localize` option) has been disabled. The development server only supports localizing a single locale per build.'); browserOptions.localize = false; } else if (browserOptions.localize) { // When localization is enabled with a single locale, force a flat path to maintain behavior with the existing Webpack-based dev server. browserOptions.forceI18nFlatOutput = true; } const { vendor: thirdPartySourcemaps, scripts: scriptsSourcemaps } = (0, utils_2.normalizeSourceMaps)(browserOptions.sourceMap ?? false); if (scriptsSourcemaps && browserOptions.server) { // https://nodejs.org/api/process.html#processsetsourcemapsenabledval process.setSourceMapsEnabled(true); } if (serverOptions.hmr && (browserOptions.outputHashing === schema_1.OutputHashing.All || browserOptions.outputHashing === schema_1.OutputHashing.Bundles)) { serverOptions.hmr = false; context.logger.warn(`Hot Module Replacement (HMR) is disabled because the 'outputHashing' option is set to '${browserOptions.outputHashing}'. ` + 'HMR is incompatible with this setting.'); } const componentsHmrCanBeUsed = browserOptions.aot && serverOptions.liveReload && serverOptions.hmr; // Enable to support link-based component style hot reloading (`NG_HMR_CSTYLES=1` can be used to enable) browserOptions.externalRuntimeStyles = componentsHmrCanBeUsed && environment_options_1.useComponentStyleHmr; // Enable to support component template hot replacement (`NG_HMR_TEMPLATE=0` can be used to disable selectively) // This will also replace file-based/inline styles as code if external runtime styles are not enabled. browserOptions.templateUpdates = componentsHmrCanBeUsed && environment_options_1.useComponentTemplateHmr; browserOptions.incrementalResults = true; // Setup the prebundling transformer that will be shared across Vite prebundling requests const prebundleTransformer = new internal_1.JavaScriptTransformer( // Always enable JIT linking to support applications built with and without AOT. // In a development environment the additional scope information does not // have a negative effect unlike production where final output size is relevant. { sourcemap: true, jit: true, thirdPartySourcemaps }, 1); // The index HTML path will be updated from the build results if provided by the builder let htmlIndexPath = 'index.html'; const { createServer, normalizePath } = await Promise.resolve().then(() => __importStar(require('vite'))); let server; let serverUrl; let hadError = false; const generatedFiles = new Map(); const assetFiles = new Map(); const externalMetadata = { implicitBrowser: [], implicitServer: [], explicitBrowser: [], explicitServer: [], }; const componentStyles = new Map(); const templateUpdates = new Map(); // Add cleanup logic via a builder teardown. let deferred; context.addTeardown(async () => { await server?.close(); await prebundleTransformer.close(); deferred?.(); }); // TODO: Switch this to an architect schedule call when infrastructure settings are supported for await (const result of builderAction(browserOptions, context, extensions?.buildPlugins)) { if (result.kind === results_1.ResultKind.Failure) { if (result.errors.length && server) { hadError = true; server.ws.send({ type: 'error', err: { message: result.errors[0].text, stack: '', loc: result.errors[0].location ?? undefined, }, }); } yield { baseUrl: '', success: false }; continue; } // Clear existing error overlay on successful result if (hadError && server) { hadError = false; // Send an empty update to clear the error overlay server.ws.send({ 'type': 'update', updates: [], }); } let needClientUpdate = true; switch (result.kind) { case results_1.ResultKind.Full: if (result.detail?.['htmlIndexPath']) { htmlIndexPath = result.detail['htmlIndexPath']; } if (serverOptions.servePath === undefined && result.detail?.['htmlBaseHref']) { const baseHref = result.detail['htmlBaseHref']; // Remove trailing slash serverOptions.servePath = baseHref !== './' && baseHref.at(-1) === '/' ? baseHref.slice(0, -1) : baseHref; } assetFiles.clear(); componentStyles.clear(); generatedFiles.clear(); for (const [outputPath, file] of Object.entries(result.files)) { (0, utils_3.updateResultRecord)(outputPath, file, normalizePath, htmlIndexPath, generatedFiles, assetFiles, componentStyles, // The initial build will not yet have a server setup !server); } // Clear stale template updates on code rebuilds templateUpdates.clear(); break; case results_1.ResultKind.Incremental: (0, node_assert_1.default)(server, 'Builder must provide an initial full build before incremental results.'); // Background updates should only update server files/options needClientUpdate = !result.background; for (const removed of result.removed) { const filePath = '/' + normalizePath(removed.path); generatedFiles.delete(filePath); assetFiles.delete(filePath); } for (const modified of result.modified) { (0, utils_3.updateResultRecord)(modified, result.files[modified], normalizePath, htmlIndexPath, generatedFiles, assetFiles, componentStyles); } for (const added of result.added) { (0, utils_3.updateResultRecord)(added, result.files[added], normalizePath, htmlIndexPath, generatedFiles, assetFiles, componentStyles); } break; case results_1.ResultKind.ComponentUpdate: (0, node_assert_1.default)(serverOptions.hmr, 'Component updates are only supported with HMR enabled.'); (0, node_assert_1.default)(server, 'Builder must provide an initial full build before component update results.'); for (const componentUpdate of result.updates) { if (componentUpdate.type === 'template') { templateUpdates.set(componentUpdate.id, componentUpdate.content); server.ws.send('angular:component-update', { id: componentUpdate.id, timestamp: Date.now(), }); } } context.logger.info('Component update sent to client(s).'); continue; default: context.logger.warn(`Unknown result kind [${result.kind}] provided by build.`); continue; } // To avoid disconnecting the array objects from the option, these arrays need to be mutated instead of replaced. (0, utils_1.updateExternalMetadata)(result, externalMetadata, browserOptions.externalDependencies); if (server) { // Update fs allow list to include any new assets from the build option. server.config.server.fs.allow = [ ...new Set([ ...server.config.server.fs.allow, ...[...assetFiles.values()].map(({ source }) => source), ]), ]; const updatedFiles = await (0, hmr_1.invalidateUpdatedFiles)(normalizePath, generatedFiles, assetFiles, server); if (needClientUpdate) { (0, hmr_1.handleUpdate)(server, serverOptions, context.logger, componentStyles, updatedFiles); } } else { const projectName = context.target?.project; if (!projectName) { throw new Error('The builder requires a target.'); } context.logger.info('NOTE: Raw file sizes do not reflect development server per-request transformations.'); if (browserOptions.ssr && serverOptions.inspect) { const { host, port } = serverOptions.inspect; const { default: inspector } = await Promise.resolve().then(() => __importStar(require('node:inspector'))); inspector.open(port, host, true); context.addTeardown(() => inspector.close()); } const { root = '' } = await context.getProjectMetadata(projectName); const projectRoot = (0, node_path_1.join)(context.workspaceRoot, root); const browsers = (0, internal_1.getSupportedBrowsers)(projectRoot, context.logger); const target = (0, internal_1.transformSupportedBrowsersToTargets)(browsers); // Needed for browser-esbuild as polyfills can be a string. const polyfills = Array.isArray((browserOptions.polyfills ??= [])) ? browserOptions.polyfills : [browserOptions.polyfills]; let ssrMode = plugins_1.ServerSsrMode.NoSsr; if (browserOptions.outputMode && typeof browserOptions.ssr === 'object' && browserOptions.ssr.entry) { ssrMode = plugins_1.ServerSsrMode.ExternalSsrMiddleware; } else if (browserOptions.ssr) { ssrMode = plugins_1.ServerSsrMode.InternalSsrMiddleware; } if (browserOptions.progress !== false && ssrMode !== plugins_1.ServerSsrMode.NoSsr) { // 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); }); } // Setup server and start listening const serverConfiguration = await (0, server_1.setupServer)(serverOptions, generatedFiles, assetFiles, browserOptions.preserveSymlinks, externalMetadata, ssrMode, prebundleTransformer, target, (0, internal_1.isZonelessApp)(polyfills), componentStyles, templateUpdates, browserOptions.loader, { ...browserOptions.define, 'ngJitMode': browserOptions.aot ? 'false' : 'true', 'ngHmrMode': browserOptions.templateUpdates ? 'true' : 'false', }, extensions?.middleware, transformers?.indexHtml, thirdPartySourcemaps); server = await createServer(serverConfiguration); await server.listen(); // Setup builder context logging for browser clients server.hot.on('angular:log', (data) => { if (typeof data?.text !== 'string') { context.logger.warn('Development server client sent invalid internal log event.'); } switch (data.kind) { case 'error': context.logger.error(`[CLIENT ERROR]: ${data.text}`); break; case 'warning': context.logger.warn(`[CLIENT WARNING]: ${data.text}`); break; default: context.logger.info(`[CLIENT INFO]: ${data.text}`); break; } }); // Setup component HMR invalidation // Invalidation occurs when the runtime cannot update a component server.hot.on('angular:invalidate', (data) => { if (typeof data?.id !== 'string') { context.logger.warn('Development server client sent invalid internal invalidate event.'); } // Clear invalid template update templateUpdates.delete(data.id); // Some cases are expected unsupported update scenarios but some may be errors. // If an error occurred, log the error in addition to the invalidation. if (data.error) { context.logger.error(`Component update failed${data.message ? `: ${data.message}` : '.'}` + '\nPlease consider reporting the error at https://github.com/angular/angular-cli/issues'); } else { context.logger.warn(`Component update unsupported${data.message ? `: ${data.message}` : '.'}`); } server?.ws.send({ type: 'full-reload', path: '*', }); context.logger.info('Page reload sent to client(s).'); }); const urls = server.resolvedUrls; if (urls && (urls.local.length || urls.network.length)) { serverUrl = new URL(urls.local[0] ?? urls.network[0]); } // log connection information server.printUrls(); server.bindCLIShortcuts({ print: true, customShortcuts: [ { key: 'r', description: 'force reload browser', action(server) { componentStyles.forEach((record) => record.used?.clear()); server.ws.send({ type: 'full-reload', path: '*', }); }, }, ], }); } // TODO: adjust output typings to reflect both development servers yield { success: true, port: serverUrl?.port, baseUrl: serverUrl?.href, }; } await new Promise((resolve) => (deferred = resolve)); } //# sourceMappingURL=index.js.map