Skip to content

Commit

Permalink
fix(ssr): skip esm proxy guard for namespace imports (#14988)
Browse files Browse the repository at this point in the history
  • Loading branch information
bluwy authored Nov 15, 2023
1 parent bedfcfa commit 82a5b11
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 50 deletions.
68 changes: 34 additions & 34 deletions packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test('default import', async () => {
expect(
await ssrTransformSimpleCode(`import foo from 'vue';console.log(foo.bar)`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\");
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"default\\"]});
console.log(__vite_ssr_import_0__.default.bar)"
`)
})
Expand All @@ -22,7 +22,7 @@ test('named import', async () => {
`import { ref } from 'vue';function foo() { return ref(0) }`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"ref\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"ref\\"]});
function foo() { return __vite_ssr_import_0__.ref(0) }"
`)
})
Expand Down Expand Up @@ -77,7 +77,7 @@ test('export named from', async () => {
expect(
await ssrTransformSimpleCode(`export { ref, computed as c } from 'vue'`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"ref\\",\\"computed\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"ref\\",\\"computed\\"]});
Object.defineProperty(__vite_ssr_exports__, \\"ref\\", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.ref }});
Object.defineProperty(__vite_ssr_exports__, \\"c\\", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.computed }});"
Expand All @@ -90,7 +90,7 @@ test('named exports of imported binding', async () => {
`import {createApp} from 'vue';export {createApp}`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"createApp\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"createApp\\"]});
Object.defineProperty(__vite_ssr_exports__, \\"createApp\\", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.createApp }});"
`)
Expand Down Expand Up @@ -132,7 +132,7 @@ test('export then import minified', async () => {
`export * from 'vue';import {createApp} from 'vue';`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"createApp\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"createApp\\"]});
const __vite_ssr_import_1__ = await __vite_ssr_import__(\\"vue\\");
__vite_ssr_exportAll__(__vite_ssr_import_1__);
"
Expand All @@ -145,7 +145,7 @@ test('hoist import to top', async () => {
`path.resolve('server.js');import path from 'node:path';`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"node:path\\");
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"node:path\\", {\\"importedNames\\":[\\"default\\"]});
__vite_ssr_import_0__.default.resolve('server.js');"
`)
})
Expand Down Expand Up @@ -173,7 +173,7 @@ test('do not rewrite method definition', async () => {
`import { fn } from 'vue';class A { fn() { fn() } }`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"fn\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"fn\\"]});
class A { fn() { __vite_ssr_import_0__.fn() } }"
`)
expect(result?.deps).toEqual(['vue'])
Expand All @@ -184,7 +184,7 @@ test('do not rewrite when variable is in scope', async () => {
`import { fn } from 'vue';function A(){ const fn = () => {}; return { fn }; }`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"fn\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"fn\\"]});
function A(){ const fn = () => {}; return { fn }; }"
`)
expect(result?.deps).toEqual(['vue'])
Expand All @@ -196,7 +196,7 @@ test('do not rewrite when variable is in scope with object destructuring', async
`import { fn } from 'vue';function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"fn\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"fn\\"]});
function A(){ let {fn, test} = {fn: 'foo', test: 'bar'}; return { fn }; }"
`)
expect(result?.deps).toEqual(['vue'])
Expand All @@ -208,7 +208,7 @@ test('do not rewrite when variable is in scope with array destructuring', async
`import { fn } from 'vue';function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"fn\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"fn\\"]});
function A(){ let [fn, test] = ['foo', 'bar']; return { fn }; }"
`)
expect(result?.deps).toEqual(['vue'])
Expand All @@ -220,7 +220,7 @@ test('rewrite variable in string interpolation in function nested arguments', as
`import { fn } from 'vue';function A({foo = \`test\${fn}\`} = {}){ return {}; }`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"fn\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"fn\\"]});
function A({foo = \`test\${__vite_ssr_import_0__.fn}\`} = {}){ return {}; }"
`)
expect(result?.deps).toEqual(['vue'])
Expand All @@ -232,7 +232,7 @@ test('rewrite variables in default value of destructuring params', async () => {
`import { fn } from 'vue';function A({foo = fn}){ return {}; }`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"fn\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"fn\\"]});
function A({foo = __vite_ssr_import_0__.fn}){ return {}; }"
`)
expect(result?.deps).toEqual(['vue'])
Expand All @@ -243,7 +243,7 @@ test('do not rewrite when function declaration is in scope', async () => {
`import { fn } from 'vue';function A(){ function fn() {}; return { fn }; }`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"fn\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"fn\\"]});
function A(){ function fn() {}; return { fn }; }"
`)
expect(result?.deps).toEqual(['vue'])
Expand All @@ -254,7 +254,7 @@ test('do not rewrite catch clause', async () => {
`import {error} from './dependency';try {} catch(error) {}`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\", {\\"namedImportSpecifiers\\":[\\"error\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\", {\\"importedNames\\":[\\"error\\"]});
try {} catch(error) {}"
`)
expect(result?.deps).toEqual(['./dependency'])
Expand All @@ -267,7 +267,7 @@ test('should declare variable for imported super class', async () => {
`import { Foo } from './dependency';` + `class A extends Foo {}`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\", {\\"namedImportSpecifiers\\":[\\"Foo\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\", {\\"importedNames\\":[\\"Foo\\"]});
const Foo = __vite_ssr_import_0__.Foo;
class A extends Foo {}"
`)
Expand All @@ -281,7 +281,7 @@ test('should declare variable for imported super class', async () => {
`export class B extends Foo {}`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\", {\\"namedImportSpecifiers\\":[\\"Foo\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./dependency\\", {\\"importedNames\\":[\\"Foo\\"]});
const Foo = __vite_ssr_import_0__.Foo;
class A extends Foo {}
class B extends Foo {}
Expand Down Expand Up @@ -354,7 +354,7 @@ test('overwrite bindings', async () => {
`function g() { const f = () => { const inject = true }; console.log(inject) }\n`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"inject\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"inject\\"]});
const a = { inject: __vite_ssr_import_0__.inject }
const b = { test: __vite_ssr_import_0__.inject }
function c() { const { test: inject } = { test: true }; console.log(inject) }
Expand Down Expand Up @@ -383,7 +383,7 @@ function c({ _ = bar() + foo() }) {}
`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"namedImportSpecifiers\\":[\\"foo\\",\\"bar\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"importedNames\\":[\\"foo\\",\\"bar\\"]});
const a = ({ _ = __vite_ssr_import_0__.foo() }) => {}
Expand All @@ -405,7 +405,7 @@ const a = () => {
`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"namedImportSpecifiers\\":[\\"n\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"importedNames\\":[\\"n\\"]});
const a = () => {
Expand All @@ -428,7 +428,7 @@ const foo = {}
`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"namedImportSpecifiers\\":[\\"n\\",\\"m\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"importedNames\\":[\\"n\\",\\"m\\"]});
const foo = {}
Expand Down Expand Up @@ -471,7 +471,7 @@ objRest()
`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"remove\\",\\"add\\",\\"get\\",\\"set\\",\\"rest\\",\\"objRest\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"remove\\",\\"add\\",\\"get\\",\\"set\\",\\"rest\\",\\"objRest\\"]});
Expand Down Expand Up @@ -521,7 +521,7 @@ const obj = {
`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\");
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"importedNames\\":[\\"default\\"]});
Expand Down Expand Up @@ -553,7 +553,7 @@ class A {
`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"remove\\",\\"add\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"remove\\",\\"add\\"]});
Expand Down Expand Up @@ -585,7 +585,7 @@ class A {
`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\");
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"importedNames\\":[\\"default\\"]});
Expand Down Expand Up @@ -631,7 +631,7 @@ bbb()
`,
),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"namedImportSpecifiers\\":[\\"aaa\\",\\"bbb\\",\\"ccc\\",\\"ddd\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"vue\\", {\\"importedNames\\":[\\"aaa\\",\\"bbb\\",\\"ccc\\",\\"ddd\\"]});
Expand Down Expand Up @@ -676,8 +676,8 @@ test('jsx', async () => {
const result = await transformWithEsbuild(code, id)
expect(await ssrTransformSimpleCode(result.code, '/foo.jsx'))
.toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"react\\");
const __vite_ssr_import_1__ = await __vite_ssr_import__(\\"foo\\", {\\"namedImportSpecifiers\\":[\\"Foo\\",\\"Slot\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"react\\", {\\"importedNames\\":[\\"default\\"]});
const __vite_ssr_import_1__ = await __vite_ssr_import__(\\"foo\\", {\\"importedNames\\":[\\"Foo\\",\\"Slot\\"]});
function Bar({ Slot: Slot2 = /* @__PURE__ */ __vite_ssr_import_0__.default.createElement(__vite_ssr_import_1__.Foo, null) }) {
Expand Down Expand Up @@ -752,7 +752,7 @@ import foo from "foo"`,
),
).toMatchInlineSnapshot(`
"#!/usr/bin/env node
const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\");
const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foo\\", {\\"importedNames\\":[\\"default\\"]});
console.log(__vite_ssr_import_0__.default);
"
`)
Expand Down Expand Up @@ -788,7 +788,7 @@ export class Test {
};`.trim()

expect(await ssrTransformSimpleCode(code)).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foobar\\", {\\"namedImportSpecifiers\\":[\\"foo\\",\\"bar\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foobar\\", {\\"importedNames\\":[\\"foo\\",\\"bar\\"]});
if (false) {
const foo = 'foo'
Expand Down Expand Up @@ -830,7 +830,7 @@ function test() {
return [foo, bar]
}`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foobar\\", {\\"namedImportSpecifiers\\":[\\"foo\\",\\"bar\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foobar\\", {\\"importedNames\\":[\\"foo\\",\\"bar\\"]});
function test() {
Expand All @@ -857,7 +857,7 @@ function test() {
return bar;
}`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foobar\\", {\\"namedImportSpecifiers\\":[\\"foo\\",\\"bar\\",\\"baz\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"foobar\\", {\\"importedNames\\":[\\"foo\\",\\"bar\\",\\"baz\\"]});
function test() {
Expand Down Expand Up @@ -889,7 +889,7 @@ for (const test in tests) {
console.log(test)
}`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./test.js\\", {\\"namedImportSpecifiers\\":[\\"test\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./test.js\\", {\\"importedNames\\":[\\"test\\"]});
Expand Down Expand Up @@ -921,7 +921,7 @@ const Baz = class extends Foo {}
`,
)
expect(result?.code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./foo\\", {\\"namedImportSpecifiers\\":[\\"Bar\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./foo\\", {\\"importedNames\\":[\\"default\\",\\"Bar\\"]});
Expand Down Expand Up @@ -963,7 +963,7 @@ export * from './b'
console.log(foo + 2)
`),
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./foo\\", {\\"namedImportSpecifiers\\":[\\"foo\\"]});
"const __vite_ssr_import_0__ = await __vite_ssr_import__(\\"./foo\\", {\\"importedNames\\":[\\"foo\\"]});
const __vite_ssr_import_1__ = await __vite_ssr_import__(\\"./a\\");
__vite_ssr_exportAll__(__vite_ssr_import_1__);
const __vite_ssr_import_2__ = await __vite_ssr_import__(\\"./b\\");
Expand Down
31 changes: 23 additions & 8 deletions packages/vite/src/node/ssr/ssrModuleLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,16 @@ interface NodeImportResolveOptions

interface SSRImportMetadata {
isDynamicImport?: boolean
namedImportSpecifiers?: string[]
/**
* Imported names before being transformed to `ssrImportKey`
*
* import foo, { bar as baz, qux } from 'hello'
* => ['default', 'bar', 'qux']
*
* import * as namespace from 'world
* => undefined
*/
importedNames?: string[]
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
Expand Down Expand Up @@ -367,15 +376,13 @@ function analyzeImportedModDifference(

// For non-ESM, named imports is done via static analysis with cjs-module-lexer in Node.js.
// If the user named imports a specifier that can't be analyzed, error.
if (metadata?.namedImportSpecifiers?.length) {
const missingBindings = metadata.namedImportSpecifiers.filter(
(s) => !(s in mod),
)
if (metadata?.importedNames?.length) {
const missingBindings = metadata.importedNames.filter((s) => !(s in mod))
if (missingBindings.length) {
const lastBinding = missingBindings[missingBindings.length - 1]
// Copied from Node.js
throw new SyntaxError(`\
Named export '${lastBinding}' not found. The requested module '${rawId}' is a CommonJS module, which may not support all module.exports as named exports.
[vite] Named export '${lastBinding}' not found. The requested module '${rawId}' is a CommonJS module, which may not support all module.exports as named exports.
CommonJS modules can always be imported via the default export, for example using:
import pkg from '${rawId}';
Expand All @@ -389,12 +396,20 @@ const {${missingBindings.join(', ')}} = pkg;
* Guard invalid named exports only, similar to how Node.js errors for top-level imports.
* But since we transform as dynamic imports, we need to emulate the error manually.
*/
function proxyGuardOnlyEsm(mod: any, rawId: string) {
function proxyGuardOnlyEsm(
mod: any,
rawId: string,
metadata?: SSRImportMetadata,
) {
// If the module doesn't import anything explicitly, e.g. `import 'foo'` or
// `import * as foo from 'foo'`, we can skip the proxy guard.
if (!metadata?.importedNames?.length) return mod

return new Proxy(mod, {
get(mod, prop) {
if (prop !== 'then' && !(prop in mod)) {
throw new SyntaxError(
`The requested module '${rawId}' does not provide an export named '${prop.toString()}'`,
`[vite] The requested module '${rawId}' does not provide an export named '${prop.toString()}'`,
)
}
return mod[prop]
Expand Down
Loading

0 comments on commit 82a5b11

Please sign in to comment.