dsh-taiwind 0.3.1 → 0.3.3

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
@@ -33,7 +33,7 @@ flowchart LR
33
33
 
34
34
  - **CSS-first**:保留未编译的 `main.css`,支持 `@theme`、`@utility`、`@custom-variant`、`@apply`、`@source inline(...)`。
35
35
  - **官方编译器**:Tailwind 扫描器提取候选类,统一编译;不加载 Preflight,不重置宿主界面。
36
- - **单一入口**:首屏注入 `<head>`,客户端接管同一个 `<style data-plugin="dsh-taiwind" data-plugin-css="utilities">`。
36
+ - **单一入口**:首屏注入 `<head>`,客户端接管同一个 `<style data-plugin="dsh-taiwind" data-plugin-css="utilities">`;编译就绪前先输出阻塞式 `<link>`,接管后移除,页面不会先亮一帧无样式内容。
37
37
  - **热更新**:插件安装/卸载/启停,或客户端产物、`main.css` 改动后都会自动重新编译;已打开的页面无需刷新即可换上新样式,也不必重启宿主。
38
38
  - **条件请求**:带 ETag 的 CSS 接口,命中返回 304;内容没变就不读产物、不跑 Tailwind。
39
39
  - **完整清理**:停用移除路由、监听、定时器与客户端样式,并取消进行中的请求。
@@ -136,6 +136,7 @@ CSS 入口存在时**不会自动加载根目录 JS 配置**,需要时在 CSS
136
136
  | 动态值 | 适合 CSS 变量或内联样式,不要拼接不完整类名 |
137
137
  | 共享编译器成本 | 需安装 Tailwind、Oxide 原生扫描器等宿主依赖;有磁盘占用(约 8mb~10mb)与宿主 CPU 开销 |
138
138
  | 共享不是隔离 | 共用主题与全局选项;CSS 定义冲突只告警,非法配置与不兼容的 JS 全局选项仍会阻止更新;不适合要求完全独立 Tailwind 配置的插件 |
139
+ | 首屏时序 | 编译未就绪时页面通过阻塞式 `<link>` 等样式,最多 15 秒;超时才放弃等待并显示无样式内容 |
139
140
 
140
141
  ## 🛠️ 开发与贡献
141
142
 
package/dist/client.cjs CHANGED
@@ -1,4 +1,4 @@
1
1
  window.__ModuleLoader__.load({id:"dsh-taiwind",factory:(require)=>{var module={exports:{}};var exports=module.exports;
2
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=`dsh-taiwind`;function t(){let t=document.head.querySelector(`[data-plugin="dsh-taiwind"][data-plugin-css="utilities"]`),n=t??document.createElement(`style`);n.dataset.plugin=e,n.dataset.pluginCss=`utilities`,t||document.head.append(n);let r=new AbortController,i=``,a=!1,o,s=``,c=async()=>{let t=new AbortController,l=setTimeout(()=>t.abort(),15e3),u=()=>t.abort();r.signal.addEventListener(`abort`,u,{once:!0});try{let e=await fetch(new URL(`plugins/taiwind.css`,document.baseURI),{credentials:`same-origin`,cache:`no-cache`,headers:i?{"If-None-Match":i}:{},signal:t.signal});if(e.status!==304){if(!e.ok)throw Error(`Stylesheet request failed (${e.status})`);let t=await e.text();a||(n.textContent!==t&&(n.textContent=t),i=e.headers.get(`ETag`)??``)}s=``}catch(t){if(!a){let n=t instanceof Error?t.message:String(t);s!==n&&(s=n,console.warn(`${e}: ${n}; keeping previous stylesheet`))}}finally{clearTimeout(l),r.signal.removeEventListener(`abort`,u),a||(o=setTimeout(()=>void c(),2e3))}};return c(),()=>{a=!0,r.abort(),clearTimeout(o),n.remove()}}function n(e){e.effect(t)}exports.apply=n;
2
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=`dsh-taiwind`;function t(){let t=document.head.querySelector(`[data-plugin="dsh-taiwind"][data-plugin-css="utilities"]`),n=t??document.createElement(`style`);n.dataset.plugin=e,n.dataset.pluginCss=`utilities`,t||document.head.append(n);let r=document.head.querySelector(`link[data-plugin="dsh-taiwind"][data-plugin-css="utilities"]`),i=new AbortController,a=``,o=!1,s,c=``,l=async()=>{let t=new AbortController,u=setTimeout(()=>t.abort(),15e3),d=()=>t.abort();i.signal.addEventListener(`abort`,d,{once:!0});try{let e=await fetch(new URL(`plugins/taiwind.css`,document.baseURI),{credentials:`same-origin`,cache:`no-cache`,headers:a?{"If-None-Match":a}:{},signal:t.signal});if(e.status!==304){if(!e.ok)throw Error(`Stylesheet request failed (${e.status})`);let t=await e.text();o||(n.textContent!==t&&(n.textContent=t),r?.remove(),a=e.headers.get(`ETag`)??``)}c=``}catch(t){if(!o){let n=t instanceof Error?t.message:String(t);c!==n&&(c=n,console.warn(`${e}: ${n}; keeping previous stylesheet`))}}finally{clearTimeout(u),i.signal.removeEventListener(`abort`,d),o||(s=setTimeout(()=>void l(),2e3))}};return l(),()=>{o=!0,i.abort(),clearTimeout(s),n.remove(),r?.remove()}}function n(e){e.effect(t)}exports.apply=n;
3
3
  return module.exports;}});
