fix(es-dev-server): don't rely on serverStart for built-in plugins

This commit is contained in:
Lars den Bakker
2020-05-15 19:25:28 +02:00
parent 48e4339f6d
commit 7bb2b3f654
7 changed files with 98 additions and 65 deletions

View File

@@ -62,6 +62,9 @@ export function createMiddlewares(
watch,
logErrorsToBrowser,
watchDebounce,
babelExclude,
babelModernExclude,
babelModuleExclude,
} = config;
const middlewares: Middleware[] = [];
@@ -90,22 +93,40 @@ export function createMiddlewares(
const setupMessageChanel = watch || (logErrorsToBrowser && (setupCompatibility || nodeResolve));
if (fileExtensions.length > 0) {
plugins.unshift(fileExtensionsPlugin());
plugins.unshift(fileExtensionsPlugin({ fileExtensions }));
}
if (nodeResolve || hasHook(plugins, 'resolveId')) {
plugins.push(resolveModuleImportsPlugin());
if (nodeResolve) {
plugins.push(nodeResolvePlugin());
plugins.push(nodeResolvePlugin({ rootDir, fileExtensions, nodeResolve }));
}
plugins.push(resolveModuleImportsPlugin({ rootDir, plugins }));
}
if (setupCompatibility || setupBabel) {
plugins.push(babelTransformPlugin());
plugins.push(
babelTransformPlugin({
rootDir,
readUserBabelConfig,
nodeResolve,
compatibilityMode,
customBabelConfig,
fileExtensions,
babelExclude,
babelModernExclude,
babelModuleExclude,
}),
);
}
if (polyfillsLoader && setupCompatibility) {
plugins.push(polyfillsLoaderPlugin());
plugins.push(
polyfillsLoaderPlugin({
rootDir,
compatibilityMode,
polyfillsLoaderConfig,
}),
);
}
// strips a base path from requests

View File

@@ -1,4 +1,5 @@
/* eslint-disable no-console */
import { Options } from '@rollup/plugin-node-resolve';
import { Plugin } from '../Plugin';
import { createCompatibilityTransform, TransformJs } from '../utils/compatibility-transform';
import { getUserAgentCompat } from '../utils/user-agent-compat';
@@ -10,14 +11,27 @@ import { parse as parseHtml, serialize as serializeHtml } from 'parse5';
import { URL, pathToFileURL, fileURLToPath } from 'url';
import { Context } from 'koa';
import { isPolyfill } from '../utils/utils';
import { TransformOptions } from '@babel/core';
function createFilePath(context: Context, rootDir: string) {
return fileURLToPath(new URL(`.${context.path}`, `${pathToFileURL(rootDir)}/`));
}
export function babelTransformPlugin(): Plugin {
let rootDir: string;
let compatibilityTransform: TransformJs;
interface BabelTransformConfig {
rootDir: string;
readUserBabelConfig: boolean;
nodeResolve: boolean | Options;
compatibilityMode: string;
customBabelConfig?: TransformOptions;
fileExtensions: string[];
babelExclude: string[];
babelModernExclude: string[];
babelModuleExclude: string[];
}
export function babelTransformPlugin(config: BabelTransformConfig): Plugin {
const compatibilityTransform = createCompatibilityTransform(config);
const { rootDir } = config;
async function transformJs(context: Context, code: string) {
const filePath = createFilePath(context, rootDir);
@@ -32,11 +46,6 @@ export function babelTransformPlugin(): Plugin {
}
return {
serverStart({ config }) {
({ rootDir } = config);
compatibilityTransform = createCompatibilityTransform(config);
},
async transform(context) {
// transform a single file
if (context.response.is('js') && !isPolyfill(context.url)) {

View File

@@ -1,16 +1,16 @@
import { Plugin } from '../Plugin';
interface FileExtensionsConfig {
fileExtensions: string[];
}
/**
* Plugin which serves configured file extensions as JS.
*/
export function fileExtensionsPlugin(): Plugin {
let fileExtensions: string[];
export function fileExtensionsPlugin(config: FileExtensionsConfig): Plugin {
const { fileExtensions } = config;
return {
serverStart({ config }) {
fileExtensions = config.fileExtensions.map(ext => (ext.startsWith('.') ? ext : `.${ext}`));
},
resolveMimeType(context) {
if (fileExtensions.some(ext => context.path.endsWith(ext))) {
return 'js';

View File

@@ -1,4 +1,4 @@
import createRollupResolve from '@rollup/plugin-node-resolve';
import createRollupResolve, { Options } from '@rollup/plugin-node-resolve';
import { Plugin as RollupPlugin } from 'rollup';
import path from 'path';
import { URL, pathToFileURL, fileURLToPath } from 'url';
@@ -17,27 +17,29 @@ const fakePluginContext = {
},
};
export function nodeResolvePlugin(): Plugin {
let fileExtensions: string[];
let rootDir: string;
let nodeResolve: RollupPlugin;
interface NodeResolveConfig {
rootDir: string;
fileExtensions: string[];
nodeResolve: boolean | Options;
}
export function nodeResolvePlugin(config: NodeResolveConfig): Plugin {
const { fileExtensions, rootDir } = config;
const options = {
rootDir,
// allow resolving polyfills for nodejs libs
preferBuiltins: false,
extensions: fileExtensions,
...(typeof config.nodeResolve === 'object' ? config.nodeResolve : {}),
};
const nodeResolve = createRollupResolve(options);
// call buildStart
const preserveSymlinks = options?.customResolveOptions?.preserveSymlinks;
nodeResolve.buildStart?.call(fakePluginContext as any, { preserveSymlinks });
return {
async serverStart({ config }) {
({ rootDir, fileExtensions } = config);
const options = {
rootDir,
// allow resolving polyfills for nodejs libs
preferBuiltins: false,
extensions: fileExtensions,
...(typeof config.nodeResolve === 'object' ? config.nodeResolve : {}),
};
nodeResolve = createRollupResolve(options);
// call buildStart
const preserveSymlinks = options?.customResolveOptions?.preserveSymlinks;
nodeResolve.buildStart?.call(fakePluginContext as any, { preserveSymlinks });
},
async serverStart({ config }) {},
async resolveImport({ source, context }) {
if (whatwgUrl.parseURL(source) != null) {

View File

@@ -13,27 +13,24 @@ interface IndexHTMLData {
inlineScripts: GeneratedFile[];
}
export interface PolyfillsLoaderMiddlewareConfig {}
export interface PolyfillsLoaderPluginConfig {
rootDir: string;
compatibilityMode: string;
polyfillsLoaderConfig?: Partial<PolyfillsLoaderConfig>;
}
/**
* Creates plugin which injects polyfills and code into HTML pages which allows
* it to run on legacy browsers.
*/
export function polyfillsLoaderPlugin(): Plugin {
export function polyfillsLoaderPlugin(config: PolyfillsLoaderPluginConfig): Plugin {
const { compatibilityMode, rootDir, polyfillsLoaderConfig = {} } = config;
// index html data, keyed by url
const indexHTMLData = new Map<string, IndexHTMLData>();
// polyfills, keyed by request path
const polyfills = new Map<string, string>();
let compatibilityMode: string;
let rootDir: string;
let polyfillsLoaderConfig: Partial<PolyfillsLoaderConfig>;
return {
serverStart({ config }) {
({ compatibilityMode, rootDir, polyfillsLoaderConfig = {} } = config);
},
async serve(context) {
const uaCompat = getUserAgentCompat(context);

View File

@@ -186,16 +186,16 @@ async function resolveWithPluginHooks(
return resolveModuleImports(jsCode, filePath, resolveImport);
}
export function resolveModuleImportsPlugin(): Plugin {
let rootDir: string;
let resolvePlugins: Plugin[];
export interface ResolveModuleImportsPlugin {
rootDir: string;
plugins?: Plugin[];
}
export function resolveModuleImportsPlugin(config: ResolveModuleImportsPlugin): Plugin {
const { rootDir, plugins = [] } = config;
const resolvePlugins = plugins.filter(pl => !!pl.resolveImport);
return {
serverStart({ config }) {
({ rootDir } = config);
resolvePlugins = config.plugins.filter(pl => !!pl.resolveImport);
},
async transform(context) {
if (resolvePlugins.length === 0) {
return;

View File

@@ -192,21 +192,22 @@ const host = 'http://localhost:8080/';
describe('resolveModuleImportsPlugin', () => {
it('lets plugins resolve imports using the resolveImport hook', async () => {
const rootDir = path.resolve(__dirname, '..', 'fixtures', 'simple');
const plugins: Plugin[] = [
{
resolveImport({ source }) {
return `RESOLVED__${source}`;
},
},
resolveModuleImportsPlugin(),
];
plugins.push(resolveModuleImportsPlugin({ rootDir, plugins }));
let server;
try {
({ server } = await startServer(
createConfig({
port: 8080,
rootDir: path.resolve(__dirname, '..', 'fixtures', 'simple'),
rootDir,
compatibility: 'none',
plugins,
}),
@@ -223,21 +224,22 @@ describe('resolveModuleImportsPlugin', () => {
});
it('resolved imports in inline modules in HTML files', async () => {
const rootDir = path.resolve(__dirname, '..', 'fixtures', 'simple');
const plugins: Plugin[] = [
{
resolveImport({ source }) {
return `RESOLVED__${source}`;
},
},
resolveModuleImportsPlugin(),
];
plugins.push(resolveModuleImportsPlugin({ rootDir, plugins }));
let server;
try {
({ server } = await startServer(
createConfig({
port: 8080,
rootDir: path.resolve(__dirname, '..', 'fixtures', 'simple'),
rootDir,
compatibility: 'none',
plugins,
}),
@@ -254,21 +256,22 @@ describe('resolveModuleImportsPlugin', () => {
});
it('unmatched resolve leaves import untouched', async () => {
const rootDir = path.resolve(__dirname, '..', 'fixtures', 'simple');
const plugins: Plugin[] = [
{
resolveImport() {
return undefined;
},
},
resolveModuleImportsPlugin(),
];
plugins.push(resolveModuleImportsPlugin({ rootDir, plugins }));
let server;
try {
({ server } = await startServer(
createConfig({
port: 8080,
rootDir: path.resolve(__dirname, '..', 'fixtures', 'simple'),
rootDir,
compatibility: 'none',
plugins,
}),
@@ -285,6 +288,7 @@ describe('resolveModuleImportsPlugin', () => {
});
it('first matching plugin takes priority', async () => {
const rootDir = path.resolve(__dirname, '..', 'fixtures', 'simple');
const plugins: Plugin[] = [
{
resolveImport({ source, context }) {
@@ -298,15 +302,15 @@ describe('resolveModuleImportsPlugin', () => {
return `RESOLVED__B__${source}`;
},
},
resolveModuleImportsPlugin(),
];
plugins.push(resolveModuleImportsPlugin({ rootDir, plugins }));
let server;
try {
({ server } = await startServer(
createConfig({
port: 8080,
rootDir: path.resolve(__dirname, '..', 'fixtures', 'simple'),
rootDir,
compatibility: 'none',
plugins,
}),