webpack详解~~~
webpack详解~~~
手写loader
loader
编写原则
- 单一原则: 每个
Loader
只做一件事; - 链式调用:
Webpack
会按顺序链式调用每个Loader
; - 统一原则: 遵循
Webpack
制定的设计规则和结构,输入与输出均为字符串,各个Loader
完全独立,即插即用;
在日常开发环境中,为了方便调试我们往往会加入许多console
打印。但是我们不希望在生产环境中存在打印的值。那么这里我们自己实现一个loader
去除代码中的console
知识点普及之
AST
。AST
通俗的来说,假设我们有一个文件a.js
,我们对a.js
里面的1000行进行一些操作处理,比如为所有的await
增加try catch
,以及其他操作,但是a.js
里面的代码本质上来说就是一堆字符串。那我们怎么办呢,那就是转换为带标记信息的对象(抽象语法树)我们方便进行增删改查。这个带标记的对象(抽象语法树)就是AST
。这里推荐一篇不错的AST文章 AST快速入门
npm i -D @babel/parser @babel/traverse @babel/generator @babel/types
复制代码
@babel/parser
将源代码解析成AST
@babel/traverse
对AST
节点进行递归遍历,生成一个便于操作、转换的path
对象@babel/generator
将AST
解码生成js
代码@babel/types
通过该模块对具体的AST
节点进行进行增、删、改、查
为什么需要loader?
webpack 实际上只能处理js文件,那么对于除了js文件的其他类型的文件 比如 css sass 等。。我们不能直接用webpack来处理。
我们需要一个翻译员(loader)来帮我们的文件处理一下。有时候我们不只需要一个翻译员来工作,比如要把文言文翻译成外语,首先要转换成白话文,然后转换为外语。
Loader就像一个翻译员,能将源文件经过转化后输出新的结果,并且一个文件还可以链式的经过多个翻译员翻译。
以scss文件为例:
先将scss源代码交给sass-loader,将scss转换成css;
将sass-loader输出的css提交给css-loader处理,找出css中依赖的资源,压缩css;
将css-loader输出的css提交给style-loader处理,转换成通过脚本加载的javascript代码。
最终的结果一定是javascript代码。
编写loader的原则
职责单一: 一个loader只做一件事情。优点:容易维护且能够链式调用
模块化:
保证输出模块化,loader生成的模块与普通块遵循的相同的设计原则
无状态:
确保loader在不同模块转换之间,不保存状态。每次运行都应该独立于其他编译模块以及相同模块之前的编译结果。
手写一个webpack Loader
3.2 手写webpack plugin
在
Webpack
运行的生命周期中会广播出许多事件,Plugin
可以监听这些事件,在合适的时机通过Webpack
提供的API
改变输出结果。通俗来说:一盘美味的 盐豆炒鸡蛋 需要经历烧油 炒制 调味到最后的装盘等过程,而plugin
相当于可以监控每个环节并进行操作,比如可以写一个少放胡椒粉plugin
,监控webpack
暴露出的生命周期事件(调味),在调味的时候执行少放胡椒粉操作。那么它与loader
的区别是什么呢?上面我们也提到了loader
的单一原则,loader
只能一件事,比如说less-loader
,只能解析less
文件,plugin
则是针对整个流程执行广泛的任务。
一个基本的plugin插件结构如下
class firstPlugin {
constructor (options) {
console.log('firstPlugin options', options)
}
apply (compiler) {
compiler.plugin('done', compilation => {
console.log('firstPlugin')
))
}
}
module.exports = firstPlugin
复制代码
compiler 、compilation是什么?
compiler
对象包含了Webpack
环境所有的的配置信息。这个对象在启动webpack
时被一次性建立,并配置好所有可操作的设置,包括options
,loader
和plugin
。当在webpack
环境中应用一个插件时,插件将收到此compiler
对象的引用。可以使用它来访问webpack
的主环境。compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。当运行webpack
开发环境中间件时,每当检测到一个文件变化,就会创建一个新的compilation
,从而生成一组新的编译资源。compilation
对象也提供了很多关键时机的回调,以供插件做自定义处理时选择使用。
compiler和 compilation的区别在于
-
compiler代表了整个webpack从启动到关闭的生命周期,而compilation 只是代表了一次新的编译过程
-
compiler和compilation暴露出许多钩子,我们可以根据实际需求的场景进行自定义处理
下面我们手动开发一个简单的需求,在生成打包文件之前自动生成一个关于打包出文件的大小信息
新建一个webpack-firstPlugin.js
class firstPlugin{
constructor(options){
this.options = options
}
apply(compiler){
compiler.plugin('emit',(compilation,callback)=>{
let str = ''
for (let filename in compilation.assets){
str += `文件:${filename} 大小${compilation.assets[filename]['size']()}\n`
}
// 通过compilation.assets可以获取打包后静态资源信息,同样也可以写入资源
compilation.assets['fileSize.md'] = {
source:function(){
return str
},
size:function(){
return str.length
}
}
callback()
})
}
}
module.exports = firstPlugin
复制代码
如何使用
const path = require('path')
const firstPlugin = require('webpack-firstPlugin.js')
module.exports = {
// 省略其他代码
plugins:[
new firstPlugin()
]
}
复制代码
执行 npm run build
即可看到在dist
文件夹中生成了一个包含打包文件信息的fileSize.md
上面两个
loader
与plugin
案例只是一个引导,实际开发需求中的loader
与plugin
要考虑的方面很多,建议大家自己多动手尝试一下。
附上官网 如何编写一个plugin