背景
为了优化图片资源,使用了webp图片,但是webp图片兼容性不好,需要使用pciture标签做兼容处理。于是萌生想在开发者无感知的情况下自动将代码里面的img标签转成picture标签,并对图片做压缩与图片类型的转化处理。
目标
<img src="a.png" alt="" />
--------经过转化---------
<picture >
<source srcset="a.webp" type="image/webp" />
<img src="a.png" alt="" />
</picture>
调研
前端中大部分项目的打包工具使用的是webpack,但webpack只认识js和json文件。所以当其他文件类型想要参与打包的话,则需要通过lodaer将类型转换成js或者json。
webpack做的事情,仅仅是分析出各种模块的依赖关系,然后形成资源列表,最终打包生成到指定的文件中。
loader是运行在打包文件之前(将文件进行转化后才参与打包),它只专注于转化文件(transform)这一个领域。
loader 用于对模块的"源代码"进行转换,在 import 或"加载"模块时预处理文件
plugin有各种灵活的功能,例如打包优化、资源管理、环境变量注入等,它们会运行在 webpack 的不同阶段(钩子 / 生命周期),贯穿了webpack整个编译周期。
如图查看plugin与loader运行时期:
故此,如果我需要对源码进行修改,那么我就要写个loader,会比较方便。
如果我需要对整个构建过程进行处理,那么我就要需要写个plugin。
通过对需求整理,我需要将vue文件里面的img标签转化成picture标签即可。也就是说对源码进行处理,故此我需要写一个loader。
loader
它是一个函数,接受上一个loader处理文件后的结果。
他的作用:通过webpack配置,能够指定文件,对文件里面的内容进行处理,处理完成后返回出去给下一个loader处理。
loader的工作流程
指定文件所使用的loader,如果存在多个loader,则是都是从右往左进行转化处理的。
{
...
module: {
rules:[
// 处理vue文件
{
test: /\.vue$/,
use: [
'vue-loader',
'vue-webp-loader', // vue文件先由处理完vue-webp-loader再转给vue-loader处理。
]
},
]
}
...
}
实践
loader的初始化
// 初始化loader,终端指令输入
mkdir vue-webp-loader
cd vue-webp-loader
npm init -y
touch index.js
这个index.js文件就是loader的入口。
webpack项目链接本地loader
方法是使用npm link 建立软链接,其他教程说使用<path-to-loader>,但我发现引用是失败的。
进入到vue-webp-loader项目下,将vue-webp-loader转成全局包
npm link
进入到webpack项目下,与全局包vue-webp-loader建立成链接,此时node_module里面则会有vue-webp-loader文件夹的软链接
npm link vue-webp-loader
这样当我们修改vue-webp-loader的时候,对应的webpack项目所引用的包都会发生内容改变。
vue-webp-loader
这个loader的功能是将项目里面
vue-picture-loader
这个loader的功能是将img标签转换成picture标签
// index.js
module.exports = function(source,sourceMaps){
// 获取vue文件里面所有的img标签
let imgArrays = source.match(/<img\s.*>/g)
// 将读取到的每一个img标签切换成picutre标签,并添加webp图片
imgArrays && imgArrays.forEach(img => {
let src = img.match(/:?src=(('[@\/\S]*')|("[@\/\S]*"))/g);
if(src) {
let path = src[0].split("=")[1].replace(/\.[a-zA-Z]+/,'.webp');
let picture = `<picture><source srcset=${path} type="image/webp">${img}</picture>`
let oldImg = new RegExp(img + '(?!<\/picture>)');
source = source.replace(oldImg,picture)
}
})
this.callback(null,source,sourceMaps);
/*
this.callback(
// 当无法转换原内容时,给 Webpack 返回一个 Error
err: Error | null,
// 原内容转换后的内容
content: string | Buffer,
// 用于把转换后的内容得出原内容的 Source Map,方便调试
sourceMap?: SourceMap,
// 如果本次转换为原内容生成了 AST 语法树,可以把这个 AST 返回,以方便之后需要 AST 的 Loader 复用该 AST,以避免重复生成 AST,提升性能
abstractSyntaxTree?: AST
);
*/
}