背景与问题
在微信小程序中,颜色定义通常需要在多个地方使用:
- CSS 文件:用于样式定义
- JavaScript 文件:用于组件属性、动态样式等
传统的做法是在两个地方分别定义:
// _variables.scss
$Primary: #228891;
$Gray100: #f7f7f7;
// colors.js
export const COLORS = {
Primary: "#228891",
Gray100: "#f7f7f7",
}
存在的问题:
- ❌ 重复定义:同一个颜色值需要在多个地方维护
- ❌ 容易不一致:修改时可能遗漏某个地方
- ❌ 维护成本高:新增或修改颜色需要同步多个文件
- ❌ 容易出错:手动同步容易产生错误
解决方案
采用 Single Source of Truth (SSOT) 原则,使用单一数据源 + 构建时代码生成的方式:
单一数据源 (colors.js)
↓
构建时自动生成
↓
┌─────────────────────┬─────────────────────┐
│ _colors-generated.scss │ colors-generated.js │
│ (SCSS 变量) │ (JS 常量) │
└─────────────────────┴─────────────────────┘
技术实现
1. 单一数据源:colors.js
所有颜色定义在一个 JavaScript 文件中,支持注释和分组:
/**
* Color definitions - single source of truth
* This file is used to generate SCSS variables and JS constants via build/generateColors.js
*/
// Base colors
const white = "#fff"
const black = "#000"
// Primary colors
const Primary = "#228891"
const Primary100 = "#eff6fa"
const Primary200 = "#c1dbe7"
// Composite colors (using variables for clarity)
// These are calculated from base colors above, making the relationship clear
const Outline = "rgba(255, 255, 255, 0.08)" // rgba(white, 0.08)
const Overlay1 = "rgba(0, 0, 0, 0.1)" // rgba(black, 0.1)
// Export all colors as a flat object for the generator
module.exports = {
Primary,
black,
white,
Primary100,
Primary200,
Outline,
Overlay1,
// ... more colors
}
优势:
- ✅ 可以添加注释说明颜色的用途和来源
- ✅ 可以使用变量和表达式(通过注释说明关系)
- ✅ 可以分组组织,更易维护
- ✅ 复合颜色可以清晰标注来源(如
rgba(white, 0.08))
2. 代码生成器:generateColors.js
构建时脚本,读取 colors.js 并生成两个文件:
/**
* Generate color files at build time
* Generate SCSS variable file and JS constants file from colors.js
*/
const path = require("path")
const colorsJsPath = path.resolve(__dirname, "../src/style/colors.js")
const scssOutputPath = path.resolve(
__dirname,
"../src/style/_colors-generated.scss"
)
const jsOutputPath = path.resolve(__dirname, "../src/style/colors-generated.js")
function generateColorFiles() {
// Read JS file (use require to execute JS code)
// Clear require cache to ensure we always get the latest content
delete require.cache[require.resolve(colorsJsPath)]
const colors = require(colorsJsPath)
// Generate SCSS variable file
let scssContent =
"// Auto-generated file by build/generateColors.js, do not edit manually\n"
scssContent += "// Color definitions are in src/style/colors.js\n\n"
Object.entries(colors).forEach(([key, value]) => {
scssContent += `$${key}: ${value};\n`
})
// Generate JS constants file
let jsContent = "/**\n"
jsContent +=
" * Auto-generated file by build/generateColors.js, do not edit manually\n"
jsContent += " * Color definitions are in src/style/colors.js\n"
jsContent += " */\n\n"
jsContent += "export const COLORS = {\n"
Object.entries(colors).forEach(([key, value]) => {
jsContent += ` ${key}: "${value}",\n`
})
jsContent += "};\n"
// Write files
const fs = require("fs")
fs.writeFileSync(scssOutputPath, scssContent, "utf8")
fs.writeFileSync(jsOutputPath, jsContent, "utf8")
console.log("✅ Color files generated successfully:")
console.log(` - ${scssOutputPath}`)
console.log(` - ${jsOutputPath}`)
}
// Execute if this script is run directly
if (require.main === module) {
generateColorFiles()
}
module.exports = generateColorFiles
关键点:
- 清除
require缓存,确保每次获取最新内容 - 支持直接运行:
node generateColors.js - 支持模块导入:供 Webpack 插件使用
3. Webpack 插件:ColorGeneratorPlugin.js
集成到构建流程,实现自动化:
/**
* Webpack plugin: Auto-generate SCSS and JS color files from colors.js
*/
const fs = require("fs")
const path = require("path")
const generateColorFiles = require("./generateColors")
class ColorGeneratorPlugin {
apply(compiler) {
// Generate color files before compilation starts
compiler.hooks.beforeRun.tap("ColorGeneratorPlugin", () => {
generateColorFiles()
})
// Watch colors.js file changes
compiler.hooks.afterCompile.tap("ColorGeneratorPlugin", (compilation) => {
const colorsJsPath = path.resolve(__dirname, "../src/style/colors.js")
if (fs.existsSync(colorsJsPath)) {
compilation.fileDependencies.add(colorsJsPath)
}
})
// Regenerate when colors.js changes
compiler.hooks.invalid.tap("ColorGeneratorPlugin", (fileName) => {
if (fileName && fileName.includes("colors.js")) {
generateColorFiles()
}
})
}
}
module.exports = ColorGeneratorPlugin
Webpack Hooks 说明:
beforeRun:编译开始前生成颜色文件,确保文件存在afterCompile:将colors.js添加到文件依赖,让 Webpack 监听变化invalid:当colors.js变化时,立即重新生成颜色文件
工作流程:
用户修改 colors.js
↓
Webpack 检测到变化(通过 afterCompile 添加的依赖)
↓
触发 invalid hook
↓
重新生成颜色文件
↓
Webpack 开始重新编译
4. 集成到项目
在 webpack.config.js 中配置:
const ColorGeneratorPlugin = require("./build/ColorGeneratorPlugin")
module.exports = {
configureWebpack(config) {
const plugins = [
new ColorGeneratorPlugin(), // 添加插件
// ... other plugins
]
return { plugins }
},
}
使用方式
在 SCSS 中使用
// _variables.scss
@import "./_colors-generated";
// 直接使用
.my-class {
color: $Primary;
background: $Gray100;
border: 1px solid $Border;
}
在 JavaScript/模板中使用
// 1. 导入颜色常量
import { COLORS } from "@style/colors-generated"
// 2. 在组件中使用
createPage({
data() {
return {
primaryColor: COLORS.Primary,
}
},
})
<!-- 3. 在模板中使用 -->
<Custom-Com color="{{primaryColor}}" />
优势总结
| 优势 | 说明 |
|---|---|
| ✅ 单一数据源 | 所有颜色在一个地方定义,避免重复 |
| ✅ 自动同步 | 修改 colors.js 后自动生成所有格式 |
| ✅ 类型安全 | 减少手动同步导致的错误 |
| ✅ 易于维护 | 新增颜色只需在一个地方添加 |
| ✅ 支持注释 | JS 文件支持注释,说明颜色用途 |
| ✅ 构建时生成 | 不增加运行时开销 |
| ✅ 热更新支持 | 开发时修改 colors.js 自动重新生成 |
注意事项
- ⚠️ 不要手动修改生成的文件(
_colors-generated.scss和colors-generated.js) - ✅ 所有颜色修改都在
colors.js中进行 - ✅ 生成的文件已加入
.gitignore,不会提交到仓库 - ✅ 修改
colors.js后,Webpack 会自动重新生成文件
手动生成
如果需要手动生成颜色文件,可以运行:
npm run generate:colors
项目结构
project/
├── src/
│ └── style/
│ ├── colors.js # 单一数据源(手动维护)
│ ├── _colors-generated.scss # 自动生成(不要手动修改)
│ ├── colors-generated.js # 自动生成(不要手动修改)
│ └── _variables.scss # 引入生成的文件
├── build/
│ ├── generateColors.js # 代码生成器
│ └── ColorGeneratorPlugin.js # Webpack 插件
└── webpack.config.js # 配置插件
总结
通过单一数据源 + 构建时代码生成的方案,我们实现了:
- 🎯 消除重复:颜色定义只在一个地方
- 🚀 自动化:修改后自动同步到所有格式
- 📝 可维护:支持注释和分组,代码更清晰
- 🔄 实时更新:开发时支持热更新
这是一个典型的 DRY (Don't Repeat Yourself) 原则的实践,通过构建工具自动化解决了多格式同步的问题。
相关文件:
- 数据源:
colors.js - 生成器:
build/generateColors.js - Webpack 插件:
build/ColorGeneratorPlugin.js