基于less、sass的在webpack或vite等的预设多主题的编译方案

22 篇文章 0 订阅

https://zhuanlan.zhihu.com/p/375347921

 切换效果预览

已封装的工具

  • 主要的实现就是修改了 less 和 sass 的 render 的编译逻辑,以达到所有的less、scss文件(包括支持css modules的)经过相同的编译逻辑,请使用@zougt/some-loader-utilsgetLessgetSass方法,替代当前构建环境中的less和sass编译器,目前在 webpack 和 vite 中使用测试过。
  • 如需对编译后的主题 css 抽取成独立的文件请看 webpack 插件@zougt/theme-css-extract-webpack-plugin
  • 如需 vite 版本的只需要 vite 插件@zougt/vite-plugin-theme-preprocessor即可。

多主题编译示例(以 sass+webpack 为例)

在webpack中,简单的配置sass-loader的属性implementation ,也可以直接查看 @zougt/some-loader-utils

webpack.config.js

const path = require("path");
// const sass = require("sass");
const { getSass } = require("@zougt/some-loader-utils");
const multipleScopeVars = [
  {
    scopeName: "theme-default",
    path: path.resolve("src/theme/default-vars.scss"),
  },
  {
    scopeName: "theme-mauve",
    path: path.resolve("src/theme/mauve-vars.scss"),
  },
];
module.exports = {
  module: {
    rules: [
      {
        test: /\.scss$/i,
        loader: "sass-loader",
        options: {
          sassOptions: {
            // 不使用 getMultipleScopeVars 时,也可从这里传入 multipleScopeVars
            // multipleScopeVars
          },
          implementation: getSass({
            // getMultipleScopeVars优先于 sassOptions.multipleScopeVars
            getMultipleScopeVars: (sassOptions) => multipleScopeVars,
            // 可选项
            // implementation:less
          }),
        },
      },
    ],
  },
};

对应的两种预设主题的 scss 变量文件

/src/theme/default-vars.scss

/**
*此scss变量文件作为multipleScopeVars去编译时,会自动移除!default以达到变量提升
*同时此scss变量文件作为默认主题变量文件,被其他.scss通过 @import 时,必需 !default
*/
$primary-color: #0081ff !default;

/src/theme/mauve-vars.scss

$primary-color: #9c26b0 !default;

一个组件的 scss

//src/components/Button/style.scss
@import "../../theme/default-vars";
.un-btn {
  position: relative;
  display: inline-block;
  font-weight: 400;
  white-space: nowrap;
  text-align: center;
  border: 1px solid transparent;
  background-color: $primary-color;
  .anticon {
    line-height: 1;
  }
}

编译之后

src/components/Button/style.css

.un-btn {
  position: relative;
  display: inline-block;
  font-weight: 400;
  white-space: nowrap;
  text-align: center;
  border: 1px solid transparent;
}
.theme-default .un-btn {
  background-color: #0081ff;
}
.theme-mauve .un-btn {
  background-color: #9c26b0;
}
.un-btn .anticon {
  line-height: 1;
}

html中改变 classname 切换主题,只作用于 html 标签 :

<!DOCTYPE html>
<html lang="zh" class="theme-default">
  <head>
    <meta charset="utf-8" />
    <title>title</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>
document.documentElement.className = "theme-mauve";

如果不想 html 的权重 classname,可以使用@zougt/theme-css-extract-webpack-plugin分离出独立的主题 css 文件,在线切换主题 css 文件即可:

const toggleTheme = (scopeName = "theme-default") => {
  let styleLink = document.getElementById("theme-link-tag");
  if (styleLink) {
    // 假如存在id为theme-link-tag 的link标签,直接修改其href
    styleLink.href = `/${scopeName}.css`;
    // document.documentElement.className = scopeName;
  } else {
    // 不存在的话,则新建一个
    styleLink = document.createElement("link");
    styleLink.type = "text/css";
    styleLink.rel = "stylesheet";
    styleLink.id = "theme-link-tag";
    styleLink.href = `/${scopeName}.css`;
    // document.documentElement.className = scopeName;
    document.head.append(styleLink);
  }
};

如果是webpack,在 webpack.config.js 需要对css-loader (v4.0+) 的 modules 属性添加 getLocalIdent:

const path = require("path");
// const sass = require("sass");
const { getSass } = require("@zougt/some-loader-utils");
const { interpolateName } = require("loader-utils");
function normalizePath(file) {
  return path.sep === "\\" ? file.replace(/\\/g, "/") : file;
}
const multipleScopeVars = [
  {
    scopeName: "theme-default",
    path: path.resolve("src/theme/default-vars.scss"),
  },
  {
    scopeName: "theme-mauve",
    path: path.resolve("src/theme/mauve-vars.scss"),
  },
];
module.exports = {
  module: {
    rules: [
      {
        test: /\.module.scss$/i,
        use: [
          {
            loader: "css-loader",
            options: {
              importLoaders: 1,
              modules: {
                localIdentName:
                  process.env.NODE_ENV === "production"
                    ? "[hash:base64:5]"
                    : "[path][name]_[local]-[hash:base64:5]",
                //使用 getLocalIdent 自定义模块化名称 , css-loader v4.0+
                getLocalIdent: (
                  loaderContext,
                  localIdentName,
                  localName,
                  options
                ) => {
                  if (
                    multipleScopeVars.some(
                      (item) => item.scopeName === localName
                    )
                  ) {
                    //localName 属于 multipleScopeVars 的不用模块化
                    return localName;
                  }
                  const { context, hashPrefix } = options;
                  const { resourcePath } = loaderContext;
                  const request = normalizePath(
                    path.relative(context, resourcePath)
                  );
                  // eslint-disable-next-line no-param-reassign
                  options.content = `${hashPrefix + request}\x00${localName}`;
                  const inname = interpolateName(
                    loaderContext,
                    localIdentName,
                    options
                  );

                  return inname.replace(/\\?\[local\\?]/gi, localName);
                },
              },
            },
          },
          {
            loader: "sass-loader",
            options: {
              implementation: getSass({
                // getMultipleScopeVars优先于 sassOptions.multipleScopeVars
                getMultipleScopeVars: (sassOptions) => multipleScopeVars,
                // 可选项
                // implementation:sass
              }),
            },
          },
        ],
      },
    ],
  },
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值