Skip to content

Commit

Permalink
fix(vite): improve build hash generation, close #158
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Nov 22, 2021
1 parent be8c24f commit 2bac3a1
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 43 deletions.
7 changes: 6 additions & 1 deletion packages/plugins-common/layers.ts
@@ -1,7 +1,6 @@
export const VIRTUAL_ENTRY_ALIAS = [
/^(?:virtual:)?uno(?::(.+))?\.css(\?.*)?$/,
]
export const LAYER_PLACEHOLDER_RE = /(\\?")?#--unocss--\s*{\s*layer\s*:\s*(.+?);?\s*}/g
export const LAYER_MARK_ALL = '__ALL__'

export function resolveId(id: string) {
Expand All @@ -21,6 +20,12 @@ export function resolveId(id: string) {
}
}

export const LAYER_PLACEHOLDER_RE = /(\\?")?#--unocss--\s*{\s*layer\s*:\s*(.+?);?\s*}/g
export function getLayerPlaceholder(layer: string) {
return `#--unocss--{layer:${layer}}`
}

export const HASH_PLACEHOLDER_RE = /#--unocss-hash--\s*{\s*content\s*:\s*"(.+?)";?\s*}/g
export function getHashPlaceholder(hash: string) {
return `#--unocss-hash--{content:"${hash}"}`
}
File renamed without changes.
93 changes: 54 additions & 39 deletions packages/vite/src/modes/global/build.ts
@@ -1,24 +1,29 @@
import type { Plugin } from 'vite'
import { createFilter } from '@rollup/pluginutils'
import { getHash, getPath } from '../../utils'
import { getHash, getPath } from '../../../../plugins-common/utils'
import { UnocssPluginContext } from '../../context'
import { defaultExclude, defaultInclude } from '../../../../plugins-common/defaults'
import { LAYER_MARK_ALL, getLayerPlaceholder, LAYER_PLACEHOLDER_RE, resolveId } from '../../../../plugins-common/layers'
import { LAYER_MARK_ALL, getLayerPlaceholder, LAYER_PLACEHOLDER_RE, resolveId, HASH_PLACEHOLDER_RE, getHashPlaceholder } from '../../../../plugins-common/layers'

export function GlobalModeBuildPlugin({ uno, config, scan, tokens }: UnocssPluginContext): Plugin[] {
export function GlobalModeBuildPlugin({ uno, config, scan, tokens, modules }: UnocssPluginContext): Plugin[] {
const filter = createFilter(
config.include || defaultInclude,
config.exclude || defaultExclude,
)

const tasks: Promise<any>[] = []
const entries = new Map<string, string>()
const vfsLayerMap = new Map<string, string>()
let tasks: Promise<any>[] = []
let cssPlugin: Plugin | undefined

return [
{
name: 'unocss:global:build:scan',
apply: 'build',
enforce: 'pre',
buildStart() {
tasks = []
vfsLayerMap.clear()
},
transform(code, id) {
if (filter(id))
tasks.push(scan(code, id))
Expand All @@ -33,23 +38,56 @@ export function GlobalModeBuildPlugin({ uno, config, scan, tokens }: UnocssPlugi
resolveId(id) {
const entry = resolveId(id)
if (entry) {
entries.set(entry.id, entry.layer)
vfsLayerMap.set(entry.id, entry.layer)
return entry.id
}
},
load(id) {
const layer = entries.get(getPath(id))
const layer = vfsLayerMap.get(getPath(id))
if (layer)
return getLayerPlaceholder(layer)
},
// we inject a hash to chunk before the dist hash calculation to make sure
// the hash is different when unocss changes
async renderChunk(_, chunk) {
const chunks = Object.keys(chunk.modules).filter(i => modules.has(i))

if (!chunks.length)
return null

const tokens = new Set<string>()
await Promise.all(chunks.map(c => uno.applyExtractors(modules.get(c) || '', c, tokens)))
const { css } = await uno.generate(tokens, { minify: true })
if (!css)
return null
const hash = getHash(css)

// fool the css plugin to generate the css in corresponding chunk
const fakeCssId = `${chunk.fileName}-unocss-hash.css`
// @ts-ignore
await cssPlugin!.transform(getHashPlaceholder(hash), fakeCssId)
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
},
configResolved(config) {
cssPlugin = config.plugins.find(i => i.name === 'vite:css-post')
},
enforce: 'post',
async generateBundle(options, bundle) {
// rewrite the css placeholders
async generateBundle(_, bundle) {
const files = Object.keys(bundle)
const cssFiles = files
.filter(i => i.endsWith('.css'))
Expand All @@ -61,45 +99,22 @@ export function GlobalModeBuildPlugin({ uno, config, scan, tokens }: UnocssPlugi
const result = await uno.generate(tokens, { minify: true })
let replaced = false

const cssReplacedMap: Record<string, string> = {}
for (const file of cssFiles) {
const chunk = bundle[file]
if (chunk.type === 'asset' && typeof chunk.source === 'string') {
let currentReplaced = false
chunk.source = chunk.source.replace(LAYER_PLACEHOLDER_RE, (_, __, layer) => {
currentReplaced = true
replaced = true
return layer === LAYER_MARK_ALL
? result.getLayers(undefined, Array.from(entries.values()))
: result.getLayer(layer) || ''
})
// recalculate hash
if (currentReplaced) {
const newName = chunk.fileName.replace(/\.(\w+)\.css$/, `.${getHash(chunk.source)}.css`)
cssReplacedMap[chunk.fileName] = newName
chunk.fileName = newName
}
chunk.source = chunk.source
.replace(HASH_PLACEHOLDER_RE, '')
.replace(LAYER_PLACEHOLDER_RE, (_, __, layer) => {
replaced = true
return layer === LAYER_MARK_ALL
? result.getLayers(undefined, Array.from(vfsLayerMap.values()))
: result.getLayer(layer) || ''
})
}
}

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'))

// rewrite updated CSS hash
const entires = Object.entries(cssReplacedMap)
if (!entires.length)
return
for (const file of files) {
const chunk = bundle[file]
if (chunk.type === 'chunk' && typeof chunk.code === 'string') {
for (const [k, v] of entires)
chunk.code = chunk.code.replace(k, v)
}
else if (chunk.type === 'asset' && typeof chunk.source === 'string') {
for (const [k, v] of entires)
chunk.source = chunk.source.replace(k, v)
}
}
},
},
]
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/modes/global/dev.ts
@@ -1,6 +1,6 @@
import type { Plugin, ViteDevServer } from 'vite'
import { createFilter } from '@rollup/pluginutils'
import { getPath } from '../../utils'
import { getPath } from '../../../../plugins-common/utils'
import { UnocssPluginContext } from '../../context'
import { defaultExclude, defaultInclude } from '../../../../plugins-common/defaults'
import { LAYER_MARK_ALL, resolveId } from '../../../../plugins-common/layers'
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/modes/per-module.ts
@@ -1,6 +1,6 @@
import type { Plugin, ViteDevServer } from 'vite'
import { createFilter } from '@rollup/pluginutils'
import { getHash } from '../utils'
import { getHash } from '../../../plugins-common/utils'
import { UnocssPluginContext } from '../context'
import { defaultExclude, defaultInclude } from '../../../plugins-common/defaults'

Expand Down
2 changes: 1 addition & 1 deletion packages/webpack/src/index.ts
Expand Up @@ -3,7 +3,7 @@ import { loadConfig } from '@unocss/config'
import { createUnplugin, UnpluginOptions, ResolvedUnpluginOptions } from 'unplugin'
import { createFilter } from '@rollup/pluginutils'
import WebpackSources from 'webpack-sources'
import { getPath } from '../../vite/src/utils'
import { getPath } from '../../plugins-common/utils'
import { defaultInclude, defaultExclude } from '../../plugins-common/defaults'
import { resolveId, LAYER_MARK_ALL, LAYER_PLACEHOLDER_RE, getLayerPlaceholder } from '../../plugins-common/layers'
import { PluginOptions } from '../../plugins-common/types'
Expand Down

0 comments on commit 2bac3a1

Please sign in to comment.