/** * 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[Jio] 开始提取 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(`[Jio] 样式路径不存在,已跳过: ${componentPath}`) } else { console.error(`[Jio] 样式提取异常,已跳过: ${componentPath}: ${err && err.message}`) } failCount++ } } const cssOutput = allCss.join('\n\n') if (!cssOutput.trim()) { console.warn('[Jio] 警告:未提取到任何 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(`[Jio] 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('[Jio] 样式提取失败:', err) process.exit(1) })