Skip to content

Commit 17a8f9e

Browse files
authored
fix(optimize-deps): hoist CJS interop assignment (#22156)
1 parent 7611eb0 commit 17a8f9e

File tree

6 files changed

+75
-57
lines changed

6 files changed

+75
-57
lines changed

packages/vite/src/node/__tests__/plugins/import.spec.ts

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@ describe('runTransform', () => {
1919
isNodeMode,
2020
config,
2121
)
22-
if (result !== undefined) {
23-
expect(result.split('\n').length, 'result line count').toBe(
24-
importExp.split('\n').length,
25-
)
26-
}
27-
return result?.replaceAll(';', ';\n')
22+
if (result === undefined) return undefined
23+
const joined = result.hoistedAssignments
24+
? `${result.hoistedAssignments}; ${result.importLine}`
25+
: result.importLine
26+
expect(joined.split('\n').length, 'result line count').toBe(
27+
importExp.split('\n').length,
28+
)
29+
return joined.replaceAll(';', ';\n')
2830
}
2931

3032
beforeEach(() => {
@@ -38,54 +40,54 @@ describe('runTransform', () => {
3840
false,
3941
),
4042
).toMatchInlineSnapshot(`
41-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
42-
const useState = __vite__cjsImport0_react["useState"];
43+
"const useState = __vite__cjsImport0_react["useState"];
4344
const Component = __vite__cjsImport0_react["Component"];
44-
const fake = __vite__cjsImport0_react["👋"]"
45+
const fake = __vite__cjsImport0_react["👋"];
46+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
4547
`)
4648
expect(
4749
runTransformCjsImport(
4850
'import { useState, Component, "👋" as fake } from "react"',
4951
true,
5052
),
5153
).toMatchInlineSnapshot(`
52-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
53-
const useState = __vite__cjsImport0_react["useState"];
54+
"const useState = __vite__cjsImport0_react["useState"];
5455
const Component = __vite__cjsImport0_react["Component"];
55-
const fake = __vite__cjsImport0_react["👋"]"
56+
const fake = __vite__cjsImport0_react["👋"];
57+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
5658
`)
5759
})
5860

5961
test('import default specifier', () => {
6062
expect(runTransformCjsImport('import React from "react"', false))
6163
.toMatchInlineSnapshot(`
62-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
63-
const React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default"
64+
"const React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default;
65+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
6466
`)
6567
expect(runTransformCjsImport('import React from "react"', true))
6668
.toMatchInlineSnapshot(`
67-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
68-
const React = __vite__cjsImport0_react"
69+
"const React = __vite__cjsImport0_react;
70+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
6971
`)
7072

7173
expect(
7274
runTransformCjsImport('import { default as React } from "react"', false),
7375
).toMatchInlineSnapshot(`
74-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
75-
const React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default"
76+
"const React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default;
77+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
7678
`)
7779
})
7880

7981
test('import all specifier', () => {
8082
expect(runTransformCjsImport('import * as react from "react"', false))
8183
.toMatchInlineSnapshot(`
82-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
83-
const react = ((m, n) => n || !m?.__esModule ? { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m} : m)(__vite__cjsImport0_react, 0)"
84+
"const react = ((m, n) => n || !m?.__esModule ? { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m} : m)(__vite__cjsImport0_react, 0);
85+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
8486
`)
8587
expect(runTransformCjsImport('import * as react from "react"', true))
8688
.toMatchInlineSnapshot(`
87-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
88-
const react = ((m, n) => n || !m?.__esModule ? { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m} : m)(__vite__cjsImport0_react, 1)"
89+
"const react = ((m, n) => n || !m?.__esModule ? { ...typeof m === "object" && !Array.isArray(m) || typeof m === "function" ? m : {}, default: m} : m)(__vite__cjsImport0_react, 1);
90+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
8991
`)
9092
})
9193

@@ -115,23 +117,23 @@ describe('runTransform', () => {
115117
false,
116118
),
117119
).toMatchInlineSnapshot(`
118-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
119-
const __vite__cjsExportI_useState = __vite__cjsImport0_react["useState"];
120+
"const __vite__cjsExportI_useState = __vite__cjsImport0_react["useState"];
120121
const __vite__cjsExportI_Component = __vite__cjsImport0_react["Component"];
121122
const __vite__cjsExportL_1d0452e3 = __vite__cjsImport0_react["👋"];
122-
export { __vite__cjsExportI_useState as useState, __vite__cjsExportI_Component as Component, __vite__cjsExportL_1d0452e3 as "👋" }"
123+
export { __vite__cjsExportI_useState as useState, __vite__cjsExportI_Component as Component, __vite__cjsExportL_1d0452e3 as "👋" };
124+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
123125
`)
124126
expect(
125127
runTransformCjsImport(
126128
'export { useState, Component, "👋" } from "react"',
127129
true,
128130
),
129131
).toMatchInlineSnapshot(`
130-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
131-
const __vite__cjsExportI_useState = __vite__cjsImport0_react["useState"];
132+
"const __vite__cjsExportI_useState = __vite__cjsImport0_react["useState"];
132133
const __vite__cjsExportI_Component = __vite__cjsImport0_react["Component"];
133134
const __vite__cjsExportL_1d0452e3 = __vite__cjsImport0_react["👋"];
134-
export { __vite__cjsExportI_useState as useState, __vite__cjsExportI_Component as Component, __vite__cjsExportL_1d0452e3 as "👋" }"
135+
export { __vite__cjsExportI_useState as useState, __vite__cjsExportI_Component as Component, __vite__cjsExportL_1d0452e3 as "👋" };
136+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
135137
`)
136138

137139
expect(
@@ -140,34 +142,34 @@ describe('runTransform', () => {
140142
false,
141143
),
142144
).toMatchInlineSnapshot(`
143-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
144-
const __vite__cjsExportI_useStateAlias = __vite__cjsImport0_react["useState"];
145+
"const __vite__cjsExportI_useStateAlias = __vite__cjsImport0_react["useState"];
145146
const __vite__cjsExportI_ComponentAlias = __vite__cjsImport0_react["Component"];
146147
const __vite__cjsExportL_5d57d39e = __vite__cjsImport0_react["👋"];
147-
export { __vite__cjsExportI_useStateAlias as useStateAlias, __vite__cjsExportI_ComponentAlias as ComponentAlias, __vite__cjsExportL_5d57d39e as "👍" }"
148+
export { __vite__cjsExportI_useStateAlias as useStateAlias, __vite__cjsExportI_ComponentAlias as ComponentAlias, __vite__cjsExportL_5d57d39e as "👍" };
149+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
148150
`)
149151
})
150152

