Alan Agius 32dbf659ac feat(@angular-devkit/build-angular): update webpack-dev-server to version 4
BREAKING CHANGE:

The dev-server now uses WebSockets to communicate changes to the browser during HMR and live-reloaded. If during your development you are using a proxy you will need to enable proxying of WebSockets.
2021-08-27 15:40:10 +02:00

230 lines
6.6 KiB
TypeScript

/**
* @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.io/license
*/
import { logging, tags } from '@angular-devkit/core';
import { existsSync, readFileSync } from 'fs';
import { posix, resolve } from 'path';
import * as url from 'url';
import * as webpack from 'webpack';
import { Configuration } from 'webpack-dev-server';
import { WebpackConfigOptions, WebpackDevServerOptions } from '../../utils/build-options';
import { getIndexOutputFile } from '../../utils/webpack-browser-config';
import { HmrLoader } from '../plugins/hmr/hmr-loader';
export function getDevServerConfig(
wco: WebpackConfigOptions<WebpackDevServerOptions>,
): webpack.Configuration {
const {
buildOptions: { host, port, index, headers, watch, hmr, main, liveReload, proxyConfig },
logger,
root,
} = wco;
const servePath = buildServePath(wco.buildOptions, logger);
const extraRules: webpack.RuleSetRule[] = [];
if (hmr) {
extraRules.push({
loader: HmrLoader,
include: [main].map((p) => resolve(wco.root, p)),
});
}
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: any) => {
compiler.hooks.afterEnvironment.tap('angular-cli', () => {
// eslint-disable-next-line @typescript-eslint/no-empty-function
compiler.watchFileSystem = { watch: () => {} };
});
},
});
}
const webSocketPath = posix.join(servePath, 'ws');
return {
plugins: extraPlugins,
module: {
rules: extraRules,
},
devServer: {
host,
port,
headers: {
'Access-Control-Allow-Origin': '*',
...headers,
},
historyApiFallback: !!index && {
index: posix.join(servePath, getIndexOutputFile(index)),
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
rewrites: [
{
from: new RegExp(`^(?!${servePath})/.*`),
to: (context) => url.format(context.parsedUrl),
},
],
},
webSocketServer: {
options: {
path: webSocketPath,
},
},
compress: false,
static: false,
https: getSslConfig(root, wco.buildOptions),
allowedHosts: getAllowedHostsConfig(wco.buildOptions),
devMiddleware: {
publicPath: servePath,
stats: false,
},
liveReload,
hot: hmr && !liveReload ? 'only' : hmr,
proxy: addProxyConfig(root, proxyConfig),
client: {
logging: 'info',
webSocketURL: getPublicHostOptions(wco.buildOptions, webSocketPath),
overlay: {
errors: true,
warnings: false,
},
},
},
};
}
/**
* 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.
*/
export function buildServePath(
options: WebpackDevServerOptions,
logger: logging.LoggerApi,
): string {
let servePath = options.servePath;
if (servePath === undefined) {
const defaultPath = findDefaultServePath(options.baseHref, options.deployUrl);
if (defaultPath == null) {
logger.warn(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.substr(0, servePath.length - 1);
}
if (!servePath.startsWith('/')) {
servePath = `/${servePath}`;
}
return servePath;
}
/**
* Private method to enhance a webpack config with SSL configuration.
* @private
*/
function getSslConfig(root: string, options: WebpackDevServerOptions): Configuration['https'] {
const { ssl, sslCert, sslKey } = options;
if (ssl && sslCert && sslKey) {
return {
key: readFileSync(resolve(root, sslKey), 'utf-8'),
cert: readFileSync(resolve(root, sslCert), 'utf-8'),
};
}
return ssl;
}
/**
* Private method to enhance a webpack config with Proxy configuration.
* @private
*/
function addProxyConfig(root: string, proxyConfig: string | undefined) {
if (!proxyConfig) {
return undefined;
}
const proxyPath = resolve(root, proxyConfig);
if (existsSync(proxyPath)) {
return require(proxyPath);
}
throw new Error('Proxy config file ' + proxyPath + ' does not exist.');
}
/**
* 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?: string, deployUrl?: string): string | null {
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: WebpackDevServerOptions): Configuration['allowedHosts'] {
if (options.disableHostCheck) {
return 'all';
} else if (options.allowedHosts?.length) {
return options.allowedHosts;
}
return undefined;
}
function getPublicHostOptions(options: WebpackDevServerOptions, webSocketPath: string): string {
let publicHost: string | null | undefined = options.publicHost;
if (publicHost) {
if (!/^\w+:\/\//.test(publicHost)) {
publicHost = `https://${publicHost}`;
}
publicHost = url.parse(publicHost).host;
}
return `auto://${publicHost || '0.0.0.0:0'}${webSocketPath}`;
}