@unocss/vite
Version:
The Vite plugin for UnoCSS
1,061 lines (1,060 loc) • 34.6 kB
JavaScript
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 };