问题
在前台前端项目中实现了换肤功能,根据不同的环境显示不同的皮肤颜色。例如,在开思电商环境下,元素以红色背景呈现;而在博世环境下,元素以蓝色背景呈现。然而,当访问博世环境时,发现有一些元素没有正确显示,如下图所示:
分析
换肤的实现原理是通过sass-loader
中的prependData
属性,在构建过程中将自定义的样式变量添加到每个scss
样式文件的头部。这样做可以覆盖默认设置的样式变量,从而实现换肤的效果。
以下是根据实际项目代码简化的示例代码:
// config-overrides.js
function rewireSassLoader(config, tenantId) {
// 查找对应的rule
const rules = findRules(config, 'sass-loader');
rules.forEach(rule => {
rule.options.prependData = function(loaderContext) {
const tenantId = process.env.tenantId || TenantType.CASS;
const isBosch = tenantId === TenantType.BOSCH;
if (isBosch) {
const scssPath = path.resolve(__dirname, `src/styles/theme/${tenantId.toLowerCase()}.scss`);
const variables = require('fs').readFileSync(scssPath, 'utf-8');
return variables;
}
return '';
}
});
}
module.exports = function override(config, env) {
// 根据环境注入样式变量
rewireSassLoader(config);
return config;
};
源代码中追加样式变量的方式
// node_modules/sass-loader/dist/getSassOptions.js
options.data = loaderOptions.prependData ? typeof loaderOptions.prependData === 'function' ? loaderOptions.prependData(loaderContext) + _os.default.EOL + content : loaderOptions.prependData + _os.default.EOL + content : content; // opt.outputStyle
配置的脚本命令
// --run--
"scripts": {
"start": "cross-env PUBLIC_PATH=./ react-app-rewired start NODE_ENV=development",
"start-bosch": "cross-env tenantId=BOSCH PUBLIC_PATH=./ react-app-rewired start NODE_ENV=development",
}
在执行yarn start-bosch
命令时没有达到换肤效果,其中没有显示正确的元素基本都是@casstime/bricks
组件库中的元素,所以我们来分析下追加的样式变量为何在@casstime/bricks
组件没有生效。
拿到问题时,我首先怀疑的是追加样式变量在文件头部,会不会被已存在的样式变量覆盖掉,例如:
$color: #f2f2f2; // 追加的样式变量
$color: #2b2b2b; // 已存在的样式变量
.text {
color: $color;
}
// 编译后
.text {
color: #2b2b2b;
}
在实际项目中使用@casstime/bricks
基础组件库时,并不会单独引入每一个组件的样式文件,而是在入口文件处全局引入组件库样式
// app.tsx
import React from 'react';
import { createRoot } from 'react-dom/client';
import { renderRoutes, RouteConfig } from 'react-router-config';
import { HashRouter } from 'react-router-dom';
import routes from './config/routes';
import '@casstime/bricks/dist/bricks.production.css';
const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(<HashRouter>{renderRoutes(routes as RouteConfig[])}</HashRouter>);
项目中全局引入的组件样式文件是普通的CSS
文件而不是SCSS
文件,那么这些CSS
文件不会经过Sass Loader
加载器的处理,也就不会往其中追加样式变量,使用的还是默认样式,所以@casstime/bricks
组件元素样式显示不正确。
在入口文件处调整为引入组件库的SCSS
样式文件
import '@casstime/bricks/lib/styles/bricks.scss';
再次执行yarn start-bosch
命令,可以看到元素都正确显示了
为什么调整为全局引入组件库的SCSS
样式文件后不会发生追加样式变量被已存在的样式变量覆盖呢?
仔细翻阅组件库样式文件书写,其结构与书写方式如下(简化):
// ├── styles
// ├── _button.scss
// ├── _variables.scss
// ├── _colors.scss
// ├── ...
// └── bricks.scss
// _colors.scss
$primary-color: #da2227 !default; // 主色、突出色
// _variables.scss
@import 'colors';
$border-width: 1px !default; // 默认边框宽度
// _button.scss
.br-button-primary {
backgroud-color: $primary-color;
}
// bricks.scss
@import 'variables';
@import 'button';
假如现在有根据环境变量往bricks.scss
中追加样式变量$primary-color: #237fd6;
,经过sass-loader
处理后的结果如下:
// bricks.scss
$primary-color: #237fd6; // 追加的样式变量
$primary-color: #da2227 !default; // 已存在的样式变量
$border-width: 1px !default; // 默认边框宽度
.br-button-primary {
backgroud-color: $primary-color;
}
处理的结果中其中有一个$primary-color
是带有!default
标记的变量,如果在之前的代码中没有定义或赋值这个变量,它将被默认设置为#da2227
。但是,之前代码已经定义了$primary-color: #237fd6;
,那么默认值将会被忽略。所以,最后会采用追加的样式变量。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。