1

问题

在前台前端项目中实现了换肤功能,根据不同的环境显示不同的皮肤颜色。例如,在开思电商环境下,元素以红色背景呈现;而在博世环境下,元素以蓝色背景呈现。然而,当访问博世环境时,发现有一些元素没有正确显示,如下图所示:

image.png

分析

换肤的实现原理是通过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命令,可以看到元素都正确显示了

image.png

为什么调整为全局引入组件库的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;,那么默认值将会被忽略。所以,最后会采用追加的样式变量。


记得要微笑
1.9k 声望4.5k 粉丝

知不足而奋进,望远山而前行,卯足劲,不减热爱。


引用和评论

0 条评论