mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
fix(vite): extract styles for shared chunks (#25455)
This commit is contained in:
parent
80b1c7077f
commit
c446602529
@ -8,7 +8,7 @@ import type { Component } from '@nuxt/schema'
|
|||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import { findStaticImports } from 'mlly'
|
import { findStaticImports } from 'mlly'
|
||||||
|
|
||||||
import { isCSS } from '../utils'
|
import { isCSS, isVue } from '../utils'
|
||||||
|
|
||||||
interface SSRStylePluginOptions {
|
interface SSRStylePluginOptions {
|
||||||
srcDir: string
|
srcDir: string
|
||||||
@ -107,25 +107,31 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
renderChunk (_code, chunk) {
|
renderChunk (_code, chunk) {
|
||||||
if (!chunk.facadeModuleId) { return null }
|
const isEntry = chunk.facadeModuleId === options.entry
|
||||||
|
if (isEntry) {
|
||||||
// 'Teleport' CSS chunks that made it into the bundle on the client side
|
options.clientCSSMap[chunk.facadeModuleId!] ||= new Set()
|
||||||
// to be inlined on server rendering
|
|
||||||
if (options.mode === 'client') {
|
|
||||||
options.clientCSSMap[chunk.facadeModuleId] ||= new Set()
|
|
||||||
for (const id of chunk.moduleIds) {
|
|
||||||
if (isCSS(id)) {
|
|
||||||
options.clientCSSMap[chunk.facadeModuleId].add(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
for (const moduleId of [chunk.facadeModuleId, ...chunk.moduleIds].filter(Boolean) as string[]) {
|
||||||
|
// 'Teleport' CSS chunks that made it into the bundle on the client side
|
||||||
|
// to be inlined on server rendering
|
||||||
|
if (options.mode === 'client') {
|
||||||
|
options.clientCSSMap[moduleId] ||= new Set()
|
||||||
|
if (isCSS(moduleId)) {
|
||||||
|
// Vue files can (also) be their own entrypoints as they are tracked separately
|
||||||
|
if (isVue(moduleId)) {
|
||||||
|
options.clientCSSMap[moduleId].add(moduleId)
|
||||||
|
}
|
||||||
|
// This is required to track CSS in entry chunk
|
||||||
|
if (isEntry) {
|
||||||
|
options.clientCSSMap[chunk.facadeModuleId!].add(moduleId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
const id = relativeToSrcDir(chunk.facadeModuleId)
|
const relativePath = relativeToSrcDir(moduleId)
|
||||||
for (const file in chunk.modules) {
|
|
||||||
const relativePath = relativeToSrcDir(file)
|
|
||||||
if (relativePath in cssMap) {
|
if (relativePath in cssMap) {
|
||||||
cssMap[relativePath].inBundle = cssMap[relativePath].inBundle ?? !!id
|
cssMap[relativePath].inBundle = cssMap[relativePath].inBundle ?? ((isVue(moduleId) && relativeToSrcDir(moduleId)) || isEntry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { hash } from 'ohash'
|
import { hash } from 'ohash'
|
||||||
|
|
||||||
|
export { isVue } from '../../../nuxt/src/core/utils/plugins'
|
||||||
|
|
||||||
export function uniq<T> (arr: T[]): T[] {
|
export function uniq<T> (arr: T[]): T[] {
|
||||||
return Array.from(new Set(arr))
|
return Array.from(new Set(arr))
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ describe('pages', () => {
|
|||||||
// should apply attributes to client-only components
|
// should apply attributes to client-only components
|
||||||
expect(html).toContain('<div style="color:red;" class="client-only"></div>')
|
expect(html).toContain('<div style="color:red;" class="client-only"></div>')
|
||||||
// should render server-only components
|
// should render server-only components
|
||||||
expect(html.replace(/ data-island-uid="[^"]*"/, '')).toContain('<div class="server-only" style="background-color:gray;"> server-only component </div>')
|
expect(html.replace(/ data-island-uid="[^"]*"/, '')).toContain('<div class="server-only" style="background-color:gray;"> server-only component <div> server-only component child (non-server-only) </div></div>')
|
||||||
// should register global components automatically
|
// should register global components automatically
|
||||||
expect(html).toContain('global component registered automatically')
|
expect(html).toContain('global component registered automatically')
|
||||||
expect(html).toContain('global component via suffix')
|
expect(html).toContain('global component via suffix')
|
||||||
@ -1382,6 +1382,8 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
|||||||
'{--assets:"assets"}', // <script>
|
'{--assets:"assets"}', // <script>
|
||||||
'{--postcss:"postcss"}', // <style lang=postcss>
|
'{--postcss:"postcss"}', // <style lang=postcss>
|
||||||
'{--scoped:"scoped"}', // <style lang=css>
|
'{--scoped:"scoped"}', // <style lang=css>
|
||||||
|
'{--shared-component:"shared-component"}', // styles in a chunk shared between pages
|
||||||
|
'{--server-only-child:"server-only-child"}', // child of a server-only component
|
||||||
'{--server-only:"server-only"}' // server-only component not in client build
|
'{--server-only:"server-only"}' // server-only component not in client build
|
||||||
// TODO: ideally both client/server components would have inlined css when used
|
// TODO: ideally both client/server components would have inlined css when used
|
||||||
// '{--client-only:"client-only"}', // client-only component not in server build
|
// '{--client-only:"client-only"}', // client-only component not in server build
|
||||||
@ -1392,7 +1394,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
|||||||
it('should inline styles', async () => {
|
it('should inline styles', async () => {
|
||||||
const html = await $fetch('/styles')
|
const html = await $fetch('/styles')
|
||||||
for (const style of inlinedCSS) {
|
for (const style of inlinedCSS) {
|
||||||
expect(html).toContain(style)
|
expect.soft(html).toContain(style)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1403,7 +1405,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
|||||||
]
|
]
|
||||||
const html = await $fetch('/route-rules/spa')
|
const html = await $fetch('/route-rules/spa')
|
||||||
for (const style of globalCSS) {
|
for (const style of globalCSS) {
|
||||||
expect(html).toContain(style)
|
expect.soft(html).toContain(style)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1414,7 +1416,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
|
|||||||
expect(files.map(m => m.replace(/\.\w+(\.\w+)$/, '$1'))).toContain('css-only-asset.svg')
|
expect(files.map(m => m.replace(/\.\w+(\.\w+)$/, '$1'))).toContain('css-only-asset.svg')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should not include inlined CSS in generated CSS file', async () => {
|
it('should not include inlined CSS in generated CSS file', async () => {
|
||||||
const html: string = await $fetch('/styles')
|
const html: string = await $fetch('/styles')
|
||||||
const cssFiles = new Set([...html.matchAll(/<link [^>]*href="([^"]*\.css)">/g)].map(m => m[1]))
|
const cssFiles = new Set([...html.matchAll(/<link [^>]*href="([^"]*\.css)">/g)].map(m => m[1]))
|
||||||
let css = ''
|
let css = ''
|
||||||
@ -1950,6 +1952,11 @@ describe('component islands', () => {
|
|||||||
expect(result.head).toMatchInlineSnapshot(`
|
expect(result.head).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
"link": [
|
"link": [
|
||||||
|
{
|
||||||
|
"href": "/_nuxt/components/SharedComponent.vue?vue&type=style&index=0&scoped=3ee84738&lang.css",
|
||||||
|
"key": "island-link",
|
||||||
|
"rel": "stylesheet",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"href": "/_nuxt/components/islands/PureComponent.vue?vue&type=style&index=0&scoped=c0c0cf89&lang.css",
|
"href": "/_nuxt/components/islands/PureComponent.vue?vue&type=style&index=0&scoped=c0c0cf89&lang.css",
|
||||||
"key": "island-link",
|
"key": "island-link",
|
||||||
|
@ -5,6 +5,7 @@ prerenderRoutes(['/some/url/from/server-only/component'])
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
server-only component
|
server-only component
|
||||||
|
<ServerOnlyComponentChild />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
11
test/fixtures/basic/components/ServerOnlyComponentChild.vue
vendored
Normal file
11
test/fixtures/basic/components/ServerOnlyComponentChild.vue
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
server-only component child (non-server-only)
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--server-only-child: 'server-only-child';
|
||||||
|
}
|
||||||
|
</style>
|
9
test/fixtures/basic/components/SharedComponent.vue
vendored
Normal file
9
test/fixtures/basic/components/SharedComponent.vue
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<span class="shared-component" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.shared-component {
|
||||||
|
--shared-component: 'shared-component';
|
||||||
|
}
|
||||||
|
</style>
|
1
test/fixtures/basic/pages/styles.vue
vendored
1
test/fixtures/basic/pages/styles.vue
vendored
@ -3,6 +3,7 @@
|
|||||||
<ClientOnlyScript />
|
<ClientOnlyScript />
|
||||||
<FunctionalComponent />
|
<FunctionalComponent />
|
||||||
<ServerOnlyComponent />
|
<ServerOnlyComponent />
|
||||||
|
<SharedComponent />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
2
test/fixtures/basic/pages/vueuse-head.vue
vendored
2
test/fixtures/basic/pages/vueuse-head.vue
vendored
@ -21,5 +21,7 @@ useLegacyVueUseHead()
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h1>VueUse head polyfill test</h1>
|
<h1>VueUse head polyfill test</h1>
|
||||||
|
<!-- This component is only here to make it a shared chunk for test in `styles.vue` -->
|
||||||
|
<SharedComponent />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
Reference in New Issue
Block a user