From 12d0cc0e5860f63ea54c75e2b000d2692682b2d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BF=A0=20/=20green?= Date: Mon, 9 May 2022 23:07:04 +0900 Subject: [PATCH] fix(css): preserve dynamic import css code (fix #5348) (#7746) --- .../src/node/plugins/importAnalysisBuild.ts | 18 ++++++++++++------ .../__tests__/css-codesplit.spec.ts | 18 +++++++++++++++++- playground/css-codesplit/async.css | 3 +++ playground/css-codesplit/index.html | 9 +++++++++ playground/css-codesplit/inline.css | 3 +++ playground/css-codesplit/main.js | 15 ++++++++++++--- playground/css-codesplit/mod.module.css | 3 +++ 7 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 playground/css-codesplit/async.css create mode 100644 playground/css-codesplit/inline.css create mode 100644 playground/css-codesplit/mod.module.css diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index a4b091dc76da3a..5b7d738c2da264 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -145,11 +145,15 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { d: dynamicIndex } = imports[index] - if (dynamicIndex > -1 && insertPreload) { + const isDynamic = dynamicIndex > -1 + + if (isDynamic && insertPreload) { needPreloadHelper = true - const original = source.slice(expStart, expEnd) - const replacement = `${preloadMethod}(() => ${original},${isModernFlag}?"${preloadMarker}":void 0)` - str().overwrite(expStart, expEnd, replacement, { contentOnly: true }) + str().prependLeft(expStart, `${preloadMethod}(() => `) + str().appendRight( + expEnd, + `,${isModernFlag}?"${preloadMarker}":void 0)` + ) } // Differentiate CSS imports that use the default export from those that @@ -159,14 +163,16 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { if ( specifier && isCSSRequest(specifier) && - source.slice(expStart, start).includes('from') && + // always inject ?used query when it is a dynamic import + // because there is no way to check whether the default export is used + (source.slice(expStart, start).includes('from') || isDynamic) && // already has ?used query (by import.meta.glob) !specifier.match(/\?used(&|$)/) && // edge case for package names ending with .css (e.g normalize.css) !(bareImportRE.test(specifier) && !specifier.includes('/')) ) { const url = specifier.replace(/\?|$/, (m) => `?used${m ? '&' : ''}`) - str().overwrite(start, end, dynamicIndex > -1 ? `'${url}'` : url, { + str().overwrite(start, end, isDynamic ? `'${url}'` : url, { contentOnly: true }) } diff --git a/playground/css-codesplit/__tests__/css-codesplit.spec.ts b/playground/css-codesplit/__tests__/css-codesplit.spec.ts index 95fe97a1b953ba..789adba23ae021 100644 --- a/playground/css-codesplit/__tests__/css-codesplit.spec.ts +++ b/playground/css-codesplit/__tests__/css-codesplit.spec.ts @@ -1,8 +1,23 @@ import { findAssetFile, getColor, isBuild, readManifest } from '../../testUtils' -test('should load both stylesheets', async () => { +test('should load all stylesheets', async () => { expect(await getColor('h1')).toBe('red') expect(await getColor('h2')).toBe('blue') + expect(await getColor('.dynamic')).toBe('green') +}) + +test('should load dynamic import with inline', async () => { + const css = await page.textContent('.dynamic-inline') + expect(css).toMatch('.inline') + + expect(await getColor('.inline')).not.toBe('yellow') +}) + +test('should load dynamic import with module', async () => { + const css = await page.textContent('.dynamic-module') + expect(css).toMatch('_mod_') + + expect(await getColor('.mod')).toBe('yellow') }) if (isBuild) { @@ -10,6 +25,7 @@ if (isBuild) { expect(findAssetFile(/style.*\.js$/)).toBe('') expect(findAssetFile('main.*.js$')).toMatch(`/* empty css`) expect(findAssetFile('other.*.js$')).toMatch(`/* empty css`) + expect(findAssetFile(/async.*\.js$/)).toBe('') }) test('should generate correct manifest', async () => { diff --git a/playground/css-codesplit/async.css b/playground/css-codesplit/async.css new file mode 100644 index 00000000000000..4902b2e7bee811 --- /dev/null +++ b/playground/css-codesplit/async.css @@ -0,0 +1,3 @@ +.dynamic { + color: green; +} diff --git a/playground/css-codesplit/index.html b/playground/css-codesplit/index.html index 6b7b3bb2b4dc2d..63bdb59e11dc6b 100644 --- a/playground/css-codesplit/index.html +++ b/playground/css-codesplit/index.html @@ -1,2 +1,11 @@ +

This should be red

+

This should be blue

+ +

This should be green

+

This should not be yellow

+

+

This should be yellow

+

+
diff --git a/playground/css-codesplit/inline.css b/playground/css-codesplit/inline.css new file mode 100644 index 00000000000000..b2a2b5f1ead51f --- /dev/null +++ b/playground/css-codesplit/inline.css @@ -0,0 +1,3 @@ +.inline { + color: yellow; +} diff --git a/playground/css-codesplit/main.js b/playground/css-codesplit/main.js index 8c80df2c181511..eb6e703f79e718 100644 --- a/playground/css-codesplit/main.js +++ b/playground/css-codesplit/main.js @@ -1,6 +1,15 @@ import './style.css' import './main.css' -document.getElementById( - 'app' -).innerHTML = `

This should be red

This should be blue

` +import('./async.css') + +import('./inline.css?inline').then((css) => { + document.querySelector('.dynamic-inline').textContent = css.default +}) + +import('./mod.module.css').then((css) => { + document.querySelector('.dynamic-module').textContent = JSON.stringify( + css.default + ) + document.querySelector('.mod').classList.add(css.default.mod) +}) diff --git a/playground/css-codesplit/mod.module.css b/playground/css-codesplit/mod.module.css new file mode 100644 index 00000000000000..7f84410485a32c --- /dev/null +++ b/playground/css-codesplit/mod.module.css @@ -0,0 +1,3 @@ +.mod { + color: yellow; +}