UNPKG

@unocss/vite

Version:

The Vite plugin for UnoCSS

1,061 lines (1,060 loc) 34.6 kB
import process from "node:process"; import { createRecoveryConfigLoader } from "@unocss/config"; import { BetterMap, LAYER_IMPORTS, LAYER_PREFLIGHTS, createGenerator, cssIdRE, notNull, toEscapedSelector } from "@unocss/core"; import { createFilter } from "unplugin-utils"; import UnocssInspector from "@unocss/inspector"; import fs from "node:fs"; import { dirname, isAbsolute, resolve } from "node:path"; import { fileURLToPath } from "node:url"; import fs$1 from "node:fs/promises"; import { glob } from "tinyglobby"; import remapping from "@jridgewell/remapping"; import MagicString from "magic-string"; import { resolve as resolve$1 } from "pathe"; import crypto from "node:crypto"; import { Buffer } from "node:buffer"; const CSS_PLACEHOLDER = "@unocss-placeholder"; const SKIP_START_COMMENT = "@unocss-skip-start"; const SKIP_END_COMMENT = "@unocss-skip-end"; const SKIP_COMMENT_RE = new RegExp(`(\/\/\\s*?${SKIP_START_COMMENT}\\s*?|\\/\\*\\s*?${SKIP_START_COMMENT}\\s*?\\*\\/|<!--\\s*?${SKIP_START_COMMENT}\\s*?-->)[\\s\\S]*?(\/\/\\s*?${SKIP_END_COMMENT}\\s*?|\\/\\*\\s*?${SKIP_END_COMMENT}\\s*?\\*\\/|<!--\\s*?${SKIP_END_COMMENT}\\s*?-->)`, "g"); const VIRTUAL_ENTRY_ALIAS = [/^(?:virtual:)?uno(?::(.+))?\.css(\?.*)?$/]; //#endregion //#region ../../virtual-shared/integration/src/defaults.ts const defaultPipelineExclude = [cssIdRE]; const defaultPipelineInclude = [/\.(vue|svelte|[jt]sx|vine.ts|mdx?|astro|elm|php|phtml|marko|html)($|\?)/]; //#endregion //#region ../../virtual-shared/integration/src/context.ts function createContext(configOrPath, defaults = {}, extraConfigSources = [], resolveConfigResult = () => {}) { let root = process.cwd(); let rawConfig = {}; let configFileList = []; let uno; const _uno = createGenerator(rawConfig, defaults).then((r) => { uno = r; return r; }); let rollupFilter = createFilter(defaultPipelineInclude, defaultPipelineExclude, { resolve: typeof configOrPath === "string" ? configOrPath : root }); const invalidations = []; const reloadListeners = []; const modules = new BetterMap(); const tokens = /* @__PURE__ */ new Set(); const tasks = []; const affectedModules = /* @__PURE__ */ new Set(); const loadConfig = createRecoveryConfigLoader(); let ready = reloadConfig(); async function reloadConfig() { await _uno; const result = await loadConfig(root, configOrPath, extraConfigSources, defaults); resolveConfigResult(result); result.config; rawConfig = result.config; configFileList = result.sources; await uno.setConfig(rawConfig); uno.config.envMode = "dev"; rollupFilter = rawConfig.content?.pipeline === false ? () => false : createFilter(rawConfig.content?.pipeline?.include || defaultPipelineInclude, rawConfig.content?.pipeline?.exclude || defaultPipelineExclude, { resolve: typeof configOrPath === "string" ? configOrPath : root }); tokens.clear(); await Promise.all(modules.map((code, id) => uno.applyExtractors(code.replace(SKIP_COMMENT_RE, ""), id, tokens))); invalidate(); dispatchReload(); return result; } async function updateRoot(newRoot) { if (newRoot !== root) { root = newRoot; ready = reloadConfig(); } return await ready; } function invalidate() { invalidations.forEach((cb) => cb()); } function dispatchReload() { reloadListeners.forEach((cb) => cb()); } async function extract(code, id) { const uno = await _uno; if (id) modules.set(id, code); const len = tokens.size; await uno.applyExtractors(code.replace(SKIP_COMMENT_RE, ""), id, tokens); if (tokens.size > len) invalidate(); } function filter(code, id) { if (code.includes("@unocss-ignore")) return false; return code.includes("@unocss-include") || code.includes("@unocss-placeholder") || rollupFilter(id.replace(/\?v=\w+$/, "")); } async function getConfig() { await ready; return rawConfig; } async function flushTasks() { const _tasks = [...tasks]; await Promise.all(_tasks); if (tasks[0] === _tasks[0]) tasks.splice(0, _tasks.length); } /** * Get regexes to match virtual module ids */ const vmpCache = /* @__PURE__ */ new Map(); async function getVMPRegexes() { const prefix = (await getConfig()).virtualModulePrefix || "__uno"; if (vmpCache.has(prefix)) return vmpCache.get(prefix); const regexes = { prefix, RESOLVED_ID_WITH_QUERY_RE: new RegExp(`[/\\\\]${prefix}(_.*?)?\\.css(\\?.*)?$`), RESOLVED_ID_RE: new RegExp(`[/\\\\]${prefix}(?:_(.*?))?\.css$`) }; vmpCache.set(prefix, regexes); return regexes; } return { get ready() { return ready; }, tokens, modules, affectedModules, tasks, flushTasks, invalidate, onInvalidate(fn) { invalidations.push(fn); }, filter, reloadConfig, onReload(fn) { reloadListeners.push(fn); }, get uno() { if (!uno) throw new Error("Run `await context.ready` before accessing `context.uno`"); return uno; }, extract, getConfig, get root() { return root; }, updateRoot, getConfigFileList: () => configFileList, getVMPRegexes }; } //#endregion //#region src/config-hmr.ts function ConfigHMRPlugin(ctx) { const { ready } = ctx; return { name: "unocss:config", async configResolved(config) { await ctx.updateRoot(config.root); }, async configureServer(server) { const { sources } = await ready; ctx.uno.config.envMode = "dev"; if (!sources.length) return; server.watcher.add(sources); server.watcher.on("change", async (p) => { if (!sources.includes(p)) return; await ctx.reloadConfig(); server.ws.send({ type: "custom", event: "unocss:config-changed" }); }); } }; } //#endregion //#region src/devtool.ts const _dirname = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url)); const DEVTOOLS_MODULE_ID = "virtual:unocss-devtools"; const MOCK_CLASSES_MODULE_ID = "virtual:unocss-mock-classes"; const MOCK_CLASSES_PATH = "/@unocss/mock-classes"; const DEVTOOLS_PATH = "/@unocss/devtools"; const DEVTOOLS_CSS_PATH = "/@unocss/devtools.css"; const devtoolCss = /* @__PURE__ */ new Set(); const MODULES_MAP = { [DEVTOOLS_MODULE_ID]: DEVTOOLS_PATH, [MOCK_CLASSES_MODULE_ID]: MOCK_CLASSES_PATH }; const BASE_POST_PATH = "/@unocss-devtools-update"; function getBodyJson(req) { return new Promise((resolve, reject) => { let body = ""; req.on("data", (chunk) => body += chunk); req.on("error", reject); req.on("end", () => { try { resolve(JSON.parse(body) || {}); } catch (e) { reject(e); } }); }); } function createDevtoolsPlugin(ctx, pluginConfig) { let config; let server; let clientCode = ""; let devtoolTimer; let lastUpdate = Date.now(); let postPath = BASE_POST_PATH; function toClass(name) { return `${toEscapedSelector(name)}{}`; } function updateDevtoolClass() { clearTimeout(devtoolTimer); devtoolTimer = setTimeout(() => { lastUpdate = Date.now(); if (!server) return; const mod = server.moduleGraph.getModuleById(DEVTOOLS_CSS_PATH); if (!mod) return; server.moduleGraph.invalidateModule(mod); server.ws.send({ type: "update", updates: [{ acceptedPath: DEVTOOLS_CSS_PATH, path: DEVTOOLS_CSS_PATH, timestamp: lastUpdate, type: "js-update" }] }); }, 100); } async function getMockClassesInjector() { const suggest = Object.keys(ctx.uno.config.rulesStaticMap); const comment = "/* unocss CSS mock class names for devtools auto-completion */\n"; const css = suggest.map(toClass).join(""); return ` const style = document.createElement('style') style.setAttribute('type', 'text/css') style.innerHTML = ${JSON.stringify(comment + css)} document.head.prepend(style) `; } return [{ name: "unocss:devtools", configResolved(_config) { config = _config; postPath = `${config.base?.replace(/\/$/, "") ?? ""}${BASE_POST_PATH}`; }, configureServer(_server) { server = _server; server.middlewares.use(async (req, res, next) => { if (req.url !== postPath) return next(); try { const data = await getBodyJson(req); const type = data?.type; let changed = false; switch (type) { case "add-classes": data.data.forEach((key) => { if (!devtoolCss.has(key)) { devtoolCss.add(key); changed = true; } }); if (changed) updateDevtoolClass(); } res.statusCode = 200; } catch (e) { console.error(e); res.statusCode = 500; } res.end(); }); }, resolveId(id) { if (id === DEVTOOLS_CSS_PATH) return DEVTOOLS_CSS_PATH; return MODULES_MAP[id]; }, async load(id) { if (id === DEVTOOLS_PATH) { if (!clientCode) clientCode = [ await fs.promises.readFile(resolve(_dirname, "client.mjs"), "utf-8"), `import('${MOCK_CLASSES_MODULE_ID}')`, `import('${DEVTOOLS_CSS_PATH}')` ].join("\n").replace("__POST_PATH__", `${config.server?.origin ?? ""}${postPath}`).replace("__POST_FETCH_MODE__", pluginConfig.fetchMode ?? "cors"); return config.command === "build" ? "" : clientCode; } else if (id === MOCK_CLASSES_PATH) return await getMockClassesInjector(); else if (id === DEVTOOLS_CSS_PATH) { const { css } = await ctx.uno.generate(devtoolCss); return css; } } }]; } //#endregion //#region src/modes/chunk-build.ts function ChunkModeBuildPlugin(ctx) { let cssPlugin; const files = {}; return { name: "unocss:chunk", apply: "build", enforce: "pre", configResolved(config) { cssPlugin = config.plugins.find((i) => i.name === "vite:css-post"); }, async transform(code, id) { await ctx.ready; if (!ctx.filter(code, id)) return; files[id] = code; return null; }, async renderChunk(_, chunk) { const chunks = Object.keys(chunk.modules).map((i) => files[i]).filter(Boolean); if (!chunks.length) return null; await ctx.ready; const tokens = /* @__PURE__ */ new Set(); await Promise.all(chunks.map((c) => ctx.uno.applyExtractors(c, void 0, tokens))); const { css } = await ctx.uno.generate(tokens); const cssPostTransformHandler = "handler" in cssPlugin.transform ? cssPlugin.transform.handler : cssPlugin.transform; const fakeCssId = `${chunk.fileName}.css`; await cssPostTransformHandler.call(this, css, fakeCssId); chunk.modules[fakeCssId] = { code: null, originalLength: 0, removedExports: [], renderedExports: [], renderedLength: 0 }; return null; }, async transformIndexHtml(code) { await ctx.ready; const { css } = await ctx.uno.generate(code); if (css) return `${code}<style>${css}</style>`; } }; } //#endregion //#region ../../virtual-shared/integration/src/utils.ts function getPath(id) { return id.replace(/\?.*$/, ""); } function hash$1(str) { let i; let l; let hval = 2166136261; for (i = 0, l = str.length; i < l; i++) { hval ^= str.charCodeAt(i); hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24); } return `00000${(hval >>> 0).toString(36)}`.slice(-6); } function transformSkipCode(code, map, SKIP_RULES_RE, keyFlag) { for (const item of Array.from(code.matchAll(SKIP_RULES_RE))) if (item != null) { const matched = item[0]; const withHashKey = `${keyFlag}${hash$1(matched)}`; map.set(withHashKey, matched); code = code.replace(matched, withHashKey); } return code; } function restoreSkipCode(code, map) { for (const [withHashKey, matched] of map.entries()) code = code.replaceAll(withHashKey, matched); return code; } //#endregion //#region ../../virtual-shared/integration/src/transformers.ts async function applyTransformers(ctx, original, id, enforce = "default") { if (original.includes("@unocss-ignore")) return; const transformers = (ctx.uno.config.transformers || []).filter((i) => (i.enforce || "default") === enforce); if (!transformers.length) return; const skipMap = /* @__PURE__ */ new Map(); let code = original; let s = new MagicString(transformSkipCode(code, skipMap, SKIP_COMMENT_RE, "@unocss-skip-placeholder-")); 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 = restoreSkipCode(s.toString(), skipMap); maps.push(s.generateMap({ hires: true, source: id })); s = new MagicString(code); } } if (code !== original) return { code, map: remapping(maps, (_, ctx) => { ctx.content = code; return null; }) }; } //#endregion //#region ../../virtual-shared/integration/src/content.ts async function setupContentExtractor(ctx, shouldWatch = false) { const { content } = await ctx.getConfig(); const { extract, tasks, root, filter } = ctx; if (content?.inline) await Promise.all(content.inline.map(async (c, idx) => { if (typeof c === "function") c = await c(); if (typeof c === "string") c = { code: c }; return extract(c.code, c.id ?? `__plain_content_${idx}__`); })); if (content?.filesystem) { const files = await glob(content.filesystem, { cwd: root, expandDirectories: false }); async function extractFile(file) { file = isAbsolute(file) ? file : resolve(root, file); const code = await fs$1.readFile(file, "utf-8"); if (!filter(code, file)) return; const preTransform = await applyTransformers(ctx, code, file, "pre"); await applyTransformers(ctx, (await applyTransformers(ctx, preTransform?.code || code, file))?.code || preTransform?.code || code, file, "post"); return await extract(preTransform?.code || code, file); } if (shouldWatch) { const { watch } = await import("chokidar"); watch(files, { ignorePermissionErrors: true, ignored: ["**/{.git,node_modules}/**"], cwd: root, ignoreInitial: true }).on("all", (type, file) => { if (type === "add" || type === "change") { const absolutePath = resolve(root, file); tasks.push(extractFile(absolutePath)); } }); } await Promise.all(files.map(extractFile)); } } //#endregion //#region ../../virtual-shared/integration/src/layers.ts async function resolveId(ctx, id, importer) { const { RESOLVED_ID_WITH_QUERY_RE, prefix } = await ctx.getVMPRegexes(); if (id.match(RESOLVED_ID_WITH_QUERY_RE)) return id; for (const alias of VIRTUAL_ENTRY_ALIAS) { const match = id.match(alias); if (match) { let virtual = match[1] ? `${prefix}_${match[1]}.css` : `${prefix}.css`; virtual += match[2] || ""; if (importer) virtual = resolve$1(importer, "..", virtual); else virtual = `/${virtual}`; return virtual; } } } async function resolveLayer(ctx, id) { const { RESOLVED_ID_RE } = await ctx.getVMPRegexes(); const match = id.match(RESOLVED_ID_RE); if (match) return match[1] || "__ALL__"; } function getLayerPlaceholder(layer) { return `#--unocss--{layer:${layer};escape-view:\\"\\'\\\`\\\\}`; } //#endregion //#region src/modes/global/shared.ts const MESSAGE_UNOCSS_ENTRY_NOT_FOUND = "[unocss] Entry module not found. Did you add `import 'uno.css'` in your main entry?"; //#endregion //#region src/modes/global/build.ts function GlobalModeBuildPlugin(ctx) { const { ready, extract, tokens, filter, getConfig, tasks, flushTasks } = ctx; const vfsLayers = /* @__PURE__ */ new Map(); const resolveContexts = /* @__PURE__ */ new Map(); const unocssImporters = /* @__PURE__ */ new Set(); let viteConfig; const cssPostPlugins = /* @__PURE__ */ new Map(); const cssPlugins = /* @__PURE__ */ new Map(); async function applyCssTransform(css, id, dir, ctx) { const { postcss = true } = await getConfig(); if (!cssPlugins.get(dir) || !postcss) return css; const cssPlugin = cssPlugins.get(dir); const result = await ("handler" in cssPlugin.transform ? cssPlugin.transform.handler : cssPlugin.transform).call(ctx, 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; } let lastTokenSize = 0; let lastResult; async function generateAll() { await flushTasks(); if (lastResult && lastTokenSize === tokens.size) return lastResult; lastResult = await ctx.uno.generate(tokens, { minify: true }); lastTokenSize = tokens.size; return lastResult; } const cssContentCache = /* @__PURE__ */ new Map(); return [ { name: "unocss:global:build:scan", apply: "build", enforce: "pre", async buildStart() { vfsLayers.clear(); cssContentCache.clear(); tasks.length = 0; lastTokenSize = 0; lastResult = void 0; }, transform(code, id) { if (filter(code, id)) tasks.push(extract(code, id)); return null; }, transformIndexHtml: { order: "pre", handler(code, { filename }) { tasks.push(extract(code, filename)); }, enforce: "pre", transform(code, { filename }) { tasks.push(extract(code, filename)); } }, async resolveId(id, importer) { const entry = await resolveId(ctx, id, importer); if (entry) { const layer = await resolveLayer(ctx, entry); if (layer) { if (importer) unocssImporters.add(importer); if (vfsLayers.has(layer)) { this.warn(`[unocss] ${JSON.stringify(id)} is being imported multiple times in different files, using the first occurrence: ${JSON.stringify(vfsLayers.get(layer))}`); return vfsLayers.get(layer); } vfsLayers.set(layer, entry); resolveContexts.set(layer, this); } return entry; } }, async load(id) { const layer = await resolveLayer(ctx, getPath(id)); if (layer) { if (!vfsLayers.has(layer)) this.error(`[unocss] layer ${JSON.stringify(id)} is imported but not being resolved before, it might be an internal bug of UnoCSS`); return { code: getLayerPlaceholder(layer), map: null, moduleSideEffects: true }; } }, shouldTransformCachedModule({ id }) { return unocssImporters.delete(id); }, async configResolved(config) { const distDirs = [resolve(config.root, config.build.outDir)]; if (config.build.rollupOptions.output) { const outputOptions = config.build.rollupOptions.output; (Array.isArray(outputOptions) ? outputOptions.map((option) => option.dir).filter(Boolean) : outputOptions.dir ? [outputOptions.dir] : []).forEach((dir) => { distDirs.push(dir); if (!isAbsolute(dir)) distDirs.push(resolve(config.root, dir)); }); } const cssPostPlugin = config.plugins.find((i) => i.name === "vite:css-post"); const cssPlugin = config.plugins.find((i) => i.name === "vite:css"); if (cssPostPlugin) distDirs.forEach((dir) => cssPostPlugins.set(dir, cssPostPlugin)); if (cssPlugin) distDirs.forEach((dir) => cssPlugins.set(dir, cssPlugin)); await ready; } }, { name: "unocss:global:content", enforce: "pre", configResolved(config) { viteConfig = config; }, buildStart() { tasks.push(setupContentExtractor(ctx, viteConfig.mode !== "test" && viteConfig.command === "serve")); } }, { name: "unocss:global:build:generate", apply: "build", async renderChunk(_, chunk, options) { const { RESOLVED_ID_RE } = await ctx.getVMPRegexes(); const entryModules = Object.keys(chunk.modules).filter((id) => RESOLVED_ID_RE.test(id)); if (!entryModules.length) return null; const cssPost = cssPostPlugins.get(options.dir); if (!cssPost) { this.warn("[unocss] failed to find vite:css-post plugin. It might be an internal bug of UnoCSS"); return null; } const cssPostTransformHandler = "handler" in cssPost.transform ? cssPost.transform.handler : cssPost.transform; const result = await generateAll(); const fakeCssId = `${viteConfig.root}/${chunk.fileName}-unocss-hash.css`; const preflightLayers = ctx.uno.config.preflights?.map((i) => i.layer).concat(LAYER_PREFLIGHTS).filter(Boolean); await Promise.all(preflightLayers.map((i) => result.setLayer(i, async (layerContent) => { const preTransform = await applyTransformers(ctx, layerContent, fakeCssId, "pre"); const defaultTransform = await applyTransformers(ctx, preTransform?.code || layerContent, fakeCssId); return (await applyTransformers(ctx, defaultTransform?.code || preTransform?.code || layerContent, fakeCssId, "post"))?.code || defaultTransform?.code || preTransform?.code || layerContent; }))); for (const mod of entryModules) { const layer = RESOLVED_ID_RE.exec(mod)?.[1] || "__ALL__"; const css = await applyCssTransform(layer === "__ALL__" ? result.getLayers(void 0, [LAYER_IMPORTS, ...vfsLayers.keys()]) : result.getLayer(layer) || "", mod, options.dir, resolveContexts.get(layer) || this); await cssPostTransformHandler.call(this, css, mod); } }, async buildEnd() { if (!vfsLayers.size) { if ((await getConfig()).checkImport) this.warn(MESSAGE_UNOCSS_ENTRY_NOT_FOUND); } } } ]; } //#endregion //#region ../../virtual-shared/integration/src/hash.ts const hash = crypto.hash ?? ((algorithm, data, outputEncoding) => crypto.createHash(algorithm).update(data).digest(outputEncoding)); function getHash(input, length = 8) { return hash("sha256", input, "hex").substring(0, length); } //#endregion //#region src/modes/global/dev.ts const WARN_TIMEOUT = 2e4; const WS_EVENT_PREFIX = "unocss:hmr"; const HASH_LENGTH = 6; function GlobalModeDevPlugin(ctx) { const { tokens, tasks, flushTasks, affectedModules, onInvalidate, extract, filter, getConfig } = ctx; const servers = []; const entries = /* @__PURE__ */ new Set(); let invalidateTimer; const lastServedHash = /* @__PURE__ */ new Map(); let lastServedTime = Date.now(); let resolved = false; let resolvedWarnTimer; async function generateCSS(layer) { await flushTasks(); let result; let tokensSize = tokens.size; do { result = await ctx.uno.generate(tokens); if (tokensSize === tokens.size) break; tokensSize = tokens.size; } while (true); const css = layer === "__ALL__" ? result.getLayers(void 0, await Promise.all(Array.from(entries).map((i) => resolveLayer(ctx, i))).then((layers) => layers.filter((i) => !!i))) : result.getLayer(layer); const hash = getHash(css || "", HASH_LENGTH); lastServedHash.set(layer, hash); lastServedTime = Date.now(); return { hash, css }; } function invalidate(timer = 10, ids = entries) { for (const server of servers) for (const id of ids) { const mod = server.moduleGraph.getModuleById(id); if (!mod) continue; server.moduleGraph.invalidateModule(mod); } clearTimeout(invalidateTimer); invalidateTimer = setTimeout(() => { lastServedHash.clear(); sendUpdate(ids); }, timer); } function sendUpdate(ids) { for (const server of servers) server.ws.send({ type: "update", updates: Array.from(ids).map((id) => { const mod = server.moduleGraph.getModuleById(id); if (!mod) return null; return { acceptedPath: mod.url, path: mod.url, timestamp: lastServedTime, type: "js-update" }; }).filter(notNull) }); } async function setWarnTimer() { if (!resolved && !resolvedWarnTimer && (await getConfig()).checkImport) resolvedWarnTimer = setTimeout(() => { if (process.env.TEST || process.env.NODE_ENV === "test") return; if (!resolved) { console.warn(MESSAGE_UNOCSS_ENTRY_NOT_FOUND); servers.forEach(({ ws }) => ws.send({ type: "error", err: { message: MESSAGE_UNOCSS_ENTRY_NOT_FOUND, stack: "" } })); } }, WARN_TIMEOUT); } function clearWarnTimer() { if (resolvedWarnTimer) { clearTimeout(resolvedWarnTimer); resolvedWarnTimer = void 0; } } onInvalidate(() => { invalidate(10, new Set([...entries, ...affectedModules])); }); return [{ name: "unocss:global", apply: "serve", enforce: "pre", async configureServer(_server) { servers.push(_server); _server.ws.on(WS_EVENT_PREFIX, async ([layer]) => { const preHash = lastServedHash.get(layer); await generateCSS(layer); if (lastServedHash.get(layer) !== preHash) sendUpdate(entries); }); }, buildStart() { ctx.uno.generate([], { preflights: true }); }, transform(code, id) { if (filter(code, id)) tasks.push(extract(code, id)); return null; }, transformIndexHtml: { order: "pre", handler(code, { filename }) { setWarnTimer(); tasks.push(extract(code, filename)); }, enforce: "pre", transform(code, { filename }) { setWarnTimer(); tasks.push(extract(code, filename)); } }, async resolveId(id) { const entry = await resolveId(ctx, id); if (entry) { resolved = true; clearWarnTimer(); entries.add(entry); return entry; } }, async load(id) { const layer = await resolveLayer(ctx, getPath(id)); if (!layer) return null; const { hash, css } = await generateCSS(layer); return { code: `${css}__uno_hash_${hash}{--:'';}`, map: { mappings: "" } }; }, closeBundle() { clearWarnTimer(); } }, { name: "unocss:global:post", apply(config, env) { return env.command === "serve" && !config.build?.ssr; }, enforce: "post", async transform(code, id) { const layer = await resolveLayer(ctx, getPath(id)); if (layer && code.includes("import.meta.hot")) { let hmr = ` try { let hash = __vite__css.match(/__uno_hash_(\\w{${HASH_LENGTH}})/) hash = hash && hash[1] if (!hash) console.warn('[unocss-hmr]', 'failed to get unocss hash, hmr might not work') else await import.meta.hot.send('${WS_EVENT_PREFIX}', ['${layer}']); } catch (e) { console.warn('[unocss-hmr]', e) } if (!import.meta.url.includes('?')) await new Promise(resolve => setTimeout(resolve, 100))`; if ((await getConfig()).hmrTopLevelAwait === false) hmr = `;(async function() {${hmr}\n})()`; hmr = `\nif (import.meta.hot) {${hmr}}`; const s = new MagicString(code); s.append(hmr); return { code: s.toString(), map: s.generateMap() }; } } }]; } //#endregion //#region src/modes/global/index.ts function GlobalModePlugin(ctx) { return [...GlobalModeBuildPlugin(ctx), ...GlobalModeDevPlugin(ctx)]; } //#endregion //#region src/modes/per-module.ts const VIRTUAL_PREFIX = "/@unocss/"; const SCOPE_IMPORT_RE = / from (['"])(@unocss\/scope)\1/; function PerModuleModePlugin(ctx) { const moduleMap = /* @__PURE__ */ new Map(); let server; const invalidate = (hash) => { if (!server) return; const id = `${VIRTUAL_PREFIX}${hash}.css`; const mod = server.moduleGraph.getModuleById(id); if (!mod) return; server.moduleGraph.invalidateModule(mod); server.ws.send({ type: "update", updates: [{ acceptedPath: id, path: id, timestamp: +Date.now(), type: "js-update" }] }); }; return [{ name: "unocss:module-scope:pre", enforce: "pre", async resolveId(id) { const entry = await resolveId(ctx, id); if (entry) return entry; }, async load(id) { if (!await resolveLayer(ctx, getPath(id))) return null; await ctx.ready; const { css } = await ctx.uno.generate("", { preflights: true }); if (!css) return null; return { code: css, map: null }; }, async transform(code, id) { await ctx.ready; if (!ctx.filter(code, id)) return; const hash = getHash(id); const hasScope = SCOPE_IMPORT_RE.test(code); const { css } = await ctx.uno.generate(code, { id, scope: hasScope ? `.${hash}` : void 0, preflights: false }); if (!css && !hasScope) return null; if (hasScope) code = code.replace(SCOPE_IMPORT_RE, ` from 'data:text/javascript;base64,${Buffer.from(`export default () => "${hash}"`).toString("base64")}'`); moduleMap.set(hash, [id, css]); invalidate(hash); return null; } }, { name: "unocss:module-scope", enforce: "post", configureServer(_server) { server = _server; }, async transform(code, id) { await ctx.ready; if (!ctx.filter(code, id)) return; const hash = getHash(id); invalidate(hash); if ((moduleMap.get(hash) || []).length) return { code: `import "${VIRTUAL_PREFIX}${hash}.css";${code}`, map: null }; }, resolveId(id) { return id.startsWith(VIRTUAL_PREFIX) ? id : null; }, load(id) { if (!id.startsWith(VIRTUAL_PREFIX)) return null; const hash = id.slice(9, -4); const [source, css] = moduleMap.get(hash) || []; if (source) this.addWatchFile(source); return `\n/* unocss ${source} */\n${css}`; } }]; } //#endregion //#region src/modes/shadow-dom.ts function ShadowDomModuleModePlugin(ctx) { const partExtractorRegex = /^part-\[(.+)\]:/; const nameRegexp = /<([^\s^!>]+)\s*([^>]*)>/; const vueSFCStyleRE = /<style[^>]*>[\s\S]*@unocss-placeholder[\s\S]*<\/style>/; const checkElement = (useParts, idxResolver, element) => { if (!element) return null; const applyParts = useParts.filter((p) => element[2].includes(p.rule)); if (applyParts.length === 0) return null; const name = element[1]; const idx = idxResolver(name); return { name, entries: applyParts.map(({ rule, part }) => [`.${rule.replace(/[:[\]]/g, "\\$&")}::part(${part})`, `${name}:nth-of-type(${idx})::part(${part})`]) }; }; const idxMapFactory = () => { const elementIdxMap = /* @__PURE__ */ new Map(); return { idxResolver: (name) => { let idx = elementIdxMap.get(name); if (!idx) { idx = 1; elementIdxMap.set(name, idx); } return idx; }, incrementIdx: (name) => { elementIdxMap.set(name, elementIdxMap.get(name) + 1); } }; }; const transformWebComponent = async (code, id) => { if (!code.match("@unocss-placeholder")) return code; await ctx.ready; let { css, matched } = await ctx.uno.generate(code, { preflights: true, safelist: true }); if (css && matched) { const useParts = Array.from(matched).reduce((acc, rule) => { const matcher = rule.match(partExtractorRegex); if (matcher) acc.push({ part: matcher[1], rule }); return acc; }, new Array()); if (useParts.length > 0) { let useCode = code; let element; const partsToApply = /* @__PURE__ */ new Map(); const { idxResolver, incrementIdx } = idxMapFactory(); while (element = nameRegexp.exec(useCode)) { const result = checkElement(useParts, idxResolver, element); if (result) { result.entries.forEach(([name, replacement]) => { let list = partsToApply.get(name); if (!list) { list = []; partsToApply.set(name, list); } list.push(replacement); }); incrementIdx(result.name); } useCode = useCode.slice(element[0].length + 1); } if (partsToApply.size > 0) css = Array.from(partsToApply.entries()).reduce((k, [r, name]) => { return k.replace(r, name.join(",\n")); }, css); } } if (id.includes("?vue&type=style") || id.endsWith(".vue") && vueSFCStyleRE.test(code)) return code.replace(new RegExp(`(\\/\\*\\s*)?${CSS_PLACEHOLDER}(\\s*\\*\\/)?`), css || ""); return code.replace(CSS_PLACEHOLDER, css?.replace(/\\/g, "\\\\")?.replace(/`/g, "\\`") ?? ""); }; return { name: "unocss:shadow-dom", enforce: "pre", async transform(code, id) { return { code: await transformWebComponent(code, id), map: null }; }, handleHotUpdate(ctx) { const read = ctx.read; ctx.read = async () => { return await transformWebComponent(await read(), ctx.file); }; } }; } //#endregion //#region src/modes/vue-scoped.ts function VueScopedPlugin(ctx) { let filter = createFilter([/\.vue$/], defaultPipelineExclude); async function transformSFC(code) { await ctx.ready; const { css } = await ctx.uno.generate(code); if (!css) return null; return `${code}\n<style scoped>${css}</style>`; } return { name: "unocss:vue-scoped", enforce: "pre", async configResolved() { const { config } = await ctx.ready; filter = config.content?.pipeline === false ? () => false : createFilter(config.content?.pipeline?.include ?? [/\.vue$/], config.content?.pipeline?.exclude ?? defaultPipelineExclude); }, async transform(code, id) { if (!filter(id) || !id.endsWith(".vue")) return; const css = await transformSFC(code); if (css) return { code: css, map: null }; }, handleHotUpdate(ctx) { const read = ctx.read; if (filter(ctx.file)) ctx.read = async () => { const code = await read(); return await transformSFC(code) || code; }; } }; } //#endregion //#region src/transformers.ts function createTransformerPlugins(ctx) { return [ "default", "pre", "post" ].map((_order) => { const order = _order === "default" ? void 0 : _order; const htmlHandler = (code) => { return applyTransformers(ctx, code, "index.html", order).then((t) => t?.code); }; return { name: `unocss:transformers:${_order}`, enforce: order, transform(code, id) { return applyTransformers(ctx, code, id, order); }, transformIndexHtml: { order, handler: htmlHandler, enforce: order, transform: htmlHandler } }; }); } //#endregion //#region src/index.ts function defineConfig(config) { return config; } function UnocssPlugin(configOrPath, defaults = {}) { const ctx = createContext(configOrPath, { envMode: process.env.NODE_ENV === "development" ? "dev" : "build", ...defaults, legacy: typeof configOrPath !== "string" ? configOrPath?.legacy || { renderModernChunks: true } : { renderModernChunks: true } }); const inlineConfig = configOrPath && typeof configOrPath !== "string" ? configOrPath : {}; const mode = inlineConfig.mode ?? "global"; const plugins = [ ConfigHMRPlugin(ctx), ...createTransformerPlugins(ctx), ...createDevtoolsPlugin(ctx, inlineConfig), { name: "unocss:api", api: { getContext: () => ctx, getMode: () => mode } } ]; if (inlineConfig.inspector !== false) plugins.push(UnocssInspector(ctx)); if (mode === "per-module") plugins.push(...PerModuleModePlugin(ctx)); else if (mode === "vue-scoped") plugins.push(VueScopedPlugin(ctx)); else if (mode === "svelte-scoped") throw new Error("[unocss] svelte-scoped mode is now its own package, please use @unocss/svelte-scoped according to the docs"); else if (mode === "shadow-dom") plugins.push(ShadowDomModuleModePlugin(ctx)); else if (mode === "global") plugins.push(...GlobalModePlugin(ctx)); else if (mode === "dist-chunk") plugins.push(ChunkModeBuildPlugin(ctx), ...GlobalModeDevPlugin(ctx)); else throw new Error(`[unocss] unknown mode "${mode}"`); return plugins.filter(Boolean); } //#endregion export { ChunkModeBuildPlugin, GlobalModeBuildPlugin, GlobalModeDevPlugin, GlobalModePlugin, PerModuleModePlugin, VueScopedPlugin, UnocssPlugin as default, defineConfig };