Files
wordpress-theme-aksrt/assets/admin/extract-naive-styles.mjs
T
kate522andClaude Fable 5 1a88498d57 fix(admin): stop dropping naive-ui styles for core components
提取脚本有两类问题,都只表现为「跳过 N 个」而不报错:

1. render() 漏传 props:CNode 形态的导出走
   defaultExport.render(),cssr 的 render 需要 props 对象,
   否则读 props.bPrefix 直接抛错。card / data-table / dialog /
   checkbox / slider / list / descriptions 的样式因此整体没进包——
   后台正是由卡片和表格搭起来的,等于一直基本没样式。
2. 路径写错:多数组件的样式不是单个 index.cssr.mjs,而是按子组件
   拆分的文件(form / input / radio / typography / layout);
   space 只用内联 style、tooltip 复用 popover 共享样式、
   _styles/transitions 返回的是运行时过渡对象而非 CSS,都不该列在表里。

- 两个导出形态统一传 { clsPrefix: 'n' }
- 逐条修正路径,去掉 space / tooltip / transitions 三类无效条目
- date-picker 的 indexcode 笔误一并修正

提取结果:成功 67 / 跳过 0(此前 42 / 22),CSS 197.0 KB → 293.0 KB,
类名 356 → 583。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-10-26 21:34:50 +08:00

182 lines
6.6 KiB
JavaScript

/**
* extract-naive-styles.mjs
*
* 在构建时直接调用 NaiveUI 组件的 .cssr.mjs 样式函数的 render() 方法,
* 提取所有组件的运行时 JSS 为静态 CSS 文件。
* 彻底解决 WordPress 后台环境中样式丢失问题。
*
* 原理:
* 每个 NaiveUI 组件都有 src/styles/index.cssr.mjs 文件
* 导出的函数返回 css-render 的 CNode 对象
* 调用 CNode.render({ clsPrefix: 'n' }) 即可获取纯 CSS 字符串
* 无需虚拟 DOM、无需 Vue、无任何浏览器 API 依赖
*/
import { writeFile, mkdir } from 'fs/promises'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const BUILD_DIR = join(__dirname, 'build')
// ============================================================
// 需要提取样式的组件列表(对应各组件的 .cssr.mjs 入口)
// 格式:[导入路径在 naive-ui/es 内部的相对路径]
// ============================================================
const COMPONENT_STYLES = [
// 内部基础组件
['_internal/scrollbar/src/styles/index.cssr.mjs'],
// 布局
['layout/src/styles/layout.cssr.mjs'],
// 导航
['menu/src/styles/index.cssr.mjs'],
['breadcrumb/src/styles/index.cssr.mjs'],
['tabs/src/styles/index.cssr.mjs'],
['steps/src/styles/index.cssr.mjs'],
['pagination/src/styles/index.cssr.mjs'],
// 表单
['form/src/styles/form.cssr.mjs'],
['form/src/styles/form-item.cssr.mjs'],
['input/src/styles/input.cssr.mjs'],
['input/src/styles/input-group.cssr.mjs'],
['input-number/src/styles/input-number.cssr.mjs'],
['switch/src/styles/index.cssr.mjs'],
['select/src/styles/index.cssr.mjs'],
['radio/src/styles/radio.cssr.mjs'],
['radio/src/styles/radio-group.cssr.mjs'],
['checkbox/src/styles/index.cssr.mjs'],
['slider/src/styles/index.cssr.mjs'],
['rate/src/styles/index.cssr.mjs'],
['date-picker/src/styles/index.cssr.mjs'],
['time-picker/src/styles/index.cssr.mjs'],
// 展示
['card/src/styles/index.cssr.mjs'],
['tag/src/styles/index.cssr.mjs'],
['alert/src/styles/index.cssr.mjs'],
['data-table/src/styles/index.cssr.mjs'],
['descriptions/src/styles/index.cssr.mjs'],
['empty/src/styles/index.cssr.mjs'],
['result/src/styles/index.cssr.mjs'],
['statistic/src/styles/index.cssr.mjs'],
['list/src/styles/index.cssr.mjs'],
['collapse/src/styles/index.cssr.mjs'],
['timeline/src/styles/index.cssr.mjs'],
// 反馈
['button/src/styles/index.cssr.mjs'],
['modal/src/styles/index.cssr.mjs'],
['dialog/src/styles/index.cssr.mjs'],
['message/src/styles/index.cssr.mjs'],
['notification/src/styles/index.cssr.mjs'],
['popconfirm/src/styles/index.cssr.mjs'],
['popover/src/styles/index.cssr.mjs'],
// tooltip 复用 popover 的共享样式(自身没有独立 cssr 文件)。
['dropdown/src/styles/index.cssr.mjs'],
// 其他
['icon/src/styles/index.cssr.mjs'],
['icon-wrapper/src/styles/index.cssr.mjs'],
// space 全部用内联 style 布局,没有 cssr 文件,故不在此列。
['divider/src/styles/index.cssr.mjs'],
['gradient-text/src/styles/index.cssr.mjs'],
['ellipsis/src/styles/index.cssr.mjs'],
['spin/src/styles/index.cssr.mjs'],
['loading-bar/src/styles/index.cssr.mjs'],
['upload/src/styles/index.cssr.mjs'],
['image/src/styles/index.cssr.mjs'],
['carousel/src/styles/index.cssr.mjs'],
['transfer/src/styles/index.cssr.mjs'],
['tree-select/src/styles/index.cssr.mjs'],
['tree/src/styles/index.cssr.mjs'],
['thing/src/styles/index.cssr.mjs'],
['color-picker/src/styles/index.cssr.mjs'],
['typography/src/styles/p.cssr.mjs'],
['typography/src/styles/a.cssr.mjs'],
['typography/src/styles/blockquote.cssr.mjs'],
['typography/src/styles/list.cssr.mjs'],
['typography/src/styles/hr.cssr.mjs'],
['typography/src/styles/header.cssr.mjs'],
['auto-complete/src/styles/index.cssr.mjs'],
['cascader/src/styles/index.cssr.mjs'],
['dynamic-input/src/styles/index.cssr.mjs'],
['dynamic-tags/src/styles/index.cssr.mjs'],
['input-otp/src/styles/index.cssr.mjs'],
// 全局
['_styles/global/index.cssr.mjs'],
]
async function extractStyles() {
console.log('\n[AKSRT] 开始提取 NaiveUI 组件样式...\n')
const allCss = []
let successCount = 0
let failCount = 0
for (const [componentPath] of COMPONENT_STYLES) {
try {
// 动态导入每个组件的 cssr 样式模块
const styleModule = await import(
`naive-ui/es/${componentPath}`
)
const defaultExport = styleModule.default || styleModule
// cssr 模块有两种导出形态:函数本体,或带 render() 的 CNode 对象。
// 两者都必须接收 props 对象(至少 {}),否则 render 内部读
// props.bPrefix 会抛错——card / data-table / dialog 等组件的样式
// 曾因漏传参数被整体跳过。
const css = typeof defaultExport === 'function'
? defaultExport({ clsPrefix: 'n' })
: defaultExport.render({ clsPrefix: 'n' })
if (typeof css === 'string' && css.trim()) {
allCss.push(`/* ${componentPath} */\n${css}`)
successCount++
}
} catch (err) {
const code = err && err.code
// 路径写错时 import 会直接抛 MODULE_NOT_FOUND。这类错误必须显式报出来:
// 静默跳过的后果是组件样式整体缺失(取色器曾因此没有尺寸和边框,糊成一团),
// 而页面不报错,只能靠肉眼发现。
if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {
console.error(`[AKSRT] 样式路径不存在,已跳过: ${componentPath}`)
} else {
console.error(`[AKSRT] 样式提取异常,已跳过: ${componentPath}: ${err && err.message}`)
}
failCount++
}
}
const cssOutput = allCss.join('\n\n')
if (!cssOutput.trim()) {
console.warn('[AKSRT] 警告:未提取到任何 NaiveUI 样式!')
return null
}
// 确保 build 目录存在
await mkdir(BUILD_DIR, { recursive: true })
// 写入 CSS 文件
const cssFile = join(BUILD_DIR, 'naive-ui.css')
const fullContent = `/* Auto-extracted by extract-naive-styles.mjs */
/* Naive UI v2 Component Styles - Build-time extraction */
/* DO NOT EDIT MANUALLY */
${cssOutput}`
await writeFile(cssFile, fullContent, 'utf-8')
console.log(`[AKSRT] NaiveUI 样式提取完成:`)
console.log(` 成功: ${successCount} 个组件`)
console.log(` 跳过: ${failCount} 个组件`)
console.log(` 输出: ${cssFile}`)
console.log(` 大小: ${(Buffer.byteLength(fullContent) / 1024).toFixed(1)} KB\n`)
return cssFile
}
// 执行
extractStyles().catch((err) => {
console.error('[AKSRT] 样式提取失败:', err)
process.exit(1)
})