80行代码教你写一个Webpack插件并发布到npm
1. 前言
最近在学习 Webpack
相关的原理,以前只知道 Webpack 的配置方法,但并不知道其内部流程,经过一轮的学习,感觉获益良多,为了巩固学习的内容,我决定尝试自己动手写一个插件。
这个插件实现的功能比较简单:
- 默认清除
js
代码中的console.log
的打印输出; - 可通过传入配置,实现移除
console
的其它方法,如console.warn
、console.error
等;
2. Webpack 的构建流程以及 plugin 的原理
2.1 Webpack 构建流程
Webpack
的主要构建流程,可以分为三个阶段:
- 初始化阶段:启动构建,读取与合并配置参数,加载
Plugin
,实例化Compiler
。 - 编译阶段:从
Entry
发出,针对每个Module
串行调用对应的Loader
去翻译文件内容,再找到该Module
依赖的Module
,递归地进行编译处理。 - 生成阶段:对编译后的
Module
组合成Chunk
,把Chunk
转换成文件,输出到文件系统。
如果 Webpack
打包生产环境文件时,只会执行一次构建,以上阶段会按顺序执行一遍。但是在开启监听模式时,如开发环境,Webpack 会持续的进行构建。
2.2 plugin 原理
Webpack
插件通常是一个带有 apply
函数的类,其中 constructor
可以接收传入的配置项。插件被安装时,apply
函数会被调用一次,并接收 Compiler
对象,然后我们可以在 Compiler
对象上监听不同的事件钩子,从而进行插件功能的开发。
// 定义一个插件
class MyPlugin {
// 构造函数,接收插件的配置项 options
constructor(options) {
// 获取配置项,初始化插件
}
// 插件安装时会调用 apply,并传入 compiler
apply(compiler) {
// 获取 comolier 独享,可以监听事件钩子
// 功能开发 ...
}
}
2.3 compiler 和 compilation 对象
在开发 Plugin
过程中最常用的两个对象就是 Compiler
和 Compilation
:
Compiler
对象在Webpack
启动时被实例化,该对象包含了Webpack
环境所有的配置信息,包括options
、loaders
、plugins
等。在整个Webpack
构建过程中,Compiler
对象是全局唯一的, 它提供了很多事件钩子回调供插件使用。Compilation
对象包含了当前的模块资源、编译生成资源、变化的文件等。Compilation
对象在Webpack
构建过程中并不是唯一的,如果在开发模式下Webpack
开启了文件检测功能,每当文件变化时,Webpack
会重新构建,此时会生成一个新的Compilation
对象。Compilation
对象也提供了很多事件回调供插件做扩展。
3. 插件开发
3.1 项目目录
该插件实现的功能比较简单,文件目录也不复杂。首先新建一个空文件夹 remove-console-Webpack-plugin
,并在该文件夹目录下运行 npm init
,根据提示来填写 package.json
相关信息。然后再新建一个 src
文件夹,插件主要代码就放在 src/index.js
里面。如果你需要把项目放到 github
上,最好也添加一下 .gitignore
、README.md
等文件。
// remove-console-Webpack-plugin
├─src
│ └─index.js
├─.gitignore
├─package.json
└─README.md
3.2 插件代码
插件代码逻辑也并不复杂,主要有几点:
- 在构造函数中接收配置参数,并对参数进行合并,得到需要清除的
console
函数, 存放在removed
数组中; - 在
apply
函数中监听compiler.hook.compilation
钩子,该钩子触发后,拿到compilation
后进一步监听它的钩子,这里Webpack4
和Webpack5
的钩子不一样,需要做兼容; - 定义
assetsHandler
方法来处理js
文件,利用正则表达式清除removed
中包括的console
函数;
class RemoveConsoleWebpackPlugin {
// 构造函数接受配置参数
constructor(options) {
let include = options && options.include;
let removed = ['log']; // 默认清除的方法
if (include) {
if (!Array.isArray(include)) {
console.error('options.include must be an Array.');
} else if (include.includes('*')) {
// 传入 * 表示清除所有 console 的方法
removed = Object.keys(console).filter(fn => {
return typeof console[fn] === 'function';
})
} else {
removed = include; // 根据传入配置覆盖
}
}
this.removed = removed;
}
// Webpack 会调用插件实例的 apply 方法,并传入compiler 对象
apply(compiler) {
// js 资源代码处理函数
let assetsHandler = (assets, compilation) => {
let removedStr = this.removed.reduce((a, b) => (a + '|' + b));
let reDict = {
1: [RegExp(`\\.console\\.(${removedStr})\\(\\)`, 'g'), ''],
2: [RegExp(`\\.console\\.(${removedStr})\\(`, 'g'), ';('],
3: [RegExp(`console\\.(${removedStr})\\(\\)`, 'g'), ''],
4: [RegExp(`console\\.(${removedStr})\\(`, 'g'), '(']
}
Object.entries(assets).forEach(([filename, source]) => {
// 匹配js文件
if (/\.js$/.test(filename)) {
// 处理前文件内容
let outputContent = source.source();
Object.keys(reDict).forEach(i => {
let [re, s] = reDict[i];
outputContent = outputContent.replace(re, s);
})
compilation.assets[filename] = {
// 返回文件内容
source: () => {
return outputContent
},
// 返回文件大小
size: () => {
return Buffer.byteLength(outputContent, 'utf8')
}
}
}
})
}
/**
* 通过 compiler.hooks.compilation.tap 监听事件
* 在回调方法中获取到 compilation 对象
*/
compiler.hooks.compilation.tap('RemoveConsoleWebpackPlugin',
compilation => {
// Webpack 5
if (compilation.hooks.processAssets) {
compilation.hooks.processAssets.tap(
{ name: 'RemoveConsoleWebpackPlugin' },
assets => assetsHandler(assets, compilation)
);
} else if (compilation.hooks.optimizeAssets) {
// Webpack 4
compilation.hooks.optimizeAssets.tap(
'RemoveConsoleWebpackPlugin',
assets => assetsHandler(assets, compilation)
);
}
})
}
}
// export Plugin
module.exports = RemoveConsoleWebpackPlugin;
4. 发布到npm
希望别人能使用到你的插件,就需要把插件发布到 npm
上,发布的主要流程:
首先在
npm
官网上注册账号,然后打开命令行工具,在任意目录下输入npm login
并按提示登录;
登录后可用
npm whoami
查看是否登录成功;
发布前检查一下根目录下的
package.json
文件信息是否填写正确,主要字段:
name:决定用户下载你的插件时用的名称,不可与npm
上已有的第三方包重名,否则无法发布;
main:插件主文件入口,Webpack
引入插件时,就从该目录导入;
version:每次更新发布时,需要与上一版本的版本号不一样,否则上传不成功;
repository:如果你的插件代码放在github
、gitee
等网站,可以填一下;
private:不能设置为true
,否则无法发布;
一切准备就绪后,切换到插件所在的目录下,运行
npm publish
即可上传插件;
5. 结尾
本文是我学习了 Webpack
原理并开发了一个小插件后的总结,由于 Webpack 的内容实在太多了,所以可能会有理解不到位的地方,还请大佬们多多指正。另外,如果这篇文章对你有帮助,可以给我点个赞,或者给我的插件项目点个star,你的鼓励是我最大的动力哈~
80行代码教你写一个Webpack插件并发布到npm的更多相关文章
- 只有20行Javascript代码!手把手教你写一个页面模板引擎
http://www.toobug.net/article/how_to_design_front_end_template_engine.html http://barretlee.com/webs ...
- 怎样写一个webpack loader
div{display:table-cell;vertical-align:middle}#crayon-theme-info .content *{float:left}#crayon-theme- ...
- 半个小时教你写一个装(bi)逼(she)之地图搜租房
半个小时教你写一个装(bi)逼(she)之地图搜租房 首先需要一个Python3环境,怎么准备我就不多说了,实在不会的出门右转看一下廖雪峰老师的博客. HTML部分 代码来自:高德API+Python ...
- iOS开发——实用技术OC篇&8行代码教你搞定导航控制器全屏滑动返回效果
8行代码教你搞定导航控制器全屏滑动返回效果 前言 如果自定了导航控制器的自控制器的leftBarButtonItem,可能会引发边缘滑动pop效果的失灵,是由于 self.interactivePop ...
- 案例实战之如何写一个webpack plugin
案例实战之如何写一个webpack plugin 1.写一个生成打包文件目录的file.md文件 // 生成一个目录项目目录的文件夹 class FileListPlugin { constructo ...
- 【vps】教你写一个属于自己的随机图API
[vps]教你写一个自己的随机图API 前言 刚刚开始使用halo博客的时候,我就发现halo博客系统是可以使用随机图当背景的,所以也是使用了网上一些比较火的随机图API. 在上次发现了各种图片API ...
- 为PhoneGap写一个android插件
为PhoneGap写一个android插件,要怎么做? 其实这句话应该反过来说,为android写一个PhoneGap插件,要怎么做? 这里以最简单的Hello World!为例,做个说明: 1.第一 ...
- 如何写一个jquery插件
本文总结整理一下如何写一个jquery插件?虽然现今各种mvvm框架异常火爆,但是jquery这个陪伴我们成长,给我们带来很多帮助的优秀的库不应该被我们抛弃,写此文章,作为对以往欠下的笔记的补充, ...
- python 拼写检查代码(怎样写一个拼写检查器)
原文:http://norvig.com/spell-correct.html 翻译:http://blog.youxu.info/spell-correct.html 怎样写一个拼写检查器 Pete ...
随机推荐
- 【linux】系统编程-3-system-V IPC 信号量
目录 前言 5. 信号量 5.1 概念 5.2 工作原理 5.3 操作函数 5.3.1 semget() 5.3.2 semop() 5.3.3 semctl() 5.4 例程 参考: 前言 原文链接 ...
- 体验用yarp当网关
Yarp是微软开源的一个用.net实现的反向代理工具包,github库就叫reverse-proxy(反向代理)(吐槽一下微软起名字233333) nuget包preview9之前都叫Microsof ...
- Java入门环境的搭建
入门环境搭建 Java帝国的诞生 C & C++ 1972年C诞生 贴近硬件,运行极快,效率极高 操作系统,编译器,数据库,网络系统等 指针和内存管理 1982年C++诞生 面向对象 兼容C ...
- Etcd常用运维命令
目录 常用命令 常见操作 如何缩容? 如何扩容? 数据目录丢失或被误删除,节点启动失败或者加入集群报错? 操作步骤 操作步骤不正确的各种常见错误日志 常用命令 #查看集群member情况 etcdct ...
- 第21 章 : Kubernetes 存储架构及插件使用
Kubernetes 存储架构及插件使用 本文将主要分享以下三方面的内容: Kubernetes 存储体系架构: Flexvolume 介绍及使用: CSI 介绍及使用. Kubernetes 存储体 ...
- 从零玩转SpringSecurity+JWT整合前后端分离
从零玩转SpringSecurity+JWT整合前后端分离 2021年4月9日 · 预计阅读时间: 50 分钟 一.什么是Jwt? Json web token (JWT), 是为了在网络应用环境间传 ...
- Python是啥?为什么这么多职业人和学生就算报班也要学它?!
嗨,大家好 这里是汐仔 首先我们先来考究一下近几年的头条和新闻. 1.早在2018年python就已经被纳入高考之一了 2.Python加入全国计算机等级考试,从2018年九月起新增为大学计算机二级考 ...
- Git简单操作及原理
设置签名: 用户名:tom Email地址:goodMorning@atguigu.com git config user.name tom_pro git config user.e ...
- day17.网络编程2+进程
1 加入链接循环的套接字服务端 1.1 服务端 ''' 2.1 基于文件类型的套接字家族 套接字家族的名字:AF_UNIX unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套 ...
- adb 简介与常用命令
1. abd 简介 2. adb 常用命令 1. abd 简介 adb 的全称为 Android Debug Bridge,就是起到调试桥的作用. 借助 adb 工具,我们可以管理设备或手机模拟器的状 ...