Support vite cache invalidation

This commit is contained in:
Alex 2024-06-28 03:11:15 -04:00
parent 2a3e7390cf
commit 2a6df0dd4a
8 changed files with 2956 additions and 2439 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@aet/tailwind",
"version": "0.0.1-beta.6",
"version": "0.0.1-beta.8",
"main": "dist/index.js",
"license": "MIT",
"private": true,
@ -12,38 +12,40 @@
"dist"
],
"devDependencies": {
"@aet/eslint-rules": "^0.0.25",
"@aet/eslint-rules": "^0.0.33",
"@types/babel__core": "^7.20.5",
"@types/bun": "^1.1.0",
"@types/bun": "^1.1.6",
"@types/dedent": "^0.7.2",
"@types/lodash": "^4.17.0",
"@types/node": "^20.12.7",
"@types/lodash": "^4.17.6",
"@types/node": "^20.14.9",
"@types/postcss-safe-parser": "^5.0.4",
"@types/react": "^18.3.3",
"cli-highlight": "^2.1.11",
"clsx": "^2.1.1",
"colord": "^2.9.3",
"css-what": "^6.1.0",
"dedent": "^1.5.3",
"esbuild": "^0.20.2",
"esbuild": "^0.21.5",
"esbuild-register": "^3.5.0",
"eslint": "^8.57.0",
"postcss-nested": "^6.0.1",
"postcss-safe-parser": "^7.0.0",
"prettier": "^3.2.5",
"tailwindcss": "^3.4.3",
"tsup": "^8.0.2",
"typescript": "^5.4.5",
"vite": "^5.2.10",
"vitest": "^1.5.2"
"prettier": "^3.3.2",
"tailwindcss": "^3.4.4",
"tsup": "^8.1.0",
"typescript": "^5.5.2",
"vite": "^5.3.2",
"vitest": "^1.6.0"
},
"peerDependencies": {
"tailwindcss": "^3.4.3"
},
"dependencies": {
"@babel/core": "^7.24.4",
"@babel/core": "^7.24.7",
"@emotion/hash": "^0.9.1",
"lodash": "^4.17.21",
"postcss": "^8.4.38",
"type-fest": "^4.17.0"
"type-fest": "^4.20.1"
},
"prettier": {
"arrowParens": "avoid",
@ -53,4 +55,4 @@
"singleQuote": false,
"trailingComma": "es5"
}
}
}

5215
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,51 @@
#!/usr/bin/env bun
import { promises as fs } from "node:fs";
import { type Options, build } from "tsup";
import { build, defineConfig } from "tsup";
import { pick } from "lodash";
import tsupConfig from "../tsup.config";
import pkg from "../package.json" with { type: "json" };
await build(tsupConfig as Options);
await Bun.write(
"dist/package.json",
JSON.stringify(
pick(pkg, ["name", "version", "license", "dependencies", "author"]),
null,
2
)
);
const tsupConfig = defineConfig({
outDir: "dist",
splitting: false,
sourcemap: false,
dts: true,
treeshake: true,
platform: "node",
define: {
"process.env.BABEL_TAILWIND_BUILD": "true",
},
banner: {
js: "/* eslint-disable */",
},
});
await Bun.write(
`dist/base.d.ts`,
`/**\n * \`@tailwind base\` component.\n */\nexport {};`
);
await fs.copyFile("README.md", "dist/README.md");
await fs.copyFile("LICENSE.md", "dist/LICENSE.md");
await Promise.all([
build({
...tsupConfig,
entry: ["src/classed.tsx"],
outDir: "dist",
external: ["react", "react/jsx-runtime", "clsx"],
format: "esm",
clean: true,
}),
build({
...tsupConfig,
entry: ["src/index.ts"],
}),
Bun.write(
"dist/package.json",
JSON.stringify(
pick(pkg, ["name", "version", "license", "dependencies", "author"]),
null,
2
)
),
Bun.write(`dist/base.d.ts`, `/**\n * \`@tailwind base\` component.\n */\nexport {};`),
]);
await Promise.all([
fs.copyFile("README.md", "dist/README.md"),
fs.copyFile("LICENSE.md", "dist/LICENSE.md"),
]);
process.exit(0);

View File

