人生当中总是有你能力所不及的范围,但是如果在你能力所及的范畴内,你尽到了自己全部的努力,那你还有什么可以遗憾呢?
问题描述
在某次项目开发中,我发现了一个有趣的问题:当我在本地环境中访问页面时,标签元素的布局看起来很正常;但是当我将项目部署到开发环境后,标签元素的布局有些偏上,不再是之前的正常布局。这个问题引起了我的注意,并且我开始进行排查。
定位分析
为了找出标签布局不一致的原因,我比较了两个环境下作用于标签的样式,以确定它们之间是否有差异:
本地环境 | 开发环境 |
---|---|
对比可以发现,作用于标签的类样式.bre-label-inner_container
和.brand-item_discountTag__SlxDf
在两个环境下加载顺序不一样,而它们都存在一个样式属性vertical-align
,会互相覆盖,所以才导致两个环境下标签布局不一致。
为什么类样式的加载顺序会有所不同呢?
两个环境下代码都是一样的,唯一的区别是构建过程中对样式处理方式不同。
本地环境处理样式
本地环境构建采用Development
模式,采用style-loader
处理样式,当 webpack
处理 CSS
样式文件时,style-loader
会将 CSS
样式文件转换为 JavaScript
模块,并将这些模块嵌入到生成的 JavaScript bundle
文件中。在浏览器加载 JavaScript bundle
文件时,style-loader
会在 HTML
页面中动态创建 <style>
标签,并将 CSS
样式插入到这些标签中,从而使样式生效。
实际项目中组件嵌套层级结构和样式引用如以下示例:
// Main组件
import Label from "@/components/Label";
import "@casstime/bre-label/styles/index.scss"; // 含有.bre-label-inner_container样式
const Main = () => {
return <Label />
}
// Labe组件
import styles from "./index.module.scss"; // 含有.brand-item_discountTag__SlxDf样式
const Label = () => {
return <div className=`bre-label-inner_container ${styles.discountTag}`>...</div>
}
当上面代码构建后,会编译成一个个代码块结构,由webpack
内置函数__webpack_require__
深度调用执行,调用栈过程如下:
不难发现./index.module.scss
样式文件先于@casstime/bre-label/styles/index.scss
样式文件通过style
标签注入到页面中,因此,本地环境会看到.bre-label-inner_container { vertical-align: middle; }
覆盖.brand-item_discountTag__SlxDf { vertical-align: top; }
<html>
<head>
<style>.brand-item_discountTag__SlxDf { vertical-align: top; }</style>
<style>.bre-label-inner_container { vertical-align: middle; }</style>
</head>
</html>
开发环境处理样式
开发环境构建采用Production
模式,采用mini-css-extract-plugin
处理样式,MiniCssExtractPlugin.loader
用于将 CSS
样式从 webpack
打包生成的 JavaScript
文件中提取到单独的文件中,这些文件的名称和路径由 MiniCssExtractPlugin
插件的 filename
和 chunkFilename
配置选项决定。为了确保生成的 CSS
文件能够正确地应用到 HTML
页面中,结合插件html-webpack-plugin
自动生成 HTML
文件,并自动以 link
标签引入生成的 CSS
文件。
MiniCssExtractPlugin
插件本身并没有拆分 CSS
的功能,它只负责将 CSS
样式提取到单独的文件中,并将这些文件与 webpack
打包生成的 JavaScript
文件一起输出到指定的目录中。
如果您希望在使用 MiniCssExtractPlugin
插件时拆分生成的 CSS
文件,可以结合使用 optimization.splitChunks
配置选项来实现。具体来说,您可以将 optimization.splitChunks
配置选项设置为一个对象,并在其中指定要拆分的 chunk
类型、最小体积和最小使用次数等参数。这样,webpack
将会自动将符合条件的 chunk
拆分成多个文件,并将其中的 CSS
样式提取到单独的文件中。
需要注意的是,为了正确地使用 MiniCssExtractPlugin.loader
,您必须先在 webpack
配置文件中引入 MiniCssExtractPlugin
插件,并将其添加到插件列表中。只有在插件被实例化并添加到插件列表中后,MiniCssExtractPlugin.loader
才能正确地将 CSS
样式提取到文件中。
MiniCssExtractPlugin
插件提取样式的顺序是按照 webpack
打包时模块的依赖关系顺序提取的。具体来说,如果一个样式文件 A
依赖了另一个样式文件 B
,那么在打包时,会先提取样式文件 B
,然后再提取样式文件 A
。这是因为在 webpack
的打包过程中,每个模块的依赖关系会被分析出来,并按照依赖关系顺序生成依赖图,这个依赖图中包括了样式文件之间的依赖关系。因此,在提取样式文件时,会按照依赖图的顺序依次提取,以确保样式文件的依赖关系被正确地处理。
需要注意的是,在提取样式文件时,还会考虑样式文件的引用顺序。例如,如果在 HTML
页面中先引用了样式文件 A
,再引用样式文件 B
,那么在提取样式文件时,会按照这个引用顺序先提取样式文件 A
,再提取样式文件 B
。这样可以确保在页面加载时,样式文件的加载顺序与 HTML
页面中的引用顺序一致,避免了样式被错误地覆盖或重写的问题。
假设有三个样式文件:a.scss
、b.scss
和 c.scss
,其中 a.scss
依赖于 b.scss
,a.scss
在 c.scss
前面引入:
// a.scss
@import 'b.scss';
/* ... */
// c.scss
/* ... */
在 webpack
的配置文件中,使用 MiniCssExtractPlugin
插件提取样式文件:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
// ...
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css'
})
]
};
在提取样式文件时,b.scss
会先被提取,然后是 a.scss
,最后是 c.scss
。在生成的 CSS
文件中,样式规则的顺序则取决于它们在样式文件中的出现顺序。
假设 b.scss
定义了以下样式规则:
/* b.scss */
.foo {
color: red;
}
a.scss
依赖于 b.scss
,并定义了以下样式规则:
// a.scss
@import 'b.scss';
.bar {
font-size: 16px;
}
c.scss
定义了以下样式规则:
/* c.scss */
.baz {
text-align: center;
}
在生成的 CSS
文件中,样式规则的顺序会按照它们在样式文件中的出现顺序排列,即先是 b.scss
中定义的样式规则,然后是 a.scss
中定义的样式规则,最后是 c.scss
中定义的样式规则。因此,生成的 CSS
文件中样式规则的顺序如下:
/* styles.css */
/* b.scss */
.foo {
color: red;
}
/* a.scss */
.bar {
font-size: 16px;
}
/* c.scss */
.baz {
text-align: center;
}
我们实际项目引用顺序是这样的:
最终提取到 CSS
文件中的样式规则顺序应当是这样子:
/* @casstime/bre-label/styles/index.scss */
.bre-label-inner_container {
vertical-align: middle; // 被覆盖
}
/* ./index.module.scss */
.brand-item_discountTag__SlxDf {
vertical-align: top;
}
.brand-item_discountTag__SlxDf { vertical-align: top; }
覆盖.bre-label-inner_container { vertical-align: middle; }
,因此开发环境标签布局有些偏上。
解决方案
我们需要制定相应的规则来优化样式文件引用顺序,以防止类似问题再次发生。这些规则应该不仅仅解决当前问题,而且应该考虑到长远的解决方案。
为了优化样式文件引用顺序,我们建议将基础组件和业务组件等第三方组件的样式文件放置于项目入口文件头部进行引入。这些样式规则的优先级应该是最低的,以便业务代码中的样式规则可以根据实际情况进行修改和覆盖。这样可以确保样式规则的继承和覆盖关系得到正确的处理,同时也有助于提高项目的可维护性和可扩展性。
// Main组件
import "@casstime/bre-label/styles/index.scss"; // 含有.bre-label-inner_container样式
import Label from "@/components/Label"; // 第三方业务组件
const Main = () => {
return <Label />
}
// Labe组件
import styles from "./index.module.scss"; // 含有.brand-item_discountTag__SlxDf样式
const Label = () => {
return <div className=`bre-label-inner_container ${styles.discountTag}`>...</div>
}
// 无论在本地环境还是开发环境,`.brand-item_discountTag__SlxDf { vertical-align: top; // 为了布局正确,可以改动此样式属性 }`都会覆盖`.bre-label-inner_container { vertical-align: middle; }`,
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。