diff --git a/package.json b/package.json index 05c9422..1a7bd57 100644 --- a/package.json +++ b/package.json @@ -11,10 +11,10 @@ "dist" ], "devDependencies": { - "@aet/eslint-rules": "^0.0.18", + "@aet/eslint-rules": "^0.0.19", "@types/babel__core": "^7.20.5", "@types/lodash": "^4.17.0", - "@types/node": "^20.11.30", + "@types/node": "^20.12.2", "esbuild": "^0.20.2", "esbuild-register": "^3.5.0", "prettier": "^3.2.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3663350..446707b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,8 +20,8 @@ dependencies: devDependencies: '@aet/eslint-rules': - specifier: ^0.0.18 - version: 0.0.18(eslint@8.57.0)(typescript@5.4.3) + specifier: ^0.0.19 + version: 0.0.19(eslint@8.57.0)(typescript@5.4.3) '@types/babel__core': specifier: ^7.20.5 version: 7.20.5 @@ -29,8 +29,8 @@ devDependencies: specifier: ^4.17.0 version: 4.17.0 '@types/node': - specifier: ^20.11.30 - version: 20.11.30 + specifier: ^20.12.2 + version: 20.12.2 esbuild: specifier: ^0.20.2 version: 0.20.2 @@ -51,7 +51,7 @@ devDependencies: version: 5.4.3 vitest: specifier: ^1.4.0 - version: 1.4.0(@types/node@20.11.30) + version: 1.4.0(@types/node@20.12.2) packages: @@ -60,8 +60,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /@aet/eslint-rules@0.0.18(eslint@8.57.0)(typescript@5.4.3): - resolution: {integrity: sha512-Pq+7cF+bOZUASsTjGgcrVMzTv2rnVQvY6AWlYEIF0t+0WT7LNoTs882tBCfyi5tKFL/4uZFNaSfhVlxiV71h0A==} + /@aet/eslint-rules@0.0.19(eslint@8.57.0)(typescript@5.4.3): + resolution: {integrity: sha512-RO9JBZcdY2HVvWPvqlob2yNwszXwOPUL81uXCg3IrjDi7Ka48zWsfEyMpF/w/3jNgwhYxDBLTJAhHABuJ2LtXQ==} peerDependencies: eslint: ^8.53.0 typescript: ^5.2.2 @@ -1034,8 +1034,8 @@ packages: resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} dev: true - /@types/node@20.11.30: - resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} + /@types/node@20.12.2: + resolution: {integrity: sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==} dependencies: undici-types: 5.26.5 dev: true @@ -3402,7 +3402,7 @@ packages: spdx-expression-parse: 3.0.1 dev: true - /vite-node@1.4.0(@types/node@20.11.30): + /vite-node@1.4.0(@types/node@20.12.2): resolution: {integrity: sha512-VZDAseqjrHgNd4Kh8icYHWzTKSCZMhia7GyHfhtzLW33fZlG9SwsB6CEhgyVOWkJfJ2pFLrp/Gj1FSfAiqH9Lw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -3411,7 +3411,7 @@ packages: debug: 4.3.4 pathe: 1.1.2 picocolors: 1.0.0 - vite: 5.2.7(@types/node@20.11.30) + vite: 5.2.7(@types/node@20.12.2) transitivePeerDependencies: - '@types/node' - less @@ -3423,7 +3423,7 @@ packages: - terser dev: true - /vite@5.2.7(@types/node@20.11.30): + /vite@5.2.7(@types/node@20.12.2): resolution: {integrity: sha512-k14PWOKLI6pMaSzAuGtT+Cf0YmIx12z9YGon39onaJNy8DLBfBJrzg9FQEmkAM5lpHBZs9wksWAsyF/HkpEwJA==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -3451,7 +3451,7 @@ packages: terser: optional: true dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.2 esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.13.2 @@ -3459,7 +3459,7 @@ packages: fsevents: 2.3.3 dev: true - /vitest@1.4.0(@types/node@20.11.30): + /vitest@1.4.0(@types/node@20.12.2): resolution: {integrity: sha512-gujzn0g7fmwf83/WzrDTnncZt2UiXP41mHuFYFrdwaLRVQ6JYQEiME2IfEjU3vcFL3VKa75XhI3lFgn+hfVsQw==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -3484,7 +3484,7 @@ packages: jsdom: optional: true dependencies: - '@types/node': 20.11.30 + '@types/node': 20.12.2 '@vitest/expect': 1.4.0 '@vitest/runner': 1.4.0 '@vitest/snapshot': 1.4.0 @@ -3502,8 +3502,8 @@ packages: strip-literal: 2.1.0 tinybench: 2.6.0 tinypool: 0.8.3 - vite: 5.2.7(@types/node@20.11.30) - vite-node: 1.4.0(@types/node@20.11.30) + vite: 5.2.7(@types/node@20.12.2) + vite-node: 1.4.0(@types/node@20.12.2) why-is-node-running: 2.2.2 transitivePeerDependencies: - less diff --git a/src/index.test.ts b/src/index.test.ts index 42df88a..9d63a0f 100644 --- a/src/index.test.ts +++ b/src/index.test.ts @@ -2,6 +2,9 @@ import { promises as fs } from "node:fs"; import { resolve } from "node:path"; import { build } from "esbuild"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { OutputChunk, rollup } from "rollup"; +import rollupBabel from "@rollup/plugin-babel"; +import rollupCSS from "rollup-plugin-css-only"; import { type TailwindPluginOptions, babelPlugin, @@ -26,7 +29,7 @@ describe("babel-tailwind", () => { return resolved; } - async function compile(options: TailwindPluginOptions, javascript: string) { + async function compileESBuild(options: TailwindPluginOptions, javascript: string) { const tailwind = getTailwindPlugins(options); const result = await build({ bundle: true, @@ -45,8 +48,8 @@ describe("babel-tailwind", () => { return outputFiles; } - it("renders", async () => { - const outputFiles = await compile( + it("supports ESBuild", async () => { + const outputFiles = await compileESBuild( { tailwindConfig: {}, clsx: "emotion", diff --git a/src/index.ts b/src/index.ts index f1b93f7..c00770a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,11 +3,14 @@ import { basename, dirname, extname, join } from "node:path"; import { once } from "lodash"; import hash from "@emotion/hash"; import type babel from "@babel/core"; -import { type NodePath, type types as t, transform } from "@babel/core"; -import { type Plugin } from "esbuild"; +import { type NodePath, type types as t, transformSync } from "@babel/core"; +import type * as esbuild from "esbuild"; import tailwind, { type Config } from "tailwindcss"; import postcss from "postcss"; +const PLUGIN_NAME = "tailwind"; +const ESBUILD_NAMESPACE = "babel-tailwind"; + const definePlugin = (fn: (runtime: typeof babel) => babel.Visitor) => (runtime: typeof babel) => { @@ -29,8 +32,6 @@ function matchPath( fn?.(nodePath); } -const ESBUILD_NAMESPACE = "babel-tailwind"; - export interface TailwindPluginOptions { /** * Tailwind CSS configuration @@ -226,7 +227,7 @@ export const babelPlugin = ({ }: { filter?: RegExp; getPlugins(file: { path: string; contents: string }): babel.PluginItem[]; -}): Plugin => ({ +}): esbuild.Plugin => ({ name: "babel-plugin", setup(build) { build.onLoad({ filter }, ({ path }) => { @@ -242,7 +243,7 @@ export const babelPlugin = ({ return; } - const { code } = transform(load(), { + const { code } = transformSync(load(), { parserOpts: { plugins: ["jsx", "typescript"], }, @@ -258,21 +259,26 @@ export const babelPlugin = ({ }, }); -const tailwindPlugin = ( +type Compile = ReturnType; + +function createPostCSS({ tailwindConfig, postCSSPlugins = [] }: TailwindPluginOptions) { + const post = postcss([ + tailwind({ + ...tailwindConfig, + content: [{ raw: "
", extension: "html" }], + }), + ...postCSSPlugins, + ]); + return (css: string) => post.process(css, { from: undefined }); +} + +const esbuildPlugin = ( styleMap: Map, - { tailwindConfig, postCSSPlugins = [] }: TailwindPluginOptions -): Plugin => ({ - name: "tailwind", + compile: Compile +): esbuild.Plugin => ({ + name: PLUGIN_NAME, setup(build) { - const post = postcss([ - tailwind({ - ...tailwindConfig, - content: [{ raw: "
", extension: "html" }], - }), - ...postCSSPlugins, - ]); - build.onResolve({ filter: /^tailwind:.+\.css$/ }, ({ path, importer }) => { const resolved = join(dirname(importer), path.replace(/^tailwind:/, "")); if (styleMap.has(resolved)) { @@ -285,7 +291,7 @@ const tailwindPlugin = ( build.onLoad({ filter: /.*/, namespace: ESBUILD_NAMESPACE }, async ({ path }) => { if (!styleMap.has(path)) return; - const result = await post.process(styleMap.get(path)!, { from: undefined }); + const result = await compile(styleMap.get(path)!); return { contents: result.css, @@ -296,7 +302,9 @@ const tailwindPlugin = ( }); /** - * Main entry. Returns the esbuild and babel plugins for tailwind. + * Main entry. Returns the esbuild, babel plugins and utilities for processing + * Tailwind classNames in JS. + * * @example * import { build } from "esbuild"; * import getTailwindPlugins, { babelPlugin } from "babel-tailwind"; @@ -311,9 +319,13 @@ const tailwindPlugin = ( */ export function getTailwindPlugins(options: TailwindPluginOptions) { const styleMap = new Map(); + const compile = createPostCSS(options); + return { + compile, babel: babelTailwind(styleMap, options), - esbuild: tailwindPlugin(styleMap, options), + esbuild: esbuildPlugin(styleMap, compile), + styleMap, [Symbol.dispose]() { styleMap.clear(); }, diff --git a/src/modules.d.ts b/src/modules.d.ts new file mode 100644 index 0000000..3e5b8da --- /dev/null +++ b/src/modules.d.ts @@ -0,0 +1,32 @@ +declare module "rollup-plugin-css-only" { + import { OutputBundle, Plugin } from "rollup"; + + namespace css { + interface Options { + /** + * All CSS files will be parsed by default, but you can also specifically include files + */ + include?: ReadonlyArray | string | RegExp | null; + /** + * CSS files to exclude from being parsed + */ + exclude?: ReadonlyArray | string | RegExp | null; + /** + * Callback that will be called ongenerate + */ + output?: + | boolean + | string + | (( + styles: string, + styleNodes: Record, + bundle: OutputBundle + ) => void) + | null + | undefined; + } + } + + function css(options?: css.Options): Plugin; + export = css; +}