diff --git a/packages/wmr/src/lib/npm-middleware.js b/packages/wmr/src/lib/npm-middleware.js index 34ba3b62..02f78016 100644 --- a/packages/wmr/src/lib/npm-middleware.js +++ b/packages/wmr/src/lib/npm-middleware.js @@ -6,11 +6,11 @@ export function npmEtagCache() { const url = new URL(req.url, 'https://localhost'); let id = path.posix.normalize(url.pathname); - if (!id.startsWith('/@npm/@id/')) { + if (!id.startsWith('/@npm/')) { return next(); } - id = id.slice('/@npm/@id/'.length); + id = id.slice('/@npm/'.length); if (!isValidPackageName(id)) { return next(); } diff --git a/packages/wmr/src/lib/plugins.js b/packages/wmr/src/lib/plugins.js index 0fefd6b9..6714a30f 100644 --- a/packages/wmr/src/lib/plugins.js +++ b/packages/wmr/src/lib/plugins.js @@ -1,3 +1,4 @@ +import path from 'path'; import htmPlugin from '../plugins/htm-plugin.js'; import sucrasePlugin from '../plugins/sucrase-plugin.js'; import wmrPlugin from '../plugins/wmr/plugin.js'; @@ -28,6 +29,7 @@ import { lessPlugin } from '../plugins/less-plugin.js'; import { workerPlugin } from '../plugins/worker-plugin.js'; import { npmPlugin } from '../plugins/npm-plugin/index.js'; import tsConfigPathsPlugin from '../plugins/tsconfig-paths-plugin.js'; +import { getNpmPlugins } from '../plugins/npm-plugin/npm-bundle.js'; /** * @param {import("wmr").Options & { isIIFEWorker?: boolean}} options @@ -51,6 +53,14 @@ export function getPlugins(options) { registry } = options; + const npmCacheDir = path.join(cwd, '.cache', '@npm'); + + /** + * Map of package name to folder on disk + * @type {Map} + */ + const resolutionCache = new Map(); + // Plugins are pre-sorted let split = plugins.findIndex(p => p.enforce === 'post'); if (split === -1) split = plugins.length; @@ -102,7 +112,20 @@ export function getPlugins(options) { // Only transpile CommonJS in node_modules and explicit .cjs files: include: /(^npm\/|[/\\]node_modules[/\\]|\.cjs$)/ }), - npmPlugin({ cwd, autoInstall, production, registryUrl: registry }), + + ...(production + ? getNpmPlugins({ + autoInstall, + production, + cacheDir: npmCacheDir, + cwd, + registryUrl: registry, + resolutionCache, + browserReplacement: new Map() + }) + : []), + !production && + npmPlugin({ cwd, cacheDir: npmCacheDir, autoInstall, production, registryUrl: registry, resolutionCache, alias }), resolveExtensionsPlugin({ extensions: ['.ts', '.tsx', '.js', '.cjs'], index: true diff --git a/packages/wmr/src/plugins/npm-plugin/commonjs.js b/packages/wmr/src/plugins/npm-plugin/commonjs.js index c887becc..4715999c 100644 --- a/packages/wmr/src/plugins/npm-plugin/commonjs.js +++ b/packages/wmr/src/plugins/npm-plugin/commonjs.js @@ -20,20 +20,28 @@ export function commonjsPlugin({ production }) { if (!hasCjsKeywords && hasEsmKeywords) return; - const result = transform(code, { - parse: this.parse, - plugins: [ - replace({ 'process.env.NODE_ENV': 'development', __DEV__: !!production }), - optimize(), - commonjsToEsm() - ] - }); + let result; + try { + result = transform(code, { + parse: this.parse, + plugins: [ + replace({ 'process.env.NODE_ENV': 'development', __DEV__: !!production }), + optimize(), + commonjsToEsm() + ] + }); - return { - code: result.code, - // FIXME: Sourcemap - map: null - }; + if (code !== result.code) { + console.log('CJS', id, result.code); + return { + code: result.code, + map: result.map + }; + } + } catch (err) { + console.log('ERR', code); + throw err; + } } }; } diff --git a/packages/wmr/src/plugins/npm-plugin/index.js b/packages/wmr/src/plugins/npm-plugin/index.js index a49a0936..3c666fe8 100644 --- a/packages/wmr/src/plugins/npm-plugin/index.js +++ b/packages/wmr/src/plugins/npm-plugin/index.js @@ -14,13 +14,14 @@ const log = debug('npm', 196); * @param {boolean} options.autoInstall * @param {boolean} options.production * @param {string} options.registryUrl + * @param {string} options.cacheDir + * @param {Record} options.alias + * @param {Map} options.resolutionCache * @returns {import('rollup').Plugin} */ -export function npmPlugin({ cwd, autoInstall, production, registryUrl }) { +export function npmPlugin({ cwd, cacheDir, autoInstall, production, registryUrl, resolutionCache, alias }) { const PREFIX = '\0npm:'; - const cacheDir = path.join(cwd, '.cache', '@npm'); - /** @type {Map} */ const chunkCache = new Map(); @@ -38,10 +39,11 @@ export function npmPlugin({ cwd, autoInstall, production, registryUrl }) { * @param {object} options * @param {string} options.packageName * @param {string} options.diskCacheDir + * @param {Record} options.alias * @param {Map} options.resolutionCache * @returns {Promise<{ code: string, map: any }>} */ - async function bundleNpmPackage(id, { packageName, diskCacheDir, resolutionCache }) { + async function bundleNpmPackage(id, { packageName, diskCacheDir, resolutionCache, alias }) { const deferred = new Deferred(); pending.set(id, deferred); @@ -53,7 +55,7 @@ export function npmPlugin({ cwd, autoInstall, production, registryUrl }) { } log(kl.dim(`bundle: `) + kl.cyan(id)); - let result = await npmBundle(id, { autoInstall, production, cacheDir, cwd, resolutionCache, registryUrl }); + let result = await npmBundle(id, { autoInstall, production, cacheDir, cwd, resolutionCache, registryUrl, alias }); await Promise.all( result.output.map(async chunkOrAsset => { @@ -88,12 +90,6 @@ export function npmPlugin({ cwd, autoInstall, production, registryUrl }) { return chunk; } - /** - * Map of package name to folder on disk - * @type {Map} - */ - const resolutionCache = new Map(); - return { name: 'npm-plugin', async resolveId(id) { @@ -114,7 +110,7 @@ export function npmPlugin({ cwd, autoInstall, production, registryUrl }) { log(kl.dim(`asset ${id}, wait for bundling `) + kl.cyan(name)); const diskCacheDir = path.join(cacheDir, escapeFilename(name)); if (!deferred) { - await bundleNpmPackage(name, { packageName: name, diskCacheDir, resolutionCache }); + await bundleNpmPackage(name, { packageName: name, diskCacheDir, resolutionCache, alias }); } else { await deferred; } diff --git a/packages/wmr/src/plugins/npm-plugin/npm-bundle.js b/packages/wmr/src/plugins/npm-plugin/npm-bundle.js index f7d71977..94faa16a 100644 --- a/packages/wmr/src/plugins/npm-plugin/npm-bundle.js +++ b/packages/wmr/src/plugins/npm-plugin/npm-bundle.js @@ -11,6 +11,7 @@ import { npmAutoInstall } from './npm-auto-install.js'; import jsonPlugin from '../json-plugin.js'; import sizeWarningPlugin from './size-warning-plugin.js'; import { onWarn } from '../../lib/output-utils.js'; +import aliasPlugin from '../aliases-plugin.js'; /** @type {import('rollup').WarningHandlerWithDefault} */ function customWarn(warning) { @@ -22,6 +23,41 @@ function customWarn(warning) { onWarn(warning); } +/** + * @param {object} options + * @param {boolean} options.autoInstall + * @param {boolean} options.production + * @param {string} options.cacheDir + * @param {string} options.cwd + * @param {string} options.registryUrl + * @param {string} [options.requestId] + * @param {Map} options.resolutionCache + * @param {Map} options.browserReplacement + * @returns {import('rollup').Plugin[]} + */ +export function getNpmPlugins({ + autoInstall, + production, + cacheDir, + cwd, + resolutionCache, + registryUrl, + browserReplacement, + requestId +}) { + // @ts-ignore + return [ + browserFieldPlugin({ browserReplacement }), + !production && requestId && npmExternalDeps({ requestId }), + !process.env.DISABLE_LOCAL_NPM && npmLocalPackage({ root: cwd }), + autoInstall && npmAutoInstall({ cacheDir, registryUrl }), + npmLoad({ browserReplacement, resolutionCache, production }), + commonjsPlugin({ production }), + subPackageLegacy(), + sizeWarningPlugin() + ].filter(Boolean); +} + /** * @param {string} requestId * @param {object} options @@ -30,29 +66,38 @@ function customWarn(warning) { * @param {string} options.cacheDir * @param {string} options.cwd * @param {string} options.registryUrl + * @param {Record} options.alias * @param {Map} options.resolutionCache */ -export async function npmBundle(requestId, { autoInstall, production, cacheDir, cwd, resolutionCache, registryUrl }) { +export async function npmBundle( + requestId, + { autoInstall, production, cacheDir, cwd, resolutionCache, registryUrl, alias } +) { const meta = getPackageInfo(requestId); const pkgName = meta.name; /** @type {Map} */ const browserReplacement = new Map(); + console.log('REQUEST', requestId); + const bundle = await rollup.rollup({ input: requestId, external: [...builtinModules], onwarn: customWarn, plugins: [ - browserFieldPlugin({ browserReplacement }), - npmExternalDeps({ requestId }), - !process.env.DISABLE_LOCAL_NPM && npmLocalPackage({ root: cwd }), - autoInstall && npmAutoInstall({ cacheDir, registryUrl }), - npmLoad({ browserReplacement, resolutionCache }), + aliasPlugin({ alias }), jsonPlugin({ root: cwd }), - commonjsPlugin({ production }), - subPackageLegacy({ rootId: requestId }), - sizeWarningPlugin() + ...getNpmPlugins({ + requestId, + autoInstall, + production, + cacheDir, + cwd, + resolutionCache, + registryUrl, + browserReplacement + }) ] }); diff --git a/packages/wmr/src/plugins/npm-plugin/npm-load.js b/packages/wmr/src/plugins/npm-plugin/npm-load.js index d6cb0fb6..508f92cc 100644 --- a/packages/wmr/src/plugins/npm-plugin/npm-load.js +++ b/packages/wmr/src/plugins/npm-plugin/npm-load.js @@ -11,9 +11,10 @@ const log = debug('npm-load'); * @param {object} options * @param {Map} options.browserReplacement * @param {Map} options.resolutionCache + * @param {boolean} options.production * @returns {import('rollup').Plugin} */ -export function npmLoad({ browserReplacement, resolutionCache }) { +export function npmLoad({ browserReplacement, resolutionCache, production }) { return { name: 'npm-load', async resolveId(id, importer) { @@ -82,7 +83,7 @@ export function npmLoad({ browserReplacement, resolutionCache }) { const subPkg = await readJson(path.join(modDir, pathname, 'package.json')); entry = path.join(modDir, pathname, subPkg.module || subPkg.main || 'index.js'); } catch (err) { - entry = pathname; + entry = path.join(modDir, pathname); } } } @@ -93,7 +94,7 @@ export function npmLoad({ browserReplacement, resolutionCache }) { // Some packages use non-js entry files, but rollup only supports js. // So we expect other plugins to handle assets. - if (!/\.(?:[tj]sx?|[cm]js|[mc]ts)/.test(path.extname(entry))) { + if (!production && !/\.(?:[tj]sx?|[cm]js|[mc]ts)/.test(path.extname(entry))) { return { code: '', map: null, diff --git a/packages/wmr/src/plugins/npm-plugin/sub-package-legacy.js b/packages/wmr/src/plugins/npm-plugin/sub-package-legacy.js index 5af89ccd..7afb3a7c 100644 --- a/packages/wmr/src/plugins/npm-plugin/sub-package-legacy.js +++ b/packages/wmr/src/plugins/npm-plugin/sub-package-legacy.js @@ -5,11 +5,9 @@ import { isDirectory } from '../../lib/fs-utils.js'; /** * Legacy way of defining package entry points before the * "export" field in `package.json` was a thing. - * @param {object} options - * @param {string} options.rootId * @returns {import('rollup').Plugin} */ -export function subPackageLegacy({ rootId }) { +export function subPackageLegacy() { return { name: 'legacy-sub-package', async resolveId(id, importer) { diff --git a/packages/wmr/src/plugins/npm-plugin/utils.js b/packages/wmr/src/plugins/npm-plugin/utils.js index 0c77c53a..98204467 100644 --- a/packages/wmr/src/plugins/npm-plugin/utils.js +++ b/packages/wmr/src/plugins/npm-plugin/utils.js @@ -49,12 +49,11 @@ export function isValidPackageName(id) { !/node_modules|favicon\.ico/.test(id) && // Must not be a built-in node module !builtins.has(id) && - // Must be lowercase - id.toLowerCase() === id && + // Package name must be lowercase and contain path segment + // if scoped + /^(?:@[^/A-Z]+\/[^/A-Z]+|[^/A-Z]+)/.test(id) && // Must not contain special characters - !/[~'!()*;,?:&=+$]/.test(id) && - // Must contain a second path segment if scoped - ((id[0] === '@' && id.indexOf('/') > 0) || true); + !/[~'!()*;,?:&=+$]/.test(id); return isValid; } diff --git a/packages/wmr/src/wmr-middleware.js b/packages/wmr/src/wmr-middleware.js index 35a302d6..ea03ffc6 100644 --- a/packages/wmr/src/wmr-middleware.js +++ b/packages/wmr/src/wmr-middleware.js @@ -246,7 +246,7 @@ export default function wmrMiddleware(options) { // Workaround for transform forcing extensionless ids to be // non-js - let hasIdPrefix = false; + let isVirtual = false; let file = ''; let id = path; @@ -257,7 +257,7 @@ export default function wmrMiddleware(options) { // Path for virtual modules that refer to an unprefixed id. if (path.startsWith('/@id/')) { // Virtual paths have no exact file match, so we don't set `file` - hasIdPrefix = true; + isVirtual = true; id = path.slice('/@id/'.length); // Add back leading slash if it was part of the virtual id. @@ -265,6 +265,10 @@ export default function wmrMiddleware(options) { if (req.path.startsWith('/@id//')) { id = '/' + id; } + } else if (path.startsWith('/@npm/')) { + // Virtual paths have no exact file match, so we don't set `file` + id = path.slice('/@npm/'.length); + isVirtual = true; } else if (path.startsWith('/@alias/')) { id = posix.normalize(path.slice('/@alias/'.length)); @@ -279,7 +283,7 @@ export default function wmrMiddleware(options) { if (path.startsWith('/@id/')) { // Virtual paths have no exact file match, so we don't set `file` - hasIdPrefix = true; + isVirtual = true; path = path.slice('/@id'.length); } @@ -296,7 +300,7 @@ export default function wmrMiddleware(options) { // Normalize the cacheKey so it matches what will be in the WRITE_CACHE, where we store in native paths cacheKey = cacheKey.split(posix.sep).join(sep); - if (!hasIdPrefix) { + if (!isVirtual) { id = `./${id}`; } @@ -331,7 +335,7 @@ export default function wmrMiddleware(options) { } else if (queryParams.has('asset')) { cacheKey += '?asset'; transform = TRANSFORMS.asset; - } else if (prefix || hasIdPrefix || isModule || /\.([mc]js|[tj]sx?)$/.test(file) || STYLE_REG.test(file)) { + } else if (prefix || isVirtual || isModule || /\.([mc]js|[tj]sx?)$/.test(file) || STYLE_REG.test(file)) { transform = TRANSFORMS.js; } else if (file.startsWith(root + sep) && (await isFile(file))) { // Ignore dotfiles @@ -591,7 +595,7 @@ export const TRANSFORMS = { spec = relative(root, spec).split(sep).join(posix.sep); } // Retain bare specifiers when serializing to url - else if (!/^\.?\.\//.test(spec)) { + else if (!/^\.?\.\//.test(spec) && prefix !== 'npm') { spec = `@id/${spec}`; } diff --git a/packages/wmr/test/fixtures.test.js b/packages/wmr/test/fixtures.test.js index 110be263..9ee83208 100644 --- a/packages/wmr/test/fixtures.test.js +++ b/packages/wmr/test/fixtures.test.js @@ -1039,16 +1039,18 @@ describe('fixtures', () => { expect(await env.page.evaluate(`import('/@npm/exports-fallbacks-requirefirst')`)).toEqual({ default: 'import' }); - expect(await env.page.evaluate(`import('/@npm/exports-fallbacks-defaultfirst')`)).toEqual({ - default: 'default' + await withLog(instance.output, async () => { + expect(await env.page.evaluate(`import('/@npm/exports-fallbacks-defaultfirst')`)).toEqual({ + default: 'import' + }); }); // When import/module/browser isn't present (but a random other one is!), we fall back to require/default: expect(await env.page.evaluate(`import('/@npm/exports-fallbacks-requirefallback')`)).toEqual({ - default: 'default' + default: 'require' }); expect(await env.page.evaluate(`import('/@npm/exports-fallbacks-defaultfallback')`)).toEqual({ - default: 'default' + default: 'require' }); }); }); diff --git a/packages/wmr/test/fixtures/npm-auto-install-css-2/index.html b/packages/wmr/test/fixtures/npm-auto-install-css-2/index.html new file mode 100644 index 00000000..9cfb45a0 --- /dev/null +++ b/packages/wmr/test/fixtures/npm-auto-install-css-2/index.html @@ -0,0 +1,2 @@ +

it doesn't work

+ diff --git a/packages/wmr/test/fixtures/npm-auto-install-css-2/index.js b/packages/wmr/test/fixtures/npm-auto-install-css-2/index.js new file mode 100644 index 00000000..cae2c97e --- /dev/null +++ b/packages/wmr/test/fixtures/npm-auto-install-css-2/index.js @@ -0,0 +1,4 @@ +import Calendar from 'react-calendar'; +import 'react-calendar/dist/Calendar.css'; + +document.querySelector('h1').textContent = typeof Calendar === 'function' ? 'it works' : "it doesn't work"; diff --git a/packages/wmr/test/fixtures/npm-auto-install-css-2/wmr.config.mjs b/packages/wmr/test/fixtures/npm-auto-install-css-2/wmr.config.mjs new file mode 100644 index 00000000..11152591 --- /dev/null +++ b/packages/wmr/test/fixtures/npm-auto-install-css-2/wmr.config.mjs @@ -0,0 +1,5 @@ +export default { + alias: { + react: 'preact/compat' + } +}; diff --git a/packages/wmr/test/npm.test.js b/packages/wmr/test/npm.test.js index 3c2eb12f..d4b5bd96 100644 --- a/packages/wmr/test/npm.test.js +++ b/packages/wmr/test/npm.test.js @@ -308,6 +308,19 @@ describe('node modules', () => { }); }); }); + + it('should load CSS from installed package #2', async () => { + await loadFixture('npm-auto-install-css-2', env); + instance = await runWmrFast(env.tmp.path, '--autoInstall', { env: { DISABLE_LOCAL_NPM: true } }); + await getOutput(env, instance); + + await withLog(instance.output, async () => { + await waitForPass(async () => { + const color = await env.page.$eval('a', el => getComputedStyle(el).color); + expect(color).toBe('rgb(17, 139, 238)'); + }); + }); + }); }); }); diff --git a/packages/wmr/test/production.test.js b/packages/wmr/test/production.test.js index bfe8dfbf..1032c6ab 100644 --- a/packages/wmr/test/production.test.js +++ b/packages/wmr/test/production.test.js @@ -474,7 +474,7 @@ describe('production', () => { for (const d of ['dist', 'node_modules', '.cache']) { await fs.rmdir(path.join(env.tmp.path, d), { recursive: true }); } - instance = await runWmr(env.tmp.path, 'build', '--prerender'); + instance = await runWmr(env.tmp.path, 'build', '--prerender', '--autoInstall'); const code = await instance.done; const output = instance.output.join('\n'); console.log(output); @@ -572,62 +572,64 @@ describe('production', () => { describe('CSS Asset Graph Optimization', () => { it('should hoist dynamically imported CSS into unconditionally loaded parent', async () => { await loadFixture('css', env); - instance = await runWmr(env.tmp.path, 'build'); - const code = await instance.done; - console.info(instance.output.join('\n')); - expect(code).toBe(0); + instance = await runWmr(env.tmp.path, 'build', '--autoInstall'); - const readdir = async f => (await fs.readdir(path.join(env.tmp.path, f))).filter(f => f[0] !== '.'); - const assets = await readdir('dist/assets'); - const chunks = await readdir('dist/chunks'); + await withLog(instance.output, async () => { + const code = await instance.done; + expect(code).toBe(0); - expect(assets).toEqual([expect.stringMatching(/^style\.\w+\.css$/)]); - expect(chunks).toEqual([expect.stringMatching(/^index\.\w+\.js$/), expect.stringMatching(/^index\.\w+\.js$/)]); + const readdir = async f => (await fs.readdir(path.join(env.tmp.path, f))).filter(f => f[0] !== '.'); + const assets = await readdir('dist/assets'); + const chunks = await readdir('dist/chunks'); - const css = await fs.readFile(path.join(env.tmp.path, 'dist/assets', assets[0]), 'utf-8'); - // ensure all the CSS got merged: - expect(css).toMatch(/body\s*,\s*html/); - expect(css).toMatch(/\.app_\w+/); - expect(css).toMatch(/\.home_\w+/); - expect(css).toMatch(/\.profile_\w+/); + expect(assets).toEqual([expect.stringMatching(/^style\.\w+\.css$/)]); + expect(chunks).toEqual([expect.stringMatching(/^index\.\w+\.js$/), expect.stringMatching(/^index\.\w+\.js$/)]); - const { address, stop } = serveStatic(path.join(env.tmp.path, 'dist')); - cleanup.push(stop); + const css = await fs.readFile(path.join(env.tmp.path, 'dist/assets', assets[0]), 'utf-8'); + // ensure all the CSS got merged: + expect(css).toMatch(/body\s*,\s*html/); + expect(css).toMatch(/\.app_\w+/); + expect(css).toMatch(/\.home_\w+/); + expect(css).toMatch(/\.profile_\w+/); - const logs = []; - function log(type, text) { - logs.push(`${type}: ${text}`); - console.log(` ${type}: ${text}`); - } - env.page.on('console', m => log(m.type(), m.text())); - env.page.on('error', err => log('error', err)); - env.page.on('pageerror', err => log('page error', err)); + const { address, stop } = serveStatic(path.join(env.tmp.path, 'dist')); + cleanup.push(stop); - const requests = []; - await env.page.setCacheEnabled(false); - await env.page.setRequestInterception(true); - page.on('request', req => { - requests.push(req.url().replace(/^https?:\/\/[^/]+/, '')); - req.continue(); - }); + const logs = []; + function log(type, text) { + logs.push(`${type}: ${text}`); + console.log(` ${type}: ${text}`); + } + env.page.on('console', m => log(m.type(), m.text())); + env.page.on('error', err => log('error', err)); + env.page.on('pageerror', err => log('page error', err)); + + const requests = []; + await env.page.setCacheEnabled(false); + await env.page.setRequestInterception(true); + page.on('request', req => { + requests.push(req.url().replace(/^https?:\/\/[^/]+/, '')); + req.continue(); + }); - await env.page.goto(address, { waitUntil: ['domcontentloaded', 'networkidle2'] }); - expect(await env.page.content()).toMatch(/This is the home page/); + await env.page.goto(address, { waitUntil: ['domcontentloaded', 'networkidle2'] }); + expect(await env.page.content()).toMatch(/This is the home page/); - expect(logs).toEqual([]); - expect(requests.filter(url => /\.css$/.test(url))).toEqual([ - expect.stringMatching(/^\/assets\/style\.\w+\.css$/) - ]); + expect(logs).toEqual([]); + expect(requests.filter(url => /\.css$/.test(url))).toEqual([ + expect.stringMatching(/^\/assets\/style\.\w+\.css$/) + ]); - logs.length = requests.length = 0; + logs.length = requests.length = 0; - await env.page.goto(address + '/profile/foo', { waitUntil: ['domcontentloaded', 'networkidle2'] }); - expect(await env.page.content()).toMatch(/This is the profile page for foo/); + await env.page.goto(address + '/profile/foo', { waitUntil: ['domcontentloaded', 'networkidle2'] }); + expect(await env.page.content()).toMatch(/This is the profile page for foo/); - expect(logs).toEqual([]); - expect(requests.filter(url => /\.css$/.test(url))).toEqual([ - expect.stringMatching(/^\/assets\/style\.\w+\.css$/) - ]); + expect(logs).toEqual([]); + expect(requests.filter(url => /\.css$/.test(url))).toEqual([ + expect.stringMatching(/^\/assets\/style\.\w+\.css$/) + ]); + }); }); it('should merge duplicate CSS imports', async () => { @@ -943,9 +945,12 @@ describe('production', () => { describe('Prerender', () => { it('should remove search params', async () => { await loadFixture('prod-routes', env); - instance = await runWmr(env.tmp.path, 'build', '--prerender'); - const code = await instance.done; - expect(code).toBe(0); + + instance = await runWmr(env.tmp.path, 'build', '--prerender', '--autoInstall'); + await withLog(instance.output, async () => { + const code = await instance.done; + expect(code).toBe(0); + }); const readdir = async f => (await fs.readdir(path.join(env.tmp.path, f))).filter(f => f[0] !== '.');