@teacss/rsbuild 0.9.3 → 0.10.2

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/README.md CHANGED
@@ -1,65 +1,42 @@
1
1
  # @teacss/rsbuild
2
2
 
3
- Native Rsbuild integration for TeaCSS.
3
+ The Rsbuild plugin for TeaCSS. An Rspack loader calls the Rust engine (`@teacss/engine`) in process and replaces the CSS entry's `@teacss;` with the generated CSS. It needs Node.js 26 or later and Rsbuild 1 or later.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install --save-dev @teacss/rsbuild @rsbuild/core
9
+ ```
10
+
11
+ ## Use
4
12
 
5
13
  ```ts
14
+ // rsbuild.config.ts
6
15
  import { defineConfig } from "@rsbuild/core";
7
16
  import { pluginTeacss } from "@teacss/rsbuild";
8
17
 
9
- export default defineConfig({
10
- plugins: [pluginTeacss()],
11
- });
18
+ export default defineConfig({ plugins: [pluginTeacss()] });
12
19
  ```
13
20
 
21
+ Configuration lives in a CSS entry. Create `index.css` at the project root:
22
+
14
23
  ```css
15
- @presets "standard";
16
- @source "./src/**/*.{ts,tsx}";
24
+ @presets "standard,icons,official";
25
+ @source "./src/**/*.{js,jsx,ts,tsx,html}";
17
26
  @teacss;
18
27
  ```
19
28
 
20
- The plugin generates utilities, transforms TeaCSS directives, tracks positive
21
- `@source` roots, and can optimize output with Lightning CSS. Stable source
22
- roots let new matching files trigger rebuilds; negative patterns are not watch
23
- roots. Explicit sources may include directories skipped by default discovery.
24
- The resolved source graph includes directives from unconditional local CSS
25
- imports, with paths relative to the declaring file. Entries without `@source`
26
- use default discovery, excluding dependencies and build output. The `contents`
27
- option replaces content scanning even when the entry declares its own preset;
28
- an empty array scans no content.
29
-
30
- An entry may keep all its configuration in imported CSS. Its utilities and
31
- `@apply` share that resolved configuration. Ordinary CSS imports preserve
32
- programmatic configuration, and imported safelists augment it.
33
-
34
- A custom entry `test` is combined with the directive transform matcher, so
35
- entries such as `.pcss` still reach TeaCSS.
36
-
37
- Tooling that compiles an entry outside a build, such as a Storybook setup or a
38
- test, uses `generateEntryCss` with `makeReadSources`:
29
+ Import the entry from a script, not through a CSS `@import`:
39
30
 
40
31
  ```ts