151153
test('export default specifier', () => {
152154
expect(runTransformCjsImport('export { default } from "react"', false))
153155
.toMatchInlineSnapshot(`
154-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
155-
const __vite__cjsExportDefault_0 = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default;
156-
export default __vite__cjsExportDefault_0"
156+
"const __vite__cjsExportDefault_0 = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default;
157+
export default __vite__cjsExportDefault_0;
158+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
157159
`)
158160
expect(runTransformCjsImport('export { default } from "react"', true))
159161
.toMatchInlineSnapshot(`
160-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
161-
const __vite__cjsExportDefault_0 = __vite__cjsImport0_react;
162-
export default __vite__cjsExportDefault_0"
162+
"const __vite__cjsExportDefault_0 = __vite__cjsImport0_react;
163+
export default __vite__cjsExportDefault_0;
164+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
163165
`)
164166

165167
expect(
166168
runTransformCjsImport('export { default as React} from "react"', false),
167169
).toMatchInlineSnapshot(`
168-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
169-
const __vite__cjsExportI_React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default;
170-
export { __vite__cjsExportI_React as React }"
170+
"const __vite__cjsExportI_React = !__vite__cjsImport0_react.__esModule ? __vite__cjsImport0_react : __vite__cjsImport0_react.default;
171+
export { __vite__cjsExportI_React as React };
172+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
171173
`)
172174

173175
expect(
@@ -176,9 +178,9 @@ describe('runTransform', () => {
176178
false,
177179
),
178180
).toMatchInlineSnapshot(`
179-
"import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js";
180-
const __vite__cjsExportDefault_0 = __vite__cjsImport0_react["Component"];
181-
export default __vite__cjsExportDefault_0"
181+
"const __vite__cjsExportDefault_0 = __vite__cjsImport0_react["Component"];
182+
export default __vite__cjsExportDefault_0;
183+
import __vite__cjsImport0_react from "./node_modules/.vite/deps/react.js""
182184
`)
183185
})
184186
})

