假设我们的代码里提供了蓝色、黑色和红色等多个主题风格样式,并且一个项目上只需要使用一套主题风格:
假如某个项目需要使用蓝色主题,那么我们可能会在main.js
中这样从theme-blue
下引入对应的css文件:
...
import '../assets/theme-blue/index.css';
...
然后我们执行打包命令,蓝色主题下的样式文件就会被引入,于是项目就应用了蓝色主题。
假如现在另一个项目需要生成一套红色主题该怎么办呢?
非常简单,把上面的代码改成从红色主题下引入文件即可:
import '../assets/theme-red/index.css';
这样的做法我们可能已经司空见惯了。但是为了应用不同主题而直接修改源代码是非常不好的方式,它会导致代码的稳定性急剧下降。
我们有更好的做法:配置打包命令。下面我们来介绍如何仅通过配置打包命令来生成多套主题样式(关于打包命令的基本说明请参考我之前的博客:多页vue应用的单页面打包方法(内含打包模式的应用))。
1. 新增打包命令
我们首先打开package.json
,向script
字段添加两个新的命令,分别是:build-blue
和build-red
,我们将会分别用这两个命令打包蓝色主题和红色主题。命令如下:
{
...
"scripts": {
...
"build-blue": "vue-cli-service build --mode themeBlue",
"build-red": "vue-cli-service build --mode themeRed"
}
}
前文我们说过,--mode
后面的参数指定的是打包模式,这里我们分别为两个命令应用themeBlue
和themeRed
这两个打包模式。
那么这两个模式到底是什么呢?我们还没有定义。接下来我们就去定义这两个模式。
2. 配置env文件
前文我们也谈到过,启用一个打包模式的本质就是启用一组变量。这组变量会被写入process.env
对象(process是webpack打包的进程对象)内,供webpack打包时读取。
每个打包模式使用的变量必须定义在项目根目录下,命名为.env.xxx
(其中xxx就是对应的打包模式)。因此我们在项目根目录下新建以下两个文件:
project-demo
|-- src
|-- ...
.env.themeBlue
.env.themeRed
注意,这两个文件均以.
开头,没有后缀,.env.
后面的部分是对应的打包模式。
当执行npm run build-blue
时,启用的是.env.themeBlue
中定义的变量,build-red
同理。
我们现在编辑这两个文件,设置一个变量:
.env.themeBlue
theme='theme-blue'
.env.themeRed
theme='theme-red'
现在当执行npm run build-blue
时,webpack就会读取.env.themeBlue
,然后把process.env.theme
的值设置为'theme-blue'
。
注意:如果要在src
中使用这个变量,它必须以VUE_APP_
开头(如VUE_APP_THEME
),否则访问不到,src之外没有这个限制(如vue.config.js
中)。
3. 配置路径别名
如果是在js中使用打包命令,上面两步就足够了,这个我们在前文已经讲过,不再赘述。
但是这样对于样式文件是行不通的,因为样式文件一般是用import导入的,它会在静态分析阶段就导入进来,因此无法在路径中写入js变量。也就是说下面的写法是无效的:
import '../assets/' + process.env.XXX + '.css';
既然不能在css路径里使用变量,那我们怎么根据打包命令引入不同的主题样式呢?
方法就是配置路径别名。
我们修改vue.config.js
文件,配置一个别名:
const path = require('path');
function resolve(dir) {
return path.join(__dirname, dir);
}
let themeName = process.env.theme;
module.exports = {
...
chainWebpack: (config) => {
config.resolve.alias.
set('&', resolve('src/assets/' + themeName));
}
}
现在根据打包模式的不同,&
可以表示不同的路径。当执行npm run build-blue
时,&
指代的路径是:src/assets/theme-blue
;而执行npm run build-red
时,它指代的是src/assets/theme-red
。
于是我们可以使用&
作为主题样式路径的别名,像这样引入样式文件:
import '&/index.css';
现在当执行npm run build-blue
时,引入的样式是来自于theme-blue
文件夹;而执行npm run build-red
时,引入的样式来自于theme-red
。仅仅依靠执行不同的打包命令,我们就可以打包出不同主题的项目,再也不用为了切换主题修改代码了!
注意事项
(1). 不要在module.exports中读取process
经过测试,下面的代码运行无效:
chainWebpack: (config) => {
config.resolve.alias
.set('&', resolve('src/assets/css/'
+ process.env.theme));
}
webpack打包时并没有正确解析出process.env.theme
的值,因此请将process.env.theme
的值保存在module.exports
外部的变量里,然后直接引入该变量。
(2). 使用@import引入样式
当在css中使用@import引入主题样式时,不能像import
语句一样书写。
因为原生的css在使用@import语句时是运行时加载的,也就是说它不会在打包阶段被解析,所以下面的代码会被原样输出到打包结果中:
<style>
@import '&/index.css';
</style>
这样样式文件必然会加载失败。那么怎么办呢?
一般我们的项目中都会引入一个loader:postcss
来兼容样式问题,这时我们可以通过在路径开头添加一个~
,告诉postcss这是一个需要在打包阶段编译的依赖:
<style>
@import '~&/index.css';
</style>
这样,webpack在解析时,就会立即执行&/index.css
这个资源,添加到依赖关系中,对应的样式文件就被正确加载了。
注意,这里的~
只是一个标志符,配置别名时不需要带,postcss在解析路径时会自动去掉它。另外,测试发现,当vue.config.js
中配置的别名以~
和$
开头时,路径无法被正确解析(其他特殊字符未完全测试,是否合法以实际效果为准),因此请避免在别名中以这两个字符开头。