4
4
  //# sourceMappingURL=client.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.cjs","names":[],"sources":["../src/constants.ts","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["export const PLUGIN_ID = 'dsh-taiwind'\nexport const STYLESHEET_PATH = 'plugins/taiwind.css'\nexport const STYLE_SELECTOR = '[data-plugin=\"dsh-taiwind\"][data-plugin-css=\"utilities\"]'\nexport const POLL_INTERVAL_MS = 2000\n","import { PLUGIN_ID, POLL_INTERVAL_MS, STYLE_SELECTOR, STYLESHEET_PATH } from '../constants'\n\nexport function mountStyles(): () => void {\n const existing = document.head.querySelector<HTMLStyleElement>(STYLE_SELECTOR)\n const style = existing ?? document.createElement('style')\n style.dataset.plugin = PLUGIN_ID\n style.dataset.pluginCss = 'utilities'\n if (!existing)\n document.head.append(style)\n\n const controller = new AbortController()\n let etag = ''\n let disposed = false\n let timer: ReturnType<typeof setTimeout> | undefined\n let lastError = ''\n\n const update = async (): Promise<void> => {\n const request = new AbortController()\n const timeout = setTimeout(() => request.abort(), 15000)\n const abort = (): void => request.abort()\n controller.signal.addEventListener('abort', abort, { once: true })\n try {\n const response = await fetch(new URL(STYLESHEET_PATH, document.baseURI), {\n credentials: 'same-origin',\n cache: 'no-cache',\n headers: etag ? { 'If-None-Match': etag } : {},\n signal: request.signal,\n })\n if (response.status !== 304) {\n if (!response.ok)\n throw new Error(`Stylesheet request failed (${response.status})`)\n const css = await response.text()\n if (!disposed) {\n if (style.textContent !== css)\n style.textContent = css\n etag = response.headers.get('ETag') ?? ''\n }\n }\n lastError = ''\n }\n catch (error) {\n if (!disposed) {\n const message = error instanceof Error ? error.message : String(error)\n if (lastError !== message) {\n lastError = message\n console.warn(`${PLUGIN_ID}: ${message}; keeping previous stylesheet`)\n }\n }\n }\n finally {\n clearTimeout(timeout)\n controller.signal.removeEventListener('abort', abort)\n if (!disposed)\n timer = setTimeout(() => void update(), POLL_INTERVAL_MS)\n }\n }\n void update()\n\n return () => {\n disposed = true\n controller.abort()\n clearTimeout(timer)\n style.remove()\n }\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport { mountStyles } from './styles'\n\nexport function apply(ctx: Context): void {\n ctx.effect(mountStyles)\n}\n"],"mappings":";mEAAA,MAAa,EAAY,cCEzB,SAAgB,GAA0B,CACxC,IAAM,EAAW,SAAS,KAAK,cAAgC,0DAAc,EACvE,EAAQ,GAAY,SAAS,cAAc,OAAO,EACxD,EAAM,QAAQ,OAAS,EACvB,EAAM,QAAQ,UAAY,YACrB,GACH,SAAS,KAAK,OAAO,CAAK,EAE5B,IAAM,EAAa,IAAI,gBACnB,EAAO,GACP,EAAW,GACX,EACA,EAAY,GAEV,EAAS,SAA2B,CACxC,IAAM,EAAU,IAAI,gBACd,EAAU,eAAiB,EAAQ,MAAM,EAAG,IAAK,EACjD,MAAoB,EAAQ,MAAM,EACxC,EAAW,OAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EACjE,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,IAAI,IAAI,sBAAiB,SAAS,OAAO,EAAG,CACvE,YAAa,cACb,MAAO,WACP,QAAS,EAAO,CAAE,gBAAiB,CAAK,EAAI,CAAC,EAC7C,OAAQ,EAAQ,MAClB,CAAC,EACD,GAAI,EAAS,SAAW,IAAK,CAC3B,GAAI,CAAC,EAAS,GACZ,MAAU,MAAM,8BAA8B,EAAS,OAAO,EAAE,EAClE,IAAM,EAAM,MAAM,EAAS,KAAK,EAC3B,IACC,EAAM,cAAgB,IACxB,EAAM,YAAc,GACtB,EAAO,EAAS,QAAQ,IAAI,MAAM,GAAK,GAE3C,CACA,EAAY,EACd,OACO,EAAO,CACZ,GAAI,CAAC,EAAU,CACb,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACjE,IAAc,IAChB,EAAY,EACZ,QAAQ,KAAK,GAAG,EAAU,IAAI,EAAQ,8BAA8B,EAExE,CACF,QACQ,CACN,aAAa,CAAO,EACpB,EAAW,OAAO,oBAAoB,QAAS,CAAK,EAC/C,IACH,EAAQ,eAAiB,KAAK,EAAO,EAAG,GAAgB,EAC5D,CACF,EAGA,OAFA,EAAY,MAEC,CACX,EAAW,GACX,EAAW,MAAM,EACjB,aAAa,CAAK,EAClB,EAAM,OAAO,CACf,CACF,CC7DA,SAAgB,EAAM,EAAoB,CACxC,EAAI,OAAO,CAAW,CACxB"}