41
- import { generateEntryCss, makeReadSources } from "@teacss/rsbuild";
42
-
43
- const { css } = await generateEntryCss(entryCss, {
44
- base: root,
45
- entryFile,
46
- readSources: makeReadSources(root),
47
- });
32
+ // src/index.ts
33
+ import "../index.css";
48
34
  ```
49
35
 
50
- The returned CSS is not optimized.
51
-
52
- The optimizer preserves native `:dir()` selectors. The plugin also excludes
53
- their language-based rewrite in Rsbuild's CSS loader and inherited minimizer
54
- defaults, so explicit and nested `dir` values stay correct. Other browser
55
- targets and transformations are unchanged; directional styles require native
56
- `:dir()` support. Custom host CSS options must preserve this exclusion.
36
+ The generated CSS covers the files `@source` matches, so it does not depend on the order Rspack builds modules in.
57
37
 
58
- Calculated Bézier x coordinates are guarded before both adapter optimization
59
- and Rsbuild's native Lightning CSS processing, including imported CSS, CSS
60
- Modules and PostCSS output. The internal loader preserves and composes source
61
- maps. Literal x coordinates, y overshoot, feature queries and custom-property
62
- definitions remain unchanged. See the
63
- [easing audit](../../docs/knowledge/steps-and-cubic-bezier-easing.md#authored-css-exposure).
38
+ ## Documentation
64
39
 
65
- Pre-1.0. No PostCSS plugin is required beside this adapter.
40
+ - [Build integrations](https://github.com/teacss/teacss-rs/blob/master/skills/teacss/references/integrations.md) and [their options](https://github.com/teacss/teacss-rs/blob/master/skills/teacss/references/integrations.md#options), including the Rsbuild-only `test`
41
+ - [The CSS entry](https://github.com/teacss/teacss-rs/blob/master/skills/teacss/references/setup.md#css-entry)
42
+ - [The TeaCSS skill](https://github.com/teacss/teacss-rs/blob/master/skills/teacss/SKILL.md) for syntax, presets and tooling
@@ -0,0 +1,6 @@
1
+ import type { Rspack } from "@rsbuild/core";
2
+ type Sources = Pick<typeof Rspack.sources, "RawSource" | "ReplaceSource">;
3
+ /** Moves every bundled font of each CSS asset to `teacss-fonts/teacss-<digest>.woff2` and points its `url()` there. */
4
+ export declare function emitBundledFonts(compilation: Rspack.Compilation, sources: Sources): void;
5
+ export {};
6
+ //# sourceMappingURL=fonts.d.ts.map
package/dist/fonts.js ADDED
@@ -0,0 +1,26 @@
1
+ import { posix } from "node:path";
2
+ import { BUNDLED_FONT_URL, extractBundledFonts } from "@teacss/engine";
3
+ /** Moves every bundled font of each CSS asset to `teacss-fonts/teacss-<digest>.woff2` and points its `url()` there. */
4
+ export function emitBundledFonts(compilation, sources) {
5
+ for (const { name, source } of compilation.getAssets()) {
6
+ if (!name.endsWith(".css"))
7
+ continue;
8
+ const css = source.source().toString();
9
+ const { fonts } = extractBundledFonts(css, (digest, bytes) => {
10
+ const fileName = `teacss-fonts/teacss-${digest}.woff2`;
11
+ if (!compilation.getAsset(fileName))
12
+ compilation.emitAsset(fileName, new sources.RawSource(Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength)), { immutable: true });
13
+ return `./${posix.relative(posix.dirname(name), fileName)}`;
14
+ });
15
+ if (fonts.length === 0)
16
+ continue;
17
+ // Replacing each `url()` in place keeps the asset's source map (`output.sourceMap.css`); a new RawSource drops it.
18
+ const paths = new Map(fonts.map(({ digest, path }) => [digest, path]));
19
+ const replaced = new sources.ReplaceSource(source);
20
+ for (const match of css.matchAll(BUNDLED_FONT_URL)) {
21
+ replaced.replace(match.index, match.index + match[0].length - 1, `url(${JSON.stringify(paths.get(match[2] ?? ""))})`);
22
+ }
23
+ compilation.updateAsset(name, replaced);
24
+ }
25
+ }
26
+ //# sourceMappingURL=fonts.js.map
package/dist/index.d.ts CHANGED
@@ -1,90 +1,9 @@
1
- import { RsbuildPlugin } from "@rsbuild/core";
2
- import { UserConfig } from "@teacss/core";
3
- import { Targets } from "lightningcss";
4
- //#region src/plugin.d.ts
5
- interface TeacssGenerateOptions {
6
- /** Project root to scan + resolve config from. @default process.cwd() */
7
- cwd?: string;
8
- /** Path to the CSS entry, or a directory to discover it from. Ignored when `config` is given. */
9
- configPath?: string;
10
- /** Inline config — skips file loading (useful for tests / programmatic use). */
11
- config?: UserConfig;
12
- /** Inline source contents to extract utilities from — skips the filesystem scan. */
13
- contents?: string[];
14
- /** Globs ignored during the filesystem scan. */
15
- ignore?: string[];
16
- }
17
- interface TeacssPluginOptions extends TeacssGenerateOptions {
18
- /**
19
- * Optimize the output with lightningcss (minify + vendor prefixing).
20
- * `true`/`false` toggles it; `{ minify }` overrides just minification.
21
- * @default in production, only when Rspack's own lightningcss loader or CSS
22
- * minifier is off or `targets` is set (otherwise they optimize this output);
23
- * skip in development.
24
- */
25
- optimize?: boolean | {
26
- minify?: boolean;
27
- };
28
- /** lightningcss targets override. @default derived from the environment's browserslist. */
29
- targets?: Targets;
30
- /** Which CSS entry carries the `@teacss` directive. @default /index\.css$/ */
31
- test?: RegExp;
32
- /**
33
- * Inline `@apply` (plus `@screen` / `theme()` / `icon()`) in `.css` files —
34
- * `@apply p:4 rd:2x;` inside a rule becomes the resolved declarations,
35
- * conditioned tokens (`@apply p:4@hover`) become host-scoped rules. A lexical
36
- * guard skips config and transformer setup for ordinary CSS. `false` disables
37
- * it; `{ test }` narrows the scope (default: all CSS).
38
- * @default enabled, scope = all `.css`
39
- */
40
- apply?: boolean | {
1
+ import type { RsbuildPlugin } from "@rsbuild/core";
2
+ import { type ShellOptions } from "@teacss/engine";
3
+ export type { ShellOptions } from "@teacss/engine";
4
+ export interface PluginTeacssOptions extends ShellOptions {
5
+ /** Modules the loader sees; `node_modules` is always excluded. */
41
6
  test?: RegExp;
42
- };
43
- }
44
- /** Build a `readSources` that scans `@source` include/exclude globs relative to the entry dir; the runtime reads the files. */
45
- export declare function makeReadSources(base: string, ignore?: string[]): (includes: string[], excludes: string[]) => Promise<{
46
- files: string[];
47
- sources?: string[];
48
- }>;
49
- interface GenerateEntryCssOptions {
50
- /** Entry file directory — base for `@presets` relative specifiers and `@source` globs. */
51
- base: string;
52
- /** Entry filename, when known, so local `@import` cycles include the root identity. */
53
- entryFile?: string;
54
- /** Scan content for include/exclude globs; `sources` may be omitted so the runtime reads the files. Omit when `contents` is given. */
55
- readSources?: (includes: string[], excludes: string[]) => Promise<{
56
- files: string[];
57
- sources?: string[];
58
- }>;
59
- /** Inline content (tests / programmatic use) — skips `readSources`. */
60
- contents?: string[];
61
7
  }
62
- /**
63
- * The entry-CSS-driven build: parse the entry's directives, generate, and splice
64
- * the layers back in (replace `@teacss`, strip consumed directives, hoist
65
- * `@import`). Returns the final (unoptimized) CSS, the scanned files, and the
66
- * utility count. Optimization (lightningcss) is the caller's step.
67
- */
68
- export declare function generateEntryCss(entryCss: string, options: GenerateEntryCssOptions): Promise<{
69
- css: string;
70
- files: string[];
71
- matched: number;
72
- unknown: string[];
73
- hasPreset: boolean;
74
- }>;
75
- /**
76
- * Rsbuild plugin: replace the `@teacss` directive in a CSS entry with generated
77
- * utilities, optimized with lightningcss, and inline `@apply` in any `.css`.
78
- * PostCSS-free.
79
- *
80
- * - Leaves production optimization (minify + prefix) to Rspack's lightningcss
81
- * loader and minimizer; runs its own pass only when those are off or
82
- * `optimize`/`targets` asks for it. Development emits the raw layers.
83
- * - Registers scanned files as build dependencies, so editing content re-runs
84
- * generation in watch/dev mode.
85
- * - `@apply` (+ `@screen`/`theme()`/`icon()`) is inlined in every `.css`
86
- * (fast-bailed), configurable via `apply`.
87
- */
88
- export declare function pluginTeacss(options?: TeacssPluginOptions): RsbuildPlugin;
89
- //#endregion
90
- export type { GenerateEntryCssOptions, TeacssGenerateOptions, TeacssPluginOptions };
8
+ export declare function pluginTeacss(options?: PluginTeacssOptions): RsbuildPlugin;
9
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -1 +1,35 @@
1
- import{createRequire as e}from"node:module";import{statSync as t}from"node:fs";import{dirname as n,isAbsolute as r,posix as i,resolve as a}from"node:path";import o from"node:process";import{fileURLToPath as s}from"node:url";import{enableNodeCompileCache as c}from"@teacss/runtime/internal/compile-cache";import{DEFAULT_SOURCE_IGNORE as l,generateSources as u}from"@teacss/runtime/source-generation";import{bundledAssetName as d,collectBundledAssets as f}from"@teacss/runtime/internal/bundled-assets";var p=class{apply(e){e.hooks.thisCompilation.tap(`teacss-fonts`,t=>{t.hooks.processAssets.tap({name:`teacss-fonts`,stage:e.webpack.Compilation.PROCESS_ASSETS_STAGE_PRE_PROCESS},()=>{let{RawSource:n,ReplaceSource:r}=e.webpack.sources;for(let{name:e,source:a}of t.getAssets()){if(!e.endsWith(`.css`))continue;let o=f(a.source().toString());if(!o.length)continue;let s=new r(a);for(let{start:r,end:a,asset:c}of o){let o=`teacss-fonts/${d(c)}`;t.getAsset(o)||t.emitAsset(o,new n(Buffer.from(c.bytes)),{immutable:!0}),s.replace(r,a-1,`url(${JSON.stringify(`./${i.relative(i.dirname(e),o)}`)})`)}t.updateAsset(e,s)}})})}};c();const m=/\.(css|postcss|sass|scss|less|stylus|styl)(\?|$)/,h=(e,t=0)=>e<<16|t<<8,g={safari:h(14),chrome:h(90),firefox:h(88),edge:h(90)};function _(e,t){e.lastIndex=0;let n=e.test(t);return e.lastIndex=0,n}function ee(e){return/@(presets?|primary|neutral|success|warning|failure|general|focused|custom|source|shortcut|safelist)\b/.test(e)||e.includes(`@apply`)||e.includes(`--at-apply`)||e.includes(`@screen`)||e.includes(`theme(`)||e.includes(`icon(`)}function v(e,n,r,i){let a=t(e,{throwIfNoEntry:!1});a?a.isDirectory()?i(e):n(e):r(e)}const y=/[*?[\]{}()]/;function b(e,t){let r=t;for(;y.test(r);)r=n(r);return a(e,r)}async function x(e,t,n){let{scanSourceFiles:r}=await import(`@teacss/config/internal/source-scan`);return r(t,{cwd:e,expandDirectories:!0,ignore:n})}async function te(e,t,r,i=[]){let{cwd:a=o.cwd(),contents:s}=e,c=r??(await import(`@teacss/runtime`)).defaultFilesystemGlobs,d=i[0]?n(i[0]):a,f=t.config.content?.filesystem,p=f??c,m=e.ignore??(f===void 0?l:[]),h=s?[]:await x(d,p,m),g=await u(t,h,s);return{css:g.getLayers()||``,utilities:g.matched.size,files:h}}function ne(e,t=[]){return async(n,r)=>{let{scanSourceFiles:i}=await import(`@teacss/config/internal/source-scan`);return{files:await i(n,{cwd:e,expandDirectories:!0,ignore:[...r,...t]})}}}const S={icons:`@teacss/preset-icons`,official:`@teacss/preset-official`,standard:`@teacss/preset-standard`};function C(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 re(e){return e.filter(e=>!e.startsWith(`.`)&&!r(e))}function ie(t,n){let r=e(a(n,`_`));return t.map(e=>{let t=Object.hasOwn(S,e)?S[e]:void 0;return t?C(t,r):r.resolve(e)})}function ae(e,t){return e.length===t.length&&e.every((e,n)=>e===t[n])}async function w(e,t){return oe(e,t,await j(e,t.base,t.entryFile))}async function T(e,n){let r=[];for(let i of e.sources){let e;try{e=t(i,{throwIfNoEntry:!1})}catch{return}if(e&&n!==void 0&&Math.max(e.mtimeMs,e.ctimeMs)>=n)return;r.push(e?`${e.mtimeMs}:${e.ctimeMs}:${e.size}:${e.ino}`:`missing`)}let{probeSourceDeclarations:i}=await import(`@teacss/config/internal/source-scan`),a=await i(e.declarations,n!==void 0);return JSON.stringify([r,a.signature])}async function E(e){let t=e.config.content?.filesystem,n=t??(await import(`@teacss/runtime`)).defaultFilesystemGlobs;return{includes:n.filter(e=>!e.startsWith(`!`)),excludes:[...n.filter(e=>e.startsWith(`!`)).map(e=>e.slice(1)),...t===void 0?l:[]]}}async function oe(e,t,n){let{spliceEntry:r}=await import(`@teacss/directives`),{includes:i,excludes:a}=await E(n.generator),{files:o,sources:s}=t.contents?{files:[],sources:t.contents}:t.readSources?await t.readSources(i,a):{files:[],sources:[]},c=await u(n.generator,o,s);return{css:r(e,c.getLayers()||``),files:o,matched:c.matched.size,unknown:n.unknown,hasPreset:n.hasPreset}}async function se(e,{minify:t=!0,targets:n,browserslist:r}={}){let{transform:i,browserslistToTargets:a,Features:o}=await import(`lightningcss`),{prepareEasingStylesheet:s}=await import(`@teacss/directives/internal/easing`),c=n??(r?.length?a(r):g),l=O(s(e)),u=i({filename:`teacss.css`,code:Buffer.from(l.css),minify:t,errorRecovery:!0,targets:c,exclude:o.DirSelector});return{css:l.restore(u.code.toString()),skipped:u.warnings?.length??0}}const D=/(^|[;{])((?:\s|\/\*[\s\S]*?\*\/)*)all((?:\s|\/\*[\s\S]*?\*\/)*):((?:\s|\/\*[\s\S]*?\*\/)*)(?=(?:var|env)\()/gi;function O(e){let t=`--teacss-optimized-all`;for(;e.includes(t);)t+=`-x`;return{css:e.replace(D,(e,n,r,i,a)=>`${n}${r}${t}${i}:${a}`),restore:e=>e.replaceAll(t,`all`)}}function k(e,t){return typeof e==`boolean`||e==null?(e??!0)&&t:e.css!==!1&&(e.css===`always`||t)}async function A(e,t,n=[]){let{cwd:r=o.cwd(),configPath:i,config:a}=e,{createGenerator:s}=await import(`@teacss/core`),c=t??(await import(`@teacss/config`)).createRecoveryConfigLoader(),l=a?{config:a,sources:[]}:await c(r,i);return{generator:await s(n.length?{...l.config,safelist:[...l.config.safelist??[],...n]}:l.config,{engine:`auto`}),sources:l.sources}}async function j(e,t,n){let[{resolveEntryConfig:r},{sourceDeclarationStateOf:i},{createGenerator:a}]=await Promise.all([import(`@teacss/config`),import(`@teacss/config/internal/source-scan`),import(`@teacss/core`)]),o=await r(e,t,n),{config:s,sources:c,unknown:l}=o;return{generator:await a(s,{engine:`auto`}),sources:c,unknown:l,declarations:i(o),hasPreset:!!s.presets?.length,hasEntryConfig:!!(s.presets?.length||s.content||s.shortcuts||Object.keys(s.theme??{}).length||l.length),safelist:(s.safelist??[]).filter(e=>typeof e==`string`)}}async function ce(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()}function M(e={}){let{optimize:r,targets:i,test:c=/index\.css$/,apply:l,...u}=e,d=l!==!1,f=typeof l==`object`&&l.test||m;return{name:`teacss`,setup(e){e.modifyRspackConfig(e=>{e.plugins??=[],e.plugins.push(new p)}),e.modifyRsbuildConfig((e,{mergeRsbuildConfig:t})=>t({tools:{lightningcssLoader:{exclude:{dirSelector:!0}}}},e)),e.modifyBundlerChain({order:`post`,handler(e,{CHAIN_ID:t,environment:n}){let r=n.config;if(r.tools.lightningcssLoader===!1&&!k(r.output.minify,r.mode===`production`))return;let i=s(new URL(import.meta.url.endsWith(`.ts`)?`./easing-loader.ts`:`./easing-loader.js`,import.meta.url)),a=[...e.module.rules.values()];for(let e of a){if(a.push(...e.rules.values(),...e.oneOfs.values()),!e.uses.has(t.USE.CSS))continue;let n=e.uses.has(`teacss-easing`),r=e.uses.has(t.USE.LIGHTNINGCSS)?t.USE.LIGHTNINGCSS:t.USE.CSS;e.use(`teacss-easing`).loader(i).after(r),n||e.use(t.USE.CSS).tap(e=>{let t=e;return{...t,importLoaders:(t.importLoaders??0)+1}})}}});let l,h=()=>l??=import(`@teacss/config`).then(({createRecoveryConfigLoader:e})=>e()),g=new Map,y=new Map,x=new Map,S=e=>{let t=g.get(e);if(t)return t;let n=h().then(e=>A(u,e));return g.set(e,n),n};e.onBeforeEnvironmentCompile(({environment:e})=>{g.delete(e.name),y.delete(e.name)}),e.transform({test:d?[m,c]:c},async({code:s,environment:l,addDependency:p,addMissingDependency:m,addContextDependency:C,resourcePath:w})=>{let D=w||``,O=_(c,D),M=d&&ee(s),N=M&&_(f,D);if(!O&&!N)return s;let{parseEntry:le,scanTopLevelAtRules:ue,diagnoseEntry:de}=await import(`@teacss/config`),P=O?le(s):void 0,F=P?.hasTeacss===!0;if(!F&&!N)return s;let I=!!(P&&(de(s).length||P.rolePalettes||P.presets.length>0||P.sources.length>0||P.custom.length>0||P.shortcuts.length>0)),fe=F&&ue(s).some(e=>e.name===`import`&&e.terminator===`;`),L=I||fe,R=n(w||`${u.cwd??o.cwd()}/index.css`),z=w||a(R,`index.css`);for(let e of P?.sources??[])e.exclude||v(b(R,e.glob),p,m,C);let B=re(P?.presets??[]),V,H,U,W;if(F){if(L)V=(async()=>{let e=x.get(l.name);if(e?.id===z&&e.code===s&&e.signature!==void 0&&await T(e.state)===e.signature)return W=e.signature,e.state})().then(async e=>{if(e)return H=e;let t=Date.now(),r=await j(s,R,z),i=!I&&!r.hasEntryConfig;if(i){let e=await A(u,await h(),r.safelist);r={...r,generator:e.generator,sources:[...r.sources,...e.sources],hasPreset:!!e.generator.config.presets.length,scanBase:e.sources[0]?n(e.sources[0]):u.cwd??o.cwd()}}return H={...r,sources:Array.from(new Set([z,...r.sources]))},i||(W=await T(H,t)),H}).catch(async t=>{let{getConfigSourcesFromError:n}=await import(`@teacss/config`),r=n(t);for(let e of r)v(e,p,m,C);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}}),y.set(l.name,V),d?g.set(l.name,V):g.delete(l.name);else if(P.safelist.length>0){let{expandClassGroups:e,splitClassTokens:t}=await import(`@teacss/core`),n=P.safelist.flatMap(n=>t(e(n))),r=h().then(e=>A(u,e,n));g.set(l.name,r),y.delete(l.name),x.delete(l.name)}else g.delete(l.name),y.delete(l.name),x.delete(l.name)}let G=()=>{H&&U&&V&&y.get(l.name)===V&&x.set(l.name,{id:z,code:s,signature:W,packagePresetDependencies:U,packagePresetSpecifiers:B,state:H})},K=s,q;if(M&&(F||N)){try{q=await S(l.name)}catch(e){if(!V){let{getConfigSourcesFromError:t}=await import(`@teacss/config`);for(let n of t(e))v(n,p,m,C)}throw e}for(let e of q.sources)v(e,p,m,C);K=await ce(s,D,q.generator)}if(!F)return K===s?s:{code:K};let J,Y,X,Z=[],pe=!0,Q=[];if(L){let n=V?await V:await j(K,R,z),r=n.scanBase??R;for(let e of(await E(n.generator)).includes)v(b(r,e),p,m,C);({css:J,files:X,matched:Y,unknown:Z,hasPreset:pe}=await oe(K,{base:R,contents:u.contents,readSources:ne(r,u.ignore)},n));try{U=ie(B,R)}catch(n){let r=x.get(l.name);if(!(r?.id===z&&ae(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{q??=await S(l.name);let e=await te(u,q.generator,void 0,q.sources),{spliceEntry:t}=await import(`@teacss/directives`);J=t(K,e.css),Y=e.utilities,X=e.files,Q=q.sources}pe||e.logger.warn(`@teacss/rsbuild: ${w||`entry`} declares no @presets — no utilities generated. Add one, e.g. \`@presets "standard";\``),Z.length&&e.logger.warn(`@teacss/rsbuild: ${Z.length} @custom var(s) not registered (unknown namespace / illegal color step): ${Z.join(`, `)}`);for(let e of X)p(e);for(let e of Q)v(e,p,m,C);let $=(l.config.mode??o.env.NODE_ENV)===`production`,me=i===void 0&&l.config.tools?.lightningcssLoader!==!1&&k(l.config.output.minify,$);if(!(r??($&&!me)))return e.logger.info(`@teacss/rsbuild: ${Y} utilities (${$?`optimized by Rspack`:`dev, unoptimized`}) → ${(J.length/1024).toFixed(1)}KB`),G(),{code:J};let he=typeof r==`object`&&r.minify!==void 0?r.minify:k(l.config.output.minify,$),{css:ge,skipped:_e}=await se(J,{minify:he,targets:i,browserslist:l.browserslist});return e.logger.info(`@teacss/rsbuild: ${Y} utilities${_e?`, ${_e} skipped`:``} → ${(ge.length/1024).toFixed(1)}KB`),G(),{code:ge}})}}}export{w as generateEntryCss,ne as makeReadSources,M as pluginTeacss};
1
+ import { fileURLToPath } from "node:url";
2
+ import { createProject } from "@teacss/engine";
3
+ import { emitBundledFonts } from "./fonts.js";
4
+ const DEFAULT_TEST = /\.(?:[cm]?[jt]sx?|vue|svelte|astro|html|md|mdx|css)$/;
5
+ const NODE_MODULES = /[\\/]node_modules[\\/]/;
6
+ const LOADER = fileURLToPath(new URL("./loader.js", import.meta.url));
7
+ /** Rsbuild's `output.minify` rule for CSS: on in production unless turned off, "always" in every mode. */
8
+ function rspackMinifiesCss(minify, isProd) {
9
+ if (typeof minify === "boolean" || minify == null)
10
+ return (minify ?? true) && isProd;
11
+ return minify.css !== false && (minify.css === "always" || isProd);
12
+ }
13
+ export function pluginTeacss(options = {}) {
14
+ const { test = DEFAULT_TEST, ...shell } = options;
15
+ return {
16
+ name: "teacss",
17
+ setup(api) {
18
+ let project;
19
+ api.modifyBundlerChain((chain, { environment }) => {
20
+ const isProd = environment.config.mode === "production";
21
+ project ??= createProject({ root: api.context.rootPath, envMode: api.context.action === "dev" ? "dev" : "build", ...shell });
22
+ const loaderOptions = { project, minify: shell.minify ?? (isProd && !rspackMinifiesCss(environment.config.output.minify, isProd)) };
23
+ chain.module.rule("teacss").test(test).exclude.add(NODE_MODULES).end().enforce("pre").use("teacss").loader(LOADER).options(loaderOptions);
24
+ });
25
+ api.processAssets({ stage: "pre-process" }, ({ compilation, sources }) => emitBundledFonts(compilation, sources));
26
+ const dispose = () => {
27
+ project?.dispose();
28
+ project = undefined;
29
+ };
30
+ api.onCloseBuild(dispose);
31
+ api.onCloseDevServer(dispose);
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,15 @@
1
+ import type { Rspack } from "@rsbuild/core";
2
+ import { type Project } from "@teacss/engine";
3
+ export interface LoaderOptions {
4
+ project: Project;
5
+ /** Passed to `engine.generate`; false when Rspack's lightningcss minimizer will minify the asset. */
6
+ minify: boolean;
7
+ }
8
+ /**
9
+ * v10.2 §3.3: a stylesheet's top-level `@teacss;` becomes the generated CSS of the project's `@source`
10
+ * files (read from disk, so the result does not depend on module build order) and of
11
+ * `inline.content.inline`, and the entry directives are dropped; the rest of the stylesheet goes through `project.transform` like any other.
12
+ * Tokens only a module outside `@source` registered are left out: Rspack may build it after the entry.
13
+ */
14
+ export default function teacssLoader(this: Rspack.LoaderContext<LoaderOptions>, source: string): void;
15
+ //# sourceMappingURL=loader.d.ts.map
package/dist/loader.js ADDED
@@ -0,0 +1,37 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { isCssId, parseEntry, replaceEntryWithMap } from "@teacss/engine";
3
+ /**
4
+ * v10.2 §3.3: a stylesheet's top-level `@teacss;` becomes the generated CSS of the project's `@source`
5
+ * files (read from disk, so the result does not depend on module build order) and of
6
+ * `inline.content.inline`, and the entry directives are dropped; the rest of the stylesheet goes through `project.transform` like any other.
7
+ * Tokens only a module outside `@source` registered are left out: Rspack may build it after the entry.
8
+ */
9
+ export default function teacssLoader(source) {
10
+ const { project, minify } = this.getOptions();
11
+ const id = this.resourcePath;
12
+ if (isCssId(id) && parseEntry(source, id).hasTeacss) {
13
+ project.reload();
14
+ const { engine } = project;
15
+ for (const path of project.info.unreadableDirectories)
16
+ this.emitWarning(new Error(`cannot read ${path} (E_IO)`));
17
+ const files = project.info.sourceFiles;
18
+ engine.updateModules(files, files.map((file) => readFileSync(file, "utf8")));
19
+ for (const file of project.watchFiles())
20
+ this.addDependency(file);
21
+ for (const file of engine.missingImports())
22
+ this.addMissingDependency(file);
23
+ for (const { dir } of project.sourceDirectories())
24
+ this.addContextDependency(dir);
25
+ const sourceTokens = new Set([...files.flatMap((file) => engine.moduleTokens(file) ?? []), ...project.inlineTokens()]);
26
+ const tokens = engine.allTokens().filter((token) => sourceTokens.has(token));
27
+ const result = engine.generate({ kind: "tokens", tokens }, { minify, css: true });
28
+ project.reportUnmatched(result, { tokens, warn: (message) => this.emitWarning(new Error(message)) });
29
+ const { code, map } = project.transform(id, source);
30
+ const entry = replaceEntryWithMap(code, () => result.css, { id, original: source, map });
31
+ this.callback(null, entry?.code ?? code, entry ? { ...entry.map, file: id } : undefined);
32
+ return;
33
+ }
34
+ const { code, map } = project.transform(id, source);
35
+ this.callback(null, code, map ? { ...map, file: map.file ?? id } : undefined);
36
+ }
37
+ //# sourceMappingURL=loader.js.map
package/package.json CHANGED
@@ -1,49 +1,56 @@
1
1
  {
2
2
  "name": "@teacss/rsbuild",
3
- "version": "0.9.3",
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",
3
+ "version": "0.10.2",
4
+ "description": "Rsbuild plugin for TeaCSS: an Rspack loader that calls the Rust engine (@teacss/engine) directly",
7
5
  "license": "MIT",
8
- "author": "Billgo <hi@billgo.me>",
9
- "sideEffects": false,
10
6
  "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/teacss/teacss-rs.git",
10
+ "directory": "packages/rsbuild"
11
+ },
12
+ "homepage": "https://github.com/teacss/teacss-rs/tree/master/packages/rsbuild#readme",
13
+ "main": "./dist/index.js",
14
+ "types": "./dist/index.d.ts",
11
15
  "exports": {
12
- ".": "./dist/index.js",
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ },
13
20
  "./package.json": "./package.json"
14
21
  },
15
- "types": "./dist/index.d.ts",
22
+ "imports": {
23
+ "#src/*": "./dist/*"
24
+ },
16
25
  "files": [
17
- "dist",
18
- "README.md"
26
+ "dist/**/*.js",
27
+ "dist/**/*.d.ts",
28
+ "README.md",
29
+ "LICENSE"
19
30
  ],
20
- "scripts": {
21
- "build": "bunx tsdown --config tsdown.config.mts",
22
- "dev": "tsdown --watch"
31
+ "sideEffects": false,
32
+ "engines": {
33
+ "node": ">=26"
34
+ },
35
+ "peerDependencies": {
36
+ "@rsbuild/core": ">=1"
23
37
  },
24
38
  "dependencies": {
25
- "@jridgewell/remapping": "^2.3.5",
26
- "@teacss/config": "0.9.3",
27
- "@teacss/core": "0.9.3",
28
- "@teacss/directives": "0.9.3",
29
- "@teacss/engine": "0.9.3",
30
- "@teacss/runtime": "0.9.3",
31
- "lightningcss": "^1.33.0",
32
- "magic-string": "^1.2.3"
39
+ "@teacss/engine": "0.10.2"
33
40
  },
34
41
  "devDependencies": {
35
- "@rsbuild/core": "^2.2.3",
36
- "@teacss/preset-official": "0.9.3",
37
- "@teacss/preset-standard": "0.9.3",
38
- "consola": "^3.4.2"
42
+ "@rsbuild/core": "2.2.14",
43
+ "@types/node": "^22.20.5",
44
+ "typescript": "^5.9.3"
39
45
  },
40
- "peerDependencies": {
41
- "@rsbuild/core": "^2"
42
- },
43
- "engines": {
44
- "node": ">=22.12.0"
46
+ "scripts": {
47
+ "build:ts": "node -e \"for (const d of ['build','dist']) require('fs').rmSync(d,{recursive:true,force:true})\" && tsc -p tsconfig.json && tsc -p tsconfig.test.json",
48
+ "build": "npm run build:ts",
49
+ "check:types": "tsc -p tests/consumer/tsconfig.json --noEmit",
50
+ "test": "npm run build:ts && npm run check:types && node --test \"build/tests/**/*.test.js\""
45
51
  },
46
52
  "publishConfig": {
47
- "access": "public"
53
+ "access": "public",
54
+ "registry": "https://registry.npmjs.org/"
48
55
  }
49
56
  }
@@ -1,10 +0,0 @@
1
- import { EncodedSourceMap } from "@jridgewell/remapping";
2
- //#region src/easing-loader.d.ts
3
- interface LoaderContext {
4
- resourcePath: string;
5
- sourceMap?: boolean;
6
- callback(error: Error | null, code: string, map?: EncodedSourceMap | string, additionalData?: unknown): void;
7
- }
8
- /** Preserve calculated easing ranges before Rsbuild's native CSS optimizer. */
9
- export default function easingLoader(this: LoaderContext, source: string, inputMap?: EncodedSourceMap | string, additionalData?: unknown): void;
10
- //#endregion
@@ -1 +0,0 @@
1
- import e from"@jridgewell/remapping";import{prepareEasingCss as t}from"@teacss/directives/internal/easing";import n from"magic-string";function r(r,i,a){let o=new n(r);if(t(o),!o.hasChanged()){this.callback(null,r,i,a);return}let s;if(this.sourceMap){let t=o.generateMap({hires:!0,includeContent:!0,source:this.resourcePath});s=i?e([t,i],()=>null):t}let c=a&&typeof a==`object`&&`ast`in a?{...a,ast:void 0}:a;this.callback(null,o.toString(),s,c)}export{r as default};