menu
HomePostsGalleryToolsAbout
search
...

Color Management with Single Source of Truth

11/13/2025
eye-

Background and Problem

In wechat mini app, color definitions are typically needed in multiple places:

  • CSS 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:

  1. ❌ Duplicate definitions: The same color value needs to be maintained in multiple places
  2. ❌ Easy to be inconsistent: May miss some places when modifying
  3. ❌ High maintenance cost: Adding or modifying colors requires synchronizing multiple files
  4. ❌ 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 require cache 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:

  1. beforeRun: Generate color files before compilation starts, ensuring files exist
  2. afterCompile: Add colors.js to file dependencies so Webpack watches for changes
  3. invalid: When colors.js changes, 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

AdvantageDescription
✅ Single SourceAll colors defined in one place, avoiding duplicates
✅ Auto SyncAutomatically generate all formats after modifying colors.js
✅ Type SafeReduces errors from manual synchronization
✅ Easy MaintenanceAdding colors only requires adding in one place
✅ Comment SupportJS files support comments to explain color usage
✅ Build-time GenerationNo runtime overhead
✅ Hot Reload SupportAuto-regenerate when modifying colors.js during development

Notes

  1. ⚠️ Do not manually edit generated files (_colors-generated.scss and colors-generated.js)
  2. ✅ All color modifications should be done in colors.js
  3. ✅ Generated files are added to .gitignore and won't be committed to the repository
  4. ✅ 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
Polish & ShipFile Upload Component
Catalog
  • Background and Problem
  • Solution
  • Technical Implementation
  • 1. Single Source of Truth: `colors.js`
  • 2. Code Generator: `generateColors.js`
  • 3. Webpack Plugin: `ColorGeneratorPlugin.js`
  • 4. Integration into Project
  • Usage
  • Using in SCSS
  • Using in JavaScript/Templates
  • Advantages Summary
  • Notes
  • Manual Generation
  • Project Structure
  • Summary