80行代码写一个Webpack插件并发布到npm


Posted in Javascript onMay 24, 2021

1. 前言

最近在学习 Webpack 相关的原理,以前只知道 Webpack 的配置方法,但并不知道其内部流程,经过一轮的学习,感觉获益良多,为了巩固学习的内容,我决定尝试自己动手写一个插件。

这个插件实现的功能比较简单:

  • 默认清除 js 代码中的 console.log 的打印输出;
  • 可通过传入配置,实现移除 console 的其它方法,如 console.warnconsole.error 等;

2. Webpack 的构建流程以及 plugin 的原理

2.1 Webpack 构建流程

Webpack 的主要构建流程,可以分为三个阶段:

  • 初始化阶段:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
  • 编译阶段:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
  • 生成阶段:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。

如果 Webpack 打包生产环境文件时,只会执行一次构建,以上阶段会按顺序执行一遍。但是在开启监听模式时,如开发环境,Webpack 会持续的进行构建。

80行代码写一个Webpack插件并发布到npm

2.2 plugin 原理

Webpack 插件通常是一个带有 apply 函数的类,其中 constructor 可以接收传入的配置项。插件被安装时,apply 函数会被调用一次,并接收 Compiler 对象,然后我们可以在 Compiler 对象上监听不同的事件钩子,从而进行插件功能的开发。

// 定义一个插件
class MyPlugin {
  // 构造函数,接收插件的配置项 options 
  constructor(options) {
    // 获取配置项,初始化插件
  }

  // 插件安装时会调用 apply,并传入 compiler
  apply(compiler) {
    // 获取 comolier 独享,可以监听事件钩子
    // 功能开发 ... 
  }
}

2.3 compiler 和 compilation 对象

在开发 Plugin 过程中最常用的两个对象就是 CompilerCompilation

  • Compiler 对象在 Webpack 启动时被实例化,该对象包含了 Webpack 环境所有的配置信息,包括 optionsloadersplugins 等。在整个 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 上,最好也添加一下 .gitignoreREADME.md 等文件。

// remove-console-Webpack-plugin
├─src
│  └─index.js  
├─.gitignore
├─package.json
└─README.md

3.2 插件代码

插件代码逻辑也并不复杂,主要有几点:

  • 在构造函数中接收配置参数,并对参数进行合并,得到需要清除的 console 函数, 存放在 removed 数组中;
  • apply 函数中监听 compiler.hook.compilation 钩子,该钩子触发后,拿到 compilation 后进一步监听它的钩子,这里 Webpack4Webpack5 的钩子不一样,需要做兼容;
  • 定义 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 并按提示登录;

80行代码写一个Webpack插件并发布到npm

登录后可用 npm whoami 查看是否登录成功;

80行代码写一个Webpack插件并发布到npm

发布前检查一下根目录下的 package.json 文件信息是否填写正确,主要字段:

  • name:决定用户下载你的插件时用的名称,不可与 npm 上已有的第三方包重名,否则无法发布;
  • main:插件主文件入口,Webpack 引入插件时,就从该目录导入;
  • version:每次更新发布时,需要与上一版本的版本号不一样,否则上传不成功;
  • repository:如果你的插件代码放在 githubgitee 等网站,可以填一下;
  • private:不能设置为 true,否则无法发布;

80行代码写一个Webpack插件并发布到npm

一切准备就绪后,切换到插件所在的目录下,运行 npm publish 即可上传插件;

80行代码写一个Webpack插件并发布到npm

上传成功后,到 npm 官网上搜索,看看是否能搜到插件;

80行代码写一个Webpack插件并发布到npm

5. 结尾

