dsh-taiwind 0.2.0 → 0.3.1

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.
Files changed (3) hide show
  1. package/README.md +22 -20
  2. package/dist/index.js +21 -40
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -43,7 +43,7 @@ flowchart LR
43
43
  ### 1. 安装
44
44
 
45
45
  ```sh
46
- pnpm add dsh-taiwind
46
+ pnpm add dsh-taiwind@latest
47
47
  ```
48
48
 
49
49
  在业务插件的 `cordis.patch.yml` 里插入它(连同自身行):
@@ -56,15 +56,24 @@ pnpm add dsh-taiwind
56
56
  name: dsh-taiwind
57
57
  ```
58
58
 
59
- 用户启用业务插件时,`dsh-taiwind` 将随行一起启用,而客户端半也必须有一行启用中的 Loader 条目才会被 `dsh-client-modules` 加载。
59
+ 用户启用业务插件时,`dsh-taiwind` 将随行一起启用。
60
60
 
61
- - 它没有界面、没有工具,唯一产物是共享样式接口 `plugins/taiwind.css` 和页面上的一个 `<style data-plugin="dsh-taiwind">`。
62
- - 宿主半要求 `clientModules`、`webServer`、`connection` 三件服务齐备。
61
+ - 它没有界面和工具,唯一产物是共享样式接口 `plugins/taiwind.css` 和页面上的 `<style data-plugin="dsh-taiwind">`。
63
62
  - 扫描范围是当前客户端图里所有插件的构建产物,插件图变化会自动触发重新编译。
64
63
 
65
- ### 2. 在插件包里写样式
64
+ ### 2. 声明样式入口
66
65
 
67
- 在插件包中保留未编译的 `main.css`:
66
+ 在插件包的 `package.json` 里显式声明未编译的 CSS 入口(相对包根,必须位于包内):
67
+
68
+ ```json
69
+ {
70
+ "dsh": {
71
+ "taiwind": "src/main.css"
72
+ }
73
+ }
74
+ ```
75
+
76
+ `main.css` 保留未编译的 CSS-first 写法:
68
77
 
69
78
  ```css
70
79
  @import "tailwindcss";
@@ -81,16 +90,9 @@ pnpm add dsh-taiwind
81
90
 
82
91
  🚀 客户端直接使用 taiwind 类名,例如 `bg-notes-accent w-notes-panel notes-compact` 即可。
83
92
 
84
- 入口发现顺序:
85
-
86
- 1. 包根目录的 `main.css`
87
- 2. 客户端入口同目录的 `main.css`(如 `client/main.css`)
88
- 3. 以上都没有时,在包内查找 `main.css`;
89
- 4. 找到多个会明确报错,不任意选择
90
-
91
93
  ### 3. 发布时携带样式文件
92
94
 
93
- 在 `package.json` 中添加 `files` 字段,发布时需要携带 `main.css`:
95
+ 在 `package.json` 中添加 `files` 字段,发布时需要携带入口 CSS 及它引用的相对 CSS:
94
96
 
