Skip to content

Commit

Permalink
feat: async chunk loading optimizations
Browse files Browse the repository at this point in the history
- load async chunk CSS and nested imports in parallel to the main chunk
- ensure CSS loaded before evaluating main chunk
  • Loading branch information
yyx990803 committed Jan 10, 2021
1 parent 6119014 commit e6f7fba
Show file tree
Hide file tree
Showing 13 changed files with 414 additions and 253 deletions.
6 changes: 3 additions & 3 deletions packages/playground/css/__tests__/css.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,10 @@ test('async chunk', async () => {
expect(await getColor(el)).toBe('teal')

if (isBuild) {
// assert that the css is inlined in the async chunk instead of in the
// assert that the css is extracted into its own file instead of in the
// main css file
expect(findAssetFile(/\.css$/)).not.toMatch('teal')
expect(findAssetFile(/async\.\w+\.js$/)).toMatch('.async{color:teal}')
expect(findAssetFile(/index\.\w+\.css$/)).not.toMatch('teal')
expect(findAssetFile(/async\.\w+\.css$/)).toMatch('.async{color:teal}')
} else {
// test hmr
editFile('async.css', (code) => code.replace('color: teal', 'color: blue'))
Expand Down
4 changes: 3 additions & 1 deletion packages/playground/css/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ import './async.css'

const div = document.createElement('div')
div.className = 'async'
div.textContent = 'async chunk (this should be teal)'
document.body.appendChild(div)
div.textContent = `async chunk (this should be teal) ${
getComputedStyle(div).color
}`
1 change: 1 addition & 0 deletions packages/playground/dynamic-import/nested/shared.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const n = 1
3 changes: 3 additions & 0 deletions packages/playground/dynamic-import/views/bar.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
import { n } from '../nested/shared'
console.log('bar' + n)

export const msg = 'Bar view'
3 changes: 3 additions & 0 deletions packages/playground/dynamic-import/views/baz.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
import { n } from '../nested/shared'
console.log('baz' + n)

export const msg = 'Baz view'
3 changes: 3 additions & 0 deletions packages/playground/dynamic-import/views/foo.js
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
import { n } from '../nested/shared'
console.log('foo' + n)

export const msg = 'Foo view'
4 changes: 2 additions & 2 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { Logger } from './logger'
import { TransformOptions } from 'esbuild'
import { CleanCSS } from 'types/clean-css'
import { dataURIPlugin } from './plugins/dataUri'
import { importGlobPlugin } from './plugins/importGlob'
import { buildImportAnalysisPlugin } from './plugins/importAnaysisBuild'

export interface BuildOptions {
/**
Expand Down Expand Up @@ -204,7 +204,6 @@ export function resolveBuildPlugins(
extensions: ['.js', '.cjs']
}),
dataURIPlugin(),
importGlobPlugin(config),
buildDefinePlugin(config),
dynamicImportVars({
warnOnError: true,
Expand All @@ -213,6 +212,7 @@ export function resolveBuildPlugins(
...(options.rollupOptions.plugins || [])
],
post: [
buildImportAnalysisPlugin(config),
buildEsbuildPlugin(config),
...(options.minify && options.minify !== 'esbuild'
? [terserPlugin(options.terserOptions)]
Expand Down
24 changes: 7 additions & 17 deletions packages/vite/src/node/plugins/css.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,23 +210,13 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
if (config.build.minify) {
chunkCSS = await minifyCSS(chunkCSS, config)
}
// for non-entry chunks, collect its css and inline it as JS strings.
if (!chunk.isEntry) {
const placeholder = `__VITE_CSS__`
code =
`let ${placeholder} = document.createElement('style');` +
`${placeholder}.innerHTML = ${JSON.stringify(chunkCSS)};` +
`document.head.appendChild(${placeholder});` +
code
} else {
// for entry chunks, emit corresponding css file
const fileHandle = this.emitFile({
name: chunk.name + '.css',
type: 'asset',
source: chunkCSS
})
chunkToEmittedCssFileMap.set(chunk, fileHandle)
}
// emit corresponding css file
const fileHandle = this.emitFile({
name: chunk.name + '.css',
type: 'asset',
source: chunkCSS
})
chunkToEmittedCssFileMap.set(chunk, fileHandle)
return {
code,
map: null
Expand Down
48 changes: 12 additions & 36 deletions packages/vite/src/node/plugins/dynamicImportPolyfill.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import MagicString from 'magic-string'
import { ResolvedConfig } from '..'
import { Plugin } from '../plugin'
import { isModernFlag } from './importAnaysisBuild'

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

export function dynamicImportPolyfillPlugin(config: ResolvedConfig): Plugin {
const skip = config.command === 'serve' || config.build.ssr
let polyfillLoaded = false
const polyfillString =
`${__dynamic_import_polyfill__.toString()};` +
`__dynamic_import_polyfill__(${JSON.stringify(config.build.base)});`
`${isModernFlag}&&${__dynamic_import_polyfill__.name}(${JSON.stringify(
config.build.base
)});`

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

Expand All @@ -43,38 +44,9 @@ export function dynamicImportPolyfillPlugin(config: ResolvedConfig): Plugin {
`your custom entry.`
)
}
return {
left: '__import__(',
right: ')'
}
},

renderChunk(code, chunk, { format }) {
if (skip) {
return null
}
if (!chunk.facadeModuleId) {
return null
}
const i = code.indexOf(polyfillPlaceholder)
if (i < 0) {
return null
}
// if format is not ES, simply empty it
const replacement = format === 'es' ? polyfillString : ''
if (config.build.sourcemap) {
const s = new MagicString(code)
s.overwrite(i, i + polyfillPlaceholder.length, replacement)
return {
code: s.toString(),
map: s.generateMap({ hires: true })
}
} else {
return {
code: code.replace(polyfillPlaceholder, replacement),
map: null
}
}
// we do not actually return anything here because rewriting here would
// make it impossible to use es-module-lexer on the rendered chunks, which
// we need for import graph optimization in ./importAnalysisBuild.
}
}
}
Expand Down Expand Up @@ -115,6 +87,10 @@ function __dynamic_import_polyfill__(
modulePath = '.',
importFunctionName = '__import__'
) {
// @ts-ignore injected by ./importAnalysisBuild
if (!__VITE_IS_MODERN__) {
return
}
try {
self[importFunctionName] = new Function('u', `return import(u)`)
} catch (error) {
Expand Down
1 change: 1 addition & 0 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
tag: 'script',
attrs: {
type: 'module',
crossorigin: true,
src: toPublicPath(chunk.fileName, config)
}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/plugins/importAnalysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { checkPublicFile } from './asset'
import { parse as parseJS } from 'acorn'
import type { Node } from 'estree'
import { makeLegalIdentifier } from '@rollup/pluginutils'
import { transformImportGlob } from './importGlob'
import { transformImportGlob } from './importAnaysisBuild'

const isDebug = !!process.env.DEBUG
const debugRewrite = createDebugger('vite:rewrite')
Expand Down
Loading

0 comments on commit e6f7fba

Please sign in to comment.