工程化思维,主题切换架构

86 阅读2分钟

参考链接:cloud.tencent.com/developer/a…

思路:利用postcss的相关插件能力来实现

用vue-cli创建的vue2项目

vue-cli的版本:@vue/cli 5.0.8 根据文章中描述,分别创建了index.css测试文件、vue.config.js(项目创建时,自动创建)、postcss插件,几个文件的具体内容如下:

/* index.css文件 */
* {
  color: rgb(197, 153, 153);
}
div {
  color: cc(G01);
  display: flex;
}
.hello {
  border: 1px solid cc(G01);
}
// postcss-theme.js
const defaults = {
  function: 'cc',
  groups: {},
  colors: {},
  useCustomProperties: false,
  darkThemeSelector: 'html[data-theme="dark"]',
  nestingPlugin: null,
}
const resolveColor = (options, theme, group, defaultValue) => {
  const [lightColor, darkColor] = options.groups[group] || []
  const color = theme === 'dark' ? darkColor : lightColor
  if (!color) {
    return defaultValue
  }
  if (options.useCustomProperties) {
    return color.startsWith('--') ? 'var(${color})' : 'var(--${color})'
  }
  return options.colors[color] || defaultValue
}

/**
 * @type {import('postcss').PluginCreator}
 */
module.exports = (options = {}) => {
  // Work with options here
  options = Object.assign({}, defaults, options)
  // 获取色值函数(默认为cc)
  const reGroup = new RegExp(`\\b${options.function}\\(([^)]+)\\)`, 'g')

  // 获取最终的CSS值
  const getValue = (value, theme) => {
    return value.replace(reGroup, (match, group) => {
      return resolveColor(options, theme, group, match)
    })
  }

  return {
    postcssPlugin: 'postcss-theme-colors',
    prepare(result){
      const hasPlugin = name => name.replace(/^postcss-/, '') === options.nestingPlugin || result.processor.plugins.some(p => p.postcssPlugin === name)

      return {
        Declaration (decl, postcss) {
          // The faster way to find Declaration node
          const value = decl.value
          // 如果不含有色值函数调用,则提前退出
          if (!value || !reGroup.test(value)) {
            return
          }
          const lightValue = getValue(value, 'light')
          const darkValue = getValue(value, 'dark')
          const darkDecl = decl.clone({ value: darkValue })
          let darkRule
          // 使用插件,生成Dark主题模式
          if (hasPlugin('postcss-nesting')) {
            darkRule = postcss.atRule({
              name: 'nest',
              params: `${options.darkThemeSelector} &`,
            })
          } else if (hasPlugin('postcss-nested')) {
            darkRule = postcss.rule({
              selector: `${options.darkThemeSelector} &`,
            })
          } else {
            decl.warn(result, 'Plugin(postcss-nesting or postcss-nested) not found')
          }
          console.log('darkDecl:', darkDecl)
          // 添加Dark主题模式到目标HTML根节点中
          if (darkRule) {
            darkRule.append(darkDecl)
            decl.after(darkRule)
          }
          const lightDecl = decl.clone({ value: lightValue })
          decl.replaceWith(lightDecl)
        }
      }
    }
    /*
    Root (root, postcss) {
      // Transform CSS AST here
    }
    */



    /*
    Declaration: {
      color: (decl, postcss) {
        // The fastest way find Declaration node if you know property name
      }
    }
    */
  }
}

module.exports.postcss = true

这里主要的实现方式与文章中的实现方式一致,只是代码结构会有点差别。用文章中那种方式的时候,控制台提示此种方式已经被废弃了,然后再查看了一下postcss的官网后,使用的插件模板也是用的本文中的方式。postcss官网:postcss.org/docs/writin…

// vue.config.js
const { defineConfig } = require('@vue/cli-service')
const postcssTheme = require('./plugins/postcss-theme')

module.exports = defineConfig({
  transpileDependencies: true,
  css: {
    loaderOptions: {
      postcss: {
        postcssOptions: {
          plugins: [
            {
              // 用这个插件,主要是因为此插件内置了`autoprefixer、postcss-nesting/postcss-nested、postcss-importautoprefixer、postcss-nesting/postcss-nested、postcss-import`,
              // 我们在自定义插件内部使用了hasPlugin('postcss-nesting')和hasPlugin('postcss-nested'),实际使用中可能需要微调
              "postcss-preset-env": {
                  features: {
                      "custom-properties": {
                          preserve: false,
                          variables: {}
                      },
                      "nesting-rules": true
                  }
              },
            },
            postcssTheme({
              colors: {
                c01: '#333',
                c02: '#111'
              },
              groups: {
                G01: ['c01', 'c02']
              }
            })
          ]
        }
      }
    }
  }
})

在验证插件在vue项目中使用时,出现报错,# object Object is not a PostCSS plugin,出错的原因猜测是postcss-preset-env版本与postcss版本不匹配,但是重新更新这两个包的版本后,还是报错。后来用脚手架重新建了一个vue2的项目后,不会再出问题

自建的项目,打包工具为webpack

这里的区别与上一节的区别是使用postcss.config.js来代替vue.config.js,具体代码为:

//postcss.config.js

const postcssTheme = require('./plugins/postcss-theme')
module.exports = {
  plugins: [
    {
      "postcss-preset-env": {
          features: {
              "custom-properties": {
                  preserve: false,
                  variables: {}
              },
              "nesting-rules": true
          }
      },
    },
    postcssTheme({
      colors: {
        c01: '#222',
        c02: '#111'
      },
      groups: {
        G01: ['c01', 'c02']
      }
    })
  ]
}