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: css content (v2) #8874

Merged
merged 5 commits into from Jul 1, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,15 +1,31 @@
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) {
test('should remove empty chunk', async () => {
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 () => {
Expand Down
3 changes: 3 additions & 0 deletions packages/playground/css-codesplit/async.css
@@ -0,0 +1,3 @@
.dynamic {
color: green;
}
9 changes: 9 additions & 0 deletions packages/playground/css-codesplit/index.html
@@ -1,2 +1,11 @@
<h1>This should be red</h1>
<h2>This should be blue</h2>

<p class="dynamic">This should be green</p>
<p class="inline">This should not be yellow</p>
<p class="dynamic-inline"></p>
<p class="mod">This should be yellow</p>
<p class="dynamic-module"></p>

<script type="module" src="/main.js"></script>
<div id="app"></div>
3 changes: 3 additions & 0 deletions packages/playground/css-codesplit/inline.css
@@ -0,0 +1,3 @@
.inline {
color: yellow;
}
15 changes: 12 additions & 3 deletions packages/playground/css-codesplit/main.js
@@ -1,6 +1,15 @@
import './style.css'
import './main.css'

document.getElementById(
'app'
).innerHTML = `<h1>This should be red</h1><h2>This should be blue</h2>`
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)
})
3 changes: 3 additions & 0 deletions packages/playground/css-codesplit/mod.module.css
@@ -0,0 +1,3 @@
.mod {
color: yellow;
}
19 changes: 18 additions & 1 deletion packages/playground/css/__tests__/css.spec.ts
Expand Up @@ -342,6 +342,12 @@ test('PostCSS dir-dependency', async () => {
}
})

// skip because #8471 is reverted
test.skip('import dependency includes css import', async () => {
expect(await getColor('.css-js-dep')).toBe('green')
expect(await getColor('.css-js-dep-module')).toBe('green')
})

