css-loader、style-loader的pitch
本节来实现一下上节遗留的问题,css-loader的实现
css-loader是用来处理css文件的,可以把css中的import语法解析出来,还能解析引用的图片。
比如:
@color: green;
body {
color: @color;
background-image: url('./aa.jpg');
/*
在解析这个url的时候,如果不使用css-loader,引用的路径还是./aa.jpg
但是这个路径在打包后的目录是找不到的,所以就需要css-loader转换一下
把url变为require的形式引入 url(require('./aa.jpg')
*/
}
假如不更改就是下面的效果,找不到这个图片
准备工作
修改index.less
// webpack\webpack-loader\src\index.less
@color: green;
body {
color: @color;
background-image: url('./aa.jpg');
}
修改配置文件
// webpack\webpack-loader\webpack.config.js
let path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.less/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.jpg/,
// 根据图片生成一个md5戳,发射到打包目录下,然后返回一个打包后的图片路径
// use: 'file-loader', // 自己写一个file-loader
use: {
// 可以设定一个限制,将图片转换为base64,减少http请求
loader: 'url-loader', // 自己写一个url-loader
options: {
limit: 100 * 1024, // 如果图片小于100k,就直接将图片转换为base64
// 大于100k,就会用file-loader去处理图片
}
}
},
]
},
resolveLoader: {
// 找loader的时候,先从node_modules中找,找不到再从我们的loaders目录下找
modules: ['node_modules', path.resolve(__dirname, 'loaders')]
/* alias: { // loader的别名
loader1: path.resolve(__dirname, 'loaders', 'loader1.js')
} */
}
}
css-loader
实现css-loader
- 使用正则匹配出需要替换的url路径
- 使用正则的exec方法依次匹配,截取代码片段
- 截取的代码片段巧用数组保存,
let arr = ['let list = []']
// webpack\webpack-loader\loaders\css-loader.js
// css-loader
function loader(source) {
console.log('css-loader 加载了');
// 匹配css中的图片地址的正则
// background-image: url('./aa.jpg');
// 捕获组捕获的结果 就是 './aa.jpg'
let reg = /url\((.+?)\)/g
let arr = ['let list = []'] // 用来拼接代码
// 开始拼接替换代码块
let pos = 0
let current
// reg.exec(source) 返回的结果是一个数组 [matchUrl, 捕获组1的内容]
// matchUrl = url('./aa.jpg')
// 捕获组1的内容 = './aa.jpg'
// 使用正则的exec方法,每次匹配到的结果的lastIndex都会变化,默认从0开始
// 循环替换代码中所有的图片路径
while (current = reg.exec(source)) {
let [matchUrl, g] = current
// 把匹配的内容之前的代码保存一下
let last = reg.lastIndex - matchUrl.length
arr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`) // 拼接代码块
// 把捕获组中的图片路径 改为require的写法
arr.push(`list.push('url('+require(${g})+')')`)
// 更新pos,继续查找
pos = reg.lastIndex
}
// 把最后的代码拼接到起来
arr.push(`list.push(${JSON.stringify(source.slice(pos))})`)
// 导出最后的结果
arr.push(`module.exports = list.join('')`)
// 返回代码片段
return arr.join('\r\n')
}
module.exports = loader
最后拼接的代码片段如下 console.log(arr.join('\r\n'))
。
但是如果直接把这个代码片段导出给style-loader,打包后的效果就是下面的样子,代码片段并没有被执行,所以需要修改一下style-loader,以require的方式引用css-loader处理后的代码,就用到了之前学的pitch,阻断loader的加载。
修改style-loader
use: ['style-loader','css-loader','less-loader']
,正常这三个loader的加载顺序是倒着来,所以上面css-loader的处理过的代码片段会直接返回给style-loader,而这个代码片段是还没有执行的,所以应该使用require来引用这个结果。
所以想到了一个办法,在style-loader中,使用pitch来阻断后面的loader,然后使用内联loader处理结果,
比如require(css-loader.js!less-loader.js!./index.less)
,为了避免死循环,所以需要变成这样
!!require(css-loader.js!less-loader.js!./index.less)
// webpack\webpack-loader\loaders\style-loader.js
let loaderUtils = require('loader-utils')
// style-loader
function loader(source) {
console.log('style-loader 加载了');
// 生成一个合法的标签,塞到head里
let str = `
let style = document.createElement('style')
style.innerHTML = ${JSON.stringify(source)}
document.head.appendChild(style)
`
return str
}
// 需要再style-loader拦截一下剩余两个loader,css-loader和less-loader
// 使用行内loader加载
// 使用pitch
loader.pitch = function (remainingRequest) {
// remainingRequest 剩余的loader请求
// css-loader.js!less-loader.js!./index.less
/*
F:\code\note_code\webpack\webpack-loader\loaders\css-loader.js!F:\code\note_code\webpack\webpack-loader\loaders\less-loader.js!F:\code\note_code\webpack\webpack-loader\src\index.less
*/
// console.log(remainingRequest);
// relativePathRemainRequest
// "!!../loaders/css-loader.js!../loaders/less-loader.js!./index.less"
// loaderUtils.stringifyRequest 绝对路径转成相对路径
// 增加 !! 是避免死循环 执行行内loader时如果不加!!,发现加载index.less时,就会再次触发这三个loader
// 所以 增加 !! 只触发inline loader
let relativePathRemainRequest = loaderUtils.stringifyRequest(this, '!!' + remainingRequest)
console.log('style-loader的pitch 加载了');
// 生成一个合法的标签,塞到head里
let str = `
let style = document.createElement('style')
style.innerHTML = require(${relativePathRemainRequest})
document.head.appendChild(style)
`
return str
}
module.exports = loader
使用!!来只执行内联loader,达到了期望的结果。
打包看下效果。结果已经是转换后的代码了。
总结
本节实现了css-loader
先使用正则匹配出需要替换的url,变为require的形式
用数组拼接代码片段,导出代码片段
正则的exec方法,会改变lastIndex属性,利用这个特点依次拼接代码片段
js// webpack\webpack-loader\loaders\css-loader.js // css-loader function loader(source) { console.log('css-loader 加载了'); // 匹配css中的图片地址的正则 // background-image: url('./aa.jpg'); // 捕获组捕获的结果 就是 './aa.jpg' let reg = /url\((.+?)\)/g let arr = ['let list = []'] // 用来拼接代码 // 开始拼接替换代码块 let pos = 0 let current // reg.exec(source) 返回的结果是一个数组 [matchUrl, 捕获组1的内容] // matchUrl = url('./aa.jpg') // 捕获组1的内容 = './aa.jpg' // 使用正则的exec方法,每次匹配到的结果的lastIndex都会变化,默认从0开始 // 循环替换代码中所有的图片路径 while (current = reg.exec(source)) { let [matchUrl, g] = current // 把匹配的内容之前的代码保存一下 let last = reg.lastIndex - matchUrl.length arr.push(`list.push(${JSON.stringify(source.slice(pos, last))})`) // 拼接代码块 // 把捕获组中的图片路径 改为require的写法 arr.push(`list.push('url('+require(${g})+')')`) // 更新pos,继续查找 pos = reg.lastIndex } // 把最后的代码拼接到起来 arr.push(`list.push(${JSON.stringify(source.slice(pos))})`) // 导出最后的结果 arr.push(`module.exports = list.join('')`) // 返回代码片段 return arr.join('\r\n') } module.exports = loader
然后修改了style-loader
使用pitch,来阻断css-loader、和style-loader的执行
在pitch里使用内联loader来处理index.less
因为pitch会阻断,pitch里又有内联loader再次加载index.less,就继续会调用normal loader,而导致死循环
所以,为了避免死循环,内联loader执行的时候,就只执行内联loader,其他不执行,使用了
!!
内联loader正好会把
css-loader
的结果引进来,引进就不是之前在数组中拼接的代码片段了,而是最终module.export的结果js// webpack\webpack-loader\loaders\style-loader.js let loaderUtils = require('loader-utils') // style-loader function loader(source) { console.log('style-loader 加载了'); // 生成一个合法的标签,塞到head里 let str = ` let style = document.createElement('style') style.innerHTML = ${JSON.stringify(source)} document.head.appendChild(style) ` return str } // 需要再style-loader拦截一下剩余两个loader,css-loader和less-loader // 使用行内loader加载 // 使用pitch loader.pitch = function (remainingRequest) { // remainingRequest 剩余的loader请求 // css-loader.js!less-loader.js!./index.less /* F:\code\note_code\webpack\webpack-loader\loaders\css-loader.js!F:\code\note_code\webpack\webpack-loader\loaders\less-loader.js!F:\code\note_code\webpack\webpack-loader\src\index.less */ // console.log(remainingRequest); // relativePathRemainRequest // "!!../loaders/css-loader.js!../loaders/less-loader.js!./index.less" // loaderUtils.stringifyRequest 绝对路径转成相对路径 // 增加 !! 是避免死循环 执行行内loader时如果不加!!,发现加载index.less时,就会再次触发这三个loader // 所以 增加 !! 只触发inline loader let relativePathRemainRequest = loaderUtils.stringifyRequest(this, '!!' + remainingRequest) console.log('style-loader的pitch 加载了'); // 生成一个合法的标签,塞到head里 let str = ` let style = document.createElement('style') style.innerHTML = require(${relativePathRemainRequest}) document.head.appendChild(style) ` return str }
参考
https://www.bilibili.com/video/BV1a4411e7Bz?p=46&spm_id_from=pageDriver