Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(ssr): skip esm proxy guard for namespace imports #14988

Merged
merged 1 commit into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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