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

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 会持续的进行构建。

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 并按提示登录;

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

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

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

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

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

5. 结尾

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

(0)

相关推荐

  • webpack写jquery插件的环境配置

    客户需求要一个具备树结构.带复选框的下拉选择控件:在网上找到了select2.autocomplete都不满足要求.于是自己用ztree加bootstrap dropdown组合开发了一个下拉树选择控件.决定用webpack打包,开发一个完整的jquery控件,顺便系统的学习一下webpack. 目录结构: package.json配置: { "name": "select-tree", "version": "0.0.1",

  • 详解使用webpack打包编写一个vue-toast插件

    本文介绍了使用webpack打包编写一个vue插件,分享给大家.具体如下: 一.说明: 需求:创建一个toast插件 思路:利用vue组件创建模板,使用webpack打包生成插件再全局使用. # 项目目录: |_ package.json |_ webpack.config.js |_ .babelrc |_ dist |_ src |_ index.html |_ lib |_ index.js |_ vue-toast.vue 1.1 webpack基础 1.基础插件 - html-webp

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

    1. 前言 最近在学习 Webpack 相关的原理,以前只知道 Webpack 的配置方法,但并不知道其内部流程,经过一轮的学习,感觉获益良多,为了巩固学习的内容,我决定尝试自己动手写一个插件. 这个插件实现的功能比较简单: 默认清除 js 代码中的 console.log 的打印输出: 可通过传入配置,实现移除 console 的其它方法,如 console.warn.console.error 等: 2. Webpack 的构建流程以及 plugin 的原理 2.1 Webpack 构建流程

  • Python+tkinter使用80行代码实现一个计算器实例

    本文主要探索的是使用Python+tkinter编程实现一个简单的计算器代码示例,具体如下. 闲话不说,直奔主题.建议大家跟着敲一遍代码,体会一下代码复用.字符串方法的运用和动态创建组件的妙处,然后在这个框架的基础上进行补充和发挥. 选择任何一款Python开发环境,创建一个程序文件,命名为tkinter_Calculator.pyw,然后编写下面的代码: 1)导入标准库re和tkinter,创建并简单设置应用主程序,在窗口顶部放置一个只读的文本框用来显示信息. 2)编写计算器上各种按钮的通用处

  • Python的爬虫框架scrapy用21行代码写一个爬虫

    开发说明 开发环境:Pycharm 2017.1(目前最新) 开发框架:Scrapy 1.3.3(目前最新) 目标 爬取线报网站,并把内容保存到items.json里 页面分析 根据上图我们可以发现内容都在类为post这个div里 下面放出post的代码 <div class="post"> <!-- baidu_tc block_begin: {"action": "DELETE"} --> <div class=

  • Python用5行代码写一个自定义简单二维码

    python的优越之处就在于他可以直接调用已经封装好的包 首先,下载pillow和qrcode包  终端下键入一下命令: pip3 install pillow #python2 用pip install pillow pip3 install qrcode 实现代码: import qrcode # 定义一个类名 def qrcodeWithUrl(url): img = qrcode.make(url) # 生成一个二维码 savePath = "baidu.png" # 存储二维

  • so easy!10行代码写个"狗屁不通"文章生成器功能

    前几天,GitHub 有个开源项目特别火,只要输入标题就可以生成一篇长长的文章. 背后实现代码一定很复杂吧,里面一定有很多高深莫测的机器学习等复杂算法 不过,当我看了源代码之后 这程序不到50行 尽管我有多年的Python经验,但我竟然一时也没有看懂 这代码放到编辑器里还特么真能执行 当然啦,原作者也说了,这个代码也是在无聊中诞生的,平时撸码是不写中文变量名的, 中文变量名只是最开始瞎写的时候边写语料边写代码时懒得切英文输入法了. 中文变量名也就忍了,但代码逻辑不好懂,最后我还是忍着剧烈的头痛把

  • python实战之90行代码写个猜数字游戏

    一.导入库 import random import time 二.注册用户 我们用变量与input实现 name = str(input('请输入用户名:')) print('欢迎您,'+name) 三.注册年龄 这里我们得用except制作乱输文本就游戏结束的程序 乱输文本就结束 try: age = int(input('请输入年龄:')) except ValueError: print('非法输入') age = 30000 顺便把年龄设为30000[滑稽] 再根据年龄大小分配金币 四

  • 利用Java代码写一个并行调用模板

    目录 前言: 1. 一个串行调用的例子 2. CompletionService实现并行调用 3. 抽取通用的并行调用方法 4. 代码思考以及设计模式应用 5. 思考总结 前言: 本文主要介绍内容有: 一个串行调用的例子(App首页信息查询) CompletionService实现并行调用 抽取通用的并行调用方法 代码思考以及设计模式应用 思考总结 1. 一个串行调用的例子 如果让你设计一个APP首页查询的接口,它需要查用户信息.需要查banner信息.需要查标签信息等等. 一般情况,小伙伴会实

  • 100行代码实现一个vue分页组功能

    今天用vue来实现一个分页组件,总体来说,vue实现比较简单,样式部分模仿了elementUI.所有代码的源码可以再github上下载的到:下载地址 先来看一下实现效果: 点击查看效果 整体思路 我们先看一下使用到的文件的目录: 我们在 pageComponentsTest.vue 页面引入了 pageComponent.vue 分页组件.整体思路是通过 props 来达到组件的灵活通用的效果,整体语法是使用vue的VM语法. pageComponent.vue实现 首先实现一个分页,需要知道数

  • iOS使用核心的50行代码撸一个路由组件

    使用组件化是为了解耦处理,多个模块之间通过协议进行交互.而负责解析协议,找到目的控制器,或者是返回对象给调用者的这个组件就是路由组件.本文讲解如何使用核心的50行代码实现一个路由组件. 组件化和路由 路由的实现 路由注册实现 路由使用实现 客户端的使用 一些小想法 组件化和路由 之前看过挺多的关于路由管理.路由处理的文章,常常会和组件化出现在一起,一开始不知道为何路由和组件化出现在一起,后来公司的项目中使用了路由组件(他本身也是一个组件,确切的说是一个中间人或者中介者),才突然想明白了,原来如此

  • vue封装第三方插件并发布到npm的方法

    前言 写此文前特意google了一下,因为有较详细的开发教程我再写意义不大,有把插件封装成组件的教程,有把自己的组件封住成插件的教程,本文主要说明如何把第三方的插件封装成vue插件,简化配置,一键安装,主要提供思路,封装方法大同小异·,文章略长要有耐心. gitment gitment是一个基于github issues封装的评论插件,以这个插件作为演示,把它封装成vue插件.vue-gitment,该插件已发布到npm,并在自己的开源项目vueblog中安装使用 项目初始化 封装vue的插件用

随机推荐