Skip to content

Commit

Permalink
feat(@angular-devkit/build-angular): support ESM proxy configuration …
Browse files Browse the repository at this point in the history
…files for the dev server

The `proxyConfig` option now supports loading ESM configuration files in addition to JSON and CommonJS files. ESM files (such as those ending with `.mjs`) must provide a default export with the configuration object.
For example, a `proxy.config.mjs` containing the follow is now possible:
```
export default { "/api/*": { "target": "http://127.0.0.1:5001" } };
```

Closes #21623
  • Loading branch information
clydin committed Sep 24, 2021
1 parent 13cceab commit fb1ad7c
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 15 deletions.
Expand Up @@ -25,28 +25,67 @@ describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => {
await harness.writeFile('src/main.ts', '');
});

it('proxies requests based on the proxy configuration file provided in the option', async () => {
it('proxies requests based on the JSON proxy configuration file provided in the option', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
proxyConfig: 'proxy.config.json',
});

const proxyServer = http.createServer((request, response) => {
if (request.url?.endsWith('/test')) {
response.writeHead(200);
response.end('TEST_API_RETURN');
} else {
response.writeHead(404);
response.end();
}
const proxyServer = createProxyServer();
try {
await new Promise<void>((resolve) => proxyServer.listen(0, '127.0.0.1', resolve));
const proxyAddress = proxyServer.address() as import('net').AddressInfo;

await harness.writeFiles({
'proxy.config.json': `{ "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`,
});

const { result, response } = await executeOnceAndFetch(harness, '/api/test');

expect(result?.success).toBeTrue();
expect(await response?.text()).toContain('TEST_API_RETURN');
} finally {
await new Promise<void>((resolve) => proxyServer.close(() => resolve()));
}
});

it('proxies requests based on the JS proxy configuration file provided in the option', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
proxyConfig: 'proxy.config.js',
});

const proxyServer = createProxyServer();
try {
await new Promise<void>((resolve) => proxyServer.listen(0, '127.0.0.1', resolve));
const proxyAddress = proxyServer.address() as import('net').AddressInfo;

await harness.writeFiles({
'proxy.config.json': `{ "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`,
'proxy.config.js': `module.exports = { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`,
});

const { result, response } = await executeOnceAndFetch(harness, '/api/test');

expect(result?.success).toBeTrue();
expect(await response?.text()).toContain('TEST_API_RETURN');
} finally {
await new Promise<void>((resolve) => proxyServer.close(() => resolve()));
}
});

it('proxies requests based on the MJS proxy configuration file provided in the option', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
proxyConfig: 'proxy.config.mjs',
});

const proxyServer = createProxyServer();
try {
await new Promise<void>((resolve) => proxyServer.listen(0, '127.0.0.1', resolve));
const proxyAddress = proxyServer.address() as import('net').AddressInfo;

await harness.writeFiles({
'proxy.config.mjs': `export default { "/api/*": { "target": "http://127.0.0.1:${proxyAddress.port}" } }`,
});

const { result, response } = await executeOnceAndFetch(harness, '/api/test');
Expand Down Expand Up @@ -75,3 +114,22 @@ describeBuilder(serveWebpackBrowser, DEV_SERVER_BUILDER_INFO, (harness) => {
});
});
});

/**
* Creates an HTTP Server used for proxy testing that provides a `/test` endpoint
* that returns a 200 response with a body of `TEST_API_RETURN`. All other requests
* will return a 404 response.
*
* @returns An HTTP Server instance.
*/
function createProxyServer() {
return http.createServer((request, response) => {
if (request.url?.endsWith('/test')) {
response.writeHead(200);
response.end('TEST_API_RETURN');
} else {
response.writeHead(404);
response.end();
}
});
}
Expand Up @@ -13,12 +13,13 @@ import * as url from 'url';
import * as webpack from 'webpack';
import { Configuration } from 'webpack-dev-server';
import { WebpackConfigOptions, WebpackDevServerOptions } from '../../utils/build-options';
import { loadEsmModule } from '../../utils/load-esm';
import { getIndexOutputFile } from '../../utils/webpack-browser-config';
import { HmrLoader } from '../plugins/hmr/hmr-loader';

export function getDevServerConfig(
export async function getDevServerConfig(
wco: WebpackConfigOptions<WebpackDevServerOptions>,
): webpack.Configuration {
): Promise<webpack.Configuration> {
const {
buildOptions: { host, port, index, headers, watch, hmr, main, liveReload, proxyConfig },
logger,
Expand Down Expand Up @@ -90,7 +91,7 @@ export function getDevServerConfig(
},
liveReload,
hot: hmr && !liveReload ? 'only' : hmr,
proxy: addProxyConfig(root, proxyConfig),
proxy: await addProxyConfig(root, proxyConfig),
client: {
logging: 'info',
webSocketURL: getPublicHostOptions(wco.buildOptions, webSocketPath),
Expand Down Expand Up @@ -154,14 +155,25 @@ function getSslConfig(root: string, options: WebpackDevServerOptions): Configura
* Private method to enhance a webpack config with Proxy configuration.
* @private
*/
function addProxyConfig(root: string, proxyConfig: string | undefined) {
async function addProxyConfig(root: string, proxyConfig: string | undefined) {
if (!proxyConfig) {
return undefined;
}

const proxyPath = resolve(root, proxyConfig);
if (existsSync(proxyPath)) {
return require(proxyPath);
try {
return require(proxyPath);
} catch (e) {
if (e.code === 'ERR_REQUIRE_ESM') {
// 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.
return (await loadEsmModule<{ default: unknown }>(url.pathToFileURL(proxyPath))).default;
}

throw e;
}
}

throw new Error('Proxy config file ' + proxyPath + ' does not exist.');
Expand Down

0 comments on commit fb1ad7c

Please sign in to comment.