Skip to content

Commit

Permalink
Merge pull request #1373 from didi/feat-unocss-webplugin
Browse files Browse the repository at this point in the history
feat: 修复serve 存在cache uno失效问题
  • Loading branch information
hiyuki authored Jan 9, 2024
2 parents 99b24b6 + eeaec80 commit 8efe2cb
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 3 deletions.
2 changes: 1 addition & 1 deletion packages/unocss-plugin/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const fixRelative = require('@mpxjs/webpack-plugin/lib/utils/fix-relative')
const parseRequest = require('@mpxjs/webpack-plugin/lib/utils/parse-request')
const { has } = require('@mpxjs/webpack-plugin/lib/utils/set')
const MpxWebpackPlugin = require('@mpxjs/webpack-plugin')
const UnoCSSWebpackPlugin = require('@unocss/webpack').default
const UnoCSSWebpackPlugin = require('./web-plugin')
const transformerDirectives = require('@unocss/transformer-directives').default
const transformerVariantGroup = require('@unocss/transformer-variant-group')
const {
Expand Down
39 changes: 39 additions & 0 deletions packages/unocss-plugin/lib/web-plugin/consts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const VIRTUAL_ENTRY_ALIAS = [
/^(?:virtual:)?uno(?::(.+))?\.css(\?.*)?$/
]
const LAYER_MARK_ALL = '__ALL__'

const RESOLVED_ID_WITH_QUERY_RE = /[/\\]__uno(?:(_.*?))?\.css(\?.*)?$/
const RESOLVED_ID_RE = /[/\\]__uno(?:(_.*?))?\.css$/

function resolveId (id) {
if (id.match(RESOLVED_ID_WITH_QUERY_RE)) { return id }

for (const alias of VIRTUAL_ENTRY_ALIAS) {
const match = id.match(alias)
if (match) {
return match[1]
? `/__uno_${match[1]}.css`
: '/__uno.css'
}
}
}

function resolveLayer (id) {
const match = id.match(RESOLVED_ID_RE)
if (match) { return match[1] || LAYER_MARK_ALL }
}

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

module.exports = {
LAYER_MARK_ALL,
LAYER_PLACEHOLDER_RE,
RESOLVED_ID_RE,
getLayerPlaceholder,
resolveLayer,
resolveId
}
127 changes: 127 additions & 0 deletions packages/unocss-plugin/lib/web-plugin/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
const WebpackSources = require('webpack-sources')
const VirtualModulesPlugin = require('webpack-virtual-modules')
const node_path = require('node:path')
const process = require('process')
const fs = require('fs')
const { createContext, getPath, normalizeAbsolutePath } = require('./utils')
const { LAYER_MARK_ALL, LAYER_PLACEHOLDER_RE, RESOLVED_ID_RE, getLayerPlaceholder, resolveId, resolveLayer } = require('./consts')

const PLUGIN_NAME = 'unocss:webpack'
const VIRTUAL_MODULE_PREFIX = node_path.resolve(process.cwd(), '_virtual_')

function WebpackPlugin (configOrPath, defaults) {
return {
apply (compiler) {
const ctx = createContext(configOrPath, defaults)
const { uno, filter } = ctx
const entries = new Set()
const __vfsModules = new Set()
const __vfs = new VirtualModulesPlugin()
compiler.options.plugins.push(__vfs)
compiler.__unoCtx = ctx
// 添加解析虚拟模块插件 import 'uno.css' 并且注入layer代码
const resolverPlugin = {
apply (resolver) {
const target = resolver.ensureHook('resolve')

resolver
.getHook('resolve')
.tapAsync(PLUGIN_NAME, async (request, resolveContext, callback) => {
if (!request.request || normalizeAbsolutePath(request.request).startsWith(VIRTUAL_MODULE_PREFIX)) { return callback() }

const id = normalizeAbsolutePath(request.request)
let resolved = resolveId(id)
if (!resolved || resolved === id) {
return callback()
}
let query = ''
const queryIndex = id.indexOf('?')
if (queryIndex >= 0) {
query = id.slice(queryIndex)
}
entries.add(resolved)
resolved = resolved + query

if (!fs.existsSync(resolved)) {
resolved = normalizeAbsolutePath(
VIRTUAL_MODULE_PREFIX +
encodeURIComponent(resolved)
)
if (!__vfsModules.has(resolved)) {
const layer = getLayer(id)
__vfs.writeModule(resolved, getLayerPlaceholder(layer))
__vfsModules.add(resolved)
}
}
const newRequest = {
...request,
request: resolved
}
resolver.doResolve(target, newRequest, null, resolveContext, callback)
})
}
}
compiler.options.resolve.plugins = compiler.options.resolve.plugins || []
compiler.options.resolve.plugins.push(resolverPlugin)
// transform 提取tokens
compiler.options.module.rules.unshift({
enforce: 'pre',
use: (data) => {
if (data.resource == null) { return [] }

const id = normalizeAbsolutePath(data.resource + (data.resourceQuery || ''))
if (filter('', id) && !id.match(/\.html$/) && !RESOLVED_ID_RE.test(id)) {
return [{
loader: node_path.resolve(__dirname, './transform-loader')
}]
}

return []
}
})

compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.optimizeAssets.tapPromise(PLUGIN_NAME, async () => {
// 可以收集到cache中的tokens,可解决存在cache,二次serve中无法获取tokens的问题
const tokens = new Set()
for (const module of compilation.modules) {
const assetsInfo = module.buildInfo.assetsInfo || new Map()
for (const [, { unocssTokens } = {}] of assetsInfo) {
if (unocssTokens) {
for (const token of unocssTokens) {
tokens.add(token)
}
}
}
}
const files = Object.keys(compilation.assets)
const result = await uno.generate(tokens, { minify: true })

for (const file of files) {
if (file === '*') { return }
let code = compilation.assets[file].source().toString()
let replaced = false
code = code.replace(LAYER_PLACEHOLDER_RE, (_, quote, layer) => {
replaced = true
const css = layer === LAYER_MARK_ALL ? result.getLayers(undefined, Array.from(entries).map((i) => resolveLayer(i)).filter((i) => !!i)) : result.getLayer(layer) || ''
if (!quote) { return css }
let escaped = JSON.stringify(css).slice(1, -1)
if (quote === '\\"') { escaped = JSON.stringify(escaped).slice(1, -1) }
return quote + escaped
})
if (replaced) { compilation.assets[file] = new WebpackSources.RawSource(code) }
}
})
})
}
}
}
function getLayer (id) {
let layer = resolveLayer(getPath(id))
if (!layer) {
const entry = resolveId(id)
if (entry) { layer = resolveLayer(entry) }
}
return layer
}
module.exports = WebpackPlugin
17 changes: 17 additions & 0 deletions packages/unocss-plugin/lib/web-plugin/transform-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { applyTransformers, isCssId } = require('./utils')

