Skip to content

Commit fefd1d4

Browse files
committed
fix: support ssg
1 parent 6b0b5c1 commit fefd1d4

File tree

8 files changed

+206
-176
lines changed

8 files changed

+206
-176
lines changed

package.json

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,27 @@
3030
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
3131
},
3232
"dependencies": {
33-
"@nuxt/kit": "^3.12.4",
34-
"csso": "^5.0.5",
35-
"ohash": "^1.1.3",
33+
"@nuxt/kit": "^3.13.2",
34+
"css-tree": "^3.0.0",
35+
"cssnano": "^7.0.6",
36+
"ohash": "^1.1.4",
37+
"pathe": "^1.1.2",
3638
"purgecss": "^6.0.0",
3739
"purgecss-from-html": "^6.0.0"
3840
},
3941
"devDependencies": {
40-
"@nuxt/devtools": "^1.3.9",
42+
"@nuxt/devtools": "^1.6.0",
4143
"@nuxt/eslint-config": "^0.3.13",
42-
"@nuxt/module-builder": "^0.8.1",
43-
"@nuxt/schema": "^3.12.4",
44-
"@nuxt/test-utils": "^3.14.1",
45-
"@types/node": "^20.14.11",
46-
"changelogen": "^0.5.5",
47-
"eslint": "^9.7.0",
48-
"nuxt": "^3.12.4",
44+
"@nuxt/module-builder": "^0.8.4",
45+
"@nuxt/schema": "^3.13.2",
46+
"@nuxt/test-utils": "^3.14.4",
47+
"@types/node": "^20.16.15",
48+
"changelogen": "^0.5.7",
49+
"eslint": "^9.13.0",
50+
"nuxt": "^3.13.2",
4951
"typescript": "latest",
50-
"vitest": "^2.0.3",
51-
"vue-tsc": "^2.0.26"
52+
"vitest": "^2.1.3",
53+
"vue-tsc": "^2.1.6"
5254
},
5355
"keywords": [
5456
"style",

pnpm-lock.yaml

Lines changed: 100 additions & 84 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/module.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import fs from 'node:fs/promises'
22
import { existsSync } from 'node:fs'
3-
import { join, isAbsolute } from 'node:path'
3+
import { join, isAbsolute } from 'pathe'
44
import { hash } from 'ohash'
55
import { addPlugin, addServerPlugin, addTemplate, addTypeTemplate, createResolver, defineNuxtModule } from '@nuxt/kit'
66

@@ -71,11 +71,13 @@ export default defineNuxtModule<ModuleOptions>({
7171
cacheControl: 'public, max-age=31536000, immutable',
7272
},
7373
async setup(_options, nuxt) {
74-
const resolver = createResolver(import.meta.url)
75-
76-
addPlugin(resolver.resolve('./runtime/plugin.server'))
74+
// disable module when spa
75+
if (!nuxt.options.ssr) {
76+
return
77+
}
7778

78-
addServerPlugin(resolver.resolve('./runtime/server/plugins/style-extractor'))
79+
const resolver = createResolver(import.meta.url)
80+
const transformFile = getTransformFile()
7981

8082
const cacheDir = join(nuxt.options.buildDir, 'cache/_css/')
8183

@@ -89,17 +91,20 @@ export default defineNuxtModule<ModuleOptions>({
8991
baseName: '_css',
9092
dir: cacheDir,
9193
})
92-
const isGenerate = nuxt.options.dev === false && nuxt.options.nitro.static
93-
if (isGenerate) {
94-
nuxt.options.nitro.publicAssets ??= []
95-
nuxt.options.nitro.publicAssets.push({
96-
baseURL: '/_css',
97-
dir: cacheDir,
98-
maxAge: 0,
99-
})
94+
nuxt.options.nitro.publicAssets ??= []
95+
nuxt.options.nitro.publicAssets.push({
96+
baseURL: '/_css',
97+
dir: cacheDir,
98+
maxAge: 0,
99+
})
100+
101+
nuxt.options.nitro.virtual ??= {}
102+
nuxt.options.nitro.virtual['#style-extractor/nuxt-style-extractor-transform.js'] = () => {
103+
return fs.readFile(transformFile, 'utf-8')
100104
}
105+
addPlugin(resolver.resolve(`./runtime/plugin.server`))
101106

102-
const transformFile = getTransformFile()
107+
addServerPlugin(resolver.resolve(`./runtime/server/plugins/style-extractor`))
103108

104109
addTemplate({
105110
filename: 'nuxt-style-extractor-config-hash.js',

src/runtime/nuxt-style-extractor.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,14 @@ declare module '#build/nuxt-style-extractor-transform.js' {
1212
const optimiseCss: OptimiseCss
1313
export default optimiseCss
1414
}
15+
16+
declare module '#style-extractor/nuxt-style-extractor-transform.js' {
17+
export interface Options {
18+
html: string
19+
css: string
20+
name: string
21+
}
22+
type OptimiseCss = (options: Options) => Promise<string> | string
23+
const optimiseCss: OptimiseCss
24+
export default optimiseCss
25+
}

src/runtime/plugin.server.ts

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { hash } from 'ohash'
22
import { defineNuxtPlugin } from '#imports'
3-
import optimiseCss from '#build/nuxt-style-extractor-transform.js'
43
import { configHash } from '#build/nuxt-style-extractor-config-hash.js'
54

65
export default defineNuxtPlugin({
76
name: 'style-extractor',
7+
parallel: true,
88
setup(nuxt) {
99
nuxt.hook('app:rendered', async (nuxtCtx) => {
1010
const html = nuxtCtx.renderResult?.html || ''
@@ -25,48 +25,12 @@ export default defineNuxtPlugin({
2525
}
2626

2727
const name = hash([html, style, configHash]) + '.css'
28-
29-
const oldCss = await $fetch(`/_css/${name}`)
30-
31-
if (oldCss === '') {
32-
return
33-
}
34-
35-
if (oldCss) {
36-
ctx.tags.push({
37-
tag: 'link',
38-
props: {
39-
rel: 'stylesheet',
40-
href: `/_css/${name}`,
41-
},
42-
})
43-
return
44-
}
45-
46-
const css = await optimiseCss({
47-
html,
48-
css: style,
49-
name,
50-
})
51-
52-
await $fetch('/_css', {
53-
body: {
54-
name,
55-
css,
56-
},
57-
method: 'POST',
58-
})
59-
60-
if (css === '') {
61-
return
62-
}
63-
6428
ctx.tags.push({
65-
tag: 'link',
29+
tag: 'style',
6630
props: {
67-
rel: 'stylesheet',
68-
href: `/_css/${name}`,
31+
'data-style-extractor-name': name,
6932
},
33+
innerHTML: style,
7034
})
7135
},
7236
},
Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,48 @@
1-
import { defineNitroPlugin, defineEventHandler, useStorage, getRouterParam, setHeader, readBody } from '#imports'
1+
import { defineNitroPlugin, defineEventHandler, useStorage, getRouterParam, setHeader } from '#imports'
2+
import optimiseCss from '#style-extractor/nuxt-style-extractor-transform.js'
23

34
export default defineNitroPlugin((nitroApp) => {
5+
const nameReg = /data-style-extractor-name="(.*?)"/
6+
const styleReg = /<style[^>]*>([\s\S]*?)<\/style>/
47
const cacheStorage = useStorage<string>('cache:_css')
58
const assetsStorage = useStorage<string>('assets:_css')
69

7-
nitroApp.router.add('/_css/:name', defineEventHandler(async (event) => {
10+
nitroApp.router.get('/_css/:name', defineEventHandler(async (event) => {
811
const name = getRouterParam(event, 'name')
912
let css = await cacheStorage.getItem(name!)
1013
setHeader(event, 'Content-Type', 'text/css')
1114
if (!css) {
1215
css = await assetsStorage.getItem(name!)
1316
}
17+
1418
return css
1519
}))
1620

17-
nitroApp.router.post('/_css', defineEventHandler(async (event) => {
18-
const body = await readBody<{ name: string, css: string }>(event)
19-
const { name, css } = body
21+
nitroApp.hooks.hook('render:response', async (res) => {
22+
const html: string = res.body
23+
const [_, name] = html.match(nameReg) || []
24+
if (!name) {
25+
return
26+
}
27+
const storage = import.meta.prerender ? assetsStorage : cacheStorage
28+
const item = await storage.getItem(name)
2029

21-
await Promise.all([
22-
cacheStorage.setItem(name, css),
23-
assetsStorage.setItem(name, css),
24-
])
25-
}))
30+
if (item === null) {
31+
const [style, css] = html.match(styleReg) || ['']
32+
33+
const newCss = await optimiseCss({
34+
html,
35+
css,
36+
name,
37+
})
38+
await storage.setItem(name, newCss || '')
39+
if (css) {
40+
res.body = html.replace(style, `<link href="/_css/${name}" rel="stylesheet" />`)
41+
}
42+
return
43+
}
44+
if (item) {
45+
res.body = html.replace(styleReg, `<link href="/_css/${name}" rel="stylesheet" />`)
46+
}
47+
})
2648
})

src/runtime/transforms/best.js

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { minify } from 'csso'
1+
import cssnano from 'cssnano'
22
import { PurgeCSS } from 'purgecss'
33
import purgehtml from 'purgecss-from-html'
44

@@ -19,18 +19,23 @@ async function removeUnusedCss(options) {
1919
return result.css || ''
2020
}
2121

22-
function minifyCss(options) {
23-
const { css } = options
24-
25-
return minify(css || '', {
26-
sourceMap: false,
27-
}).css
22+
let cssnanoCtx
23+
async function minifyCss(options) {
24+
const { css, name } = options
25+
if (!cssnanoCtx) {
26+
cssnanoCtx = cssnano()
27+
}
28+
const result = await cssnanoCtx.process(css, {
29+
from: name,
30+
to: name,
31+
})
32+
return result.css
2833
}
2934

3035
async function optimiseCss(options) {
3136
options.css = await removeUnusedCss(options)
3237

33-
options.css = minifyCss(options)
38+
options.css = await minifyCss(options)
3439

3540
return options.css
3641
}

src/runtime/transforms/minify.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { minify } from 'csso'
1+
import cssnano from 'cssnano'
22

3-
export default (options) => {
4-
const { css } = options
5-
6-
return minify(css || '', {
7-
sourceMap: false,
8-
}).css
3+
let cssnanoCtx
4+
export default async (options) => {
5+
const { css, name } = options
6+
if (!cssnanoCtx) {
7+
cssnanoCtx = cssnano()
8+
}
9+
const result = await cssnanoCtx.process(css, {
10+
from: name,
11+
to: name,
12+
})
13+
return result.css
914
}

0 commit comments

Comments
 (0)