-
Notifications
You must be signed in to change notification settings - Fork 376
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1373 from didi/feat-unocss-webplugin
feat: 修复serve 存在cache uno失效问题
- Loading branch information
Showing
6 changed files
with
302 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters