{"_id":"@10coding/plugin-jsx-scoped","_rev":"2-7b9fa137299c7c0196455e07e81b7d8a","name":"@10coding/plugin-jsx-scoped","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@10coding/plugin-jsx-scoped","version":"0.1.0","license":"MIT","_id":"@10coding/plugin-jsx-scoped@0.1.0","maintainers":[{"name":"scliuyilin","email":"scliuyilin@163.com"}],"dist":{"shasum":"8fd789d16102c07d0bf7e363a8f6e1c0e145cc52","tarball":"https://registry.npmjs.org/@10coding/plugin-jsx-scoped/-/plugin-jsx-scoped-0.1.0.tgz","fileCount":8,"integrity":"sha512-QWZCw2p6LjGTvJM+aDowzjWDFeor3j2iTxAvK0w7vxhuOK8XMmvLigL19NTHd/PII3V5dToCAlHMck+NSMKgwQ==","signatures":[{"sig":"MEUCIQCFNthfgfp+VH8en02V6aQrfWJ7B8A5PKOrQ4hsNz5rcgIgD7GHFAN99XUv/1mZu9XlfIrYY+B7gproESBqjdQ7jlE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":74759},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"scripts":{"test":"pnpm build && node scripts/verify.mjs","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"scliuyilin","email":"scliuyilin@163.com"},"description":"Babel plugin for Vue-like scoped styles in JSX/TSX: computes a data-v-{hash} from the component file path and injects the attribute into JSX elements","directories":{},"sideEffects":false,"_nodeVersion":"24.14.1","dependencies":{"@babel/parser":"^7.26.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@babel/core":"^7.26.0","@types/node":"^22.10.0"},"peerDependencies":{"@babel/core":">=7.13.0"},"peerDependenciesMeta":{"@babel/core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/plugin-jsx-scoped_0.1.0_1788520481188_0.2746031034797489","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@10coding/plugin-jsx-scoped","version":"0.2.0","description":"Babel plugin for Vue-like scoped styles in JSX/TSX: computes a data-v-{hash} from the component file path and injects the attribute into JSX elements","license":"MIT","type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"sideEffects":false,"engines":{"node":">=18"},"publishConfig":{"access":"public"},"dependencies":{"@babel/parser":"^7.26.0"},"peerDependencies":{"@babel/core":">=7.13.0"},"peerDependenciesMeta":{"@babel/core":{"optional":true}},"devDependencies":{"@babel/core":"^7.26.0","@types/node":"^22.10.0","tsup":"^8.3.5","typescript":"^5.7.2"},"scripts":{"build":"tsup","typecheck":"tsc --noEmit","test":"pnpm build && node scripts/verify.mjs"},"_nodeVersion":"24.14.1","_id":"@10coding/plugin-jsx-scoped@0.2.0","dist":{"integrity":"sha512-hUXJ9Hu5eZ3GRO2XDkcr7CRu6XP02hv/+wH9XYZZ7nvXek3s0lmzpbssvgjfbPd5s4hNyECQuoysKjzAPi59Vw==","shasum":"19f4e7e88d204f2a8544c69d7056ab8d4b061183","tarball":"https://registry.npmjs.org/@10coding/plugin-jsx-scoped/-/plugin-jsx-scoped-0.2.0.tgz","fileCount":8,"unpackedSize":83624,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIF12JDB+U+LXe51lcGHt/wUHnYKO6tu+IX2Lny5F6dcrAiBgJ6taH2oC2JW9rtcrRIWs7WRyDiTwUutb2qPST5tWPg=="}]},"_npmUser":{"name":"scliuyilin","email":"scliuyilin@163.com"},"directories":{},"maintainers":[{"name":"scliuyilin","email":"scliuyilin@163.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/plugin-jsx-scoped_0.2.0_1788584061657_0.18767024174958635"},"_hasShrinkwrap":false}},"time":{"created":"2026-09-04T11:14:41.024Z","modified":"2026-09-05T04:54:22.029Z","0.1.0":"2026-09-04T11:14:41.317Z","0.2.0":"2026-09-05T04:54:21.785Z"},"license":"MIT","description":"Babel plugin for Vue-like scoped styles in JSX/TSX: computes a data-v-{hash} from the component file path and injects the attribute into JSX elements","maintainers":[{"name":"scliuyilin","email":"scliuyilin@163.com"}],"readme":"# @10coding/plugin-jsx-scoped\n\n面向 JSX / TSX（React 等框架）的 **Vue-like scoped 样式**工具链中的 **Babel 插件**。\n\n它负责「注入 scope 属性」这一环：以组件文件**绝对路径**为种子生成 hash，\n给 JSX 元素注入 `data-v-{hash}` 属性，为后续 CSS 选择器追加\n`[data-v-{hash}]`（由 postcss / vite 插件完成）提供匹配基础。\n\n> 原理复刻 Vue SFC scoped：Vue 的 hash 取自 SFC 文件路径，这里同样取自\n> `.tsx/.jsx` 组件文件路径，而不是样式文件路径。\n\n## 特性\n\n- 纯 Babel 插件，无运行时依赖（`@babel/core` 为 peer，可选）\n- 种子：组件文件绝对路径 → `md5` → 截取前 8 位 → `data-v-3f2a9c1d`\n- DOM 元素（div/span/…）注入完整属性：`<div data-v-3f2a9c1d=\"\">`\n  - 跳过 `<style>`（含 `<style scoped>`，留给上层流水线处理）\n  - 跳过 `<Fragment>` 与文本节点（本来也不是元素）\n- **组件 scoped（默认自动开启）**：自定义组件标签注入\n  `<Child scopedId=\"data-v-3f2a9c1d\">`，子组件需要时自行读取 scopedId\n  并绑定到根元素（child-root 继承父级 scope id）；`componentScoped: false` 关闭\n- **变量当标签（direct-scoped marker）**：运行时会渲染成原生 DOM 标签的大写组件\n  （如 `const Comp: any = tag || (href ? 'a' : 'button')`），加 `<Comp direct-scoped />`\n  即按普通 DOM 元素注入 `data-v-{hash}=\"\"`（不再走 scopedId）；marker 为编译期\n  指令，会从产物移除，可用 `directScopedAttributeName` 自定义\n- 元素已存在同名属性时自动覆盖，不会重复添加\n- 附带独立的 hash 工具函数，供 vite/postcss 包复用\n\n## 安装\n\n```bash\npnpm add -D @babel/core @10coding/plugin-jsx-scoped\n```\n\n## 用法\n\n### 方式一：babel.config.js\n\n```js\n// babel.config.cjs\nmodule.exports = {\n  plugins: [\n    // 需要拿到每个文件的绝对路径：babel 提供 filename，可再用插件填充\n    ['@10coding/plugin-jsx-scoped', { componentFilePath: /* __filename */ }],\n  ],\n}\n```\n\n### 方式二：@babel/core 编程式（推荐，vite 插件内部即如此调用）\n\n```ts\nimport { transformSync } from '@babel/core'\nimport jsxScoped from '@10coding/plugin-jsx-scoped'\n\nconst filename = 'E:/proj/src/demo.tsx' // 组件文件绝对路径\nconst result = transformSync(code, {\n  filename,\n  parserOpts: { plugins: ['typescript', 'jsx'] },\n  plugins: [[jsxScoped, { componentFilePath: filename }]],\n})\n```\n\n### 转换效果\n\n输入 `demo.tsx`：\n\n```tsx\nimport Child from './Child'\n\nexport function Demo() {\n  return (\n    <section className=\"demo\">\n      <h2 className=\"title\">Hello</h2>\n      <Child />\n      <style scoped>{'h2 { color: red; }'}</style>\n    </section>\n  )\n}\n```\n\n输出（scope 属性名由 `md5('E:/proj/src/demo.tsx')` 前 8 位决定）：\n\n```tsx\nimport Child from './Child'\n\nexport function Demo() {\n  return (\n    <section className=\"demo\" data-v-aa80bcf8=\"\">\n      <h2 className=\"title\" data-v-aa80bcf8=\"\">Hello</h2>\n      <Child scopedId=\"data-v-aa80bcf8\" />\n      <style scoped>{'h2 { color: red; }'}</style>\n    </section>\n  )\n}\n```\n\n### 组件 scoped（child-root 继承）\n\n自定义组件不会被直接注入 `data-v-*`（那只是普通 props），而是注入\n`scopedId=\"data-v-{hash}\"`。子组件**如果需要**继承父级 scope（让父组件 scoped\n样式能命中子组件根元素），自行读取并把该属性绑到根元素上：\n\n```tsx\n// Child.tsx\nexport default function Child({ scopedId }: { scopedId?: string }) {\n  return (\n    <div className=\"child-root\" {...(scopedId ? { [scopedId]: '' } : {})}>\n      {/* 根元素因此带上父级的 data-v-{hash} */}\n    </div>\n  )\n}\n```\n\n```scss\n/* demo.scoped.scss —— 选择器被追加 [data-v-aa80bcf8]，可命中上面的 child-root */\n.child-root { border-left: 4px solid #4f46e5; }\n```\n\n### 变量当标签：`<Comp direct-scoped />`\n\n当大写组件在**运行时其实是原生 DOM 标签**时（比如变量决定标签名），scopedId\n语义不适用——该标签不会经组件转发属性到 DOM。这种情况加一个 marker，让插件\n把它当普通 DOM 元素处理：直接注入 `data-v-{hash}=\"\"`，不再注入 scopedId。\n\n```tsx\n// 变量可能是真实组件，也可能是 'a'/'button' 这类原生标签字符串\nconst Comp: any = tag || (href ? 'a' : 'button')\n\nexport default function Demo() {\n  return (\n    // direct-scoped：告诉插件「这是个原生标签」，注入 data-v-xxx=\"\"；\n    // marker 是编译期指令，会从产物里移除（不会作为 prop 传给运行时）\n    <Comp direct-scoped className=\"vp-button\">\n      按钮\n    </Comp>\n  )\n}\n```\n\n- 大写组件、成员表达式组件（`<UI.Button direct-scoped />`）均可；\n- 原生标签上写 marker 无意义（静默忽略并移除）；\n- 属性名可通过 `directScopedAttributeName` 自定义；\n- 强类型组件会对未知 marker 属性报 TS 错——该写法面向 `any`/宽松 props 的\n  变量标签场景，必要时用 `as any`。\n\n## 配置项\n\n```ts\ninterface JsxScopedBabelOptions {\n  /** 组件文件绝对路径（.tsx/.jsx），默认用它计算 data-v-{hash} */\n  componentFilePath?: string\n  /** 完整 scope 属性名，如 'data-v-3f2a9c1d'（优先级最高） */\n  scopeAttr?: string\n  /** 仅 hash 部分，如 '3f2a9c1d'，自动拼 data-v- 前缀 */\n  scopeHash?: string\n  /** hash 位数，默认 8 */\n  hashLength?: number\n  /**\n   * 组件 scoped：默认 true。\n   * 给自定义组件标签注入 <Child scopedId=\"data-v-{hash}\">；\n   * 设为 false 则自定义组件标签不被注入任何属性。\n   */\n  componentScoped?: boolean\n  /** 注入到自定义组件标签上的属性名，默认 'scopedId' */\n  scopedIdAttributeName?: string\n  /** 「变量当标签」marker 属性名，默认 'direct-scoped' */\n  directScopedAttributeName?: string\n}\n```\n\n## 工具函数\n\n```ts\nimport {\n  generateScopeHash, // (filePath, len=8) => 'aa80bcf8'\n  createScopeAttr,   // (hash) => 'data-v-aa80bcf8'\n  computeScopeAttr,  // (filePath, len=8) => 'data-v-aa80bcf8'\n  normalizeComponentPath, // Windows 反斜杠归一化为 '/'\n} from '@10coding/plugin-jsx-scoped'\n```\n\n## 开发\n\n```bash\npnpm install\npnpm --filter @10coding/plugin-jsx-scoped build   # tsup 构建 esm/cjs/dts\npnpm --filter @10coding/plugin-jsx-scoped test    # 冒烟验证（@babel/core 实际转换）\n```\n\n## 说明\n\n- 本插件只负责 JSX AST 侧注入；「样式文件选择器追加 `[data-v-{hash}]`」\n  由仓库内的 `@10coding/postcss-jsx-scoped`、编排由\n  `@10coding/vite-plugin-jsx-scoped` 完成（见仓库根目录）。\n- License: MIT\n","readmeFilename":""}