webpack/5、写一个loader

125 阅读2分钟

背景

为了优化图片资源,使用了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运行时期:

image.png 故此,如果我需要对源码进行修改,那么我就要写个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
    );
    */
}

学习资料

手把手教你在Webpack写一个Loader