forked from unocss/unocss
/
build.ts
172 lines (156 loc) · 5.49 KB
/
build.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import type { Plugin } from 'vite'
import type { UnocssPluginContext } from '@unocss/core'
import {
HASH_PLACEHOLDER_RE, LAYER_MARK_ALL, LAYER_PLACEHOLDER_RE,
RESOLVED_ID_RE,
getHash,
getHashPlaceholder,
getLayerPlaceholder,
getPath,
replaceAsync,
resolveId,
} from '../../integration'
import type { VitePluginConfig } from '../../types'
export function GlobalModeBuildPlugin({ uno, ready, extract, tokens, modules, filter, getConfig }: UnocssPluginContext<VitePluginConfig>): Plugin[] {
const vfsLayerMap = new Map<string, string>()
const layerImporterMap = new Map<string, string>()
let tasks: Promise<any>[] = []
const cssPostPlugins = new Map<string | undefined, Plugin | undefined>()
const cssPlugins = new Map<string | undefined, Plugin | undefined>()
async function transformCSS(css: string, id: string, dir: string | undefined) {
const {
postcss = true,
} = await getConfig()
if (!cssPlugins.get(dir) || !postcss)
return css
// @ts-expect-error no this context
const result = await cssPlugins.get(dir).transform(css, id)
if (!result)
return css
if (typeof result === 'string')
css = result
else if (result.code)
css = result.code
css = css.replace(/[\n\r]/g, '')
return css
}
return [
{
name: 'unocss:global:build:scan',
apply: 'build',
enforce: 'pre',
buildStart() {
tasks = []
},
transform(code, id) {
if (filter(code, id))
tasks.push(extract(code, id))
return null
},
transformIndexHtml: {
enforce: 'pre',
transform(code, { filename }) {
tasks.push(extract(code, filename))
},
},
resolveId(id, importer) {
const entry = resolveId(id)
if (entry) {
vfsLayerMap.set(entry.id, entry.layer)
if (importer)
layerImporterMap.set(importer, entry.id)
return entry.id
}
},
load(id) {
const layer = vfsLayerMap.get(getPath(id))
if (layer)
return getLayerPlaceholder(layer)
},
moduleParsed({ id, importedIds }) {
if (!layerImporterMap.has(id))
return
const layerKey = layerImporterMap.get(id)!
if (!importedIds.includes(layerKey!)) {
layerImporterMap.delete(id)
vfsLayerMap.delete(layerKey)
}
},
async configResolved(config) {
cssPostPlugins.set(config.build.outDir, config.plugins.find(i => i.name === 'vite:css-post'))
cssPlugins.set(config.build.outDir, config.plugins.find(i => i.name === 'vite:css'))
await ready
},
// we inject a hash to chunk before the dist hash calculation to make sure
// the hash is different when unocss changes
async renderChunk(_, chunk, opt) {
if (!cssPostPlugins.get(opt.dir))
return null
const chunks = Object.keys(chunk.modules).filter(i => modules.has(i))
if (!chunks.length)
return null
const fakeCssId = `${chunk.fileName}-unocss-hash.css`
const tokens = new Set<string>()
await Promise.all(chunks.map(c => uno.applyExtractors(modules.get(c) || '', c, tokens)))
let { css } = await uno.generate(tokens, { minify: true })
if (!css)
return null
// skip hash generation on non-entry chunk
if (!Object.keys(chunk.modules).some(i => i.match(RESOLVED_ID_RE)))
return null
css = await transformCSS(css, fakeCssId, opt.dir)
const hash = getHash(css)
// @ts-expect-error no this context
await cssPostPlugins.get(opt.dir).transform(getHashPlaceholder(hash), fakeCssId)
// fool the css plugin to generate the css in corresponding chunk
chunk.modules[fakeCssId] = {
code: null,
originalLength: 0,
removedExports: [],
renderedExports: [],
renderedLength: 0,
}
return null
},
},
{
name: 'unocss:global:build:generate',
apply(options, { command }) {
return command === 'build' && !options.build?.ssr
},
enforce: 'post',
// rewrite the css placeholders
async generateBundle(opts, bundle) {
const files = Object.keys(bundle)
const cssFiles = files
.filter(i => i.endsWith('.css'))
if (!cssFiles.length)
return
if (!vfsLayerMap.size) {
const msg = '[unocss] entry module not found, have you add `import \'uno.css\'` in your main entry?'
this.warn(msg)
return
}
await Promise.all(tasks)
const result = await uno.generate(tokens, { minify: true })
let replaced = false
for (const file of cssFiles) {
const chunk = bundle[file]
if (chunk.type === 'asset' && typeof chunk.source === 'string') {
const css = chunk.source
.replace(HASH_PLACEHOLDER_RE, '')
chunk.source = await replaceAsync(css, LAYER_PLACEHOLDER_RE, async (_, __, layer) => {
replaced = true
return await transformCSS(layer === LAYER_MARK_ALL
? result.getLayers(undefined, Array.from(vfsLayerMap.values()))
: result.getLayer(layer) || '', `${chunk.fileName}.css`,
opts.dir)
})
}
}
if (!replaced)
this.error(new Error('[unocss] does not found CSS placeholder in the generated chunks,\nthis is likely an internal bug of unocss vite plugin'))
},
},
]
}