到此这篇关于80行代码写一个Webpack插件并发布到npm的文章就介绍到这了,更多相关Webpack插件发布到npm内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Javascript 相关文章推荐
Javascript调用XML制作连动下拉列表框
Jun 25 Javascript
js实现GridView单选效果自动设置交替行、选中行、鼠标移动行背景色
May 27 Javascript
javascript学习笔记(十五) js间歇调用和超时调用
Jun 20 Javascript
不用构造函数(Constructor)new关键字也能实现JavaScript的面向对象
Jan 11 Javascript
javascript unicode与GBK2312(中文)编码转换方法
Nov 14 Javascript
关闭页面时window.location事件未执行的原因分析及解决方案
Sep 01 Javascript
Jquery的基本对象转换和文档加载用法实例
Feb 25 Javascript
关于JS 预解释的相关理解
Jun 28 Javascript
解析Vue2 dist 目录下各个文件的区别
Nov 22 Javascript
开发Vue树形组件的示例代码
Dec 21 Javascript
AngularJS日期格式化常见操作实例分析
May 17 Javascript
微信小程序手机号码验证功能的实例代码
Aug 28 Javascript
Ajax请求超时与网络异常处理图文详解
May 23 #Javascript
vue-element-admin项目导入和导出的实现
May 21 #Vue.js
vue2实现provide inject传递响应式
May 21 #Vue.js
JS + HTML 罗盘式时钟的实现
JavaScript canvas实现流星特效
May 20 #Javascript
vue使用节流函数的踩坑实例指南
vue实现同时设置多个倒计时
May 20 #Vue.js
You might like
世界咖啡生产者论坛呼吁:需要立即就咖啡价格采取认真行动
2021/03/06 咖啡文化
Pain 全世界最小最简单的PHP模板引擎 (普通版)
2011/10/23 PHP
新手菜鸟必读:session与cookie的区别
2013/08/22 PHP
用javascript控制iframe滚动的代码
2007/04/10 Javascript
Prototype String对象 学习
2009/07/19 Javascript
js 弹出菜单/窗口效果
2011/10/30 Javascript
jquery获取对象的方法足以应付常见的各种类型的对象
2014/05/14 Javascript
仿淘宝TAB切换搜索框搜索切换的相关内容
2014/09/21 Javascript
jquery UI Datepicker时间控件的使用方法(加强版)
2015/11/07 Javascript
js如何判断输入字符串长度
2015/12/16 Javascript
简单谈谈关于Angular Cli打包的事
2017/09/05 Javascript
jQuery实现图片简单轮播功能示例
2018/08/13 jQuery
vue动态配置模板 'component is'代码
2019/07/04 Javascript
[03:49]DOTA2英雄基础教程 光之守卫
2014/01/14 DOTA
使用Python编写爬虫的基本模块及框架使用指南
2016/01/20 Python
Python 多核并行计算的示例代码
2017/11/07 Python
1分钟快速生成用于网页内容提取的xslt
2018/02/23 Python
wxPython实现文本框基础组件
2019/11/18 Python
python两个_多个字典合并相加的实例代码
2019/12/26 Python
tensorflow 实现自定义layer并添加到计算图中
2020/02/04 Python
keras中的backend.clip用法
2020/05/22 Python
对CSS3选择器的研究(详解)
2016/09/16 HTML / CSS
HTML5 常用语法一览(列举不支持的属性)
2010/01/26 HTML / CSS
HTML如何让IMG自动适应DIV容器大小的实现方法
2020/02/25 HTML / CSS
求两个数的乘积和商数,该作用由宏定义来实现
2013/03/13 面试题
在DELPHI中调用存储过程和使用内嵌SQL哪种方式更好
2016/11/22 面试题
教育孩子心得体会
2014/01/01 职场文书
男女朋友协议书
2014/04/23 职场文书
党在我心中演讲稿
2014/09/02 职场文书
2015教师年度考核评语
2015/03/25 职场文书
2015年村党支部工作总结
2015/04/30 职场文书
2016年综治和平安建设宣传月活动总结
2016/04/01 职场文书
2019年最新版见习人员管理制度!
2019/07/08 职场文书
vue-cli4.5.x快速搭建项目
2021/05/30 Vue.js
我家女友可不止可爱呢 公开OP主题曲无字幕动画MV
2022/04/11 日漫
python实现双链表
2022/05/25 Python