test('URL separation', async () => {
const urlSeparated = await page.$('.url-separated')
const baseUrl = 'url(images/dog.webp)'
Expand Down Expand Up @@ -417,5 +423,16 @@ test("relative path rewritten in Less's data-uri", async () => {
test('PostCSS source.input.from includes query', async () => {
const code = await page.textContent('.postcss-source-input')
// should resolve assets
expect(code).toContain('/postcss-source-input.css?query=foo')
expect(code).toContain(
isBuild
? '/postcss-source-input.css?used&query=foo'
: '/postcss-source-input.css?query=foo'
)
})

// skip because #8471 is reverted
test.skip('aliased css has content', async () => {
expect(await getColor('.aliased')).toBe('blue')
expect(await page.textContent('.aliased-content')).toMatch('.aliased')
expect(await getColor('.aliased-module')).toBe('blue')
})
3 changes: 3 additions & 0 deletions packages/playground/css/aliased/bar.module.css
@@ -0,0 +1,3 @@
.aliasedModule {
color: blue;
}
3 changes: 3 additions & 0 deletions packages/playground/css/aliased/foo.css
@@ -0,0 +1,3 @@
.aliased {
color: blue;
}
3 changes: 3 additions & 0 deletions packages/playground/css/css-js-dep/bar.module.css
@@ -0,0 +1,3 @@
.cssJsDepModule {
color: green;
}
3 changes: 3 additions & 0 deletions packages/playground/css/css-js-dep/foo.css
@@ -0,0 +1,3 @@
.css-js-dep {
color: green;
}
4 changes: 4 additions & 0 deletions packages/playground/css/css-js-dep/index.js
@@ -0,0 +1,4 @@
import './foo.css'
import barModuleClasses from './bar.module.css'

export { barModuleClasses }
7 changes: 7 additions & 0 deletions packages/playground/css/css-js-dep/package.json
@@ -0,0 +1,7 @@
{
"name": "css-js-dep",
"private": true,
"type": "module",
"version": "1.0.0",
"main": "index.js"
}
12 changes: 12 additions & 0 deletions packages/playground/css/index.html
Expand Up @@ -114,6 +114,13 @@ <h1>CSS</h1>
PostCSS dir-dependency (file 2): this should be grey too
</p>

<p class="css-js-dep">
import dependency includes 'import "./foo.css"': this should be green
</p>
<p class="css-js-dep-module">
import dependency includes 'import "./bar.module.css"': this should be green
</p>

<p class="url-separated">
URL separation preservation: should have valid background-image
</p>
Expand All @@ -138,6 +145,11 @@ <h1>CSS</h1>

<p>PostCSS source.input.from. Should include query</p>
<pre class="postcss-source-input"></pre>

<p>Aliased</p>
<p class="aliased">import '#alias': this should be blue</p>
<pre class="aliased-content"></pre>
<p class="aliased-module">import '#alias-module': this should be blue</p>
</div>

<script type="module" src="./main.js"></script>
12 changes: 12 additions & 0 deletions packages/playground/css/main.js
Expand Up @@ -47,6 +47,11 @@ text('.charset-css', charset)
import './dep.css'
import './glob-dep.css'

import { barModuleClasses } from 'css-js-dep'
document
.querySelector('.css-js-dep-module')
.classList.add(barModuleClasses.cssJsDepModule)

function text(el, text) {
document.querySelector(el).textContent = text
}
Expand Down Expand Up @@ -90,3 +95,10 @@ text('.imported-css-globEager', JSON.stringify(globEager, null, 2))

import postcssSourceInput from './postcss-source-input.css?query=foo'
text('.postcss-source-input', postcssSourceInput)

import aliasContent from '#alias'
text('.aliased-content', aliasContent)
import aliasModule from '#alias-module'
document
.querySelector('.aliased-module')
.classList.add(aliasModule.aliasedModule)
6 changes: 5 additions & 1 deletion packages/playground/css/package.json
Expand Up @@ -6,7 +6,11 @@
"dev": "vite",
"build": "vite build",
"debug": "node --inspect-brk ../../vite/bin/vite",
"preview": "vite preview"
"preview": "vite preview",
"postinstall": "ts-node ../../../scripts/patchFileDeps.ts"
},
"dependencies": {
"css-js-dep": "file:./css-js-dep"
},
"devDependencies": {
"css-dep": "link:./css-dep",
Expand Down
4 changes: 3 additions & 1 deletion packages/playground/css/vite.config.js
Expand Up @@ -10,7 +10,9 @@ module.exports = {
resolve: {
alias: {
'@': __dirname,
spacefolder: __dirname + '/folder with space'
spacefolder: __dirname + '/folder with space',
'#alias': __dirname + '/aliased/foo.css',
'#alias-module': __dirname + '/aliased/bar.module.css'
}
},
css: {
Expand Down
7 changes: 5 additions & 2 deletions packages/vite/src/node/importGlob.ts
Expand Up @@ -9,6 +9,7 @@ import {
preloadMarker,
preloadMethod
} from './plugins/importAnalysisBuild'
import { isCSSRequest } from './plugins/css'
import {
blankReplacer,
cleanUrl,
Expand Down Expand Up @@ -149,14 +150,16 @@ export async function transformImportGlob(
await fsp.readFile(path.join(base, files[i]), 'utf-8')
)},`
} else {
const importeeUrl = isCSSRequest(importee) ? `${importee}?used` : importee
if (isEager) {
const identifier = `__glob_${importIndex}_${i}`
// css imports injecting a ?used query to export the css string
importsString += `import ${
isEagerDefault ? `` : `* as `
}${identifier} from ${JSON.stringify(importee)};`
}${identifier} from ${JSON.stringify(importeeUrl)};`
entries += ` ${JSON.stringify(file)}: ${identifier},`
} else {
let imp = `import(${JSON.stringify(importee)})`
let imp = `import(${JSON.stringify(importeeUrl)})`
if (!normalizeUrl && preload) {
imp =
`(${isModernFlag}` +
Expand Down
24 changes: 12 additions & 12 deletions packages/vite/src/node/plugins/css.ts
Expand Up @@ -102,6 +102,7 @@ const htmlProxyRE = /(\?|&)html-proxy\b/
const commonjsProxyRE = /\?commonjs-proxy/
const inlineRE = /(\?|&)inline\b/
const inlineCSSRE = /(\?|&)inline-css\b/
const usedRE = /(\?|&)used\b/
const varRE = /^var\(/i

const enum PreprocessLang {
Expand Down Expand Up @@ -369,19 +370,18 @@ export function cssPostPlugin(config: ResolvedConfig): Plugin {
}

let code: string
if (modulesCode) {
code = modulesCode
} else {
let content = css
if (config.build.minify) {
content = await minifyCSS(content, config)
if (usedRE.test(id)) {
if (modulesCode) {
code = modulesCode
} else {
let content = css
if (config.build.minify) {
content = await minifyCSS(content, config)
}
code = `export default ${JSON.stringify(content)}`
}
// marking as pure to make it tree-shakable by minifier
// but the module itself is still treated as a non tree-shakable module
// because moduleSideEffects is 'no-treeshake'
code = `export default /* #__PURE__ */ (() => ${JSON.stringify(
content
)})()`
} else {
code = `export default ''`
}

return {
Expand Down
36 changes: 30 additions & 6 deletions packages/vite/src/node/plugins/importAnalysisBuild.ts
Expand Up @@ -5,11 +5,11 @@ import { init, parse as parseImports } from 'es-module-lexer'
import type { OutputChunk, SourceMap } from 'rollup'
import type { RawSourceMap } from '@ampproject/remapping'
import { transformImportGlob } from '../importGlob'
import { combineSourcemaps } from '../utils'
import { bareImportRE, combineSourcemaps } from '../utils'
import type { Plugin } from '../plugin'
import type { ResolvedConfig } from '../config'
import { genSourceMapUrl } from '../server/sourcemap'
import { removedPureCssFilesCache } from './css'
import { isCSSRequest, removedPureCssFilesCache } from './css'

/**
* A flag for injected helpers. This flag will be set to `false` if the output
Expand Down Expand Up @@ -148,9 +148,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
e: end,
ss: expStart,
se: expEnd,
n: specifier,
d: dynamicIndex
} = imports[index]

const isDynamic = dynamicIndex > -1

// import.meta.glob
if (
source.slice(start, end) === 'import.meta' &&
Expand Down Expand Up @@ -188,11 +191,32 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
continue
}

if (dynamicIndex > -1 && insertPreload) {
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
// do not by injecting a ?used query - this allows us to avoid including
// the CSS string when unnecessary (esbuild has trouble tree-shaking
// them)
if (
specifier &&
isCSSRequest(specifier) &&
// 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) &&
// 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, isDynamic ? `'${url}'` : url, {
contentOnly: true
})
}
}

Expand Down
8 changes: 7 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.