已封装的工具
- 主要的实现就是修改了 less 和 sass 的 render 的编译逻辑,以达到所有的less、scss文件(包括支持css modules的)经过相同的编译逻辑,请使用@zougt/some-loader-utils的
getLess
和getSass
方法,替代当前构建环境中的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
}),
},
},
],
},
],
},
};