Skip to content

Commit e6f7fba

Browse files
committed
feat: async chunk loading optimizations
- load async chunk CSS and nested imports in parallel to the main chunk - ensure CSS loaded before evaluating main chunk
1 parent 6119014 commit e6f7fba

File tree

13 files changed

+414
-253
lines changed

13 files changed

+414
-253
lines changed

packages/playground/css/__tests__/css.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,10 @@ test('async chunk', async () => {
104104
expect(await getColor(el)).toBe('teal')
105105

106106
if (isBuild) {
107-
// assert that the css is inlined in the async chunk instead of in the
107+
// assert that the css is extracted into its own file instead of in the
108108
// main css file
109-
expect(findAssetFile(/\.css$/)).not.toMatch('teal')
110-
expect(findAssetFile(/async\.\w+\.js$/)).toMatch('.async{color:teal}')
109+
expect(findAssetFile(/index\.\w+\.css$/)).not.toMatch('teal')
110+
expect(findAssetFile(/async\.\w+\.css$/)).toMatch('.async{color:teal}')
111111
} else {
112112
// test hmr
113113
editFile('async.css', (code) => code.replace('color: teal', 'color: blue'))

packages/playground/css/async.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,7 @@ import './async.css'
22

33
const div = document.createElement('div')
44
div.className = 'async'
5-
div.textContent = 'async chunk (this should be teal)'
65
document.body.appendChild(div)
6+
div.textContent = `async chunk (this should be teal) ${
7+
getComputedStyle(div).color
8+
}`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const n = 1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1+
import { n } from '../nested/shared'
2+
console.log('bar' + n)
3+
14
export const msg = 'Bar view'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1+
import { n } from '../nested/shared'
2+
console.log('baz' + n)
3+
14
export const msg = 'Baz view'
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1+
import { n } from '../nested/shared'
2+
console.log('foo' + n)
3+
14
export const msg = 'Foo view'

packages/vite/src/node/build.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { Logger } from './logger'
2525
import { TransformOptions } from 'esbuild'
2626
import { CleanCSS } from 'types/clean-css'
2727
import { dataURIPlugin } from './plugins/dataUri'
28-
import { importGlobPlugin } from './plugins/importGlob'
28+
import { buildImportAnalysisPlugin } from './plugins/importAnaysisBuild'
2929

3030
export interface BuildOptions {
3131
/**
@@ -204,7 +204,6 @@ export function resolveBuildPlugins(
204204
extensions: ['.js', '.cjs']
205205
}),
206206
dataURIPlugin(),
207-
importGlobPlugin(config),
208207
buildDefinePlugin(config),
209208
dynamicImportVars({
210209
warnOnError: true,
@@ -213,6 +212,7 @@ export function resolveBuildPlugins(
213212
...(options.rollupOptions.plugins || [])
214213
],
215214
post: [
215+
buildImportAnalysisPlugin(config),
216216
buildEsbuildPlugin(config),
217217
...(options.minify && options.minify !== 'esbuild'
218218
? [terserPlugin(options.terserOptions)]

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

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -210,23 +210,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
210210
if (config.build.minify) {
211211
chunkCSS = await minifyCSS(chunkCSS, config)
212212
}
213-
// for non-entry chunks, collect its css and inline it as JS strings.
214-
if (!chunk.isEntry) {
215-
const placeholder = `__VITE_CSS__`
216-
code =
217-
`let ${placeholder} = document.createElement('style');` +
218-
`${placeholder}.innerHTML = ${JSON.stringify(chunkCSS)};` +
219-
`document.head.appendChild(${placeholder});` +
220-
code
221-
} else {
222-
// for entry chunks, emit corresponding css file
223-
const fileHandle = this.emitFile({
224-
name: chunk.name + '.css',
225-
type: 'asset',
226-
source: chunkCSS
227-
})
228-
chunkToEmittedCssFileMap.set(chunk, fileHandle)
229-
}
213+
// emit corresponding css file
214+
const fileHandle = this.emitFile({
215+
name: chunk.name + '.css',
216+
type: 'asset',
217+
source: chunkCSS
218+
})
219+
chunkToEmittedCssFileMap.set(chunk, fileHandle)
230220
return {
231221
code,
232222
map: null

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

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import MagicString from 'magic-string'
21
import { ResolvedConfig } from '..'
32
import { Plugin } from '../plugin'
3+
import { isModernFlag } from './importAnaysisBuild'
44

55
export const polyfillId = 'vite/dynamic-import-polyfill'
6-
const polyfillPlaceholder = `__DYNAMIC_IMPORT_POLYFILL__()`
76

87
export function dynamicImportPolyfillPlugin(config: ResolvedConfig): Plugin {
98
const skip = config.command === 'serve' || config.build.ssr
109
let polyfillLoaded = false
1110
const polyfillString =
1211
`${__dynamic_import_polyfill__.toString()};` +
13-
`__dynamic_import_polyfill__(${JSON.stringify(config.build.base)});`
12+
`${isModernFlag}&&${__dynamic_import_polyfill__.name}(${JSON.stringify(
13+
config.build.base
14+
)});`
1415

1516
return {
1617
name: 'vite:dynamic-import-polyfill',
@@ -27,7 +28,7 @@ export function dynamicImportPolyfillPlugin(config: ResolvedConfig): Plugin {
2728
polyfillLoaded = true
2829
// return a placeholder here and defer the injection to renderChunk
2930
// so that we can selectively skip the injection based on output format
30-
return polyfillPlaceholder
31+
return polyfillString
3132
}
3233
},
3334

@@ -43,38 +44,9 @@ export function dynamicImportPolyfillPlugin(config: ResolvedConfig): Plugin {
4344
`your custom entry.`
4445
)
4546
}
46-
return {
47-
left: '__import__(',
48-
right: ')'
49-
}
50-
},
51-
52-
renderChunk(code, chunk, { format }) {
53-
if (skip) {
54-
return null
55-
}
56-
if (!chunk.facadeModuleId) {
57-
return null
58-
}
59-
const i = code.indexOf(polyfillPlaceholder)
60-
if (i < 0) {
61-
return null
62-
}
63-
// if format is not ES, simply empty it
64-
const replacement = format === 'es' ? polyfillString : ''
65-
if (config.build.sourcemap) {
66-
const s = new MagicString(code)
67-
s.overwrite(i, i + polyfillPlaceholder.length, replacement)
68-
return {
69-
code: s.toString(),
70-
map: s.generateMap({ hires: true })
71-
}
72-
} else {
73-
return {
74-
code: code.replace(polyfillPlaceholder, replacement),
75-
map: null
76-
}
77-
}
47+
// we do not actually return anything here because rewriting here would
48+
// make it impossible to use es-module-lexer on the rendered chunks, which
49+
// we need for import graph optimization in ./importAnalysisBuild.
7850
}
7951
}
8052
}
@@ -115,6 +87,10 @@ function __dynamic_import_polyfill__(
11587
modulePath = '.',
11688
importFunctionName = '__import__'
11789
) {
90+
// @ts-ignore injected by ./importAnalysisBuild
91+
if (!__VITE_IS_MODERN__) {
92+
return
93+
}
11894
try {
11995
self[importFunctionName] = new Function('u', `return import(u)`)
12096
} catch (error) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
282282
tag: 'script',
283283
attrs: {
284284
type: 'module',
285+
crossorigin: true,
285286
src: toPublicPath(chunk.fileName, config)
286287
}
287288
},

0 commit comments

Comments
 (0)