Skip to content

Commit

Permalink
fix(vite): extract styles for shared chunks (nuxt#25455)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe authored Jan 28, 2024
1 parent 80b1c70 commit c446602
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 20 deletions.
38 changes: 22 additions & 16 deletions packages/vite/src/plugins/ssr-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { Component } from '@nuxt/schema'
import MagicString from 'magic-string'
import { findStaticImports } from 'mlly'

import { isCSS } from '../utils'
import { isCSS, isVue } from '../utils'

interface SSRStylePluginOptions {
srcDir: string
Expand Down Expand Up @@ -107,25 +107,31 @@ export function ssrStylesPlugin (options: SSRStylePluginOptions): Plugin {
})
},
renderChunk (_code, chunk) {
if (!chunk.facadeModuleId) { return null }

// '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[chunk.facadeModuleId] ||= new Set()
for (const id of chunk.moduleIds) {
if (isCSS(id)) {
options.clientCSSMap[chunk.facadeModuleId].add(id)
const isEntry = chunk.facadeModuleId === options.entry
if (isEntry) {
options.clientCSSMap[chunk.facadeModuleId!] ||= new Set()
}
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
}
return
}

const id = relativeToSrcDir(chunk.facadeModuleId)
for (const file in chunk.modules) {
const relativePath = relativeToSrcDir(file)
const relativePath = relativeToSrcDir(moduleId)
if (relativePath in cssMap) {
cssMap[relativePath].inBundle = cssMap[relativePath].inBundle ?? !!id
cssMap[relativePath].inBundle = cssMap[relativePath].inBundle ?? ((isVue(moduleId) && relativeToSrcDir(moduleId)) || isEntry)
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/vite/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { hash } from 'ohash'

export { isVue } from '../../../nuxt/src/core/utils/plugins'

export function uniq<T> (arr: T[]): T[] {
return Array.from(new Set(arr))
}
Expand Down
15 changes: 11 additions & 4 deletions test/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ describe('pages', () => {
// should apply attributes to client-only components
expect(html).toContain('<div style="color:red;" class="client-only"></div>')
// 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
expect(html).toContain('global component registered automatically')
expect(html).toContain('global component via suffix')
Expand Down Expand Up @@ -1382,6 +1382,8 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
'{--assets:"assets"}', // <script>
'{--postcss:"postcss"}', // <style lang=postcss>
'{--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
// TODO: ideally both client/server components would have inlined css when used
// '{--client-only:"client-only"}', // client-only component not in server build
Expand All @@ -1392,7 +1394,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
it('should inline styles', async () => {
const html = await $fetch('/styles')
for (const style of inlinedCSS) {
expect(html).toContain(style)
expect.soft(html).toContain(style)
}
})

Expand All @@ -1403,7 +1405,7 @@ describe.skipIf(isDev() || isWebpack)('inlining component styles', () => {
]
const html = await $fetch('/route-rules/spa')
for (const style of globalCSS) {
expect(html).toContain(style)
expect.soft(html).toContain(style)
}
})

Expand All @@ -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')
})

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 cssFiles = new Set([...html.matchAll(/<link [^>]*href="([^"]*\.css)">/g)].map(m => m[1]))
let css = ''
Expand Down Expand Up @@ -1950,6 +1952,11 @@ describe('component islands', () => {
expect(result.head).toMatchInlineSnapshot(`
{
"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",
"key": "island-link",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ prerenderRoutes(['/some/url/from/server-only/component'])
<template>
<div>
server-only component
<ServerOnlyComponentChild />
</div>
</template>

Expand Down
11 changes: 11 additions & 0 deletions test/fixtures/basic/components/ServerOnlyComponentChild.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions test/fixtures/basic/components/SharedComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<template>
<span class="shared-component" />
</template>

<style scoped>
.shared-component {
--shared-component: 'shared-component';
}
</style>
1 change: 1 addition & 0 deletions test/fixtures/basic/pages/styles.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ClientOnlyScript />
<FunctionalComponent />
<ServerOnlyComponent />
<SharedComponent />
</div>
</template>

Expand Down
2 changes: 2 additions & 0 deletions test/fixtures/basic/pages/vueuse-head.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ useLegacyVueUseHead()
<template>
<div>
<h1>VueUse head polyfill test</h1>
<!-- This component is only here to make it a shared chunk for test in `styles.vue` -->
<SharedComponent />
</div>
</template>

0 comments on commit c446602

Please sign in to comment.