UNPKG

@angular-devkit/build-angular

Version:
325 lines (324 loc) 12.7 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.getDevServerConfig = getDevServerConfig; exports.buildServePath = buildServePath; const core_1 = require("@angular-devkit/core"); const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const node_url_1 = require("node:url"); const error_1 = require("../../../utils/error"); const load_esm_1 = require("../../../utils/load-esm"); const webpack_browser_config_1 = require("../../../utils/webpack-browser-config"); const hmr_loader_1 = require("../plugins/hmr/hmr-loader"); async function getDevServerConfig(wco) { const { buildOptions: { host, port, index, headers, watch, hmr, main, liveReload, proxyConfig }, logger, root, } = wco; const servePath = buildServePath(wco.buildOptions, logger); const extraRules = []; if (hmr) { extraRules.push({ loader: hmr_loader_1.HmrLoader, include: [(0, node_path_1.resolve)(wco.root, main)], }); } const extraPlugins = []; if (!watch) { // There's no option to turn off file watching in webpack-dev-server, but // we can override the file watcher instead. extraPlugins.push({ // eslint-disable-next-line @typescript-eslint/no-explicit-any apply: (compiler) => { compiler.hooks.afterEnvironment.tap('angular-cli', () => { // eslint-disable-next-line @typescript-eslint/no-empty-function compiler.watchFileSystem = { watch: () => { } }; }); }, }); } return { plugins: extraPlugins, module: { rules: extraRules, }, devServer: { host, port, headers: { 'Access-Control-Allow-Origin': '*', ...headers, }, historyApiFallback: !!index && { index: node_path_1.posix.join(servePath, (0, webpack_browser_config_1.getIndexOutputFile)(index)), disableDotRule: true, htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'], rewrites: [ { from: new RegExp(`^(?!${servePath})/.*`), to: (context) => context.parsedUrl.href, }, ], }, // When setupExitSignals is enabled webpack-dev-server will shutdown gracefully which would // require CTRL+C to be pressed multiple times to exit. // See: https://github.com/webpack/webpack-dev-server/blob/c76b6d11a3821436c5e20207c8a38deb6ab7e33c/lib/Server.js#L1801-L1827 setupExitSignals: false, compress: false, static: false, server: getServerConfig(root, wco.buildOptions), allowedHosts: getAllowedHostsConfig(wco.buildOptions), devMiddleware: { publicPath: servePath, stats: false, }, liveReload, hot: hmr && !liveReload ? 'only' : hmr, proxy: await addProxyConfig(root, proxyConfig), ...getWebSocketSettings(wco.buildOptions, servePath), }, }; } /** * Resolve and build a URL _path_ that will be the root of the server. This resolved base href and * deploy URL from the browser options and returns a path from the root. */ function buildServePath(options, logger) { let servePath = options.servePath; if (servePath === undefined) { const defaultPath = findDefaultServePath(options.baseHref, options.deployUrl); if (defaultPath == null) { logger.warn(core_1.tags.oneLine ` Warning: --deploy-url and/or --base-href contain unsupported values for ng serve. Default serve path of '/' used. Use --serve-path to override. `); } servePath = defaultPath || ''; } if (servePath.endsWith('/')) { servePath = servePath.slice(0, -1); } if (!servePath.startsWith('/')) { servePath = `/${servePath}`; } return servePath; } /** * Private method to enhance a webpack config with SSL configuration. * @private */ function getServerConfig(root, options) { const { ssl, sslCert, sslKey } = options; if (!ssl) { return 'http'; } return { type: 'https', options: sslCert && sslKey ? { key: (0, node_path_1.resolve)(root, sslKey), cert: (0, node_path_1.resolve)(root, sslCert), } : undefined, }; } /** * Private method to enhance a webpack config with Proxy configuration. * @private */ async function addProxyConfig(root, proxyConfig) { if (!proxyConfig) { return undefined; } const proxyPath = (0, node_path_1.resolve)(root, proxyConfig); if (!(0, node_fs_1.existsSync)(proxyPath)) { throw new Error(`Proxy configuration file ${proxyPath} does not exist.`); } let proxyConfiguration; switch ((0, node_path_1.extname)(proxyPath)) { case '.json': { const content = await node_fs_1.promises.readFile(proxyPath, 'utf-8'); const { parse, printParseErrorCode } = await Promise.resolve().then(() => __importStar(require('jsonc-parser'))); const parseErrors = []; proxyConfiguration = parse(content, parseErrors, { allowTrailingComma: true }); if (parseErrors.length > 0) { let errorMessage = `Proxy configuration file ${proxyPath} contains parse errors:`; for (const parseError of parseErrors) { const { line, column } = getJsonErrorLineColumn(parseError.offset, content); errorMessage += `\n[${line}, ${column}] ${printParseErrorCode(parseError.error)}`; } throw new Error(errorMessage); } break; } case '.mjs': // Load the ESM configuration file using the TypeScript dynamic import workaround. // Once TypeScript provides support for keeping the dynamic import this workaround can be // changed to a direct dynamic import. proxyConfiguration = await (0, load_esm_1.loadEsmModule)((0, node_url_1.pathToFileURL)(proxyPath)); break; case '.cjs': proxyConfiguration = require(proxyPath); break; default: // The file could be either CommonJS or ESM. // CommonJS is tried first then ESM if loading fails. try { proxyConfiguration = require(proxyPath); } catch (e) { (0, error_1.assertIsError)(e); if (e.code !== 'ERR_REQUIRE_ESM' && e.code !== 'ERR_REQUIRE_ASYNC_MODULE') { throw e; } // Load the ESM configuration file using the TypeScript dynamic import workaround. // Once TypeScript provides support for keeping the dynamic import this workaround can be // changed to a direct dynamic import. proxyConfiguration = await (0, load_esm_1.loadEsmModule)((0, node_url_1.pathToFileURL)(proxyPath)); } } if ('default' in proxyConfiguration) { proxyConfiguration = proxyConfiguration.default; } return normalizeProxyConfiguration(proxyConfiguration); } /** * Calculates the line and column for an error offset in the content of a JSON file. * @param location The offset error location from the beginning of the content. * @param content The full content of the file containing the error. * @returns An object containing the line and column */ function getJsonErrorLineColumn(offset, content) { if (offset === 0) { return { line: 1, column: 1 }; } let line = 0; let position = 0; // eslint-disable-next-line no-constant-condition while (true) { ++line; const nextNewline = content.indexOf('\n', position); if (nextNewline === -1 || nextNewline > offset) { break; } position = nextNewline + 1; } return { line, column: offset - position + 1 }; } /** * Find the default server path. We don't want to expose baseHref and deployUrl as arguments, only * the browser options where needed. This method should stay private (people who want to resolve * baseHref and deployUrl should use the buildServePath exported function. * @private */ function findDefaultServePath(baseHref, deployUrl) { if (!baseHref && !deployUrl) { return ''; } if (/^(\w+:)?\/\//.test(baseHref || '') || /^(\w+:)?\/\//.test(deployUrl || '')) { // If baseHref or deployUrl is absolute, unsupported by ng serve return null; } // normalize baseHref // for ng serve the starting base is always `/` so a relative // and root relative value are identical const baseHrefParts = (baseHref || '').split('/').filter((part) => part !== ''); if (baseHref && !baseHref.endsWith('/')) { baseHrefParts.pop(); } const normalizedBaseHref = baseHrefParts.length === 0 ? '/' : `/${baseHrefParts.join('/')}/`; if (deployUrl && deployUrl[0] === '/') { if (baseHref && baseHref[0] === '/' && normalizedBaseHref !== deployUrl) { // If baseHref and deployUrl are root relative and not equivalent, unsupported by ng serve return null; } return deployUrl; } // Join together baseHref and deployUrl return `${normalizedBaseHref}${deployUrl || ''}`; } function getAllowedHostsConfig(options) { if (options.disableHostCheck) { return 'all'; } else if (options.allowedHosts?.length) { return options.allowedHosts; } return undefined; } function getWebSocketSettings(options, servePath) { const { hmr, liveReload } = options; if (!hmr && !liveReload) { return { webSocketServer: false, client: undefined, }; } const webSocketPath = node_path_1.posix.join(servePath, 'ng-cli-ws'); return { webSocketServer: { options: { path: webSocketPath, }, }, client: { logging: 'info', webSocketURL: getPublicHostOptions(options, webSocketPath), overlay: { errors: true, warnings: false, runtimeErrors: false, }, }, }; } function getPublicHostOptions(options, webSocketPath) { let publicHost = options.publicHost; if (publicHost) { const hostWithProtocol = !/^\w+:\/\//.test(publicHost) ? `https://${publicHost}` : publicHost; publicHost = new node_url_1.URL(hostWithProtocol).host; } return `auto://${publicHost || '0.0.0.0:0'}${webSocketPath}`; } function normalizeProxyConfiguration(proxy) { return Array.isArray(proxy) ? proxy : Object.entries(proxy).map(([context, value]) => ({ context: [context], ...value })); }