Color Management with Single Source of Truth
Background and Problem
In wechat mini app, color definitions are typically needed in multiple places:
- SCSS files: For style definitions
- JavaScript files: For component properties, dynamic styles, etc.
The traditional approach is to define them separately in two places:
// _variables.scss
$Primary: #228891;
$Gray100: #f7f7f7;
// colors.js
export const COLORS = {
Primary: "#228891",
Gray100: "#f7f7f7",
}
Problems:
- ❌ Duplicate definitions: The same color value needs to be maintained in multiple places
- ❌ Easy to be inconsistent: May miss some places when modifying
- ❌ High maintenance cost: Adding or modifying colors requires synchronizing multiple files
- ❌ Error-prone: Manual synchronization is prone to errors
Solution
Adopt the Single Source of Truth (SSOT) principle, using a single data source + build-time code generation approach:
Single Source of Truth (colors.js)
↓
Build-time Auto-generation
↓
┌─────────────────────┬─────────────────────┐
│ _colors-generated.scss │ colors-generated.js │
│ (SCSS variables) │ (JS constants) │
└─────────────────────┴─────────────────────┘
Technical Implementation
1. Single Source of Truth: colors.js
All color definitions in one JavaScript file, supporting comments and grouping:
/**
* 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
}
Advantages:
- ✅ Can add comments to explain color usage and source
- ✅ Can use variables and expressions (with comments explaining relationships)
- ✅ Can organize by groups for easier maintenance
- ✅ Composite colors can clearly annotate their source (e.g.,
rgba(white, 0.08))
2. Code Generator: generateColors.js
Build-time script that reads colors.js and generates two files:
/**
* 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
Key Points:
- Clear
requirecache to ensure we always get the latest content - Support direct execution:
node generateColors.js - Support module import: for use by Webpack plugins
3. Webpack Plugin: ColorGeneratorPlugin.js
Integrate into the build process for automation:
/**
* 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 Explanation:
beforeRun: Generate color files before compilation starts, ensuring files existafterCompile: Addcolors.jsto file dependencies so Webpack watches for changesinvalid: Whencolors.jschanges, immediately regenerate color files
Workflow:
User modifies colors.js
↓
Webpack detects changes (via dependencies added in afterCompile)
↓
Triggers invalid hook
↓
Regenerate color files
↓
Webpack starts recompilation
4. Integration into Project
Configure in webpack.config.js:
const ColorGeneratorPlugin = require("./build/ColorGeneratorPlugin")
module.exports = {
configureWebpack(config) {
const plugins = [
new ColorGeneratorPlugin(), // Add plugin
// ... other plugins
]
return { plugins }
},
}
Usage
Using in SCSS
// _variables.scss
@import "./_colors-generated";
// Direct usage
.my-class {
color: $Primary;
background: $Gray100;
border: 1px solid $Border;
}
Using in JavaScript/Templates
// 1. Import color constants
import { COLORS } from "@style/colors-generated"
// 2. Use in components
createPage({
data() {
return {
primaryColor: COLORS.Primary,
}
},
})
<!-- 3. Use in templates -->
<Custom-Com color="{{primaryColor}}" />
Advantages Summary
| Advantage | Description |
|---|---|
| ✅ Single Source | All colors defined in one place, avoiding duplicates |
| ✅ Auto Sync | Automatically generate all formats after modifying colors.js |
| ✅ Type Safe | Reduces errors from manual synchronization |
| ✅ Easy Maintenance | Adding colors only requires adding in one place |
| ✅ Comment Support | JS files support comments to explain color usage |
| ✅ Build-time Generation | No runtime overhead |
| ✅ Hot Reload Support | Auto-regenerate when modifying colors.js during development |
Notes
- ⚠️ Do not manually edit generated files (
_colors-generated.scssandcolors-generated.js) - ✅ All color modifications should be done in
colors.js - ✅ Generated files are added to
.gitignoreand won't be committed to the repository - ✅ After modifying
colors.js, Webpack will automatically regenerate files
Manual Generation
If you need to manually generate color files, you can run:
npm run generate:colors
Project Structure
project/
├── src/
│ └── style/
│ ├── colors.js # Single source of truth (manually maintained)
│ ├── _colors-generated.scss # Auto-generated (do not edit manually)
│ ├── colors-generated.js # Auto-generated (do not edit manually)
│ └── _variables.scss # Import generated files
├── build/
│ ├── generateColors.js # Code generator
│ └── ColorGeneratorPlugin.js # Webpack plugin
└── webpack.config.js # Configure plugin
Summary
Through the single source of truth + build-time code generation solution, we achieved:
- 🎯 Eliminate Duplication: Color definitions in one place only
- 🚀 Automation: Auto-sync to all formats after modification
- 📝 Maintainable: Support comments and grouping for clearer code
- 🔄 Real-time Updates: Support hot reload during development
This is a typical practice of the DRY (Don't Repeat Yourself) principle, solving the multi-format synchronization problem through build tool automation.
Related Files:
- Data Source:
colors.js - Generator:
build/generateColors.js - Webpack Plugin:
build/ColorGeneratorPlugin.js