style-resources-loader 应用
通过在工作中的应用场景,来介绍此loader用法和一些发散的点
- 场景需求
- 解决方案
发散点
- loader匹配规则
- loader顺序
- 调试loader
- style-resources-loader的injector属性
场景需求
项目结构如下:
├─views
├─componets 组件目录
| ├─brand1 组件1
| ├─cmp.vue
| ├─...
| └─brand2 组件2
| ├─cmp.vue
| ├─...
├─theme 主题目录
(每个主题中颜色变量可能会重复)
| ├─brand1.less 组件1主题颜色变量
| └─brand2.less 组件2主题颜色变量
| └─other.less 其他主题颜色变量
└─...
需求:
- 组件目录使用对应的主题颜色变量,其他目录使用主题3颜色变量
- 使用主题颜色变量时不需要每次单独@import '@/theme/brandx.less'
解决方案
module: {
rules: [
{
test: /\.less$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", 'less-loader'],
},
{
test: /components\\brand1.*\.less$/i,
use: [{
loader: 'style-resources-loader',
options: {
patterns: [path.resolve(__dirname, 'src/theme/brand1.less')]
},
}],
},
{
test: /components\\brand2.*\.less$/i,
use: [{
loader: 'style-resources-loader',
options: {
patterns: [path.resolve(__dirname, 'src/theme/brand2.less')]
},
}],
},
{
test: /[^components\\].*\.less$/i,
use: [{
loader: 'style-resources-loader',
options: {
patterns: [path.resolve(__dirname, 'src/theme/brand3.less')]
},
}],
},
]
},
发散点
loader匹配规则
loader会把非js的文件转换为js文件,不同类型的文件根据定义的rules中不同test去匹配相应的loader进行处理,这里对vue组件中样式文件的处理其实是先经过了vue-loader将.vue文件中的<style lang="less">
部分转换为less文件后,再由其他匹配此规则的loader进行处理
loader顺序
在之前使用loader的过程中,我只知道use中的loader顺序是从右向左(或者说是从下到上,这个是看书写的习惯),例如下面,执行顺序是less-loader->css-loader->MiniCssExtractPlugin.loader
{
test: /\.less$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", 'less-loader'],
},
其实在rules中对于相同类型文件的匹配顺序也是一样;例如我们把上面的rules顺序换一下,此时所有的less文件在第一个规则中已经全部转换为css,无法匹配第二个规则执行style-resources-loader,导致其less变量并未注入从而引起报错
// error code
module: {
rules: [
{
test: /components\\brand1.*\.less$/i,
use: [{
loader: 'style-resources-loader',
options: {
patterns: [path.resolve(__dirname, 'src/theme/brand1.less')]
},
}],
},
{
test: /\.less$/i,
use: [MiniCssExtractPlugin.loader, "css-loader", 'less-loader'],
},
]
}
同时也可以根据build信息看到具体的loader执行顺序
这边可以自己写一个例子去感受一下
//src/loaders/a.js
module.exports = function(source) {
// this.data为在 pitch 阶段和 normal 阶段之间共享的 data 对象。
console.log('a-loader', this.data);
return source;
};
module.exports.pitch = function(remainingRequest, precedingRequest, data) {
console.log('a-pitch**************************');
console.log(remainingRequest);
console.log(precedingRequest);
console.log(data);
data.value = 'a-pitch-value';
}
//src/loaders/b.js
module.exports = function(source) {
console.log('b-loader', this.data);
return source;
};
module.exports.pitch = function(remainingRequest, precedingRequest, data) {
console.log('b-pitch**************************');
console.log(remainingRequest);
console.log(precedingRequest);
console.log(data);
data.value = 'b-pitch-value';
}
//src/loaders/c.js
module.exports = function(source) {
console.log('c-loader', this.data);
return source;
};
module.exports.pitch = function(remainingRequest, precedingRequest, data) {
console.log('c-pitch**************************');
console.log(remainingRequest);
console.log(precedingRequest);
console.log(data);
data.value = 'c-pitch-value';
}
//webpack.config.js
module: {
rules: [
{
use: [
path.join(__dirname, 'src/loaders/a.js'),
path.join(__dirname, 'src/loaders/b.js'),
path.join(__dirname, 'src/loaders/c.js'),
],
},
]
},
结果如下:
这个方法好像用的不是很多,目前就在mini-css-extract-plugin
中看到
调试loader
然后选择node.js
,项目里面会多一个.vscode
的文件夹
配置launch.json
文件
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"request": "launch",
"name": "webpack",
"program": "D:\\webpack-test\\node_modules\\webpack-cli\\bin\\cli.js",
//"args": [
// "dev"
//]
// 或者
// "runtimeExecutable": "npm",
// "runtimeArgs": [
// "run-script",
// "dev"
// ]
}
]
}
主要是配置program
,此为你执行文件的位置,args
为你执行命令时的参数,我这边只是用webpack打包,所以不需要此参数,这里等价于让vscode自带的node调试功能去帮你执行webpack这条命令,或者是直接使用调试npm脚本命令的方式,这样更简单,前提是你需要在package.json中定义好script
配置好了之后,就可以直接在node_modules里面找到相应的loader代码,然后打上断点,按F5开始进行调试
style-resources-loader的injector属性
第一次看这个属性的时候可能会看不懂,但是如果会调试了loader的话,直接去源码里面去打个断点就知道怎么用的了
injector | Function 'prepend' 'append' | 'prepend' | Controls the resources injection precisely |
此属性为精确控制资源注入
例如:
//a.less
@primary-color: red;
.text-color {
color: @primary-color;
}
//var.less
@primary-color: black;
当injector
为prepend
时
//a.css
.text-color {
color: red;
}
当injector
为append
时
//a.css
.text-color {
color: black;
}
从例子可以看出来,控制我们的变量是否会覆盖原文件中的变量
当injector
为回调函数时,官方给的例子是
module.exports = {
// ...
module: {
rules: [{
test: /\.styl$/,
use: ['style-loader', 'css-loader', 'stylus-loader', {
loader: 'style-resources-loader',
options: {
patterns: [
path.resolve(__dirname, 'path/to/stylus/variables/*.styl'),
path.resolve(__dirname, 'path/to/stylus/mixins/*.styl')
],
injector: (source, resources) => {
const combineAll = type => resources
.filter(({ file }) => file.includes(type))
.map(({ content }) => content)
.join('');
return combineAll('variables') + combineAll('mixins') + source;
}
}
}]
}]
},
// ...
}
意思就是让我们自己控制注入的变量内容是来自于哪个文件(variables、mixins)
source为源文件内容字符串
resources为注入文件的对象数组,对象包含file和content属性
- file为该文件的绝对路径
- content为该文件的文件内容字符串
源码:
const normalizeInjector = (injector: StyleResourcesLoaderOptions['injector']): StyleResourcesNormalizedInjector => {
if (typeof injector === 'undefined' || injector === 'prepend') {
return (source, resources) => resources.map(getResourceContent).join('') + source;
}
if (injector === 'append') {
return (source, resources) => source + resources.map(getResourceContent).join('');
}
return injector;
};