async function transform (code, map) {
const callback = this.async()
const ctx = this._compiler?.__unoCtx
const id = this.resource
const { extract } = ctx
await ctx.ready
const res = await applyTransformers(ctx, code, id, 'pre')
if (!isCssId(id)) {
extract.call(this, res == null ? code : res.code, id)
}

if (res == null) { callback(null, code, map) } else if (typeof res !== 'string') { callback(null, res.code, map == null ? map : (res.map || map)) } else { callback(null, res, map) }
}

module.exports = transform
111 changes: 111 additions & 0 deletions packages/unocss-plugin/lib/web-plugin/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
const pluginutils = require('@rollup/pluginutils')
const config = require('@unocss/config')
const core = require('@unocss/core')
const node_path = require('node:path')
const MagicString = require('magic-string')
const remapping = require('@ampproject/remapping')

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e }

const MagicString__default = /* #__PURE__ */_interopDefaultLegacy(MagicString)
const remapping__default = /* #__PURE__ */_interopDefaultLegacy(remapping)

const INCLUDE_COMMENT = '@unocss-include'
const IGNORE_COMMENT = '@unocss-ignore'
const CSS_PLACEHOLDER = '@unocss-placeholder'

const defaultExclude = [core.cssIdRE]
const defaultInclude = [/\.(vue|svelte|[jt]sx|mdx?|astro|elm|php|phtml|html)($|\?)/]

