@pandabox/panda-plugins
Version:
- `missing-css-warnings` - Logs a warning message when a CSS rule was used at runtime but couldn't be statically extracted - `strict-tokens-scope` - Enforce `strictTokens` only for a set of `TokenCategory` or style props - `strict-tokens-runtime` - Enfo
652 lines (627 loc) • 23.1 kB
JavaScript
;
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/index.ts
var src_exports = {};
__export(src_exports, {
pluginMinimalSetup: () => pluginMinimalSetup,
pluginMissingCssWarnings: () => pluginMissingCssWarnings,
pluginRemoveFeatures: () => pluginRemoveFeatures,
pluginRemoveNegativeSpacing: () => pluginRemoveNegativeSpacing,
pluginRemoveUnusedCss: () => pluginRemoveUnusedCss,
pluginRestrictStyledProps: () => pluginRestrictStyledProps,
pluginStrictTokensRuntime: () => pluginStrictTokensRuntime,
pluginStrictTokensScope: () => pluginStrictTokensScope,
transformFeatures: () => transformFeatures,
transformMissingCssWarnings: () => transformMissingCssWarnings,
transformNegativeSpacing: () => transformNegativeSpacing,
transformPropTypes: () => transformPropTypes,
transformRestrictStyledProps: () => transformRestrictStyledProps,
transformStrictTokensRuntime: () => transformStrictTokensRuntime
});
module.exports = __toCommonJS(src_exports);
// src/remove-features.ts
var pluginRemoveFeatures = (options) => {
return {
name: "remove-features",
hooks: {
"codegen:prepare": (args) => {
return transformFeatures(args, options);
}
}
};
};
var transformFeatures = (args, options) => {
const { features: transforms } = options;
const features = {
"no-jsx": transforms.includes("no-jsx"),
"no-styled": transforms.includes("no-styled"),
"no-recipes": transforms.includes("no-recipes"),
"no-patterns": transforms.includes("no-patterns"),
"no-cva": transforms.includes("no-cva"),
"no-sva": transforms.includes("no-sva"),
"only-css-dir": transforms.includes("only-css-dir")
};
const filtered = args.artifacts.filter((a) => {
if (features["no-jsx"] && a.dir?.at(-1) === "jsx")
return false;
if (features["no-recipes"] && a.dir?.at(-1) === "recipes")
return false;
if (features["no-patterns"] && a.dir?.at(-1) === "patterns")
return false;
if (features["only-css-dir"]) {
const dir = a.dir?.at(-1);
if (dir && ["jsx", "recipes", "patterns"].includes(dir))
return false;
}
if (features["no-styled"] && a.id === "jsx-factory")
return false;
if (features["no-cva"] && a.id.includes("cva"))
return false;
if (features["no-sva"] && a.id.includes("sva"))
return false;
return true;
});
if (features["no-styled"]) {
const jsxIndex = filtered.find((a) => a.id === "jsx-patterns-index");
if (jsxIndex) {
jsxIndex.files.forEach((f) => {
if (!f.code)
return;
const lines = f.code.split("\n");
const exportFactory = lines.findIndex((l) => l.includes("export * from './factory"));
if (exportFactory === -1)
return;
lines.splice(exportFactory, 1);
f.code = lines.join("\n");
});
}
}
if (features["no-cva"] || features["no-sva"]) {
const cssIndex = filtered.find((a) => a.id === "css-index");
if (cssIndex) {
cssIndex.files.forEach((f) => {
if (!f.code)
return;
const lines = f.code.split("\n");
if (features["no-cva"]) {
const exportIndex = lines.findIndex((l) => l.includes("export * from './cva"));
if (exportIndex > -1) {
lines.splice(exportIndex, 1);
}
}
if (features["no-sva"]) {
const exportIndex = lines.findIndex((l) => l.includes("export * from './sva"));
if (exportIndex > -1) {
lines.splice(exportIndex, 1);
}
}
f.code = lines.join("\n");
});
}
}
return filtered;
};
// src/minimal-setup.ts
var pluginMinimalSetup = (options) => {
return {
name: "minimal-setup",
hooks: {
"config:resolved": (args) => {
args.config.eject = true;
if (!args.config.presets) {
args.config.presets = [];
}
},
"codegen:prepare": (args) => {
return transformFeatures(args, options);
}
}
};
};
// src/missing-css-warnings.ts
var pluginMissingCssWarnings = (options) => {
return {
name: "missing-css-warnings",
hooks: {
"codegen:prepare": (args) => {
return transformMissingCssWarnings(args, options);
}
}
};
};
var transformMissingCssWarnings = (args, options) => {
const { enabled = true } = options ?? {};
if (!enabled)
return args.artifacts;
const helpersArtifact = args.artifacts.find((art) => art.id === "helpers");
const helpersFile = helpersArtifact?.files.find((f) => f.file.includes("helpers"));
if (!helpersFile?.code) {
return args.artifacts;
}
helpersFile.code = helpersFile.code.replace(
"classNames.add(className)",
"classNames.add(className);\nisInCss(className)"
);
helpersFile.code += `
/* eslint-disable no-control-regex */
var rcssescape = /([\\0-\\x1f\\x7f]|^-?\\d)|^-$|^-|[^\\x80-\\uFFFF\\w-]/g;
var fcssescape = function (ch, asCodePoint) {
if (!asCodePoint) return "\\\\" + ch;
if (ch === "\\0") return "\\uFFFD";
if (ch === "-" && ch.length === 1) return "\\\\-";
return ch.slice(0, -1) + "\\\\" + ch.charCodeAt(ch.length - 1).toString(16);
};
var esc = (sel) => {
return (sel + "").replace(rcssescape, fcssescape);
};
const isCssStyleRule = (rule) => rule instanceof CSSStyleRule
const isGroupingRule = (rule) => 'cssRules' in rule
function traverseCSSRule(rule, className) {
const stack = []
stack.push(rule)
while (stack.length > 0) {
const currentRule = stack.pop()
if (!currentRule) continue
if (isCssStyleRule(currentRule)) {
const selectorText = currentRule.selectorText
if (selectorText && selectorText.includes(className)) {
return currentRule
}
}
if (isGroupingRule(currentRule) && currentRule.cssRules) {
stack.push(...Array.from(currentRule.cssRules))
}
}
}
const missingRules = new Set()
const isInCss = (className) => {
if (typeof window === 'undefined') return
const escaped = '.' + esc(className)
const styleSheets = document.styleSheets
for (const styleSheet of styleSheets) {
const rules = styleSheet.cssRules || styleSheet.rules
if (!rules) continue
for (const rule of rules) {
const match = traverseCSSRule(rule, escaped)
if (match) return match
}
}
if (missingRules.has(className)) return
missingRules.add(className)
console.error(\`No matching CSS rule found for "\${className}"\`)
}`;
return args.artifacts;
};
// src/remove-negative-spacing.ts
var pluginRemoveNegativeSpacing = (options) => {
return {
name: "remove-negative-spacing",
hooks: {
"codegen:prepare": (args) => {
return transformNegativeSpacing(args, options ?? {});
}
}
};
};
var transformNegativeSpacing = (args, options) => {
const artifact = args.artifacts.find((a) => a.id === "design-tokens");
if (!artifact)
return args.artifacts;
const artifactContent = artifact.files.find((f) => f.file.includes("tokens.d"));
if (!artifactContent)
return args.artifacts;
let fileContent = artifactContent.code;
if (!fileContent)
return args.artifacts;
const { spacingTokenType = true, tokenType = true } = options;
if (spacingTokenType) {
fileContent = artifactContent.code = updateSpacingTokenType(fileContent);
}
if (tokenType) {
artifactContent.code = updateTokenType(fileContent);
}
return args.artifacts;
};
var spacingTokenRegex = /export type SpacingToken = (.+)\n/;
var tokenRegex = /export type Token = (.+)\n/;
var updateSpacingTokenType = (file) => {
const match = file.match(spacingTokenRegex);
if (!match)
return file;
const spacingTokenString = match[1];
const values = spacingTokenString.split(" | ").map((v) => v.slice(1, -1)).filter((v) => !v.startsWith("-"));
return file.replace(spacingTokenRegex, `export type SpacingToken = ${values.map((v) => `"${v}"`).join(" | ")}
`);
};
var updateTokenType = (file) => {
const match = file.match(tokenRegex);
if (!match)
return file;
const tokenString = match[1];
const values = tokenString.split(" | ").map((v) => v.slice(1, -1)).filter((v) => !v.startsWith("spacing.-"));
return file.replace(tokenRegex, `export type Token = ${values.map((v) => `"${v}"`).join(" | ")}
`);
};
// src/remove-unused-css.ts
var import_postcss_plugins = require("@pandabox/postcss-plugins");
var import_postcss = __toESM(require("postcss"));
var defaultOptions = {
removeCssVars: true,
removeKeyframes: true
};
var pluginRemoveUnusedCss = (options = defaultOptions) => {
return {
name: "remove-unused-css",
hooks: {
"cssgen:done": ({ artifact, content }) => {
const isEnabled = options.removeCssVars || options.removeKeyframes;
if (!isEnabled)
return;
if (artifact !== "styles.css")
return;
let css = content;
const plugins = [];
if (options.removeCssVars) {
plugins.push(import_postcss_plugins.removeUnusedCssVars);
} else if (options.removeKeyframes) {
plugins.push(import_postcss_plugins.removeUnusedKeyframes);
}
return (0, import_postcss.default)(plugins).process(css).toString();
}
}
};
};
// src/restrict-styled-props.ts
var supportedJsxFrameworks = ["react"];
var pluginRestrictStyledProps = () => {
return {
name: "restrict-styled-props",
hooks: {
"config:resolved": (args) => {
const jsxFramework = args.config.jsxFramework;
if (!supportedJsxFrameworks.includes(jsxFramework)) {
throw new Error(
`[plugin:restrict-styled-props]: Unsupported jsxFramework: ${jsxFramework}. This Panda plugin only supports: ${supportedJsxFrameworks.join(", ")}`
);
}
},
"codegen:prepare": (args) => {
return transformRestrictStyledProps(args);
}
}
};
};
var transformRestrictStyledProps = (args) => {
const factoryArtifact = args.artifacts.find((art) => art.id === "jsx-factory");
const factoryJs = factoryArtifact?.files.find((f) => f.file.includes(".mjs") || f.file.includes(".js"));
const jsxTypes = args.artifacts.find((art) => art.id === "types-jsx")?.files.find((f) => f.file.includes("jsx"));
if (!factoryJs?.code || !jsxTypes?.code) {
return args.artifacts;
}
factoryJs.code = factoryJs.code.replace(
"function styledFn",
`
const filterProps = (styles, allowedProps) => {
if (!allowedProps) return styles
return Object.fromEntries(Object.entries(styles).filter(([key]) => allowedProps.includes(key)))
}
function styledFn`
).replaceAll("cssStyles)", "filterProps(cssStyles, configOrCva.props))");
factoryJs.code = factoryJs.code.replace("variantProps, styleProps", "variantProps, _styleProps").replace(
"function recipeClass",
`
const styleProps = filterProps(_styleProps, configOrCva.props)
function recipeClass`
);
const startIndex = jsxTypes.code.indexOf("export interface StyledComponent");
const endIndex = jsxTypes.code.indexOf("export type JsxElements");
jsxTypes.code = jsxTypes.code.slice(0, startIndex) + `export interface StyledComponent<T extends ElementType, P extends Dict = {}, TStyleProp> {
(
props: JsxHTMLProps<
ComponentProps<T>,
Assign<TStyleProp extends never ? JsxStyleProps : Pick<JsxStyleProps, TStyleProp>, P>
>,
): JSX.Element
displayName?: string
}
interface RecipeFn {
__type: any
}
interface JsxFactoryOptions<TProps extends Dict> {
dataAttr?: boolean
defaultProps?: TProps
shouldForwardProp?(prop: string, variantKeys: string[]): boolean
}
export type JsxRecipeProps<T extends ElementType, P extends Dict> = JsxHTMLProps<ComponentProps<T>, P>
export type JsxElement<T extends ElementType, P extends Dict, TStyleProp extends StyleProp> =
T extends StyledComponent<infer A, infer B, infer TStyleProp>
? StyledComponent<A, Pretty<DistributiveUnion<P, B>>, TStyleProp>
: StyledComponent<T, P, TStyleProp>
type StyleProp = keyof SystemProperties | 'css'
interface StyledRecipeDefinition<T extends RecipeVariantRecord, TStyleProp extends StyleProp>
extends RecipeDefinition<T> {
props?: TStyleProp[]
}
export interface JsxFactory {
<T extends ElementType>(component: T): StyledComponent<T, {}>
<T extends ElementType, P extends RecipeVariantRecord, TStyleProp extends StyleProp>(
component: T,
recipe: StyledRecipeDefinition<P, TStyleProp>,
options?: JsxFactoryOptions<JsxRecipeProps<T, RecipeSelection<P>>>,
): JsxElement<T, RecipeSelection<P>, TStyleProp>
<T extends ElementType, P extends RecipeFn>(
component: T,
recipeFn: P,
options?: JsxFactoryOptions<JsxRecipeProps<T, P['__type']>>,
): JsxElement<T, P['__type']>
}` + jsxTypes.code.slice(endIndex);
return args.artifacts;
};
// src/strict-tokens-runtime.ts
var pluginStrictTokensRuntime = (options) => {
let config;
return {
name: "strict-tokens-runtime",
hooks: {
"context:created": (args) => {
config = {
strictTokens: args.ctx.config.strictTokens,
shorthands: args.ctx.config.shorthands
};
if (!config.strictTokens) {
args.logger.debug("plugin:strict-tokens-runtime", `strictTokens is not enabled, skipping`);
}
},
"codegen:prepare": (args) => {
if (!config.strictTokens)
return args.artifacts;
return transformStrictTokensRuntime(args, options, config);
}
}
};
};
var transformStrictTokensRuntime = (args, options, config) => {
const { categories, props } = options;
if (categories && !categories.length && props && !props.length)
return args.artifacts;
const cssFn = args.artifacts.find((art) => art.id === "css-fn");
const cssFile = cssFn?.files.find((f) => f.file.includes("css"));
const tokens = args.artifacts.find((art) => art.id === "design-tokens");
const tokenJs = tokens?.files.find((f) => f.file.includes("index.js") || f.file.includes("index.mjs"));
const typesStyles = args.artifacts.find((art) => art.id === "types-styles");
const propTypesDts = typesStyles?.files.find((f) => f.file.includes("prop-type"));
if (!cssFile?.code || !tokenJs?.code || !propTypesDts?.code) {
return args.artifacts;
}
const propsByCategory = mapPropsToTokenCategory(propTypesDts.code);
const shorthandMap = mapShorthandsToProperties(propTypesDts.code);
propsByCategory.forEach((set) => {
set.forEach((key) => {
const shorthand = shorthandMap[key];
if (shorthand) {
set.add(shorthand);
}
});
});
propsByCategory.forEach((set, cat) => {
if (categories && !categories.includes(cat)) {
propsByCategory.delete(cat);
return;
}
if (props?.length) {
set.forEach((prop) => {
if (!props.includes(prop)) {
set.delete(prop);
}
});
}
});
const tokenObj = tokenJs.code.slice(
tokenJs.code.indexOf("const tokens = ") + "const tokens = ".length,
tokenJs.code.indexOf("export function token")
);
const tokensJSON = JSON.parse(tokenObj);
const tokenValuesByCategory = /* @__PURE__ */ new Map();
Object.keys(tokensJSON).map((key) => {
const [category, ...tokenName] = key.split(".");
if (categories && !categories.includes(category)) {
return;
}
const prop = tokenName.join(".");
if (props?.length && !props.includes(prop)) {
return;
}
if (!tokenValuesByCategory.has(category)) {
tokenValuesByCategory.set(category, /* @__PURE__ */ new Set());
}
const set = tokenValuesByCategory.get(category);
set.add(prop);
});
cssFile.code = cssFile.code.replace(
"const context = {",
`
const tokenValues = ${JSON.stringify(mapToObj(tokenValuesByCategory), null, 2)}
const propsByCat = ${JSON.stringify(mapToObj(propsByCategory), null, 2)}
const propList = new Set(Object.values(propsByCat).flat())
const categoryByProp = new Map()
propList.forEach((prop) => {
Object.entries(propsByCat).forEach(([category, list]) => {
if (list.includes(prop)) {
categoryByProp.set(prop, category)
}
})
})
const context = {`
);
const withStrictChecks = `
// Only throw error if the property is in the list of props
// bound to a token category and the value is not a valid token value for that category
if (propList.has(prop)) {
const category = categoryByProp.get(prop)
if (category) {
const values = tokenValues[category]
if (values && !values.includes(value)) {
throw new Error(\`[super-strict-tokens]: Unknown value:
{ \${prop}: \${value} }
Valid values in \${category} are: \${values.join(', ')}\`)
}
}
}`;
if (config?.shorthands) {
cssFile.code = cssFile.code.replace(
"transform: (prop, value) => {",
`transform: (prop, value) => {
${withStrictChecks}`
);
} else {
const startStr = "transform: (key, value) => ";
const fnStartIndex = cssFile.code.indexOf(startStr) + startStr.length;
const endStr = "withoutSpace(value)}";
const fnEndIndex = cssFile.code.indexOf(endStr) + endStr.length + 4;
cssFile.code = cssFile.code.slice(0, fnStartIndex) + "{\n" + withStrictChecks + "\nreturn " + cssFile.code.slice(fnStartIndex, fnEndIndex) + "\n}" + cssFile.code.slice(fnEndIndex);
}
return args.artifacts;
};
var propertyRegex = /(\w+):.+Tokens\["(\w+)"\]\s?;/g;
var mapPropsToTokenCategory = (file) => {
const byCategories = /* @__PURE__ */ new Map();
for (const match of file.matchAll(propertyRegex)) {
const propertyName = match[1];
const propertyType = match[2];
if (!byCategories.has(propertyType)) {
byCategories.set(propertyType, /* @__PURE__ */ new Set());
}
const set = byCategories.get(propertyType);
set.add(propertyName);
}
return byCategories;
};
var shorthandRegex = /(\w+):\s+Shorthand<"(\w+)">;/g;
var mapShorthandsToProperties = (file) => {
const shorthandMap = {};
for (const match of file.matchAll(shorthandRegex)) {
const shorthand = match[1];
const fullPropertyName = match[2];
shorthandMap[fullPropertyName] = shorthand;
}
return shorthandMap;
};
var mapToObj = (map) => {
const obj = {};
map.forEach((set, key) => {
obj[key] = [...set];
});
return obj;
};
// src/strict-tokens-scope.ts
var pluginStrictTokensScope = (options) => {
let logger;
let ctx;
return {
name: "strict-tokens-scope",
hooks: {
"context:created": (context) => {
logger = context.logger;
ctx = context.ctx.processor.context;
},
"codegen:prepare": (args) => {
return transformPropTypes(args, options, ctx, logger);
}
}
};
};
var transformPropTypes = (args, options, ctx, logger) => {
const { categories = [], props = [] } = options;
if (!categories.length && !props.length)
return args.artifacts;
const artifact = args.artifacts.find((x) => x.id === "types-styles");
const content = artifact?.files.find((x) => x.file.includes("style-props"));
if (!content?.code)
return args.artifacts;
const shorthandsByProp = /* @__PURE__ */ new Map();
ctx.utility.shorthands.forEach((longhand, shorthand) => {
shorthandsByProp.set(longhand, (shorthandsByProp.get(longhand) ?? []).concat(shorthand));
});
const types = ctx.utility.getTypes();
const categoryByProp = /* @__PURE__ */ new Map();
types.forEach((values, prop) => {
const categoryType = values.find((type) => type.includes("Tokens["));
if (!categoryType)
return;
const tokenCategory = categoryType.replace('Tokens["', "").replace('"]', "");
if (!categories.includes(tokenCategory)) {
return;
}
categoryByProp.set(prop, tokenCategory);
const shorthands = shorthandsByProp.get(prop);
if (!shorthands)
return;
shorthands.forEach((shorthand) => {
categoryByProp.set(shorthand, tokenCategory);
});
});
const strictTokenProps = props.concat(Array.from(categoryByProp.keys()));
if (!strictTokenProps.length)
return args.artifacts;
if (logger) {
logger.debug("plugin:restrict-strict-tokens", `\u{1F43C} Strict token props: ${strictTokenProps.join(", ")}`);
}
const regex = /(\w+)\?: ConditionalValue<WithEscapeHatch<(.+)>>/g;
const transformed = content.code.replace(regex, (match, prop, value) => {
if (strictTokenProps.includes(prop))
return match;
const longhand = ctx.utility.shorthands.get(prop);
if (value.includes("CssProperties"))
return match;
return `${prop}?: ConditionalValue<${value} | CssProperties["${longhand || prop}"]>`;
}) + [
`
type StrictTokenProps = ${strictTokenProps.map((t) => `"${t}"`).join(" | ")}`,
`type Restrict<Key, Value, Fallback> = Key extends StrictTokenProps ? Value : Value | Fallback`
].join("\n\n");
content.code = transformed;
return args.artifacts;
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
pluginMinimalSetup,
pluginMissingCssWarnings,
pluginRemoveFeatures,
pluginRemoveNegativeSpacing,
pluginRemoveUnusedCss,
pluginRestrictStyledProps,
pluginStrictTokensRuntime,
pluginStrictTokensScope,
transformFeatures,
transformMissingCssWarnings,
transformNegativeSpacing,
transformPropTypes,
transformRestrictStyledProps,
transformStrictTokensRuntime
});