@teacss/rsbuild 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ # MIT License
2
+
3
+ Copyright (c) 2021-PRESENT Anthony Fu <https://github.com/antfu>
4
+ Copyright (c) 2026-PRESENT Billgo <hi@billgo.me>
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @teacss/rsbuild
2
+
3
+ **Rsbuild integration for TeaCSS.**
4
+
5
+ ## Purpose
6
+
7
+ The plugin replaces `@teacss` in the configured CSS entry with generated
8
+ utilities and can optimize the result with `lightningcss`.
9
+
10
+ Use this package in Rsbuild applications that want TeaCSS generation,
11
+ dependency tracking, directive transforms, and CSS optimization in the Rsbuild
12
+ lifecycle. It is a native Rsbuild plugin and does not require the PostCSS
13
+ integration; non-Rsbuild projects should choose the adapter for their own build
14
+ tool.
15
+
16
+ ## Usage
17
+
18
+ ```ts
19
+ import { defineConfig } from "@rsbuild/core";
20
+ import { pluginTeacss } from "teacss/rsbuild";
21
+
22
+ export default defineConfig({
23
+ plugins: [pluginTeacss()],
24
+ });
25
+ ```
26
+
27
+ ```css
28
+ @preset "standard";
29
+ @source "./src/**/*.{ts,tsx}";
30
+ @teacss;
31
+ ```
32
+
33
+ Positive `@source` globs register their stable base directories as Rsbuild
34
+ context dependencies. New files matching an include therefore trigger a
35
+ rebuild; negative source patterns are not registered as watch roots.
36
+ When directive transforms are enabled, their default CSS matcher is combined
37
+ with a custom entry `test`, so configured non-`.css` entries such as `.pcss`
38
+ still reach TeaCSS.
39
+
40
+ Low-level helpers such as `generate`, `transformCss`, and `optimize` are
41
+ exported for integration tests and advanced tooling.
42
+
43
+ ## Status
44
+
45
+ Pre-1.0. Install `@teacss/rsbuild` beside `teacss`, then use the stable
46
+ `teacss/rsbuild` facade subpath in applications. Integration authors may import
47
+ `@teacss/rsbuild` directly when they intentionally do not use the facade.
@@ -0,0 +1,118 @@
1
+ import { Minify, RsbuildPlugin } from "@rsbuild/core";
2
+ import { UserConfig } from "@teacss/core";
3
+ import { Targets } from "lightningcss";
4
+ //#region src/index.d.ts
5
+ declare const DEFAULT_TARGETS: Targets;
6
+ interface TeacssGenerateOptions {
7
+ /** Project root to scan + resolve config from. @default process.cwd() */
8
+ cwd?: string;
9
+ /** Path to the CSS entry, or a directory to discover it from. Ignored when `config` is given. */
10
+ configPath?: string;
11
+ /** Inline config — skips file loading (useful for tests / programmatic use). */
12
+ config?: UserConfig;
13
+ /** Inline source contents to extract utilities from — skips the filesystem scan. */
14
+ contents?: string[];
15
+ /** Globs ignored during the filesystem scan. */
16
+ ignore?: string[];
17
+ }
18
+ interface TeacssPluginOptions extends TeacssGenerateOptions {
19
+ /**
20
+ * Optimize the output with lightningcss (minify + vendor prefixing).
21
+ * `true`/`false` toggles it; `{ minify }` overrides just minification.
22
+ * @default optimize in production, skip in development.
23
+ */
24
+ optimize?: boolean | {
25
+ minify?: boolean;
26
+ };
27
+ /** lightningcss targets override. @default derived from the environment's browserslist. */
28
+ targets?: Targets;
29
+ /** Which CSS entry carries the `@teacss` directive. @default /index\.css$/ */
30
+ test?: RegExp;
31
+ /**
32
+ * Inline `@apply` (plus `@screen` / `theme()` / `icon()`) in `.css` files —
33
+ * `@apply p:4 radius:2x;` inside a rule becomes the resolved declarations,
34
+ * conditioned tokens (`@apply p:4@hover`) become host-scoped rules. A lexical
35
+ * guard skips config and transformer setup for ordinary CSS. `false` disables
36
+ * it; `{ test }` narrows the scope (default: all CSS).
37
+ * @default enabled, scope = all `.css`
38
+ */
39
+ apply?: boolean | {
40
+ test?: RegExp;
41
+ };
42
+ }
43
+ /**
44
+ * Scan content (or use inline `contents`) and generate the utilities used.
45
+ * Returns the raw layered CSS (preflight vars + utilities, no optimization),
46
+ * the match count, and the absolute paths scanned (for watch registration).
47
+ */
48
+ declare function generate(options?: TeacssGenerateOptions): Promise<{
49
+ css: string;
50
+ utilities: number;
51
+ files: string[];
52
+ }>;
53
+ /** Build a `readSources` that scans `@source` include/exclude globs relative to the entry dir. */
54
+ declare function makeReadSources(base: string, ignore?: string[]): (includes: string[], excludes: string[]) => Promise<{
55
+ files: string[];
56
+ sources: string[];
57
+ }>;
58
+ interface GenerateEntryCssOptions {
59
+ /** Entry file directory — base for `@preset` relative specifiers and `@source` globs. */
60
+ base: string;
61
+ /** Read scanned content for include/exclude globs. Omit when `contents` is given. */
62
+ readSources?: (includes: string[], excludes: string[]) => Promise<{
63
+ files: string[];
64
+ sources: string[];
65
+ }>;
66
+ /** Inline content (tests / programmatic use) — skips `readSources`. */
67
+ contents?: string[];
68
+ }
69
+ /**
70
+ * The entry-CSS-driven build: parse the entry's directives, generate, and splice
71
+ * the layers back in (replace `@teacss`, strip consumed directives, hoist
72
+ * `@import`). Returns the final (unoptimized) CSS, the scanned files, and the
73
+ * utility count. Optimization (lightningcss) is the caller's step.
74
+ */
75
+ declare function generateEntryCss(entryCss: string, options: GenerateEntryCssOptions): Promise<{
76
+ css: string;
77
+ files: string[];
78
+ matched: number;
79
+ unknown: string[];
80
+ hasPreset: boolean;
81
+ }>;
82
+ /**
83
+ * Optimize CSS with lightningcss: minify + vendor prefixing. Targets come from
84
+ * `targets`, else the given `browserslist`, else a sensible default. `errorRecovery`
85
+ * skips any single malformed rule instead of failing the build.
86
+ */
87
+ declare function optimize(css: string, { minify, targets, browserslist }?: {
88
+ minify?: boolean;
89
+ targets?: Targets;
90
+ browserslist?: string[];
91
+ }): Promise<{
92
+ css: string;
93
+ skipped: number;
94
+ }>;
95
+ /** Whether to minify, from Rsbuild's `output.minify` config + build mode. */
96
+ declare function resolveMinify(minify: Minify | undefined, isProd: boolean): boolean;
97
+ /**
98
+ * Inline `@apply` (+ `@screen` / `theme()` / `icon()`) in a CSS string, resolving
99
+ * tokens against the config (inline `config`, or discovered from `cwd`/`configPath`).
100
+ * The programmatic form of the plugin's per-`.css` pass; fast-bails to a
101
+ * byte-identical string when the input has none of those directives.
102
+ */
103
+ declare function transformCss(code: string, options?: TeacssGenerateOptions, id?: string): Promise<string>;
104
+ /**
105
+ * Rsbuild plugin: replace the `@teacss` directive in a CSS entry with generated
106
+ * utilities, optimized with lightningcss, and inline `@apply` in any `.css`.
107
+ * PostCSS-free.
108
+ *
109
+ * - Optimizes (minify + prefix) in production, skips it in development for fast
110
+ * rebuilds — overridable via `optimize`.
111
+ * - Registers scanned files as build dependencies, so editing content re-runs
112
+ * generation in watch/dev mode.
113
+ * - `@apply` (+ `@screen`/`theme()`/`icon()`) is inlined in every `.css`
114
+ * (fast-bailed), configurable via `apply`.
115
+ */
116
+ declare function pluginTeacss(options?: TeacssPluginOptions): RsbuildPlugin;
117
+ //#endregion
118
+ export { DEFAULT_TARGETS, GenerateEntryCssOptions, TeacssGenerateOptions, TeacssPluginOptions, generate, generateEntryCss, makeReadSources, optimize, pluginTeacss, resolveMinify, transformCss };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{createRequire as e}from"node:module";import{statSync as t}from"node:fs";import{dirname as n,isAbsolute as r,resolve as i}from"node:path";import a from"node:process";import{DEFAULT_SOURCE_IGNORE as o,generateSources as s,readSourceFile as c}from"@teacss/runtime/source-generation";const l=/\.(css|postcss|sass|scss|less|stylus|styl)(\?|$)/,u=(e,t=0)=>e<<16|t<<8,d={safari:u(14),chrome:u(90),firefox:u(88),edge:u(90)};function f(e,t){e.lastIndex=0;let n=e.test(t);return e.lastIndex=0,n}function p(e){return e.includes(`@apply`)||e.includes(`--at-apply`)||e.includes(`@screen`)||e.includes(`theme(`)||e.includes(`icon(`)}function m(e,n,r,i){let a=t(e,{throwIfNoEntry:!1});a?a.isDirectory()?i(e):n(e):r(e)}const h=/[*?[\]{}()]/;function ee(e,t){let r=t;for(;h.test(r);)r=n(r);return i(e,r)}async function g(e,t,n){let{glob:r}=await import(`tinyglobby`),i=await r(t,{cwd:e,absolute:!0,ignore:n});return{files:i,sources:i.map(c)}}async function _(e={}){let[{defaultFilesystemGlobs:t},n]=await Promise.all([import(`@teacss/runtime`),O(e)]);return v(e,n,t)}async function v(e,t,n){let{cwd:r=a.cwd(),contents:i,ignore:c=o}=e,l=n??(await import(`@teacss/runtime`)).defaultFilesystemGlobs,u=t.config.content?.filesystem??l,{files:d,sources:f}=i?{files:[],sources:i}:await g(r,u,c),p=await s(t,d,f);return{css:p.getLayers()||``,utilities:p.matched.size,files:d}}function y(e,t=o){return async(n,r)=>{let{glob:i}=await import(`tinyglobby`),a=await i(n,{cwd:e,absolute:!0,ignore:[...r,...t]});return{files:a,sources:await Promise.all(a.map(c))}}}const b={articles:`@teacss/preset-articles`,icons:`@teacss/preset-icons`,standard:`@teacss/preset-standard`};function x(t,n){try{return n.resolve(t)}catch(r){let i;try{i=n.resolve(`teacss/package.json`)}catch{throw r}return e(i).resolve(t)}}function te(e){return e.filter(e=>!e.startsWith(`.`)&&!r(e))}function ne(t,n){let r=e(i(n,`_`));return t.map(e=>{let t=Object.hasOwn(b,e)?b[e]:void 0;return t?x(t,r):r.resolve(e)})}function re(e,t){return e.length===t.length&&e.every((e,n)=>e===t[n])}async function S(e,t){return C(e,t,await A(e,t.base))}async function C(e,t,n){let[{parseEntry:r},{spliceEntry:i}]=await Promise.all([import(`@teacss/config`),import(`@teacss/directives`)]),a=r(e),o=a.sources.filter(e=>!e.exclude).map(e=>e.glob),c=a.sources.filter(e=>e.exclude).map(e=>e.glob),{files:l,sources:u}=t.contents?{files:[],sources:t.contents}:t.readSources?await t.readSources(o,c):{files:[],sources:[]},d=await s(n.generator,l,u);return{css:i(e,d.getLayers()||``),files:l,matched:d.matched.size,unknown:n.unknown,hasPreset:n.hasPreset}}async function w(e,{minify:t=!0,targets:n,browserslist:r}={}){let{transform:i,browserslistToTargets:a}=await import(`lightningcss`),o=n??(r?.length?a(r):d),s=E(e),c=i({filename:`teacss.css`,code:Buffer.from(s.css),minify:t,errorRecovery:!0,targets:o});return{css:s.restore(c.code.toString()),skipped:c.warnings?.length??0}}const T=/(^|[;{])((?:\s|\/\*[\s\S]*?\*\/)*)all((?:\s|\/\*[\s\S]*?\*\/)*):((?:\s|\/\*[\s\S]*?\*\/)*)(?=(?:var|env)\()/gi;function E(e){let t=`--teacss-optimized-all`;for(;e.includes(t);)t+=`-x`;return{css:e.replace(T,(e,n,r,i,a)=>`${n}${r}${t}${i}:${a}`),restore:e=>e.replaceAll(t,`all`)}}function D(e,t){return typeof e==`boolean`||e==null?(e??!0)&&t:e.css!==!1&&(e.css===`always`||t)}async function O(e){return(await k(e)).generator}async function k(e,t){let{cwd:n=a.cwd(),configPath:r,config:i}=e,{createGenerator:o}=await import(`@teacss/core`),s=t??(await import(`@teacss/config`)).createRecoveryConfigLoader(),c=i?{config:i,sources:[]}:await s(n,r);return{generator:await o(c.config),sources:c.sources}}async function A(e,t){let[{resolveEntryConfig:n},{createGenerator:r}]=await Promise.all([import(`@teacss/config`),import(`@teacss/core`)]),{config:i,sources:a,unknown:o}=await n(e,t);return{generator:await r(i),sources:a,unknown:o,hasPreset:!!i.presets?.length}}async function j(e,t,n){let[{transformDirectives:r},{default:i}]=await Promise.all([import(`@teacss/directives`),import(`magic-string`)]),a=new i(e);return await r(a,n,{},t),a.toString()}async function M(e,t={},n){return p(e)?j(e,n,await O(t)):e}function N(e={}){let{optimize:r,targets:o,test:s=/index\.css$/,apply:c,...u}=e,d=c!==!1,h=typeof c==`object`&&c.test||l;return{name:`teacss`,setup(e){let c,g=()=>c??=import(`@teacss/config`).then(({createRecoveryConfigLoader:e})=>e()),_=new Map,b=new Map,x=new Map,S=e=>{let t=_.get(e);if(t)return t;let n=g().then(e=>k(u,e));return _.set(e,n),n};e.onBeforeEnvironmentCompile(({environment:e})=>{_.delete(e.name),b.delete(e.name)}),e.transform({test:d?[l,s]:s},async({code:c,environment:l,addDependency:g,addMissingDependency:T,addContextDependency:E,resourcePath:O})=>{let k=O||``,M=f(s,k),N=d&&p(c),P=N&&f(h,k);if(!M&&!P)return c;let{parseEntry:ie}=await import(`@teacss/config`),F=M?ie(c):void 0,I=F?.hasTeacss===!0;if(!I&&!P)return c;let L=!!(F&&(F.presets.length>0||F.sources.length>0||F.custom.length>0)),R=n(O||`${u.cwd??a.cwd()}/index.css`),z=O||i(R,`index.css`);for(let e of F?.sources??[])e.exclude||m(ee(R,e.glob),g,T,E);let B=te(F?.presets??[]),V,H,U;I&&(L?(V=A(c,R).then(e=>(H={...e,sources:Array.from(new Set([z,...e.sources]))},H)).catch(async t=>{let{getConfigSourcesFromError:n}=await import(`@teacss/config`),r=n(t);for(let e of r)m(e,g,T,E);let i=x.get(l.name);if(!i||i.id!==z)throw t;let a=Array.from(new Set([...i.state.sources,...r]));return e.logger.warn(`@teacss/rsbuild: keeping the last valid config for ${z} while its preset graph is invalid`),a.length===i.state.sources.length?i.state:{...i.state,sources:a}}),b.set(l.name,V),d?_.set(l.name,V):_.delete(l.name)):(_.delete(l.name),b.delete(l.name),x.delete(l.name)));let W=()=>{H&&U&&V&&b.get(l.name)===V&&x.set(l.name,{id:z,packagePresetDependencies:U,packagePresetSpecifiers:B,state:H})},G=c,K;if(N&&(I||P)){try{K=await S(l.name)}catch(e){if(!V){let{getConfigSourcesFromError:t}=await import(`@teacss/config`);for(let n of t(e))m(n,g,T,E)}throw e}for(let e of K.sources)m(e,g,T,E);G=await j(c,k,K.generator)}if(!I)return G===c?c:{code:G};let q,J,Y,X=[],Z=!0,Q=[];if(L){let n=V?await V:await A(G,R);({css:q,files:Y,matched:J,unknown:X,hasPreset:Z}=await C(G,{base:R,readSources:y(R,u.ignore)},n));try{U=ne(B,R)}catch(n){let r=x.get(l.name);if(!(r?.id===z&&re(r.packagePresetSpecifiers,B)&&r.packagePresetDependencies.some(e=>!t(e,{throwIfNoEntry:!1}))))throw n;U=r.packagePresetDependencies,e.logger.warn(`@teacss/rsbuild: keeping the last valid package preset dependencies for ${z} while package entries are unavailable`)}Q=Array.from(new Set([...U,...n.sources]))}else{K??=await S(l.name);let e=await v(u,K.generator),{spliceEntry:t}=await import(`@teacss/directives`);q=t(G,e.css),J=e.utilities,Y=e.files,Q=K.sources}Z||e.logger.warn(`@teacss/rsbuild: ${O||`entry`} declares no @preset — no utilities generated. Add one, e.g. \`@preset "standard";\``),X.length&&e.logger.warn(`@teacss/rsbuild: ${X.length} @custom var(s) not registered (unknown namespace / illegal color step): ${X.join(`, `)}`);for(let e of Y)g(e);for(let e of Q)m(e,g,T,E);let $=(l.config.mode??a.env.NODE_ENV)===`production`;if(!(r??$))return e.logger.info(`@teacss/rsbuild: ${J} utilities (dev, unoptimized) → ${(q.length/1024).toFixed(1)}KB`),W(),{code:q};let ae=typeof r==`object`&&r.minify!==void 0?r.minify:D(l.config.output.minify,$),{css:oe,skipped:se}=await w(q,{minify:ae,targets:o,browserslist:l.browserslist});return e.logger.info(`@teacss/rsbuild: ${J} utilities${se?`, ${se} skipped`:``} → ${(oe.length/1024).toFixed(1)}KB`),W(),{code:oe}})}}}export{d as DEFAULT_TARGETS,_ as generate,S as generateEntryCss,y as makeReadSources,w as optimize,N as pluginTeacss,D as resolveMinify,M as transformCss};
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@teacss/rsbuild",
3
+ "version": "0.3.0",
4
+ "description": "Rsbuild integration for TeaCSS — generate utilities and optimize with lightningcss, no PostCSS.",
5
+ "homepage": "https://css.teasim.com",
6
+ "funding": "https://github.com/sponsors/billgo",
7
+ "license": "MIT",
8
+ "author": "Billgo <hi@billgo.me>",
9
+ "sideEffects": false,
10
+ "type": "module",
11
+ "exports": {
12
+ ".": "./dist/index.js",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "types": "./dist/index.d.ts",
16
+ "files": [
17
+ "dist",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "bunx tsdown --config tsdown.config.mts",
22
+ "dev": "tsdown --watch"
23
+ },
24
+ "dependencies": {
25
+ "@teacss/config": "0.3.0",
26
+ "@teacss/core": "0.3.0",
27
+ "@teacss/directives": "0.3.0",
28
+ "@teacss/runtime": "0.3.0",
29
+ "lightningcss": "^1.33.0",
30
+ "magic-string": "^1.1.0",
31
+ "tinyglobby": "^0.2.17"
32
+ },
33
+ "devDependencies": {
34
+ "@rsbuild/core": "^2.1.8",
35
+ "@teacss/preset-articles": "0.3.0",
36
+ "@teacss/preset-standard": "0.3.0"
37
+ },
38
+ "peerDependencies": {
39
+ "@rsbuild/core": "^2"
40
+ },
41
+ "engines": {
42
+ "node": ">=22.12.0"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
46
+ }
47
+ }