@ -1,5 +1,6 @@
import { basename, dirname, extname, join } from "node:path";
import type babel from "@babel/core";
import hash from "@emotion/hash";
import { type NodePath, type types as t } from "@babel/core";
import type { SourceLocation, StyleMapEntry } from "./shared";
import { type ResolveTailwindOptions, getClassName } from "./index";
@ -18,7 +19,7 @@ const extractJSXContainer = (attr: NonNullable<t.JSXAttribute["value"]>): t.Expr
function matchPath(
nodePath: NodePath<t.Node | null | undefined>,
fns: (dig: (nodePath: NodePath) => void) => babel.Visitor
fns: (dig: (nodePath: NodePath<t.Node | null | undefined>) => void) => babel.Visitor
) {
if (!nodePath.node) return;
const fn = fns(path => matchPath(path, fns))[nodePath.node.type] as any;
@ -42,6 +43,7 @@ export function babelTailwind(
taggedTemplateName,
jsxAttributeAction = "delete",
jsxAttributeName = "css",
vite,
}: ResolveTailwindOptions,
onCollect: ClassNameCollector | undefined
) {
@ -102,12 +104,18 @@ export function babelTailwind(
}
const cssName = basename(filename, extname(filename)) + ".css";
node.body.unshift(
t.importDeclaration([], t.stringLiteral(`tailwind:./${cssName}`))
);
const path = join(dirname(filename), cssName);
const value = Array.from(tailwindMap.values());
let importee = `tailwind:./${cssName}`;
if (vite) {
const cacheKey = hash(value.map(x => x.className).join(","));
importee += `?${cacheKey}`;
}
node.body.unshift(t.importDeclaration([], t.stringLiteral(importee)));
styleMap.set(path, value);
onCollect?.(path, value);
},
@ -172,6 +180,11 @@ export function babelTailwind(
path.replaceWith(t.stringLiteral(className));
}
},
ArrayExpression(path) {
for (const element of path.get("elements")) {
go(element);
}
},
JSXExpressionContainer(path) {
go(path.get("expression"));
},

32
src/classed.tsx Normal file
View File

@ -0,0 +1,32 @@
import cx from "clsx";
import { type FunctionComponent, forwardRef } from "react";
interface WithClassName<P = object> extends FunctionComponent<P> {
className: string;
}
export const classed: {
(
type: "input",
className: string | string[]
): WithClassName<
React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
>;
<K extends keyof JSX.IntrinsicElements>(
type: K,
className: string | string[]
): WithClassName<JSX.IntrinsicElements[K]>;
(
type: string,
className: string | string[]
): WithClassName<React.ClassAttributes<any> & React.DOMAttributes<any>>;
<P>(type: FunctionComponent<P>, className: string | string[]): WithClassName<P>;
<P>(type: React.ComponentClass<P>, className: string | string[]): WithClassName<P>;
} = (Component: any, classNameInput: string | (string | false)[]) => {
const className = cx(classNameInput);
const component: any = forwardRef<any, any>(({ className: cls, ...props }, ref) => (
<Component {...props} ref={ref} className={cx(className, cls)} />
));
component.className = className;
return component;
};

View File

@ -83,6 +83,11 @@ export interface TailwindPluginOptions {
* async css => (await postcss.process(css, { plugins: [tailwindcss()] })).css
*/
compile?(css: string): Promise<string>;
/**
* Using the vite plugin?
*/
vite?: boolean;
}
export type ResolveTailwindOptions = SetRequired<
@ -139,6 +144,7 @@ export function getTailwindPlugins(options: TailwindPluginOptions) {
compile,
babel: (onCollect?: ClassNameCollector) => babelTailwind(resolvedOptions, onCollect),
esbuild: () => esbuildPlugin(styleMap, compile),
/** Requires `options.vite` to be `true`. */
vite: () => vitePlugin(styleMap, compile),
styleMap,
options,

View File

@ -15,15 +15,17 @@ export const vitePlugin = (styleMap: StyleMap, compile: Compile): vite.Plugin =>
}
if (id.startsWith("tailwind:")) {
const resolved = join(dirname(importer!), id.slice("tailwind:".length));
const [name, cacheKey] = id.slice("tailwind:".length).split("?");
const resolved = join(dirname(importer!), name);
if (styleMap.has(resolved)) {
return {
id: ROLLUP_PREFIX + resolved,
id: ROLLUP_PREFIX + resolved + "?" + cacheKey,
moduleSideEffects: true,
};
}
}
},
async load(id: string) {
if (id.startsWith(ROLLUP_PREFIX)) {
const resolved = id.slice(ROLLUP_PREFIX.length);
@ -31,8 +33,9 @@ export const vitePlugin = (styleMap: StyleMap, compile: Compile): vite.Plugin =>
return await compile("@tailwind base");
}
if (styleMap.has(resolved)) {
return await compile(toCSSText(styleMap.get(resolved)!));
const name = resolved.split("?")[0];
if (styleMap.has(name)) {
return await compile(toCSSText(styleMap.get(name)!));
}
}
},

View File

@ -1,18 +0,0 @@
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts"],
outDir: "dist",
splitting: false,
sourcemap: false,
clean: true,
dts: true,
treeshake: true,
platform: "node",
define: {
"process.env.BABEL_TAILWIND_BUILD": "true",
},
banner: {
js: "/* eslint-disable */",
},
});