From 8c051c28de86a9d58f25c2dea232453b8838b8ba Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 4 Oct 2021 17:44:51 -0500 Subject: [PATCH 01/21] WIP: add import() support to ssrModuleLoader --- packages/vite/native-import.js | 9 +++ packages/vite/package.json | 5 +- packages/vite/rollup.config.js | 1 + packages/vite/src/node/ssr/ssrModuleLoader.ts | 60 +++++++++++++++++-- 4 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 packages/vite/native-import.js diff --git a/packages/vite/native-import.js b/packages/vite/native-import.js new file mode 100644 index 00000000000000..4f86167a2c35eb --- /dev/null +++ b/packages/vite/native-import.js @@ -0,0 +1,9 @@ +/** + * Typescript automatically compiles dynamic `import()` to `resolve()` + * so this needs to be a raw JS file until `typescript@4.5.0` lands with + * `module: "node12"` support. + * + * @param {string} fileUrl + * @returns {any} + */ +module.exports = (fileUrl) => import(fileUrl); diff --git a/packages/vite/package.json b/packages/vite/package.json index e242fb8f515fb2..254fdc03239e86 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -14,7 +14,8 @@ "dist", "client.d.ts", "src/client", - "types" + "types", + "native-import.js" ], "engines": { "node": ">=12.2.0" @@ -46,7 +47,7 @@ }, "//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!", "dependencies": { - "esbuild": "^0.13.2", + "esbuild": "^0.12.28", "postcss": "^8.3.8", "resolve": "^1.20.0", "rollup": "^2.57.0" diff --git a/packages/vite/rollup.config.js b/packages/vite/rollup.config.js index 783c3c1d993de8..4688a91864dae6 100644 --- a/packages/vite/rollup.config.js +++ b/packages/vite/rollup.config.js @@ -107,6 +107,7 @@ const createNodeConfig = (isProduction) => { }, external: [ 'fsevents', + 'vite/native-import.js', ...Object.keys(require('./package.json').dependencies), ...(isProduction ? [] diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index 24f005bd22aac8..c75edf138a4627 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -1,5 +1,6 @@ import fs from 'fs' import path from 'path' +import { pathToFileURL } from 'url'; import { ViteDevServer } from '..' import { cleanUrl, resolveFrom, unwrapId } from '../utils' import { rebindErrorStacktrace, ssrRewriteStacktrace } from './ssrStacktrace' @@ -11,6 +12,8 @@ import { ssrDynamicImportKey } from './ssrTransform' import { transformRequest } from '../server/transformRequest' +// @ts-ignore +import NATIVE_IMPORT from 'vite/native-import.js' interface SSRContext { global: NodeJS.Global @@ -91,11 +94,12 @@ async function instantiateModule( const ssrImport = async (dep: string) => { if (dep[0] !== '.' && dep[0] !== '/') { - return nodeRequire( + return await nodeRequireOrImport( dep, mod.file, server.config.root, - !!server.config.resolve.preserveSymlinks + !!server.config.resolve.preserveSymlinks, + server, ) } dep = unwrapId(dep) @@ -180,9 +184,55 @@ function nodeRequire( root: string, preserveSymlinks: boolean ) { - const mod = require(resolve(id, importer, root, preserveSymlinks)) - const defaultExport = mod.__esModule ? mod.default : mod - // rollup-style default import interop for cjs + const mdl = require(resolve(id, importer, root, preserveSymlinks)); + console.log('NODE_REQUIRE', id) + return proxyESM(mdl); +} + +function nodeRequireOrImport( + id: string, + importer: string | null, + root: string, + preserveSymlinks: boolean, + server: ViteDevServer, +): Promise { + return new Promise((res, reject) => { + if (id.startsWith('node:')) { + return res(NATIVE_IMPORT(id.slice('node:'.length))) + } + try { + const mdl = nodeRequire(id, importer, root, preserveSymlinks); + return res(mdl); + } catch (e) { + const resolvedPath = resolve(id, importer, root, preserveSymlinks); + if (e instanceof SyntaxError && /export|import/.test(e.message)) { + server.config.logger.error( + `Failed to load "${id}"!\nESM format is not natively supported in "node@${process.version}".\nPlease use CommonJS or upgrade to an LTS version of node above "node@12.17.0".`, + ); + } else if (e.code === 'ERR_REQUIRE_ESM') { + console.log('NODE_IMPORT', resolvedPath); + const url = pathToFileURL(resolvedPath); + try { + return NATIVE_IMPORT(url.toString()).then( + (mdl: any) => { + const mod = proxyESM(mdl, true); + return res(mod); + }, + (err: any) => { + reject(err) + } + ) + } catch (e) { + reject(e); + } + } + } + }) +} + +// rollup-style default import interop for cjs +function proxyESM(mod: any, isESM = mod.__esModule) { + const defaultExport = isESM ? mod.default : mod return new Proxy(mod, { get(mod, prop) { if (prop === 'default') return defaultExport From 1f3d1c6987e12f4bba4d297bad55812229401d03 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 4 Oct 2021 17:51:08 -0500 Subject: [PATCH 02/21] revert esbuild version --- packages/vite/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/package.json b/packages/vite/package.json index 254fdc03239e86..5584ca977f8a55 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -47,7 +47,7 @@ }, "//": "READ CONTRIBUTING.md to understand what to put under deps vs. devDeps!", "dependencies": { - "esbuild": "^0.12.28", + "esbuild": "^0.13.2", "postcss": "^8.3.8", "resolve": "^1.20.0", "rollup": "^2.57.0" From f8ce107344276d7351ce766caa14dbb028627390 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 4 Oct 2021 21:03:01 -0500 Subject: [PATCH 03/21] chore: remove logs --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index c75edf138a4627..d1010bc6781121 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -185,7 +185,6 @@ function nodeRequire( preserveSymlinks: boolean ) { const mdl = require(resolve(id, importer, root, preserveSymlinks)); - console.log('NODE_REQUIRE', id) return proxyESM(mdl); } @@ -210,7 +209,6 @@ function nodeRequireOrImport( `Failed to load "${id}"!\nESM format is not natively supported in "node@${process.version}".\nPlease use CommonJS or upgrade to an LTS version of node above "node@12.17.0".`, ); } else if (e.code === 'ERR_REQUIRE_ESM') { - console.log('NODE_IMPORT', resolvedPath); const url = pathToFileURL(resolvedPath); try { return NATIVE_IMPORT(url.toString()).then( From 4e872a1ec470d75e7d6bbddd05bbbcac4c0a540e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 5 Oct 2021 14:30:18 -0500 Subject: [PATCH 04/21] refactor: apply feedback --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 74 +++++++------------ 1 file changed, 26 insertions(+), 48 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index d1010bc6781121..e4037001dc5af9 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -1,6 +1,7 @@ import fs from 'fs' import path from 'path' import { pathToFileURL } from 'url'; +import { builtinModules } from 'module'; import { ViteDevServer } from '..' import { cleanUrl, resolveFrom, unwrapId } from '../utils' import { rebindErrorStacktrace, ssrRewriteStacktrace } from './ssrStacktrace' @@ -12,8 +13,13 @@ import { ssrDynamicImportKey } from './ssrTransform' import { transformRequest } from '../server/transformRequest' -// @ts-ignore -import NATIVE_IMPORT from 'vite/native-import.js' + +const builtins = new Set(builtinModules); + +// This is a workaround since typescript compiles dynamic imports to `require`. +// Thankfully, when `typescript@4.5.0` lands it won't and this can be removed! +// See #5197 for notes +const nativeImport = eval('u => import(u)'); interface SSRContext { global: NodeJS.Global @@ -94,12 +100,10 @@ async function instantiateModule( const ssrImport = async (dep: string) => { if (dep[0] !== '.' && dep[0] !== '/') { - return await nodeRequireOrImport( + return await handleImport( dep, mod.file, - server.config.root, - !!server.config.resolve.preserveSymlinks, - server, + server.config ) } dep = unwrapId(dep) @@ -178,62 +182,36 @@ async function instantiateModule( return Object.freeze(ssrModule) } -function nodeRequire( +// In node@12+ we can use dynamic import to load CJS and ESM +// Caveat https://github.com/vitejs/vite/pull/5197#discussion_r722583544 +async function nodeImport( id: string, importer: string | null, - root: string, - preserveSymlinks: boolean + config: ViteDevServer['config'], ) { - const mdl = require(resolve(id, importer, root, preserveSymlinks)); - return proxyESM(mdl); + const url = pathToFileURL(resolve(id, importer, config.root, !!config.resolve.preserveSymlinks)) + const mod = await nativeImport(url); + return proxyESM(mod); } -function nodeRequireOrImport( +async function handleImport( id: string, importer: string | null, - root: string, - preserveSymlinks: boolean, - server: ViteDevServer, + config: ViteDevServer['config'], ): Promise { - return new Promise((res, reject) => { - if (id.startsWith('node:')) { - return res(NATIVE_IMPORT(id.slice('node:'.length))) - } - try { - const mdl = nodeRequire(id, importer, root, preserveSymlinks); - return res(mdl); - } catch (e) { - const resolvedPath = resolve(id, importer, root, preserveSymlinks); - if (e instanceof SyntaxError && /export|import/.test(e.message)) { - server.config.logger.error( - `Failed to load "${id}"!\nESM format is not natively supported in "node@${process.version}".\nPlease use CommonJS or upgrade to an LTS version of node above "node@12.17.0".`, - ); - } else if (e.code === 'ERR_REQUIRE_ESM') { - const url = pathToFileURL(resolvedPath); - try { - return NATIVE_IMPORT(url.toString()).then( - (mdl: any) => { - const mod = proxyESM(mdl, true); - return res(mod); - }, - (err: any) => { - reject(err) - } - ) - } catch (e) { - reject(e); - } - } + // `resolve` doesn't handle `node:` builtins, so handle them directly + if (id.startsWith('node:') || builtins.has(id)) { + return nativeImport(id); } - }) + return nodeImport(id, importer, config); } // rollup-style default import interop for cjs -function proxyESM(mod: any, isESM = mod.__esModule) { - const defaultExport = isESM ? mod.default : mod +function proxyESM(mod: any) { + const isESM = !!(mod.__esModule ?? mod.default); return new Proxy(mod, { get(mod, prop) { - if (prop === 'default') return defaultExport + if (prop === 'default') return isESM ? mod.default : mod; return mod[prop] } }) From 44872c32919041c681eb3f638953fdc809e83355 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 5 Oct 2021 14:31:18 -0500 Subject: [PATCH 05/21] refactor: remove unused file --- packages/vite/native-import.js | 9 --------- packages/vite/package.json | 3 +-- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 packages/vite/native-import.js diff --git a/packages/vite/native-import.js b/packages/vite/native-import.js deleted file mode 100644 index 4f86167a2c35eb..00000000000000 --- a/packages/vite/native-import.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Typescript automatically compiles dynamic `import()` to `resolve()` - * so this needs to be a raw JS file until `typescript@4.5.0` lands with - * `module: "node12"` support. - * - * @param {string} fileUrl - * @returns {any} - */ -module.exports = (fileUrl) => import(fileUrl); diff --git a/packages/vite/package.json b/packages/vite/package.json index 5584ca977f8a55..e242fb8f515fb2 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -14,8 +14,7 @@ "dist", "client.d.ts", "src/client", - "types", - "native-import.js" + "types" ], "engines": { "node": ">=12.2.0" From 5621c2adce7f35f7c313d2d7fc3a52c71c6f885b Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 5 Oct 2021 14:32:51 -0500 Subject: [PATCH 06/21] refactor: remove unused setting from rollup config --- packages/vite/rollup.config.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/vite/rollup.config.js b/packages/vite/rollup.config.js index 4688a91864dae6..783c3c1d993de8 100644 --- a/packages/vite/rollup.config.js +++ b/packages/vite/rollup.config.js @@ -107,7 +107,6 @@ const createNodeConfig = (isProduction) => { }, external: [ 'fsevents', - 'vite/native-import.js', ...Object.keys(require('./package.json').dependencies), ...(isProduction ? [] From 2d5ad2a6514c69df0e87dd10417febc2f34ce2e3 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 5 Oct 2021 17:55:16 -0500 Subject: [PATCH 07/21] refactor(ssr): combine import handlers Co-authored-by: Alec Larson <1925840+aleclarson@users.noreply.github.com> --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 41 +++++++++---------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index e4037001dc5af9..bba1fd1874f22b 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -3,7 +3,7 @@ import path from 'path' import { pathToFileURL } from 'url'; import { builtinModules } from 'module'; import { ViteDevServer } from '..' -import { cleanUrl, resolveFrom, unwrapId } from '../utils' +import { cleanUrl, isObject, resolveFrom, unwrapId } from '../utils' import { rebindErrorStacktrace, ssrRewriteStacktrace } from './ssrStacktrace' import { ssrExportAllKey, @@ -100,7 +100,7 @@ async function instantiateModule( const ssrImport = async (dep: string) => { if (dep[0] !== '.' && dep[0] !== '/') { - return await handleImport( + return await nodeImport( dep, mod.file, server.config @@ -183,36 +183,35 @@ async function instantiateModule( } // In node@12+ we can use dynamic import to load CJS and ESM -// Caveat https://github.com/vitejs/vite/pull/5197#discussion_r722583544 async function nodeImport( id: string, importer: string | null, config: ViteDevServer['config'], ) { - const url = pathToFileURL(resolve(id, importer, config.root, !!config.resolve.preserveSymlinks)) + let url: string + // `resolve` doesn't handle `node:` builtins, so handle them directly + if (id.startsWith('node:') || builtins.has(id)) { + url = id + } else { + url = pathToFileURL(resolve(id, importer, config.root, !!config.resolve.preserveSymlinks)).toString() + } const mod = await nativeImport(url); - return proxyESM(mod); -} - -async function handleImport( - id: string, - importer: string | null, - config: ViteDevServer['config'], -): Promise { - // `resolve` doesn't handle `node:` builtins, so handle them directly - if (id.startsWith('node:') || builtins.has(id)) { - return nativeImport(id); - } - return nodeImport(id, importer, config); + return proxyESM(id, mod); } // rollup-style default import interop for cjs -function proxyESM(mod: any) { - const isESM = !!(mod.__esModule ?? mod.default); +function proxyESM(id: string, mod: any) { return new Proxy(mod, { get(mod, prop) { - if (prop === 'default') return isESM ? mod.default : mod; - return mod[prop] + if (prop in mod) { + return mod[prop]; + } + // commonjs interop: module whose exports are not statically analyzable + if (isObject(mod.default) && prop in mod.default) { + return mod.default[prop] + } + // throw an error like ESM import does + throw new SyntaxError(`The requested module '${id}' does not provide an export named '${prop.toString()}'`) } }) } From c488335d858b779e275d57a6059f79a5efab8e54 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 6 Oct 2021 10:47:34 -0500 Subject: [PATCH 08/21] fix(ssr): do not await nodeImport --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index bba1fd1874f22b..b28d5ee5efbab8 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -100,7 +100,7 @@ async function instantiateModule( const ssrImport = async (dep: string) => { if (dep[0] !== '.' && dep[0] !== '/') { - return await nodeImport( + return nodeImport( dep, mod.file, server.config From 8c36931895d3f0ddd440af8abb812b03617abc52 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Wed, 6 Oct 2021 10:48:52 -0500 Subject: [PATCH 09/21] test(ssr): enable --experimental-vm-modules --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 10811c42e2c02f..53f4c8cdee3e96 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ "format": "prettier --write .", "lint": "eslint packages/*/{src,types}/**", "test": "run-s test-serve test-build", - "test-serve": "jest", + "test-serve": "cross-env NODE_OPTIONS=--experimental-vm-modules jest", "debug-serve": "cross-env VITE_DEBUG_SERVE=1 node --inspect-brk ./node_modules/.bin/jest", - "test-build": "cross-env VITE_TEST_BUILD=1 jest", + "test-build": "cross-env NODE_OPTIONS=--experimental-vm-modules VITE_TEST_BUILD=1 jest", "debug-build": "cross-env VITE_TEST_BUILD=1 VITE_PRESERVE_BUILD_ARTIFACTS=1 node --inspect-brk ./node_modules/.bin/jest", "docs": "vitepress dev docs", "build-docs": "vitepress build docs", From 71e01c3c0500bdf370549cf99c45095f8d694712 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 6 Oct 2021 18:02:00 -0400 Subject: [PATCH 10/21] feat: remove commonjs check in `resolveSSRExternal` --- packages/vite/src/node/ssr/ssrExternal.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 2950846b8c8ff5..3b56d0779b985b 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -1,4 +1,3 @@ -import fs from 'fs' import path from 'path' import { tryNodeResolve, InternalResolveOptions } from '../plugins/resolve' import { @@ -17,9 +16,6 @@ const debug = createDebugger('vite:ssr-external') /** * Heuristics for determining whether a dependency should be externalized for * server-side rendering. - * - * TODO right now externals are imported using require(), we probably need to - * rework this when more libraries ship native ESM distributions for Node. */ export function resolveSSRExternal( config: ResolvedConfig, @@ -97,11 +93,7 @@ export function resolveSSRExternal( // entry is not js, cannot externalize continue } - // check if the entry is cjs - const content = fs.readFileSync(entry, 'utf-8') - if (/\bmodule\.exports\b|\bexports[.\[]|\brequire\s*\(/.test(content)) { - ssrExternals.add(id) - } + ssrExternals.add(id) } } From 77e3f9bb005f7b7bb50066d776951660339cfb58 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 6 Oct 2021 18:03:56 -0400 Subject: [PATCH 11/21] chore: use `isBuiltin` util --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index b28d5ee5efbab8..d6c8fdbdf61953 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -1,9 +1,8 @@ import fs from 'fs' import path from 'path' -import { pathToFileURL } from 'url'; -import { builtinModules } from 'module'; +import { pathToFileURL } from 'url' import { ViteDevServer } from '..' -import { cleanUrl, isObject, resolveFrom, unwrapId } from '../utils' +import { cleanUrl, isBuiltin, isObject, resolveFrom, unwrapId } from '../utils' import { rebindErrorStacktrace, ssrRewriteStacktrace } from './ssrStacktrace' import { ssrExportAllKey, @@ -14,8 +13,6 @@ import { } from './ssrTransform' import { transformRequest } from '../server/transformRequest' -const builtins = new Set(builtinModules); - // This is a workaround since typescript compiles dynamic imports to `require`. // Thankfully, when `typescript@4.5.0` lands it won't and this can be removed! // See #5197 for notes @@ -190,7 +187,7 @@ async function nodeImport( ) { let url: string // `resolve` doesn't handle `node:` builtins, so handle them directly - if (id.startsWith('node:') || builtins.has(id)) { + if (id.startsWith('node:') || isBuiltin(id)) { url = id } else { url = pathToFileURL(resolve(id, importer, config.root, !!config.resolve.preserveSymlinks)).toString() From b5d24d3f65222fc761ea6b54c7b90631d50cef61 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Wed, 6 Oct 2021 18:04:27 -0400 Subject: [PATCH 12/21] chore: run prettier --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index d6c8fdbdf61953..d2aa858afe71c1 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -16,7 +16,7 @@ import { transformRequest } from '../server/transformRequest' // This is a workaround since typescript compiles dynamic imports to `require`. // Thankfully, when `typescript@4.5.0` lands it won't and this can be removed! // See #5197 for notes -const nativeImport = eval('u => import(u)'); +const nativeImport = eval('u => import(u)') interface SSRContext { global: NodeJS.Global @@ -97,11 +97,7 @@ async function instantiateModule( const ssrImport = async (dep: string) => { if (dep[0] !== '.' && dep[0] !== '/') { - return nodeImport( - dep, - mod.file, - server.config - ) + return nodeImport(dep, mod.file, server.config) } dep = unwrapId(dep) if (!isCircular(dep) && !pendingImports.get(dep)?.some(isCircular)) { @@ -183,17 +179,19 @@ async function instantiateModule( async function nodeImport( id: string, importer: string | null, - config: ViteDevServer['config'], + config: ViteDevServer['config'] ) { let url: string // `resolve` doesn't handle `node:` builtins, so handle them directly if (id.startsWith('node:') || isBuiltin(id)) { url = id } else { - url = pathToFileURL(resolve(id, importer, config.root, !!config.resolve.preserveSymlinks)).toString() + url = pathToFileURL( + resolve(id, importer, config.root, !!config.resolve.preserveSymlinks) + ).toString() } - const mod = await nativeImport(url); - return proxyESM(id, mod); + const mod = await nativeImport(url) + return proxyESM(id, mod) } // rollup-style default import interop for cjs @@ -201,14 +199,16 @@ function proxyESM(id: string, mod: any) { return new Proxy(mod, { get(mod, prop) { if (prop in mod) { - return mod[prop]; + return mod[prop] } // commonjs interop: module whose exports are not statically analyzable if (isObject(mod.default) && prop in mod.default) { return mod.default[prop] } // throw an error like ESM import does - throw new SyntaxError(`The requested module '${id}' does not provide an export named '${prop.toString()}'`) + throw new SyntaxError( + `The requested module '${id}' does not provide an export named '${prop.toString()}'` + ) } }) } From 5bcdcba986ae2745e7ff81307a9bef44c13f48cc Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Thu, 7 Oct 2021 11:32:32 -0500 Subject: [PATCH 13/21] refactor(ssr): use `dynamicImport` util from #5213 --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index d2aa858afe71c1..8442500b23db3a 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import { pathToFileURL } from 'url' import { ViteDevServer } from '..' -import { cleanUrl, isBuiltin, isObject, resolveFrom, unwrapId } from '../utils' +import { dynamicImport, cleanUrl, isBuiltin, isObject, resolveFrom, unwrapId } from '../utils' import { rebindErrorStacktrace, ssrRewriteStacktrace } from './ssrStacktrace' import { ssrExportAllKey, @@ -13,11 +13,6 @@ import { } from './ssrTransform' import { transformRequest } from '../server/transformRequest' -// This is a workaround since typescript compiles dynamic imports to `require`. -// Thankfully, when `typescript@4.5.0` lands it won't and this can be removed! -// See #5197 for notes -const nativeImport = eval('u => import(u)') - interface SSRContext { global: NodeJS.Global } @@ -190,7 +185,7 @@ async function nodeImport( resolve(id, importer, config.root, !!config.resolve.preserveSymlinks) ).toString() } - const mod = await nativeImport(url) + const mod = await dynamicImport(url) return proxyESM(id, mod) } From f35add2fd8f50f2c824a2a1ef30d04537a94ac6e Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 11 Oct 2021 14:29:45 -0500 Subject: [PATCH 14/21] test(ssr): skip ssr cases in Jest --- jest.config.ts | 5 +++++ package.json | 4 ++-- pnpm-lock.yaml | 39 ++++++++++++++++++++++----------------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index e17a90841914e7..dd3b1751a9c6b2 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -11,6 +11,11 @@ const config: Config.InitialOptions = { globalTeardown: './scripts/jestGlobalTeardown.js', testEnvironment: './scripts/jestEnv.js', setupFilesAfterEnv: ['./scripts/jestPerTestSetup.ts'], + testPathIgnorePatterns : [ + "/packages/vite/src/node/ssr", + "/packages/playground/ssr-react", + "/packages/playground/ssr-vue", + ], watchPathIgnorePatterns: ['/packages/temp'], modulePathIgnorePatterns: ['/packages/temp'], moduleNameMapper: { diff --git a/package.json b/package.json index 53f4c8cdee3e96..10811c42e2c02f 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,9 @@ "format": "prettier --write .", "lint": "eslint packages/*/{src,types}/**", "test": "run-s test-serve test-build", - "test-serve": "cross-env NODE_OPTIONS=--experimental-vm-modules jest", + "test-serve": "jest", "debug-serve": "cross-env VITE_DEBUG_SERVE=1 node --inspect-brk ./node_modules/.bin/jest", - "test-build": "cross-env NODE_OPTIONS=--experimental-vm-modules VITE_TEST_BUILD=1 jest", + "test-build": "cross-env VITE_TEST_BUILD=1 jest", "debug-build": "cross-env VITE_TEST_BUILD=1 VITE_PRESERVE_BUILD_ARTIFACTS=1 node --inspect-brk ./node_modules/.bin/jest", "docs": "vitepress dev docs", "build-docs": "vitepress build docs", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27f51b80e6bded..9a2be60fe5f524 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -118,11 +118,14 @@ importers: specifiers: tailwindcss: ^2.2.4 dependencies: - tailwindcss: 2.2.15_ts-node@10.2.1 + tailwindcss: 2.2.15 packages/playground/cli: specifiers: {} + packages/playground/cli-module: + specifiers: {} + packages/playground/css: specifiers: css-dep: link:./css-dep @@ -499,7 +502,7 @@ importers: vue-router: ^4.0.0 dependencies: autoprefixer: 10.3.5 - tailwindcss: 2.2.15_e444c4a095b9e9ba5c645754953637f2 + tailwindcss: 2.2.15_autoprefixer@10.3.5 vue: 3.2.16 vue-router: 4.0.11_vue@3.2.16 devDependencies: @@ -621,6 +624,12 @@ importers: '@vue/babel-plugin-jsx': 1.1.0_@babel+core@7.15.5 hash-sum: 2.0.0 + packages/temp: + specifiers: + css-color-names: ^1.0.1 + devDependencies: + css-color-names: 1.0.1 + packages/vite: specifiers: '@ampproject/remapping': ^1.0.1 @@ -706,7 +715,7 @@ importers: '@rollup/plugin-dynamic-import-vars': 1.4.0_rollup@2.57.0 '@rollup/plugin-json': 4.1.0_rollup@2.57.0 '@rollup/plugin-node-resolve': 13.0.5_rollup@2.57.0 - '@rollup/plugin-typescript': 8.2.5_283b0408a190511f1240c12469d64cd5 + '@rollup/plugin-typescript': 8.2.5_rollup@2.57.0+tslib@2.3.1 '@rollup/pluginutils': 4.1.1 '@types/convert-source-map': 1.5.2 '@types/debug': 4.1.7 @@ -750,7 +759,7 @@ importers: open: 8.2.1 periscopic: 2.0.3 postcss-import: 14.0.2_postcss@8.3.8 - postcss-load-config: 3.1.0_ts-node@10.2.1 + postcss-load-config: 3.1.0 postcss-modules: 4.2.2_postcss@8.3.8 resolve.exports: 1.0.2 rollup-plugin-license: 2.5.0_rollup@2.57.0 @@ -760,7 +769,7 @@ importers: source-map-support: 0.5.20 strip-ansi: 6.0.0 terser: 5.9.0 - tsconfck: 1.0.0_typescript@4.4.3 + tsconfck: 1.0.0 tslib: 2.3.1 types: link:types ws: 7.5.5 @@ -1871,7 +1880,7 @@ packages: rollup: 2.57.0 dev: true - /@rollup/plugin-typescript/8.2.5_283b0408a190511f1240c12469d64cd5: + /@rollup/plugin-typescript/8.2.5_rollup@2.57.0+tslib@2.3.1: resolution: {integrity: sha512-QL/LvDol/PAGB2O0S7/+q2HpSUNodpw7z6nGn9BfoVCPOZ0r4EALrojFU29Bkoi2Hr2jgTocTejJ5GGWZfOxbQ==} engines: {node: '>=8.0.0'} peerDependencies: @@ -1883,7 +1892,6 @@ packages: resolve: 1.20.0 rollup: 2.57.0 tslib: 2.3.1 - typescript: 4.4.3 dev: true /@rollup/pluginutils/3.1.0_rollup@2.57.0: @@ -6871,7 +6879,7 @@ packages: postcss: 8.3.7 dev: false - /postcss-load-config/3.1.0_ts-node@10.2.1: + /postcss-load-config/3.1.0: resolution: {integrity: sha512-ipM8Ds01ZUophjDTQYSVP70slFSYg3T0/zyfII5vzhN6V57YSxMgG5syXuwi5VtS8wSf3iL30v0uBdoIVx4Q0g==} engines: {node: '>= 10'} peerDependencies: @@ -6882,7 +6890,6 @@ packages: dependencies: import-cwd: 3.0.0 lilconfig: 2.0.3 - ts-node: 10.2.1_3b624d72c50530188ff09826d1b48ebf yaml: 1.10.2 /postcss-modules-extract-imports/3.0.0_postcss@8.3.8: @@ -8086,7 +8093,7 @@ packages: strip-ansi: 6.0.0 dev: true - /tailwindcss/2.2.15_e444c4a095b9e9ba5c645754953637f2: + /tailwindcss/2.2.15: resolution: {integrity: sha512-WgV41xTMbnSoTNMNnJvShQZ+8GmY86DmXTrCgnsveNZJdlybfwCItV8kAqjYmU49YiFr+ofzmT1JlAKajBZboQ==} engines: {node: '>=12.13.0'} hasBin: true @@ -8095,7 +8102,6 @@ packages: postcss: ^8.0.9 dependencies: arg: 5.0.1 - autoprefixer: 10.3.5 bytes: 3.1.0 chalk: 4.1.2 chokidar: 3.5.2 @@ -8117,7 +8123,7 @@ packages: normalize-path: 3.0.0 object-hash: 2.2.0 postcss-js: 3.0.3 - postcss-load-config: 3.1.0_ts-node@10.2.1 + postcss-load-config: 3.1.0 postcss-nested: 5.0.6 postcss-selector-parser: 6.0.6 postcss-value-parser: 4.1.0 @@ -8131,7 +8137,7 @@ packages: - ts-node dev: false - /tailwindcss/2.2.15_ts-node@10.2.1: + /tailwindcss/2.2.15_autoprefixer@10.3.5: resolution: {integrity: sha512-WgV41xTMbnSoTNMNnJvShQZ+8GmY86DmXTrCgnsveNZJdlybfwCItV8kAqjYmU49YiFr+ofzmT1JlAKajBZboQ==} engines: {node: '>=12.13.0'} hasBin: true @@ -8140,6 +8146,7 @@ packages: postcss: ^8.0.9 dependencies: arg: 5.0.1 + autoprefixer: 10.3.5 bytes: 3.1.0 chalk: 4.1.2 chokidar: 3.5.2 @@ -8161,7 +8168,7 @@ packages: normalize-path: 3.0.0 object-hash: 2.2.0 postcss-js: 3.0.3 - postcss-load-config: 3.1.0_ts-node@10.2.1 + postcss-load-config: 3.1.0 postcss-nested: 5.0.6 postcss-selector-parser: 6.0.6 postcss-value-parser: 4.1.0 @@ -8389,7 +8396,7 @@ packages: yn: 3.1.1 dev: true - /tsconfck/1.0.0_typescript@4.4.3: + /tsconfck/1.0.0: resolution: {integrity: sha512-9+flUmolxrnyKAYbNzXZX1UPHUuZsVqM42ioZ1H55r0I+UfJgYui4b7zUxhdJ4o5emEuZo89Cx4TiWG/Dd0c/A==} engines: {node: ^12.20 || ^14.13.1 || >= 16, pnpm: '>=6.7.0'} hasBin: true @@ -8398,8 +8405,6 @@ packages: peerDependenciesMeta: typescript: optional: true - dependencies: - typescript: 4.4.3 dev: true /tslib/1.14.1: From 5a260d403b7ba1e277e0ccbea1b9650026da4da6 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 11 Oct 2021 14:38:24 -0500 Subject: [PATCH 15/21] test(ssr): do not skip node/ssr in Jest --- jest.config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/jest.config.ts b/jest.config.ts index dd3b1751a9c6b2..fba2a67bd12952 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -12,7 +12,6 @@ const config: Config.InitialOptions = { testEnvironment: './scripts/jestEnv.js', setupFilesAfterEnv: ['./scripts/jestPerTestSetup.ts'], testPathIgnorePatterns : [ - "/packages/vite/src/node/ssr", "/packages/playground/ssr-react", "/packages/playground/ssr-vue", ], From 489658ee5c62bd9ea54bb043697dc42a53178ce2 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 11 Oct 2021 15:35:16 -0500 Subject: [PATCH 16/21] test(ssr): skip optimize-deps in Jest --- jest.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/jest.config.ts b/jest.config.ts index fba2a67bd12952..1df801498438eb 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -12,6 +12,7 @@ const config: Config.InitialOptions = { testEnvironment: './scripts/jestEnv.js', setupFilesAfterEnv: ['./scripts/jestPerTestSetup.ts'], testPathIgnorePatterns : [ + "/packages/playground/optimize-deps", "/packages/playground/ssr-react", "/packages/playground/ssr-vue", ], From 9d3045e200a49a9028291d624a5b18c1830975aa Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 11 Oct 2021 17:18:18 -0500 Subject: [PATCH 17/21] test: enable ssr tests, fallback to require --- jest.config.ts | 5 ----- packages/vite/src/node/utils.ts | 6 +++++- pnpm-lock.yaml | 6 ------ 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index 1df801498438eb..e17a90841914e7 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -11,11 +11,6 @@ const config: Config.InitialOptions = { globalTeardown: './scripts/jestGlobalTeardown.js', testEnvironment: './scripts/jestEnv.js', setupFilesAfterEnv: ['./scripts/jestPerTestSetup.ts'], - testPathIgnorePatterns : [ - "/packages/playground/optimize-deps", - "/packages/playground/ssr-react", - "/packages/playground/ssr-vue", - ], watchPathIgnorePatterns: ['/packages/temp'], modulePathIgnorePatterns: ['/packages/temp'], moduleNameMapper: { diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index b23c774058572b..6128c979cd71ec 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -572,6 +572,10 @@ export const singlelineCommentsRE = /\/\/.*/g /** * Dynamically import files. It will make sure it's not being compiled away by TS/Rollup. * + * As a temporary workaround for Jest's lack of stable ESM support, we fallback to require + * if we're in a Jest environment. + * See https://github.com/vitejs/vite/pull/5197#issuecomment-938054077 + * * @param file File path to import. */ -export const dynamicImport = new Function('file', 'return import(file)') +export const dynamicImport = typeof jest === 'undefined' ? new Function('file', 'return import(file)') : new Function('file', 'return require(file)') diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9a2be60fe5f524..390378fe13c025 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -624,12 +624,6 @@ importers: '@vue/babel-plugin-jsx': 1.1.0_@babel+core@7.15.5 hash-sum: 2.0.0 - packages/temp: - specifiers: - css-color-names: ^1.0.1 - devDependencies: - css-color-names: 1.0.1 - packages/vite: specifiers: '@ampproject/remapping': ^1.0.1 From 9c0cb0ef772e62b95510eb150338032bbc2effcf Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 11 Oct 2021 17:30:12 -0500 Subject: [PATCH 18/21] test: fix ssr require? --- packages/vite/src/node/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 6128c979cd71ec..844bf874580ea0 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -578,4 +578,4 @@ export const singlelineCommentsRE = /\/\/.*/g * * @param file File path to import. */ -export const dynamicImport = typeof jest === 'undefined' ? new Function('file', 'return import(file)') : new Function('file', 'return require(file)') +export const dynamicImport = typeof jest === 'undefined' ? new Function('file', 'return import(file)') : new Function('require', 'file', 'return require(file)').bind(null, require) From 756f77c307f56bdc0d1948d2d8fba41100e01e11 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 12 Oct 2021 10:01:26 -0500 Subject: [PATCH 19/21] test: update dynamicImport util --- packages/vite/src/node/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 844bf874580ea0..beae26fca4da26 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -578,4 +578,4 @@ export const singlelineCommentsRE = /\/\/.*/g * * @param file File path to import. */ -export const dynamicImport = typeof jest === 'undefined' ? new Function('file', 'return import(file)') : new Function('require', 'file', 'return require(file)').bind(null, require) +export const dynamicImport = typeof jest === 'undefined' ? new Function('file', 'return import(file)') : require; From 2c70d997538740319279945cb567383dd193d47a Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Fri, 15 Oct 2021 13:00:48 -0400 Subject: [PATCH 20/21] Get tests passing --- packages/vite/src/node/ssr/ssrExternal.ts | 11 ++++++++- packages/vite/src/node/ssr/ssrModuleLoader.ts | 23 +++++++------------ packages/vite/src/node/utils.ts | 3 ++- 3 files changed, 20 insertions(+), 17 deletions(-) diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 3b56d0779b985b..a0bfd782b8a251 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -1,3 +1,4 @@ +import fs from 'fs' import path from 'path' import { tryNodeResolve, InternalResolveOptions } from '../plugins/resolve' import { @@ -93,7 +94,15 @@ export function resolveSSRExternal( // entry is not js, cannot externalize continue } - ssrExternals.add(id) + if (pkg.type === "module" || entry.endsWith('.mjs')) { + ssrExternals.add(id) + continue + } + // check if the entry is cjs + const content = fs.readFileSync(entry, 'utf-8') + if (/\bmodule\.exports\b|\bexports[.\[]|\brequire\s*\(/.test(content)) { + ssrExternals.add(id) + } } } diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index 2ce2225717f4e6..f402e86cd210ec 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -2,7 +2,7 @@ import fs from 'fs' import path from 'path' import { pathToFileURL } from 'url' import { ViteDevServer } from '..' -import { dynamicImport, cleanUrl, isBuiltin, isObject, resolveFrom, unwrapId } from '../utils' +import { dynamicImport, cleanUrl, isBuiltin, resolveFrom, unwrapId, usingDynamicImport } from '../utils' import { rebindErrorStacktrace, ssrRewriteStacktrace } from './ssrStacktrace' import { ssrExportAllKey, @@ -184,9 +184,10 @@ async function nodeImport( if (id.startsWith('node:') || isBuiltin(id)) { url = id } else { - url = pathToFileURL( - resolve(id, importer, config.root, !!config.resolve.preserveSymlinks) - ).toString() + url = resolve(id, importer, config.root, !!config.resolve.preserveSymlinks) + if (usingDynamicImport) { + url = pathToFileURL(url).toString() + } } const mod = await dynamicImport(url) return proxyESM(id, mod) @@ -194,19 +195,11 @@ async function nodeImport( // rollup-style default import interop for cjs function proxyESM(id: string, mod: any) { + const defaultExport = mod.__esModule ? mod.default : mod return new Proxy(mod, { get(mod, prop) { - if (prop in mod) { - return mod[prop] - } - // commonjs interop: module whose exports are not statically analyzable - if (isObject(mod.default) && prop in mod.default) { - return mod.default[prop] - } - // throw an error like ESM import does - throw new SyntaxError( - `The requested module '${id}' does not provide an export named '${prop.toString()}'` - ) + if (prop === 'default') return defaultExport + return mod[prop] } }) } diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index beae26fca4da26..c62e29da900e05 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -569,6 +569,7 @@ export function toUpperCaseDriveLetter(pathName: string): string { export const multilineCommentsRE = /\/\*(.|[\r\n])*?\*\//gm export const singlelineCommentsRE = /\/\/.*/g +export const usingDynamicImport = typeof jest === 'undefined'; /** * Dynamically import files. It will make sure it's not being compiled away by TS/Rollup. * @@ -578,4 +579,4 @@ export const singlelineCommentsRE = /\/\/.*/g * * @param file File path to import. */ -export const dynamicImport = typeof jest === 'undefined' ? new Function('file', 'return import(file)') : require; +export const dynamicImport = usingDynamicImport ? new Function('file', 'return import(file)') : require; From 7f827d3b69f47363af59656a8b41d85cf43ee83f Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Mon, 18 Oct 2021 14:51:44 -0400 Subject: [PATCH 21/21] Prefer default if it exists --- packages/vite/src/node/ssr/ssrModuleLoader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/ssr/ssrModuleLoader.ts b/packages/vite/src/node/ssr/ssrModuleLoader.ts index f402e86cd210ec..2be123db077229 100644 --- a/packages/vite/src/node/ssr/ssrModuleLoader.ts +++ b/packages/vite/src/node/ssr/ssrModuleLoader.ts @@ -195,7 +195,7 @@ async function nodeImport( // rollup-style default import interop for cjs function proxyESM(id: string, mod: any) { - const defaultExport = mod.__esModule ? mod.default : mod + const defaultExport = mod.__esModule ? mod.default : mod.default ? mod.default : mod return new Proxy(mod, { get(mod, prop) { if (prop === 'default') return defaultExport