Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@
"@types/semver": "^7.0.0",
"@types/text-table": "^0.2.1",
"@types/uuid": "^8.0.0",
"@types/webpack-dev-server": "^3.1.7",
"@types/webpack-dev-server": "^4.0.3",
"@typescript-eslint/eslint-plugin": "4.29.3",
"@typescript-eslint/parser": "4.29.3",
"@yarnpkg/lockfile": "1.1.0",
Expand Down Expand Up @@ -225,7 +225,7 @@
"verdaccio-auth-memory": "^10.0.0",
"webpack": "5.51.1",
"webpack-dev-middleware": "5.0.0",
"webpack-dev-server": "3.11.2",
"webpack-dev-server": "4.0.0",
"webpack-merge": "5.8.0",
"webpack-subresource-integrity": "5.0.0",
"zone.js": "^0.11.3"
Expand Down
2 changes: 1 addition & 1 deletion packages/angular_devkit/build_angular/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
"tslib": "2.3.1",
"webpack": "5.51.1",
"webpack-dev-middleware": "5.0.0",
"webpack-dev-server": "3.11.2",
"webpack-dev-server": "4.0.0",
"webpack-merge": "5.8.0",
"webpack-subresource-integrity": "5.0.0"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,40 +228,6 @@ export function serveWebpackBrowser(
throw new Error('Webpack Dev Server configuration was not set.');
}

if (options.liveReload && !options.hmr) {
// This is needed because we cannot use the inline option directly in the config
// because of the SuppressExtractedTextChunksWebpackPlugin
// Consider not using SuppressExtractedTextChunksWebpackPlugin when liveReload is enable.
webpackDevServer.addDevServerEntrypoints(config, {
...config.devServer,
inline: true,
});

// Remove live-reload code from all entrypoints but not main.
// Otherwise this will break SuppressExtractedTextChunksWebpackPlugin because
// 'addDevServerEntrypoints' adds addional entry-points to all entries.
if (
config.entry &&
typeof config.entry === 'object' &&
!Array.isArray(config.entry) &&
config.entry.main
) {
for (const [key, value] of Object.entries(config.entry)) {
if (key === 'main' || !Array.isArray(value)) {
continue;
}

const webpackClientScriptIndex = value.findIndex((x) =>
x.includes('webpack-dev-server/client/index.js'),
);
if (webpackClientScriptIndex >= 0) {
// Remove the webpack-dev-server/client script from array.
value.splice(webpackClientScriptIndex, 1);
}
}
}
}

let locale: string | undefined;
if (i18n.shouldInline) {
// Dev-server only supports one locale
Expand Down Expand Up @@ -306,7 +272,7 @@ export function serveWebpackBrowser(
// The below is needed as otherwise HMR for CSS will break.
// styles.js and runtime.js needs to be loaded as a non-module scripts as otherwise `document.currentScript` will be null.
// https://github.com/webpack-contrib/mini-css-extract-plugin/blob/90445dd1d81da0c10b9b0e8a17b417d0651816b8/src/hmr/hotModuleReplacement.js#L39
isHMREnabled: webpackConfig.devServer?.hot,
isHMREnabled: !!webpackConfig.devServer?.hot,
});

webpackConfig.plugins ??= [];
Expand Down Expand Up @@ -336,8 +302,8 @@ export function serveWebpackBrowser(
const serverAddress = url.format({
protocol: options.ssl ? 'https' : 'http',
hostname: options.host === '0.0.0.0' ? 'localhost' : options.host,
pathname: webpackConfig.devServer?.publicPath,
port: buildEvent.port,
pathname: webpackConfig.devServer?.devMiddleware?.publicPath,
});

if (index === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import { Architect, BuilderRun } from '@angular-devkit/architect';
import { tags } from '@angular-devkit/core';
import { createProxyServer } from 'http-proxy';
import { HTTPResponse } from 'puppeteer/lib/cjs/puppeteer/api-docs-entry';
import { Browser } from 'puppeteer/lib/cjs/puppeteer/common/Browser';
import { Page } from 'puppeteer/lib/cjs/puppeteer/common/Page';
import puppeteer from 'puppeteer/lib/cjs/puppeteer/node';
Expand Down Expand Up @@ -96,15 +95,33 @@ function createProxy(target: string, secure: boolean, ws = true): ProxyInstance
};
}

