vite-plugin-windicss
Version:
Windi CSS for Vite, it's fast!
397 lines (390 loc) • 11.4 kB
JavaScript
// src/index.ts
import { resolve as resolve2 } from "node:path";
import _debug2, { log } from "debug";
import { createUtils } from "@windicss/plugin-utils";
// ../shared/virtual-module.ts
import { existsSync, promises as fs } from "node:fs";
var MODULE_IDS = [/^virtual:windi(.*?)\.css/, /^windi(.*?)\.css/];
var MODULE_ID_VIRTUAL_PREFIX = "/@windicss/windi";
var MODULE_ID_VIRTUAL = /\/\@windicss\/windi-?(.*?)\.css/;
var MODULE_ID_VIRTUAL_MODULES = [
`${MODULE_ID_VIRTUAL_PREFIX}.css`,
`${MODULE_ID_VIRTUAL_PREFIX}-base.css`,
`${MODULE_ID_VIRTUAL_PREFIX}-utilities.css`,
`${MODULE_ID_VIRTUAL_PREFIX}-components.css`
];
function createVirtualModuleLoader(ctx) {
return {
resolveId(id) {
if (id.startsWith(MODULE_ID_VIRTUAL_PREFIX))
return id;
for (const idRegex of MODULE_IDS) {
const match = id.match(idRegex);
if (match)
return `${MODULE_ID_VIRTUAL_PREFIX}${match[1]}.css`;
}
return null;
},
async load(id) {
const match = id.match(MODULE_ID_VIRTUAL);
if (match) {
await ctx.utils.scan();
await ctx.utils.waitLocks();
ctx.utils.files.map((id2) => this.addWatchFile(id2));
const layer = match[1] || void 0;
const css = await ctx.utils.generateCSS(layer);
return css;
}
},
async watchChange(id, change) {
if (ctx.inHmr)
return;
if (change.event === "delete" || !existsSync(id))
return;
if (!ctx.utils.isDetectTarget(id))
return;
ctx.utils.lock(async () => {
const content = await fs.readFile(id, "utf-8");
await ctx.utils.extractFile(content, id, true);
});
}
};
}
// src/devtools.ts
import fs2 from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import _debug from "debug";
// src/constants.ts
var NAME = "vite-plugin-windicss";
// src/modules.ts
function getChangedModuleNames(utils) {
if (utils.hasPending)
utils.buildPendingStyles();
const moduleNames = [
`${MODULE_ID_VIRTUAL_PREFIX}.css`
];
Object.entries(utils.layersMeta).forEach(([name, meta]) => {
if (meta.cssCache == null)
moduleNames.push(`${MODULE_ID_VIRTUAL_PREFIX}-${name}.css`);
});
return moduleNames;
}
function getCssModules(server, names = MODULE_ID_VIRTUAL_MODULES) {
return names.map((name) => server.moduleGraph.getModuleById(name)).filter(Boolean);
}
function invalidateCssModules(server, modules = getCssModules(server)) {
return modules.forEach((m) => server.moduleGraph.invalidateModule(m));
}
function sendHmrReload(server, modules = getCssModules(server)) {
const timestamp = +Date.now();
server.ws.send({
type: "update",
updates: modules.map((m) => ({
acceptedPath: m.id || m.file,
path: m.id || m.file,
timestamp,
type: "js-update"
}))
});
}
function reloadChangedCssModules(server, utils) {
const cssModules = getCssModules(server, getChangedModuleNames(utils));
invalidateCssModules(server, cssModules);
sendHmrReload(server, cssModules);
return cssModules;
}
// src/devtools.ts
var _dirname = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
var debug = {
devtools: _debug(`${NAME}:devtools`)
};
var DEVTOOLS_MODULE_ID = "virtual:windi-devtools";
var MOCK_CLASSES_MODULE_ID = "virtual:windi-mock-classes";
var MOCK_CLASSES_PATH = "/@windicss/mock-classes";
var DEVTOOLS_PATH = "/@windicss/devtools";
var MODULES_MAP = {
[DEVTOOLS_MODULE_ID]: DEVTOOLS_PATH,
[MOCK_CLASSES_MODULE_ID]: MOCK_CLASSES_PATH
};
var POST_PATH = "/@windicss-devtools-update";
function getBodyJson(req) {
return new Promise((resolve3, reject) => {
let body = "";
req.on("data", (chunk) => body += chunk);
req.on("error", reject);
req.on("end", () => {
try {
resolve3(JSON.parse(body) || {});
} catch (e) {
reject(e);
}
});
});
}
function createDevtoolsPlugin(ctx) {
let config;
let server;
let clientCode = "";
function updateCSS() {
if (!server)
return;
const names = getChangedModuleNames(ctx.utils);
const modules = getCssModules(server, names);
invalidateCssModules(server, modules);
sendHmrReload(server, modules);
}
function toClass(name) {
return `.${ctx.utils.processor.e(name)}{}`;
}
function getMockClassesInjector() {
const completions = ctx.utils.getCompletions();
const comment = "/* Windi CSS mock class names for devtools auto-completion */\n";
const css = [
...completions.color,
...completions.static
].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: `${NAME}:devtools`,
configResolved(_config) {
config = _config;
},
configureServer(_server) {
server = _server;
server.middlewares.use(async (req, res, next) => {
if (req.url !== POST_PATH)
return next();
try {
const data = await getBodyJson(req);
const type = data?.type;
debug.devtools(data);
let changed = false;
switch (type) {
case "add-classes":
changed = ctx.utils.addClasses(data.data || []);
}
if (changed)
updateCSS();
res.statusCode = 200;
} catch (e) {
console.error(e);
res.statusCode = 500;
}
res.end();
});
},
resolveId(id) {
return MODULES_MAP[id];
},
async load(id, options) {
if (options?.ssr && [DEVTOOLS_PATH, MOCK_CLASSES_PATH].includes(id))
return "";
if (id === DEVTOOLS_PATH) {
if (!clientCode) {
clientCode = [
await fs2.promises.readFile(resolve(_dirname, "client.mjs"), "utf-8"),
`import('${MOCK_CLASSES_MODULE_ID}')`
].join("\n").replace("__POST_PATH__", (config.server?.origin ?? "") + POST_PATH);
}
return config.command === "build" ? "" : clientCode;
} else if (id === MOCK_CLASSES_PATH) {
return getMockClassesInjector();
}
}
}
];
}
// src/index.ts
export * from "@windicss/plugin-utils";
var debug2 = {
hmr: _debug2(`${NAME}:hmr`),
css: _debug2(`${NAME}:transform:css`),
group: _debug2(`${NAME}:transform:group`),
alias: _debug2(`${NAME}:transform:alias`),
memory: _debug2(`${NAME}:memory`)
};
function VitePluginWindicss(userOptions = {}, utilsOptions = {}) {
let utils;
let server;
let viteConfig;
const plugins = [];
plugins.push({
name: `${NAME}:alias`,
enforce: "pre",
configResolved(_config) {
viteConfig = _config;
},
async transform(code, id) {
await utils.ensureInit();
if (!utils.isDetectTarget(id))
return;
debug2.alias(id);
return utils.transformAlias(code, !!viteConfig.build.sourcemap);
}
});
if (userOptions.transformGroups !== false) {
plugins.push({
name: `${NAME}:groups`,
enforce: "pre",
async transform(code, id) {
await utils.ensureInit();
if (!utils.isDetectTarget(id))
return;
debug2.group(id);
return utils.transformGroups(code, !!viteConfig.build.sourcemap);
}
});
}
plugins.push({
name: NAME,
get api() {
return utils;
}
});
plugins.push({
name: `${NAME}:entry`,
enforce: "post",
configureServer(_server) {
server = _server;
},
async configResolved(_config) {
utils = utilsOptions.utils ?? createUtils(userOptions, {
name: NAME,
root: _config.root,
onConfigurationError(e) {
if (_config.command === "build") {
throw e;
} else {
console.error(`[${NAME}] Error on loading configurations`);
console.error(e);
}
},
...utilsOptions
});
await utils.ensureInit();
},
...createVirtualModuleLoader({
get utils() {
return utils;
},
get inHmr() {
return Boolean(server);
}
})
});
let _cssReloadTask;
function reloadCssModules(server2) {
clearTimeout(_cssReloadTask);
_cssReloadTask = setTimeout(() => {
reloadChangedCssModules(server2, utils);
}, 1);
}
plugins.push({
name: `${NAME}:hmr`,
apply: "serve",
enforce: "pre",
async configureServer(_server) {
server = _server;
await utils.ensureInit();
if (utils.configFilePath)
server.watcher.add(utils.configFilePath);
const supportsGlobs = server.config.server.watch?.disableGlobbing === false;
server.watcher.add(supportsGlobs ? utils.globs : await utils.getFiles());
},
async handleHotUpdate({ server: server2, file, read }) {
if (resolve2(file) === utils.configFilePath) {
debug2.hmr(`config file changed: ${file}`);
await utils.init();
setTimeout(() => {
log("configure file changed, reloading");
server2.ws.send({ type: "full-reload" });
}, 0);
return getCssModules(server2);
}
if (!utils.isDetectTarget(file))
return;
utils.extractFile(await read(), file, true).then((changed) => {
if (changed) {
debug2.hmr(`refreshed by ${file}`);
reloadCssModules(server2);
}
});
}
});
const { transformCSS: transformCSSOptions = true } = userOptions;
const transformCSS = (code, id) => utils.transformCSS(code, id, {
onLayerUpdated() {
if (server)
reloadCssModules(server);
}
});
if (transformCSSOptions === true) {
plugins.push({
name: `${NAME}:css`,
async transform(code, id) {
await utils.ensureInit();
if (!utils.isCssTransformTarget(id) || id.startsWith(MODULE_ID_VIRTUAL_PREFIX))
return;
debug2.css(id);
code = transformCSS(code, id);
if (viteConfig.build.sourcemap) {
return {
code: transformCSS(code, id),
map: { mappings: "" }
};
} else {
return code;
}
}
});
} else if (typeof transformCSSOptions === "string") {
plugins.push({
name: `${NAME}:css`,
enforce: transformCSSOptions,
transform(code, id) {
if (!utils.isCssTransformTarget(id) || id.startsWith(MODULE_ID_VIRTUAL_PREFIX))
return;
debug2.css(id, transformCSSOptions);
code = transformCSS(code, id);
if (viteConfig.build.sourcemap) {
return {
code: transformCSS(code, id),
map: { mappings: "" }
};
} else {
return code;
}
}
});
}
plugins.push({
name: `${NAME}:css:svelte`,
api: {
sveltePreprocess: {
style({ content, id }) {
return {
code: transformCSS(content, id)
};
}
}
}
});
plugins.push(...createDevtoolsPlugin({
get utils() {
return utils;
}
}));
return plugins;
}
var src_default = VitePluginWindicss;
export {
src_default as default
};