1
+ {"version":3,"file":"client.cjs","names":[],"sources":["../src/constants.ts","../src/client/styles.ts","../src/client/index.ts"],"sourcesContent":["export const PLUGIN_ID = 'dsh-taiwind'\nexport const STYLESHEET_PATH = 'plugins/taiwind.css'\nexport const STYLE_SELECTOR = '[data-plugin=\"dsh-taiwind\"][data-plugin-css=\"utilities\"]'\nexport const LINK_SELECTOR = 'link[data-plugin=\"dsh-taiwind\"][data-plugin-css=\"utilities\"]'\nexport const POLL_INTERVAL_MS = 2000\n","import { LINK_SELECTOR, PLUGIN_ID, POLL_INTERVAL_MS, STYLE_SELECTOR, STYLESHEET_PATH } from '../constants'\n\nexport function mountStyles(): () => void {\n const existing = document.head.querySelector<HTMLStyleElement>(STYLE_SELECTOR)\n const style = existing ?? document.createElement('style')\n style.dataset.plugin = PLUGIN_ID\n style.dataset.pluginCss = 'utilities'\n if (!existing)\n document.head.append(style)\n // 首屏阻塞式 link 在样式接管后移除,避免同一份规则加载两遍。\n const preloaded = document.head.querySelector<HTMLLinkElement>(LINK_SELECTOR)\n\n const controller = new AbortController()\n let etag = ''\n let disposed = false\n let timer: ReturnType<typeof setTimeout> | undefined\n let lastError = ''\n\n const update = async (): Promise<void> => {\n const request = new AbortController()\n const timeout = setTimeout(() => request.abort(), 15000)\n const abort = (): void => request.abort()\n controller.signal.addEventListener('abort', abort, { once: true })\n try {\n const response = await fetch(new URL(STYLESHEET_PATH, document.baseURI), {\n credentials: 'same-origin',\n cache: 'no-cache',\n headers: etag ? { 'If-None-Match': etag } : {},\n signal: request.signal,\n })\n if (response.status !== 304) {\n if (!response.ok)\n throw new Error(`Stylesheet request failed (${response.status})`)\n const css = await response.text()\n if (!disposed) {\n if (style.textContent !== css)\n style.textContent = css\n preloaded?.remove()\n etag = response.headers.get('ETag') ?? ''\n }\n }\n lastError = ''\n }\n catch (error) {\n if (!disposed) {\n const message = error instanceof Error ? error.message : String(error)\n if (lastError !== message) {\n lastError = message\n console.warn(`${PLUGIN_ID}: ${message}; keeping previous stylesheet`)\n }\n }\n }\n finally {\n clearTimeout(timeout)\n controller.signal.removeEventListener('abort', abort)\n if (!disposed)\n timer = setTimeout(() => void update(), POLL_INTERVAL_MS)\n }\n }\n void update()\n\n return () => {\n disposed = true\n controller.abort()\n clearTimeout(timer)\n style.remove()\n preloaded?.remove()\n }\n}\n","import type { Context } from '@deepseek-ai/cordis'\nimport { mountStyles } from './styles'\n\nexport function apply(ctx: Context): void {\n ctx.effect(mountStyles)\n}\n"],"mappings":";mEAAA,MAAa,EAAY,cCEzB,SAAgB,GAA0B,CACxC,IAAM,EAAW,SAAS,KAAK,cAAgC,0DAAc,EACvE,EAAQ,GAAY,SAAS,cAAc,OAAO,EACxD,EAAM,QAAQ,OAAS,EACvB,EAAM,QAAQ,UAAY,YACrB,GACH,SAAS,KAAK,OAAO,CAAK,EAE5B,IAAM,EAAY,SAAS,KAAK,cAA+B,8DAAa,EAEtE,EAAa,IAAI,gBACnB,EAAO,GACP,EAAW,GACX,EACA,EAAY,GAEV,EAAS,SAA2B,CACxC,IAAM,EAAU,IAAI,gBACd,EAAU,eAAiB,EAAQ,MAAM,EAAG,IAAK,EACjD,MAAoB,EAAQ,MAAM,EACxC,EAAW,OAAO,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EACjE,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,IAAI,IAAI,sBAAiB,SAAS,OAAO,EAAG,CACvE,YAAa,cACb,MAAO,WACP,QAAS,EAAO,CAAE,gBAAiB,CAAK,EAAI,CAAC,EAC7C,OAAQ,EAAQ,MAClB,CAAC,EACD,GAAI,EAAS,SAAW,IAAK,CAC3B,GAAI,CAAC,EAAS,GACZ,MAAU,MAAM,8BAA8B,EAAS,OAAO,EAAE,EAClE,IAAM,EAAM,MAAM,EAAS,KAAK,EAC3B,IACC,EAAM,cAAgB,IACxB,EAAM,YAAc,GACtB,GAAW,OAAO,EAClB,EAAO,EAAS,QAAQ,IAAI,MAAM,GAAK,GAE3C,CACA,EAAY,EACd,OACO,EAAO,CACZ,GAAI,CAAC,EAAU,CACb,IAAM,EAAU,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACjE,IAAc,IAChB,EAAY,EACZ,QAAQ,KAAK,GAAG,EAAU,IAAI,EAAQ,8BAA8B,EAExE,CACF,QACQ,CACN,aAAa,CAAO,EACpB,EAAW,OAAO,oBAAoB,QAAS,CAAK,EAC/C,IACH,EAAQ,eAAiB,KAAK,EAAO,EAAG,GAAgB,EAC5D,CACF,EAGA,OAFA,EAAY,MAEC,CACX,EAAW,GACX,EAAW,MAAM,EACjB,aAAa,CAAK,EAClB,EAAM,OAAO,EACb,GAAW,OAAO,CACpB,CACF,CCjEA,SAAgB,EAAM,EAAoB,CACxC,EAAI,OAAO,CAAW,CACxB"}
package/dist/index.js CHANGED
@@ -405,7 +405,7 @@ async function apply(ctx) {
405
405
  ctx.effect(() => ctx.webServer.register({
406
406
  kind: "exact",
407
407
  path: `/${STYLESHEET_PATH}`,
408
- handler: (req, res) => {
408
+ handler: async (req, res) => {
409
409
  const rejection = ctx.connection.requestRejection(req);
410
410
  if (rejection) {
411
411
  res.writeHead(rejection);
@@ -419,6 +419,7 @@ async function apply(ctx) {
419
419
  }
420
420
  res.setHeader("Cache-Control", "private, no-cache");
421
421
  res.setHeader("X-Content-Type-Options", "nosniff");
422
+ if (failed && pending) await pending.catch(() => {});
422
423
  if (failed) {
423
424
  res.writeHead(503, { "Retry-After": "2" });
424
425
  res.end();
@@ -439,7 +440,7 @@ async function apply(ctx) {
439
440
  rows.push({
440
441
  kind: "html",
441
442
  placement: "head",
442
- html: `<style data-plugin="${PLUGIN_ID}" data-plugin-css="utilities">${css.replace(/<\/style/gi, "<\\/style")}</style>`
443
+ html: css ? `<style data-plugin="${PLUGIN_ID}" data-plugin-css="utilities">${css.replace(/<\/style/gi, "<\\/style")}</style>` : `<link rel="stylesheet" data-plugin="${PLUGIN_ID}" data-plugin-css="utilities" href="${STYLESHEET_PATH}">`
443
444
  });
444
445
  });
445
446
  ctx.effect(() => {
package/icon.svg CHANGED
@@ -1 +1,3 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><rect width="64" height="64" rx="14" fill="#102a43"/><g fill="none" stroke="#67e8f9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"><path d="M14 18h12l9 14h15M14 46h12l9-14M14 32h36"/><path d="m44 26 6 6-6 6"/></g></svg>
1
+ <svg width="36" height="36" viewBox="0 0 36 36" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path transform="translate(9.36 9.3) scale(0.72)" fill="#06B6D4" d="M12.001 4.8c-3.2 0-5.2 1.6-6 4.8 1.2-1.6 2.6-2.2 4.2-1.8.913.228 1.565.89 2.288 1.624C13.666 10.618 15.027 12 18.001 12c3.2 0 5.2-1.6 6-4.8-1.2 1.6-2.6 2.2-4.2 1.8-.913-.228-1.565-.89-2.288-1.624C16.337 6.182 14.976 4.8 12.001 4.8ZM6.001 12c-3.2 0-5.2 1.6-6 4.8 1.2-1.6 2.6-2.2 4.2-1.8.913.228 1.565.89 2.288 1.624 1.177 1.194 2.538 2.576 5.512 2.576 3.2 0 5.2-1.6 6-4.8-1.2 1.6-2.6 2.2-4.2 1.8-.913-.228-1.565-.89-2.288-1.624C10.337 13.382 8.976 12 6.001 12Z"/>
3
+ </svg>
package/locale/en.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "Shared Tailwind CSS",
4
- "description": "Combines client bundle utility classes into one stylesheet, with per-plugin static Tailwind configuration."
3
+ "title": "Tailwind Styles",
4
+ "description": "Client-plugin tool with per-plugin static Tailwind configuration that builds one deduplicated stylesheet; disabling it breaks plugin styles."
5
5
  }
6
6
  }
package/locale/zh.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "meta": {
3
- "title": "共享 Tailwind 样式",
4
- "description": "聚合客户端插件的工具类,生成一份去重样式,支持各插件独立的静态 Tailwind 配置。"
3
+ "title": "Tailwind 样式",
4
+ "description": "客户端插件工具,支持各插件独立的静态 Tailwind 配置,生成去重样式,关闭将导致样式异常。"
5
5
  }
6
6
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-taiwind",
3
3
  "type": "module",
4
- "version": "0.3.1",
4
+ "version": "0.3.3",
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",