@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 +23 -46
- package/dist/fonts.d.ts +6 -0
- package/dist/fonts.js +26 -0
- package/dist/index.d.ts +7 -88
- package/dist/index.js +35 -1
- package/dist/loader.d.ts +15 -0
- package/dist/loader.js +37 -0
- package/package.json +38 -31
- package/dist/easing-loader.d.ts +0 -10
- package/dist/easing-loader.js +0 -1
package/README.md
CHANGED
|
@@ -1,65 +1,42 @@
|
|
|
1
1
|
# @teacss/rsbuild
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
package/dist/fonts.d.ts
ADDED
|
@@ -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 {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
package/dist/loader.d.ts
ADDED
|
@@ -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.
|
|
4
|
-
"description": "Rsbuild
|
|
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
|
-
".":
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
},
|
|
13
20
|
"./package.json": "./package.json"
|
|
14
21
|
},
|
|
15
|
-
"
|
|
22
|
+
"imports": {
|
|
23
|
+
"#src/*": "./dist/*"
|
|
24
|
+
},
|
|
16
25
|
"files": [
|
|
17
|
-
"dist",
|
|
18
|
-
"
|
|
26
|
+
"dist/**/*.js",
|
|
27
|
+
"dist/**/*.d.ts",
|
|
28
|
+
"README.md",
|
|
29
|
+
"LICENSE"
|
|
19
30
|
],
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
"
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=26"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"@rsbuild/core": ">=1"
|
|
23
37
|
},
|
|
24
38
|
"dependencies": {
|
|
25
|
-
"@
|
|
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": "
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
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
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
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
|
}
|
package/dist/easing-loader.d.ts
DELETED
|
@@ -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
|
package/dist/easing-loader.js
DELETED
|
@@ -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};
|