UNPKG

@pandabox/unplugin

Version:

Panda CSS as a Vite/Rollup/Webpack/Esbuild plugin

756 lines (734 loc) 27.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/rspack.ts var rspack_exports = {}; __export(rspack_exports, { default: () => rspack_default }); module.exports = __toCommonJS(rspack_exports); var import_unplugin2 = require("unplugin"); // src/plugin/core.ts var import_config = require("@pandacss/config"); var import_pluginutils = require("@rollup/pluginutils"); var import_unplugin = require("unplugin"); var import_promises = require("fs/promises"); var import_node2 = require("@pandacss/node"); // src/plugin/create-context.ts var import_postcss_plugins = require("@pandabox/postcss-plugins"); var import_node = require("@pandacss/node"); var import_shared = require("@pandacss/shared"); var import_node_path = require("path"); var import_postcss = __toESM(require("postcss"), 1); // src/plugin/ensure-absolute.ts var import_path = require("path"); var ensureAbsolute = (path3, root) => path3 ? (0, import_path.isAbsolute)(path3) ? path3 : (0, import_path.resolve)(root, path3) : root; // src/plugin/create-context.ts var createContext = (options) => { const { conf } = options; let panda = new import_node.PandaContext(conf); const root = ensureAbsolute("", options.root); const files = /* @__PURE__ */ new Map(); const toCss = async (sheet, opts) => { panda.appendLayerParams(sheet); panda.appendBaselineCss(sheet); panda.appendParserCss(sheet); let css2 = panda.getCss(sheet); if (opts.optimizeCss) { css2 = (0, import_postcss.default)([import_postcss_plugins.removeUnusedCssVars, import_postcss_plugins.removeUnusedKeyframes]).process(css2).toString(); } if (opts.minifyCss) { const { transform } = await import("esbuild"); if (transform) { const { code } = await transform(css2, { loader: "css", minify: true }); css2 = code; } } return css2; }; const css = (0, import_shared.createCss)(panda.baseSheetContext); const mergeFns = (0, import_shared.createMergeCss)(panda.baseSheetContext); const mergeCss = mergeFns.mergeCss; return { // So that we can mutate the `panda` variable and it's still reflected outside get panda() { return panda; }, css, mergeCss, reloadContext: async () => { const affecteds = await panda.diff.reloadConfigAndRefreshContext((conf2) => { panda = new import_node.PandaContext(conf2); }); await panda.hooks["config:change"]?.({ config: panda.config, changes: affecteds }); if (options.codegen) { await (0, import_node.codegen)(panda, Array.from(affecteds.artifacts)); } return panda; }, root, files, toCss, paths: { root } }; }; // src/plugin/transform.ts var import_extractor = require("@pandacss/extractor"); var import_magic_string = __toESM(require("magic-string"), 1); var import_ts_morph3 = require("ts-morph"); // src/plugin/create-cva.ts var import_shared2 = require("@pandacss/shared"); var defaults = (conf) => ({ base: {}, variants: {}, defaultVariants: {}, compoundVariants: [], ...conf }); var createCva = (config, mergeCss) => { const { base, variants, defaultVariants, compoundVariants } = defaults(config); function resolve2(props = {}) { const computedVariants = { ...defaultVariants, ...(0, import_shared2.compact)(props) }; let variantCss = { ...base }; for (const [key, value] of Object.entries(computedVariants)) { const variantStyleObj = variants[key]?.[value]; if (variantStyleObj) { variantCss = mergeCss(variantCss, variantStyleObj); } } const compoundVariantCss = getCompoundVariantCss(compoundVariants, computedVariants, mergeCss); return mergeCss(variantCss, compoundVariantCss); } return resolve2; }; function getCompoundVariantCss(compoundVariants, variantMap, mergeCss) { let result = {}; compoundVariants.forEach((compoundVariant) => { const isMatching = Object.entries(compoundVariant).every(([key, value]) => { if (key === "css") return true; const values = Array.isArray(value) ? value : [value]; return values.some((value2) => variantMap[key] === value2); }); if (isMatching) { result = mergeCss(result, compoundVariant.css); } }); return result; } var transformCva = (name, config, css) => { const { base, variants, defaultVariants, compoundVariants } = defaults(config); return `(function () { const base = ${JSON.stringify(css(base))} const variantStyles = ${JSON.stringify( Object.fromEntries( Object.entries(variants).map(([variantKey, variantMap]) => [ variantKey, Object.fromEntries( Object.entries(variantMap).map(([valueKey, variantStyle]) => [valueKey, css(variantStyle)]) ) ]) ), null, 2 )} const defaultVariants = ${JSON.stringify(defaultVariants)} return function ${name}(variants) { ${compoundVariants.length > 0 ? ` const classList = [inlineCva(base, defaultVariants, variantStyles, variants)] const compoundVariants = ${JSON.stringify(compoundVariants)} addCompoundVariantCss(compoundVariants, variantProps, classList) return classList.join(' ')` : `return inlineCva(base, defaultVariants, variantStyles, variants)`} } })()`; }; // src/plugin/get-cva-var-name.ts var import_ts_morph = require("ts-morph"); var getVariableName = (node) => { const parent = node.getParent(); if (!import_ts_morph.Node.isVariableDeclaration(parent)) return; const name = parent.getName(); return name; }; // src/plugin/get-import-declarations.ts var import_ts_path = require("@pandacss/config/ts-path"); // src/plugin/get-module-specifier-value.ts var getModuleSpecifierValue = (node) => { try { return node.getModuleSpecifierValue(); } catch { return; } }; // src/plugin/has-macro-attribute.ts var import_ts_morph2 = require("ts-morph"); var getMacroAttribute = (node, attrName = "type") => { const attrs = node.getAttributes(); if (!attrs) return null; const elements = attrs.getElements(); if (!elements.length) return null; let withAttr = null; elements.some((n) => { const name = n.getName(); if (name !== attrName) return; const value = n.getValue(); if (!import_ts_morph2.Node.isStringLiteral(value)) return; withAttr = value.getLiteralText(); return true; }); return withAttr; }; // src/plugin/get-import-declarations.ts function getImportDeclarations(context, sourceFile, onlyMacroImports = false) { const { imports, tsOptions } = context; const importDeclarations = []; sourceFile.getImportDeclarations().forEach((node) => { const mod = getModuleSpecifierValue(node); if (!mod) return; const withAttr = getMacroAttribute(node); node.getNamedImports().forEach((specifier) => { const name = specifier.getNameNode().getText(); const alias = specifier.getAliasNode()?.getText() || name; const result = { name, alias, mod, kind: "named", withAttr }; const found = imports.match(result, (mod2) => { if (!tsOptions?.pathMappings) return; return (0, import_ts_path.resolveTsPathPattern)(tsOptions.pathMappings, mod2); }); if (!found) return; importDeclarations.push(result); }); const namespace = node.getNamespaceImport(); if (namespace) { const name = namespace.getText(); const result = { name, alias: name, mod, kind: "namespace", withAttr }; const found = imports.match(result, (mod2) => { if (!tsOptions?.pathMappings) return; return (0, import_ts_path.resolveTsPathPattern)(tsOptions.pathMappings, mod2); }); if (!found) return; importDeclarations.push(result); } }); return importDeclarations; } // src/plugin/unbox-combine-result.ts var combineResult = (unboxed) => { return [...unboxed.conditions, unboxed.raw, ...unboxed.spreadConditions]; }; // src/plugin/transform.ts var tranformPanda = (ctx, options) => { const { code, optimizeJs, sourceFile, parserResult } = options; if (!parserResult) return null; const { panda, css, mergeCss } = ctx; const factoryName = panda.jsx.factoryName || "styled"; const s = new import_magic_string.default(code); const onlyMacroImports = optimizeJs === "macro"; const importDeclarations = getImportDeclarations(panda.parserOptions, sourceFile, onlyMacroImports); const file = panda.imports.file(importDeclarations); const jsxPatternKeys = panda.patterns.details.map((d) => d.jsxName); const isJsxPatternImported = file["createMatch"](file["importMap"].jsx, jsxPatternKeys); const cvaNames = collectCvaNames(parserResult); const cvaUsages = extractCvaUsages(sourceFile, cvaNames); const cvaConfigs = /* @__PURE__ */ new Map(); let needInlineCvaImport = false; let needCompoundVariantsImport = false; parserResult.all.forEach((result) => { const fnName = result.name; if (!fnName) return; if (!result.box) return; if (result.type === "jsx" || result.type === "jsx-recipe") return; const node = result.box.getNode(); let shouldOnlyTransformMacroImport = onlyMacroImports; if (result.type && !shouldOnlyTransformMacroImport && typeof optimizeJs === "object" && result.type !== "sva" && optimizeJs[result.type] === "macro") { shouldOnlyTransformMacroImport = true; } const importedIdentifier = getImportedIdentifier(node); if (!importedIdentifier) return; const importedName = fnName.split(".")[0]; const importDecl = importDeclarations.find( (imp) => imp.name === importedName && imp.alias === importedIdentifier ); if (!importDecl || importDecl.withAttr === "runtime" || shouldOnlyTransformMacroImport && importDecl.withAttr !== "macro") { return; } if (result.type?.includes("jsx")) { const isJsx = import_ts_morph3.Node.isJsxOpeningElement(node) || import_ts_morph3.Node.isJsxSelfClosingElement(node); if (!isJsx) return; const tagName = node.getTagNameNode().getText(); const isJsxPattern = panda.patterns.details.find((node2) => node2.jsxName === tagName); if (isJsxPattern && !isJsxPatternImported(tagName)) return; const isPandaComponent = file.isPandaComponent(tagName); if (!isPandaComponent) return; if (result.type === "jsx-factory" && !tagName.includes(factoryName + ".")) { return; } const styleProps = new Set(result.data.flatMap((data) => Object.keys(data))); const styleObjects2 = result.type === "jsx-pattern" ? result.data.map((data) => panda.patterns.transform(panda.patterns.find(fnName), data)) : result.data; const merged2 = mergeCss(...styleObjects2); const className2 = css(merged2); const otherProps = node.getAttributes().filter((n) => { if (import_ts_morph3.Node.isJsxAttribute(n)) { return !styleProps.has(n.getNameNode().getText()); } return true; }); let tag; if (tagName.includes(".")) { ; [, tag] = tagName.split("."); } else if (result.type === "jsx-pattern") { const patternName = panda.patterns.find(fnName); const patternConfig = panda.patterns.getConfig(patternName); tag = patternConfig.jsxElement ?? "div"; } s.update( node.getStart(), node.getEnd(), `<${tag} className="${className2}" ${otherProps.map((n) => n.getText()).join(" ")}${import_ts_morph3.Node.isJsxSelfClosingElement(node) ? "/" : ""}>` ); if (import_ts_morph3.Node.isJsxOpeningElement(node)) { const parent = node.getParent(); if (import_ts_morph3.Node.isJsxElement(parent)) { const closing = parent.getClosingElement(); if (closing) { s.update(closing.getStart(), closing.getEnd(), `</${tag}>`); } } } return; } if (!import_ts_morph3.Node.isCallExpression(node) || !fnName) return; const identifier = node.getExpression().getText(); const isRaw = identifier.includes(".raw"); if (isRaw) { const rawIndex = identifier.indexOf(".raw"); const obj = s.slice(node.getStart() + rawIndex + 4, node.getEnd()); s.update(node.getStart(), node.getEnd(), obj); return; } if (result.type === "cva") { result.data.forEach((recipe) => { const varName = getVariableName(node); if (!varName) return; const resolve2 = createCva(recipe, mergeCss); cvaConfigs.set(varName, { config: recipe, resolve: resolve2 }); s.update(node.getStart(), node.getEnd(), transformCva(varName, recipe, css)); needInlineCvaImport = true; if (recipe.compoundVariants?.length) { needCompoundVariantsImport = true; } }); cvaUsages.forEach((data, key) => { const { variants } = data; const cva = cvaConfigs.get(key); if (!cva) return; const computed = cva.resolve(variants); const className2 = css(computed); s.update(data.node.getStart() - 1, data.node.getEnd() + 1, `"${className2}"`); }); return; } const classList = /* @__PURE__ */ new Set(); const styleObjects = /* @__PURE__ */ new Set(); const processAtomic = (data) => { styleObjects.add(data); }; if (result.type === "css") { result.data.forEach((d) => processAtomic(d)); } else if (result.type === "pattern") { result.data.forEach((data) => { const styleProps = panda.patterns.transform(fnName, data); processAtomic(styleProps); }); } else if (result.type === "recipe") { const config = panda.recipes.getConfig(fnName); if (!config) return; const transform = panda.recipes.getTransform(fnName, panda.recipes.isSlotRecipe(fnName)); const base = transform("__ignore__", "__ignore__"); classList.add(base.className); config.base && processAtomic(config.base); result.data.forEach((variants) => { const computedVariants = Object.assign({}, config.defaultVariants, variants); Object.entries(computedVariants).forEach(([key, value]) => { const transformed = transform(key, value); classList.add(transformed.className); const variantStyles = config.variants?.[key]?.[value]; variantStyles && processAtomic(variantStyles); }); }); config.compoundVariants?.forEach((compoundVariant) => { if (!compoundVariant) return; processAtomic(compoundVariant.css); }); } const merged = mergeCss(...Array.from(styleObjects)); const className = result.type === "recipe" ? Array.from(classList).join(" ") : css(merged); s.update(node.getStart(), node.getEnd(), `"${className}"`); }); if (needCompoundVariantsImport) { s.prepend(`import { addCompoundVariantCss } from 'virtual:panda-compound-variants'; `); } if (needInlineCvaImport) { s.prepend(`import { inlineCva } from 'virtual:panda-inline-cva'; `); } return { code: s.toString(), map: s.generateMap({ hires: true }) }; }; var collectCvaNames = (parserResult) => { const cvaNames = /* @__PURE__ */ new Set(); parserResult.cva.forEach((cva) => { const node = cva.box?.getNode(); if (!node) return; const varName = getVariableName(node); if (!varName) return; return cvaNames.add(varName); }); return cvaNames; }; var extractCvaUsages = (sourceFile, cvaNames) => { const cvaUsages = /* @__PURE__ */ new Map(); sourceFile.forEachDescendant((node) => { if (!import_ts_morph3.Node.isIdentifier(node)) return; const fnName = node.getText(); if (!cvaNames.has(fnName)) return; const parent = node.getParent(); if (!import_ts_morph3.Node.isCallExpression(parent)) return; const array = (0, import_extractor.extractCallExpressionArguments)(parent, { flags: { skipTraverseFiles: true } }); array.value.forEach((arg) => { if (import_extractor.box.isMap(arg)) { const unboxed = combineResult((0, import_extractor.unbox)(arg)); unboxed.forEach((variants) => { cvaUsages.set(fnName, { variants, node: parent }); }); } }); }); return cvaUsages; }; var getImportedIdentifier = (node) => { if (import_ts_morph3.Node.isJsxOpeningElement(node) || import_ts_morph3.Node.isJsxSelfClosingElement(node)) { const tagName = node.getTagNameNode(); if (import_ts_morph3.Node.isIdentifier(tagName)) { return tagName.getText(); } if (import_ts_morph3.Node.isPropertyAccessExpression(tagName)) { return tagName.getExpression().getText().split(".")[0]; } } if (import_ts_morph3.Node.isCallExpression(node)) { const expr = node.getExpression(); return expr.getText().split(".")[0]; } }; // src/plugin/core.ts var import_node_path2 = __toESM(require("path"), 1); // src/plugin/cva-fns.ts function addCompoundVariantCss(compoundVariants, variants, classList) { compoundVariants.forEach(({ css, ...compoundVariant }) => { if (css) { const isMatching = Object.entries(compoundVariant).every(([key, value]) => { const values = Array.isArray(value) ? value : [value]; return values.some((value2) => variants[key] === value2); }); if (isMatching) { classList.push(css); } } }); } function inlineCva(base, defaultVariants, variantStyles, variants) { const classList = [base]; const variantProps = { ...defaultVariants, ...variants }; for (const [key, value] of Object.entries(variantProps)) { if (variantStyles[key][value]) { classList.push(variantStyles[key][value]); } } return classList.join(" "); } // src/plugin/core.ts var import_es_toolkit = require("es-toolkit"); var import_node_fs = require("fs"); var createVirtualModuleId = (id) => { const base = `virtual:panda${id}`; return { id: base, resolved: "\0" + base }; }; var ids = { css: createVirtualModuleId(".css"), inlineCva: createVirtualModuleId("-inline-cva"), compoundVariants: createVirtualModuleId("-compound-variants") }; var pandaPreamble = "/*! PANDA_CSS */"; var throttleWaitMs = 1e3; var unpluginFactory = (rawOptions) => { const options = resolveOptions(rawOptions ?? {}); const filter = (0, import_pluginutils.createFilter)(options.include, options.exclude); let outfile = options.outfile ? ensureAbsolute(options.outfile, options.cwd) : ids.css.resolved; let _ctx; let initPromise; const getCtx = async () => { await init(); if (!_ctx) throw new Error("@pandabox/unplugin context not initialized"); return _ctx; }; const init = () => { if (initPromise) return initPromise; initPromise = (0, import_config.loadConfig)({ cwd: options.cwd, file: options.configPath }).then(async (conf) => { conf.config.cwd = options.cwd; _ctx = createContext({ root: options.cwd, conf, codegen: options.codegen }); if (options.contextCreated) { await options.contextCreated({ context: _ctx.panda }); } }); return initPromise; }; let server; let lastCss; let updateCssOnTransform = true; const updateCss = async () => { const ctx = await getCtx(); const css = await ctx.toCss(ctx.panda.createSheet(), options); const isCssUpdated = lastCss !== css; lastCss = css; if (!isCssUpdated) return; if (outfile !== ids.css.resolved) { await (0, import_promises.writeFile)(outfile, css); } else { if (!server) return; const mod = server.moduleGraph.getModuleById(outfile.replaceAll("\\", "/")); if (!mod) return; await server.reloadModule(mod); } }; const requestUpdateCss = (0, import_es_toolkit.throttle)(updateCss, throttleWaitMs, { edges: ["leading", "trailing"] }); return { name: "unplugin-panda", enforce: "pre", resolveId(id) { if (id === ids.css.id) { return ids.css.resolved; } if (id === ids.inlineCva.id) { return ids.inlineCva.resolved; } if (id === ids.compoundVariants.id) { return ids.compoundVariants.resolved; } }, async load(id) { if (id === ids.inlineCva.resolved) { return `export ${inlineCva.toString()}`; } if (id === ids.compoundVariants.resolved) { return `export ${addCompoundVariantCss.toString()}`; } if (id !== outfile) return; if (!server) return pandaPreamble; const ctx = await getCtx(); const sheet = ctx.panda.createSheet(); const css = await ctx.toCss(sheet, options); return css; }, transformInclude(id) { return filter(id); }, async transform(code, id) { const ctx = await getCtx(); const { panda } = ctx; let transformResult = { code, map: void 0 }; if (options.transform) { const result2 = await options.transform({ filePath: id, content: code, context: ctx.panda }) || code; if (typeof result2 === "string") { transformResult.code = result2; } else if (result2) { transformResult = result2; } } const sourceFile = panda.project.addSourceFile(id, transformResult.code); if (options.onSourceFile) { await options.onSourceFile({ sourceFile, context: ctx.panda }); } const parserResult = panda.project.parseSourceFile(id); if (!parserResult) return null; if (!parserResult.isEmpty()) { ctx.files.set(id, code); if (updateCssOnTransform) requestUpdateCss(); } if (!options.optimizeJs) { return transformResult.code !== code ? transformResult : null; } const result = tranformPanda(ctx, { code: transformResult.code, id, sourceFile, parserResult, optimizeJs: options.optimizeJs }); return result; }, vite: { name: "unplugin-panda", configResolved(config) { if (!options.cwd) { options.cwd = config.configFile ? import_node_path2.default.dirname(config.configFile) : config.root; outfile = options.outfile ? ensureAbsolute(options.outfile, options.cwd) : ids.css.resolved; } updateCssOnTransform = config.command !== "build"; }, async configureServer(_server) { server = _server; const ctx = await getCtx(); if (outfile !== ids.css.resolved) { if (!(0, import_node_fs.existsSync)(outfile)) await (0, import_promises.writeFile)(outfile, ""); let prevState = updateCssOnTransform; updateCssOnTransform = false; try { for (const file of ctx.panda.getFiles()) { if (import_node_path2.default.basename(file) === "panda.buildinfo.json") { ctx.panda.project.parseSourceFile(file); } else { await server.transformRequest(file); } } } finally { updateCssOnTransform = prevState; } await updateCss(); } if (options.codegen) { const { msg } = await (0, import_node2.codegen)(ctx.panda); } const sources = new Set( [ctx.panda.conf.path, ...ctx.panda.conf.dependencies ?? [], ...ctx.panda.config.dependencies ?? []].map( (f) => ensureAbsolute(f, ctx.root) ) ); sources.forEach((file) => server.watcher.add(file)); server.watcher.on("change", async (file) => { const filePath = ensureAbsolute(file, ctx.root); if (!sources.has(filePath)) return; await ctx.reloadContext(); const timestamp = Date.now(); const invalidate = (file2) => { const mod = server.moduleGraph.getModuleById(file2); if (mod) { server.moduleGraph.invalidateModule(mod, /* @__PURE__ */ new Set(), timestamp, true); } }; invalidate(outfile); }); }, async generateBundle(_, bundles) { const cssBundle = Object.values(bundles).find( (bundle) => bundle.type === "asset" && bundle.name?.endsWith(".css") && typeof bundle.source === "string" && bundle.source.includes(pandaPreamble) ); if (cssBundle) { const source = cssBundle.source; const ctx = await getCtx(); const sheet = ctx.panda.createSheet(); const css = await ctx.toCss(sheet, options); cssBundle.source = source.replace(pandaPreamble, css); } } } }; }; var resolveOptions = (options) => { let optimizeJs = options.optimizeJs ?? "auto"; if (typeof optimizeJs === "object") { optimizeJs = { css: optimizeJs.css ?? "auto", cva: optimizeJs.cva ?? "auto", pattern: optimizeJs.cva ?? "auto", recipe: optimizeJs.cva ?? "auto", "jsx-factory": optimizeJs.cva ?? "auto", "jsx-pattern": optimizeJs.cva ?? "auto", ...optimizeJs }; } return { ...options, cwd: options.cwd || "", configPath: options.configPath, include: options.include || [/\.[cm]?[jt]sx?$/], exclude: options.exclude || [/node_modules/, /styled-system/], optimizeCss: options.optimizeCss ?? true, minifyCss: options.minifyCss ?? false, optimizeJs: options.optimizeJs ?? "macro", codegen: options.codegen ?? options.codeGen ?? true }; }; // src/rspack.ts var rspack_default = (0, import_unplugin2.createRspackPlugin)(unpluginFactory); exports.default = module.exports;