packages/vite/src/node/plugins/importAnalysis.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
createDebugger,
3434
fsPathFromUrl,
3535
generateCodeFrame,
36+
getFileStartIndex,
3637
getHash,
3738
injectQuery,
3839
isBuiltin,
@@ -964,9 +965,18 @@ export function interopNamedImports(
964965
config,
965966
)
966967
if (rewritten) {
967-
str.overwrite(expStart, expEnd, rewritten + getLineBreaks(exp), {
968-
contentOnly: true,
969-
})
968+
str.overwrite(
969+
expStart,
970+
expEnd,
971+
rewritten.importLine + getLineBreaks(exp),
972+
{ contentOnly: true },
973+
)
974+
if (rewritten.hoistedAssignments) {
975+
str.appendLeft(
976+
getFileStartIndex(source),
977+
rewritten.hoistedAssignments + ';',
978+
)
979+
}
970980
} else {
971981
// #1439 export * from '...'
972982
str.overwrite(
@@ -1009,7 +1019,7 @@ export function transformCjsImport(
10091019
importer: string,
10101020
isNodeMode: boolean,
10111021
config: ResolvedConfig,
1012-
): string | undefined {
1022+
): { importLine: string; hoistedAssignments?: string } | undefined {
10131023
const node = parseAst(importExp).body[0]
10141024

10151025
// `export * from '...'` may cause unexpected problem, so give it a warning
@@ -1028,7 +1038,7 @@ export function transformCjsImport(
10281038
node.type === 'ExportNamedDeclaration'
10291039
) {
10301040
if (!node.specifiers.length) {
1031-
return `import "${url}"`
1041+
return { importLine: `import "${url}"` }
10321042
}
10331043

10341044
const importNames: ImportNameSpecifier[] = []
@@ -1076,7 +1086,8 @@ export function transformCjsImport(
10761086
const cjsModuleName = makeLegalIdentifier(
10771087
`__vite__cjsImport${importIndex}_${rawUrl}`,
10781088
)
1079-
const lines: string[] = [`import ${cjsModuleName} from "${url}"`]
1089+
const importLine = `import ${cjsModuleName} from "${url}"`
1090+
const lines: string[] = []
10801091
importNames.forEach(({ importedName, localName }) => {
10811092
if (importedName === '*') {
10821093
lines.push(
@@ -1101,7 +1112,7 @@ export function transformCjsImport(
11011112
lines.push(`export { ${exportNames.join(', ')} }`)
11021113
}
11031114

1104-
return lines.join('; ')
1115+
return { importLine, hoistedAssignments: lines.join('; ') }
11051116
}
11061117
}
11071118

packages/vite/src/node/ssr/ssrTransform.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { TransformResult } from '../server/transformRequest'
1010
import {
1111
combineSourcemaps,
1212
generateCodeFrame,
13+
getFileStartIndex,
1314
isDefined,
1415
numberToPos,
1516
} from '../utils'
@@ -29,8 +30,6 @@ export const ssrExportAllKey = `__vite_ssr_exportAll__`
2930
export const ssrExportNameKey = `__vite_ssr_exportName__`
3031
export const ssrImportMetaKey = `__vite_ssr_import_meta__`
3132

32-
const hashbangRE = /^#!.*\n/
33-
3433
export async function ssrTransform(
3534
code: string,
3635
inMap: SourceMap | { mappings: '' } | null,
@@ -92,8 +91,7 @@ async function ssrTransformScript(
9291
const idToImportMap = new Map<string, string>()
9392
const declaredConst = new Set<string>()
9493

95-
// hoist at the start of the file, after the hashbang
96-
const fileStartIndex = hashbangRE.exec(code)?.[0].length ?? 0
94+
const fileStartIndex = getFileStartIndex(code)
9795
let hoistIndex = fileStartIndex
9896

9997
function defineImport(

packages/vite/src/node/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1821,3 +1821,10 @@ export function formatAndTruncateFileList(files: string[]): {
18211821
}
18221822
return { formatted: log, truncated }
18231823
}
1824+
1825+
const hashbangRE = /^#!.*\n/
1826+
1827+
// find the start of the file, after the hashbang
1828+
export function getFileStartIndex(code: string): number {
1829+
return hashbangRE.exec(code)?.[0].length ?? 0
1830+
}

playground/js-sourcemap/__tests__/js-sourcemap.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ if (!isBuild) {
142142
],
143143
"version": 3,
144144
},
145-
visualization: "https://evanw.github.io/source-map-visualization/#MjQ4AC8vIHByZXR0aWVyLWlnbm9yZQppbXBvcnQgX192aXRlX19janNJbXBvcnQwX192aXRlanNfdGVzdEltcG9ydGVlUGtnIGZyb20gIi9ub2RlX21vZHVsZXMvLnZpdGUvZGVwcy9Adml0ZWpzX3Rlc3QtaW1wb3J0ZWUtcGtnLmpzP3Y9MDAwMDAwMDAiOyBjb25zdCBmb28gPSBfX3ZpdGVfX2Nqc0ltcG9ydDBfX3ZpdGVqc190ZXN0SW1wb3J0ZWVQa2dbImZvbyJdOwpjb25zb2xlLmxvZygid2l0aC1tdWx0aWxpbmUtaW1wb3J0IiwgZm9vKTsKMjQ4AHsibWFwcGluZ3MiOiI7QUFDQSxTQUNFLFdBQ0s7QUFFUCxRQUFRLElBQUkseUJBQXlCLElBQUkiLCJzb3VyY2VzIjpbIndpdGgtbXVsdGlsaW5lLWltcG9ydC50cyJdLCJ2ZXJzaW9uIjozLCJzb3VyY2VzQ29udGVudCI6WyIvLyBwcmV0dGllci1pZ25vcmVcbmltcG9ydCB7XG4gIGZvb1xufSBmcm9tICdAdml0ZWpzL3Rlc3QtaW1wb3J0ZWUtcGtnJ1xuXG5jb25zb2xlLmxvZygnd2l0aC1tdWx0aWxpbmUtaW1wb3J0JywgZm9vKVxuIl19"
145+
visualization: "https://evanw.github.io/source-map-visualization/#MjQ3AGNvbnN0IGZvbyA9IF9fdml0ZV9fY2pzSW1wb3J0MF9fdml0ZWpzX3Rlc3RJbXBvcnRlZVBrZ1siZm9vIl07Ly8gcHJldHRpZXItaWdub3JlCmltcG9ydCBfX3ZpdGVfX2Nqc0ltcG9ydDBfX3ZpdGVqc190ZXN0SW1wb3J0ZWVQa2cgZnJvbSAiL25vZGVfbW9kdWxlcy8udml0ZS9kZXBzL0B2aXRlanNfdGVzdC1pbXBvcnRlZS1wa2cuanM/dj0wMDAwMDAwMCI7CmNvbnNvbGUubG9nKCJ3aXRoLW11bHRpbGluZS1pbXBvcnQiLCBmb28pOwoyNDgAeyJtYXBwaW5ncyI6IjtBQUNBLFNBQ0UsV0FDSztBQUVQLFFBQVEsSUFBSSx5QkFBeUIsSUFBSSIsInNvdXJjZXMiOlsid2l0aC1tdWx0aWxpbmUtaW1wb3J0LnRzIl0sInZlcnNpb24iOjMsInNvdXJjZXNDb250ZW50IjpbIi8vIHByZXR0aWVyLWlnbm9yZVxuaW1wb3J0IHtcbiAgZm9vXG59IGZyb20gJ0B2aXRlanMvdGVzdC1pbXBvcnRlZS1wa2cnXG5cbmNvbnNvbGUubG9nKCd3aXRoLW11bHRpbGluZS1pbXBvcnQnLCBmb28pXG4iXX0="
146146
}
147147
`)
148148
})

0 commit comments

Comments
 (0)