@angular-devkit/build-angular
Version:
Angular Webpack Build Facade
325 lines (324 loc) • 12.7 kB
JavaScript
/**
* @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 }));
}
;