function createContext (configOrPath, defaults = {}, extraConfigSources = []) {
const root = process.cwd()
let rawConfig = {}
const uno = core.createGenerator(rawConfig, defaults)
let rollupFilter = pluginutils.createFilter(defaultInclude, defaultExclude)
const ready = reloadConfig()
async function reloadConfig () {
const result = await config.loadConfig(root, configOrPath, extraConfigSources, defaults)
rawConfig = result.config
uno.setConfig(rawConfig)
rollupFilter = pluginutils.createFilter(
rawConfig.include || defaultInclude,
rawConfig.exclude || defaultExclude
)
const presets = /* @__PURE__ */ new Set()
uno.config.presets.forEach((i) => {
if (!i.name) { return }
if (presets.has(i.name)) { console.warn(`[unocss] duplication of preset ${i.name} found, there might be something wrong with your config.`) } else { presets.add(i.name) }
})
return result
}
async function extract (code, id) {
const tokens = new Set()
const len = tokens.size
await uno.applyExtractors(code, id, tokens)
if (tokens.size > len) {
this.emitFile(id, '', undefined, {
skipEmit: true,
unocssTokens: new Set(tokens)
})
}
}
function filter (code, id) {
if (code.includes(IGNORE_COMMENT)) { return false }
return code.includes(INCLUDE_COMMENT) || code.includes(CSS_PLACEHOLDER) || rollupFilter(id.replace(/\?v=\w+$/, ''))
}

return {
get ready () {
return ready
},
filter,
uno,
extract
}
}

async function applyTransformers (ctx, original, id, enforce = 'default') {
if (original.includes(IGNORE_COMMENT)) { return }
const transformers = (ctx.uno.config.transformers || []).filter((i) => (i.enforce || 'default') === enforce)
if (!transformers.length) { return }
let code = original
let s = new MagicString__default(code)
const maps = []
for (const t of transformers) {
if (t.idFilter) {
if (!t.idFilter(id)) { continue }
} else if (!ctx.filter(code, id)) {
continue
}
await t.transform(s, id, ctx)
if (s.hasChanged()) {
code = s.toString()
maps.push(s.generateMap({ hires: true, source: id }))
s = new MagicString__default(code)
}
}
if (code !== original) {
return {
code,
map: remapping__default(maps, () => null)
}
}
}

function normalizeAbsolutePath (path) {
if (node_path.isAbsolute(path)) { return node_path.normalize(path) } else { return path }
}

function getPath (id) {
return id.replace(/\?.*$/, '')
}
function isCssId (id) {
return core.cssIdRE.test(id)
}
module.exports = {
createContext,
applyTransformers,
getPath,
isCssId,
normalizeAbsolutePath
}
9 changes: 7 additions & 2 deletions packages/unocss-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,14 @@
"dependencies": {
"@unocss/transformer-directives": "^0.52.1",
"@unocss/transformer-variant-group": "^0.52.1",
"@unocss/webpack": "^0.52.1",
"@unocss/config": "0.52.7",
"@unocss/core": "0.52.7",
"@ampproject/remapping": "^2.2.1",
"@rollup/pluginutils": "^5.0.2",
"magic-string": "^0.30.3",
"minimatch": "^9.0.1",
"unocss": "^0.52.1"
"webpack-sources": "^3.2.3",
"webpack-virtual-modules": "^0.6.0",
"unocss": "^0.52.7"
}
}

0 comments on commit 8efe2cb

Please sign in to comment.