async function goToPageAndWaitForSockJs(page: Page, url: string): Promise<void> {
const socksRequest = `${url.endsWith('/') ? url : url + '/'}sockjs-node/info?t=`;
async function goToPageAndWaitForWS(page: Page, url: string): Promise<void> {
const baseUrl = url.replace(/^http/, 'ws');
const socksRequest = baseUrl[baseUrl.length - 1] === '/' ? `${baseUrl}ws` : `${baseUrl}/ws`;
// Create a Chrome dev tools session so that we can capturing websocket request.
// https://github.com/puppeteer/puppeteer/issues/2974

// We do this, to ensure that we make the right request with the expected host, port etc...
const client = await page.target().createCDPSession();
await client.send('Network.enable');
await client.send('Page.enable');

await Promise.all([
page.waitForResponse(
(r: HTTPResponse) => r.url().startsWith(socksRequest) && r.status() === 200,
),
new Promise<void>((resolve, reject) => {
const timeout = setTimeout(
() => reject(new Error(`A Websocket connected to ${socksRequest} was not established.`)),
2000,
);
client.on('Network.webSocketCreated', ({ url }) => {
if (url.startsWith(socksRequest)) {
clearTimeout(timeout);
resolve();
}
});
}),
page.goto(url),
]);
await client.detach();
}

describe('Dev Server Builder live-reload', () => {
Expand Down Expand Up @@ -169,7 +186,7 @@ describe('Dev Server Builder live-reload', () => {
const url = buildEvent.baseUrl as string;
switch (buildCount) {
case 0:
await goToPageAndWaitForSockJs(page, url);
await goToPageAndWaitForWS(page, url);
host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`);
break;
case 1:
Expand Down Expand Up @@ -200,7 +217,7 @@ describe('Dev Server Builder live-reload', () => {
switch (buildCount) {
case 0:
proxy = createProxy(url, false);
await goToPageAndWaitForSockJs(page, proxy.url);
await goToPageAndWaitForWS(page, proxy.url);
host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`);
break;
case 1:
Expand Down Expand Up @@ -231,43 +248,7 @@ describe('Dev Server Builder live-reload', () => {
switch (buildCount) {
case 0:
proxy = createProxy(url, true);
await goToPageAndWaitForSockJs(page, proxy.url);
host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`);
break;
case 1:
const innerText = await page.evaluate(() => document.querySelector('p').innerText);
expect(innerText).toBe('app-live-reload');
break;
}

buildCount++;
}),
take(2),
)
.toPromise();
});

it('works without https -> http proxy without websockets (dotnet emulation)', async () => {
const run = await architect.scheduleTarget(target, overrides);
runs.push(run);

let proxy: ProxyInstance | undefined;
let buildCount = 0;

await run.output
.pipe(
debounceTime(1000),
switchMap(async (buildEvent) => {
expect(buildEvent.success).toBe(true);
const url = buildEvent.baseUrl as string;
switch (buildCount) {
case 0:
proxy = createProxy(url, true, false);
await goToPageAndWaitForSockJs(page, proxy.url);
await page.waitForResponse(
(response: HTTPResponse) =>
response.url().includes('xhr_streaming') && response.status() === 200,
);
await goToPageAndWaitForWS(page, proxy.url);
host.replaceInFile('src/app/app.component.ts', `'app'`, `'app-live-reload'`);
break;
case 1:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export async function executeOnceAndFetch<T>(
mergeMap(async (executionResult) => {
let response = undefined;
if (executionResult.result?.success) {
const resolvedUrl = new URL(url, `${executionResult.result.baseUrl}/`);
let baseUrl = `${executionResult.result.baseUrl}`;
baseUrl = baseUrl[baseUrl.length - 1] === '/' ? baseUrl : `${baseUrl}/`;
const resolvedUrl = new URL(url, baseUrl);
response = await fetch(resolvedUrl, options?.request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ export function getCommonConfig(wco: WebpackConfigOptions): Configuration {
syncWebAssembly: true,
asyncWebAssembly: true,
},
infrastructureLogging: {
level: buildOptions.verbose ? 'verbose' : 'error',
},
cache: getCacheSettings(wco, buildBrowserFeatures.supportedBrowsers),
optimization: {
minimizer: extraMinimizers,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,56 +12,30 @@ import { posix, resolve } from 'path';
import * as url from 'url';
import * as webpack from 'webpack';
import { Configuration } from 'webpack-dev-server';
import { normalizeOptimization } from '../../utils';
import { WebpackConfigOptions, WebpackDevServerOptions } from '../../utils/build-options';
import { getIndexOutputFile } from '../../utils/webpack-browser-config';
import { HmrLoader } from '../plugins/hmr/hmr-loader';
import { getWatchOptions } from '../utils/helpers';

export function getDevServerConfig(
wco: WebpackConfigOptions<WebpackDevServerOptions>,
): webpack.Configuration {
const {
buildOptions: {
optimization,
host,
port,
index,
headers,
poll,
ssl,
hmr,
main,
disableHostCheck,
liveReload,
allowedHosts,
watch,
proxyConfig,
},
buildOptions: { host, port, index, headers, watch, hmr, main, liveReload, proxyConfig },
logger,
root,
} = wco;

const servePath = buildServePath(wco.buildOptions, logger);
const { styles: stylesOptimization, scripts: scriptsOptimization } = normalizeOptimization(
optimization,
);

const extraPlugins = [];

// Resolve public host and client address.
let publicHost = wco.buildOptions.publicHost;
if (publicHost) {
if (!/^\w+:\/\//.test(publicHost)) {
publicHost = `${ssl ? 'https' : 'http'}://${publicHost}`;
}

const parsedHost = url.parse(publicHost);
publicHost = parsedHost.host ?? undefined;
} else {
publicHost = '0.0.0.0:0';
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.
Expand All @@ -76,13 +50,7 @@ export function getDevServerConfig(
});
}

const extraRules: webpack.RuleSetRule[] = [];
if (hmr) {
extraRules.push({
loader: HmrLoader,
include: [main].map((p) => resolve(wco.root, p)),
});
}
const webSocketPath = posix.join(servePath, 'ws');

return {
plugins: extraPlugins,
Expand All @@ -97,7 +65,7 @@ export function getDevServerConfig(
...headers,
},
historyApiFallback: !!index && {
index: `${servePath}/${getIndexOutputFile(index)}`,
index: posix.join(servePath, getIndexOutputFile(index)),
disableDotRule: true,
htmlAcceptHeaders: ['text/html', 'application/xhtml+xml'],
rewrites: [
Expand All @@ -107,30 +75,31 @@ export function getDevServerConfig(
},
],
},
sockPath: posix.join(servePath, 'sockjs-node'),
stats: false,
compress: stylesOptimization.minify || scriptsOptimization,
watchOptions: getWatchOptions(poll),
webSocketServer: {
options: {
path: webSocketPath,
},
},
compress: false,
static: false,
https: getSslConfig(root, wco.buildOptions),
overlay: {
errors: !(stylesOptimization.minify || scriptsOptimization),
warnings: false,
allowedHosts: getAllowedHostsConfig(wco.buildOptions),
devMiddleware: {
publicPath: servePath,
stats: false,
},
public: publicHost,
allowedHosts,
disableHostCheck,
// This should always be true, but at the moment this breaks 'SuppressExtractedTextChunksWebpackPlugin'
// because it will include addition JS in the styles.js.
inline: hmr,
publicPath: servePath,
liveReload,
injectClient: liveReload,
hotOnly: hmr && !liveReload,
hot: hmr,
hot: hmr && !liveReload ? 'only' : hmr,
proxy: addProxyConfig(root, proxyConfig),
contentBase: false,
logLevel: 'error',
} as Configuration & { logLevel: Configuration['clientLogLevel'] },
client: {
logging: 'info',
webSocketURL: getPublicHostOptions(wco.buildOptions, webSocketPath),
overlay: {
errors: true,
warnings: false,
},
},
},
};
}

Expand Down Expand Up @@ -169,7 +138,7 @@ export function buildServePath(
* Private method to enhance a webpack config with SSL configuration.
* @private
*/
function getSslConfig(root: string, options: WebpackDevServerOptions) {
function getSslConfig(root: string, options: WebpackDevServerOptions): Configuration['https'] {
const { ssl, sslCert, sslKey } = options;
if (ssl && sslCert && sslKey) {
return {
Expand Down Expand Up @@ -235,3 +204,26 @@ function findDefaultServePath(baseHref?: string, deployUrl?: string): string | n
// 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}`;
}
Loading