95
97
  ```json
96
98
  {
@@ -126,14 +128,14 @@ CSS 入口存在时**不会自动加载根目录 JS 配置**,需要时在 CSS
126
128
 
127
129
  | 项目 | 说明 |
128
130
  | --- | --- |
129
- | 发布 | 原始 CSS 入口及其相对导入必须进 npm 包;只发 `dist` 时一并复制,不要用展开后的构建 CSS 代替 |
130
- | 不支持 | TypeScript 配置(只认 `.js` / `.mjs` / `.cjs`)、npm CSS 包导入、路径别名、远程 CSS、`@plugin`、文件路径形式的 `@source`、自定义 `source(...)` 扫描目录、跨包 CSS 导入 |
131
- | 冲突检测 | 自定义 token、同名 `@utility` / `@custom-variant` 定义不一致会报错并指包名;非完整 CSS 语义等价判断 |
132
- | 主题合并 | 遵循 Tailwind 4:同 token 的 CSS 定义优先;所有插件共享全局主题,不按插件隔离 |
131
+ | 发布 | 原始 CSS 入口及其相对导入必须进 npm 包; |
132
+ | 不支持 | TypeScript 配置、npm CSS 包导入、路径别名、远程 CSS、`@plugin`、文件路径形式的 `@source`、自定义 `source(...)` 扫描目录、跨包 CSS 导入 |
133
+ | 冲突检测 | CSS token、同名 `@utility` / `@custom-variant` 定义不一致时自动合并,并输出包含冲突项和双方包名的 warn 日志;同一冲突不会在每次轮询时重复刷屏 |
134
+ | 主题合并 | 按插件 ID、入口路径排序后交由 Tailwind 4 合并,CSS 定义优先于 JS 配置,同名 CSS 定义遵循后者覆盖;所有插件共享全局主题,不按插件隔离 |
133
135
  | 自定义 CSS | 一起输出,但不自动限定作用域、去重或改写资源 URL;请用插件前缀选择器和可直接访问的资源地址 |
134
136
  | 动态值 | 适合 CSS 变量或内联样式,不要拼接不完整类名 |
135
- | 共享编译器成本 | 需安装 Tailwind、Oxide 原生扫描器等宿主依赖;有磁盘占用与宿主 CPU 开销 |
136
- | 共享不是隔离 | 共用主题与全局选项,冲突可能阻止整份样式更新;不适合要求完全独立 Tailwind 配置的插件 |
137
+ | 共享编译器成本 | 需安装 Tailwind、Oxide 原生扫描器等宿主依赖;有磁盘占用(约 8mb~10mb)与宿主 CPU 开销 |
138
+ | 共享不是隔离 | 共用主题与全局选项;CSS 定义冲突只告警,非法配置与不兼容的 JS 全局选项仍会阻止更新;不适合要求完全独立 Tailwind 配置的插件 |
137
139
 
138
140
  ## 🛠️ 开发与贡献
139
141
 
package/dist/index.js CHANGED
@@ -138,43 +138,18 @@ async function bundles(directory) {
138
138
  }
139
139
  return files;
140
140
  }
141
- async function nestedPackage(directory) {
142
- try {
143
- await readFile(join(directory, "package.json"));
144
- return true;
145
- } catch (error) {
146
- if (error.code === "ENOENT") return false;
147
- throw error;
148
- }
149
- }
150
- async function cssEntry(root, clientDirectory) {
151
- for (const directory of /* @__PURE__ */ new Set([root, clientDirectory])) {
152
- const entry = (await readdir(directory, { withFileTypes: true })).find((item) => item.name === "main.css");
153
- if (entry) {
154
- if (!entry.isFile()) throw new Error(`${directory}: main.css must be a regular file`);
155
- return join(directory, entry.name);
156
- }
141
+ async function cssEntry(root, owner) {
142
+ const entry = JSON.parse(await readFile(join(root, "package.json"), "utf8")).dsh?.taiwind;
143
+ if (entry === void 0) {
144
+ if (await stat(join(root, "main.css")).then((info) => info.isFile(), () => false)) throw new Error(`${owner}: main.css is not discovered automatically anymore; declare "dsh": { "taiwind": "main.css" } in package.json`);
145
+ return;
157
146
  }
158
- const found = [];
159
- const visit = async (directory) => {
160
- for (const item of await readdir(directory, { withFileTypes: true })) {
161
- if ([
162
- "node_modules",
163
- "archive",
164
- ".git"
165
- ].includes(item.name) || item.isSymbolicLink()) continue;
166
- const path = join(directory, item.name);
167
- if (item.isDirectory()) {
168
- if (await nestedPackage(path)) continue;
169
- await visit(path);
170
- } else if (item.isFile() && item.name === "main.css") found.push(path);
171
- }
172
- };
173
- await visit(root);
174
- if (found.length > 1) throw new Error(`${root}: multiple main.css entries found: ${found.sort().join(", ")}`);
175
- return found[0];
147
+ if (typeof entry !== "string" || !entry.trim() || extname(entry) !== ".css") throw new Error(`${owner}: dsh.taiwind must be a relative .css path`);
148
+ const path = resolve(root, entry);
149
+ if (!await stat(path).then((info) => info.isFile(), () => false)) throw new Error(`${owner}: dsh.taiwind entry not found: ${entry}`);
150
+ return path;
176
151
  }
177
- async function compilePlugins(entries) {
152
+ async function compilePlugins(entries, onWarning) {
178
153
  const hash = createHash("sha256");
179
154
  const bundleFiles = [];
180
155
  const extend = Object.create(null);
@@ -185,6 +160,7 @@ async function compilePlugins(entries) {
185
160
  const stylesheets = /* @__PURE__ */ new Map();
186
161
  const cssImports = [];
187
162
  const cssDefinitions = /* @__PURE__ */ new Map();
163
+ const warnings = /* @__PURE__ */ new Set();
188
164
  const configsSeen = /* @__PURE__ */ new Set();
189
165
  const setGlobal = (key, value, owner) => {
190
166
  if (Object.hasOwn(merged, key) && canonical(merged[key]) !== canonical(value)) throw new Error(`Tailwind global configuration conflict at ${key}: ${owners.get(key)} and ${owner}`);
@@ -221,7 +197,7 @@ async function compilePlugins(entries) {
221
197
  };
222
198
  const checkDefinition = (key, value, owner) => {
223
199
  const previous = cssDefinitions.get(key);
224
- if (previous && previous.owner !== owner && previous.value !== value) throw new Error(`Tailwind CSS conflict at ${key}: ${previous.owner} and ${owner}`);
200
+ if (previous && previous.owner !== owner && previous.value !== value) warnings.add(`Tailwind CSS conflict at ${key}: ${previous.owner} and ${owner}; merging in plugin order`);
225
201
  cssDefinitions.set(key, {
226
202
  value,
227
203
  owner
@@ -301,7 +277,7 @@ async function compilePlugins(entries) {
301
277
  ]));
302
278
  bundleFiles.push(file);
303
279
  }
304
- const stylesheet = await cssEntry(root, dirname(path));
280
+ const stylesheet = await cssEntry(root, entry.id);
305
281
  if (stylesheet) {
306
282
  cssImports.push(await loadCss(stylesheet, root, entry.id));
307
283
  continue;
@@ -318,6 +294,7 @@ async function compilePlugins(entries) {
318
294
  const utilities = await readFile(new URL(import.meta.resolve("tailwindcss/utilities.css")), "utf8");
319
295
  hash.update(JSON.stringify([theme, utilities]));
320
296
  const digest = hash.digest("hex");
297
+ for (const warning of warnings) onWarning?.(warning);
321
298
  if (cached?.signature === digest) return cached.css;
322
299
  const sources = [];
323
300
  for (const file of bundleFiles) sources.push({
@@ -379,20 +356,24 @@ async function apply(ctx) {
379
356
  pending = (async () => {
380
357
  while (true) {
381
358
  const current = generation;
382
- const compiled = await compilePlugins(ctx.clientModules.graph().entries.map(({ id }) => {
359
+ const entries = ctx.clientModules.graph().entries.map(({ id }) => {
383
360
  const path = ctx.clientModules.clientPath(id);
384
361
  if (!path) throw new Error(`Missing client bundle: ${id}`);
385
362
  return {
386
363
  id,
387
364
  path
388
365
  };
389
- }));
366
+ });
367
+ const warnings = [];
368
+ const compiled = await compilePlugins(entries, (message) => warnings.push(message));
390
369
  if (disposed) return;
391
370
  if (generation !== current) continue;
392
371
  css = compiled;
393
372
  etag = `"${createHash("sha256").update(css).digest("hex")}"`;
394
373
  failed = false;
395
- lastError = "";
374
+ const warning = warnings.join("\n");
375
+ if (warning && warning !== lastError) ctx.logger.warn(`${PLUGIN_ID}: ${warning}`);
376
+ lastError = warning;
396
377
  break;
397
378
  }
398
379
  })().finally(() => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-taiwind",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.3.1",
5
5
  "description": "One shared Tailwind stylesheet for DSH plugins with per-plugin configuration",
6
6
  "author": "Hairy <wwu710632@gmail.com>",
7
7
  "license": "MIT",