你必须知道的webpack插件原理分析 「详细介绍」

在 webpack 中,专注于处理 webpack 在编译过程中的某个特定的任务的功能模块,可以称为插件。它和 loader 有以下区别:

  1. loader 是一个转换器,将 A 文件进行编译成 B 文件,比如:将 A.less 转换为 A.css,单纯的文件转换过程。webpack 自身只支持 js 和 json 这两种格式的文件,对于其他文件需要通过 loader 将其转换为 commonJS 规范的文件后,webpack 才能解析到。

  2. plugin 是一个扩展器,它丰富了 webpack 本身,针对是 loader 结束后,webpack 打包的整个过程,它并不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行广泛的任务。

plugin 的特征

webpack 插件有以下特征

  • 是一个独立的模块。
  • 模块对外暴露一个 js 函数。
  • 函数的原型 (prototype) 上定义了一个注入 compiler 对象的 apply 方法。
  • apply 函数中需要有通过 compiler 对象挂载的 webpack 事件钩子,钩子的回调中能拿到当前编译的 compilation 对象,如果是异步编译插件的话可以拿到回调 callback。
  • 完成自定义子编译流程并处理 complition 对象的内部数据。
  • 如果异步编译插件的话,数据处理完成后执行 callback 回调。
class HelloPlugin {
  // 在构造函数中获取用户给该插件传入的配置
  constructor(options) {}
  // Webpack 会调用 HelloPlugin 实例的 apply 方法给插件实例传入 compiler 对象
  apply(compiler) {
    // 在emit阶段插入钩子函数,用于特定时机处理额外的逻辑;
    compiler.hooks.emit.tap('HelloPlugin', (compilation) => {
      // 在功能流程完成后可以调用 webpack 提供的回调函数;
    })
    // 如果事件是异步的,会带两个参数,第二个参数为回调函数,
    compiler.plugin('emit', function (compilation, callback) {
      // 处理完毕后执行 callback 以通知 Webpack
      // 如果不执行 callback,运行流程将会一直卡在这不往下执行
      callback()
    })
  }
}

module.exports = HelloPlugin
复制代码
  1. webpack 读取配置的过程中会先执行 new HelloPlugin(options) 初始化一个 HelloPlugin 获得其实例。
  2. 初始化 compiler 对象后调用 HelloPlugin.apply(compiler) 给插件实例传入 compiler 对象。
  3. 插件实例在获取到 compiler 对象后,就可以通过 compiler.plugin (事件名称, 回调函数) 监听到 Webpack 广播出来的事件。 并且可以通过 compiler 对象去操作 Webpack。

事件流机制

webpack 本质上是一种事件流的机制,它的工作流程就是将各个插件串联起来,而实现这一切的核心就是 Tapable

Webpack 的 Tapable 事件流机制保证了插件的有序性,将各个插件串联起来, Webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条 webapck 机制中,去改变 webapck 的运作,使得整个系统扩展性良好。

Tapable 也是一个小型的 library,是 Webpack 的一个核心工具。类似于 node 中的 events 库,核心原理就是一个订阅发布模式。作用是提供类似的插件接口。方法如下:

//  广播事件
compiler.apply('event-name', params)
compilation.apply('event-name', params)

// 监听事件
compiler.plugin('event-name', function (params) {})
compilation.plugin('event-name', function (params) {})
复制代码

我们来看下 Tapable

function Tapable() {
  this._plugins = {}
}
//发布name消息
Tapable.prototype.applyPlugins = function applyPlugins(name) {
  if (!this._plugins[name]) return
  var args = Array.prototype.slice.call(arguments, 1)
  var plugins = this._plugins[name]
  for (var i = 0; i < plugins.length; i++) {
    plugins[i].apply(this, args)
  }
}
// fn订阅name消息
Tapable.prototype.plugin = function plugin(name, fn) {
  if (!this._plugins[name]) {
    this._plugins[name] = [fn]
  } else {
    this._plugins[name].push(fn)
  }
}
//给定一个插件数组,对其中的每一个插件调用插件自身的apply方法注册插件
Tapable.prototype.apply = function apply() {
  for (var i = 0; i < arguments.length; i++) {
    arguments[i].apply(this)
  }
}
复制代码

Tapable 为 webpack 提供了统一的插件接口(钩子)类型定义,它是 webpack 的核心功能库。webpack 中目前有十种 hooks,在 Tapable 源码中可以看到,他们是:

exports.SyncHook = require('./SyncHook')
exports.SyncBailHook = require('./SyncBailHook')
exports.SyncWaterfallHook = require('./SyncWaterfallHook')
exports.SyncLoopHook = require('./SyncLoopHook')
exports.AsyncParallelHook = require('./AsyncParallelHook')
exports.AsyncParallelBailHook = require('./AsyncParallelBailHook')
exports.AsyncSeriesHook = require('./AsyncSeriesHook')
exports.AsyncSeriesBailHook = require('./AsyncSeriesBailHook')
exports.AsyncSeriesLoopHook = require('./AsyncSeriesLoopHook')
exports.AsyncSeriesWaterfallHook = require('./AsyncSeriesWaterfallHook')
复制代码

Tapable 还统一暴露了三个方法给插件用于注入不同类型的自定义构建行为:

  • tap:可以注册同步钩子和异步钩子。
  • tapAsync:回调方式注册异步钩子。
  • tapPromise:Promise 方式注册异步钩子。

webpack 里的几个非常重要的对象,Compiler, CompilationJavascriptParser 都继承了 Tapable 类,它们身上挂着丰富的钩子。

编写一个插件

一个 webpack 插件由以下组成:

  • 一个 JavaScript 命名函数。
  • 在插件函数的 prototype 上定义一个 apply 方法。
  • 指定一个绑定到 webpack 自身的事件钩子。
  • 处理 webpack 内部实例的特定数据。
  • 功能完成后调用 webpack 提供的回调。

下面实现一个最简单的插件

class WebpackPlugin1 {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    compiler.hooks.done.tap('MYWebpackPlugin', () => {
      console.log(this.options)
    })
  }
}

module.exports = WebpackPlugin1
复制代码

然后在 webpack 的配置中注册使用就行,只需要在 webpack.config.js 里引入并实例化就可以了:

const WebpackPlugin1 = require('./src/plugin/plugin1')

module.exports = {
  entry: {
    index: path.join(__dirname, '/src/main.js'),
  },
  output: {
    path: path.join(__dirname, '/dist'),
    filename: 'index.js',
  },
  plugins: [new WebpackPlugin1({ msg: 'hello world' })],
}
复制代码

此时我们执行一下 npm run build 就能看到效果了

Compiler 对象 (负责编译)

Compiler 对象包含了当前运行 Webpack 的配置,包括 entryoutputloaders 等配置,这个对象在启动 Webpack 时被实例化,而且是全局唯一的。Plugin 可以通过该对象获取到 Webpack 的配置信息进行处理。

compiler 上暴露的一些常用的钩子:

下面来举个例子

class WebpackPlugin2 {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    compiler.hooks.run.tap('run', () => {
      console.log('开始编译...')
    })

    compiler.hooks.compile.tap('compile', () => {
      console.log('compile')
    })

    compiler.hooks.done.tap('compilation', () => {
      console.log('compilation')
    })
  }
}

module.exports = WebpackPlugin2
复制代码

此时我们执行一下 npm run build 就能看到效果了

有一些编译插件中的步骤是异步的,这样就需要额外传入一个 callback 回调函数,并且在插件运行结束时执行这个回调函数

class WebpackPlugin2 {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    compiler.hooks.beforeCompile.tapAsync('compilation', (compilation, cb) => {
      setTimeout(() => {
        console.log('编译中...')
        cb()
      }, 1000)
    })
  }
}

module.exports = WebpackPlugin2
复制代码

Compilation 对象

Compilation 对象代表了一次资源版本构建。当运行 webpack 开发环境中间件时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 Compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息,简单来讲就是把本次打包编译的内容存到内存里。Compilation 对象也提供了插件需要自定义功能的回调,以供插件做自定义处理时选择使用拓展。

简单来说,Compilation 的职责就是构建模块和 Chunk,并利用插件优化构建过程。

Compiler 代表了整个 Webpack 从启动到关闭的生命周期,而 Compilation 只是代表了一次新的编译,只要文件有改动,compilation 就会被重新创建。

Compilation 上暴露的一些常用的钩子:

CompilerCompilation 的区别

  • Compiler 代表了整个 Webpack 从启动到关闭的生命周期
  • Compilation 只是代表了一次新的编译,只要文件有改动,compilation 就会被重新创建。

手写插件 1:文件清单

在每次 webpack 打包之后,自动产生一个一个 markdown 文件清单,记录打包之后的文件夹 dist 里所有的文件的一些信息。

思路:

  1. 通过 compiler.hooks.emit.tapAsync() 来触发生成资源到 output 目录之前的钩子
  2. 通过 compilation.assets 获取文件数量
  3. 定义 markdown 文件的内容,将文件信息写入 markdown 文件内
  4. 给 dist 文件夹里添加一个资源名称为 fileListName 的变量
  5. 写入资源的内容和文件大小
  6. 执行回调,让 webpack 继续执行
class FileListPlugin {
  constructor(options) {
    // 获取插件配置项
    this.filename = options && options.filename ? options.filename : 'FILELIST.md'
  }

  apply(compiler) {
    // 注册 compiler 上的 emit 钩子
    compiler.hooks.emit.tapAsync('FileListPlugin', (compilation, cb) => {
      // 通过 compilation.assets 获取文件数量
      let len = Object.keys(compilation.assets).length

      // 添加统计信息
      let content = `# ${len} file${len > 1 ? 's' : ''} emitted by webpack\n\n`

      // 通过 compilation.assets 获取文件名列表
      for (let filename in compilation.assets) {
        content += `- ${filename}\n`
      }

      // 往 compilation.assets 中添加清单文件
      compilation.assets[this.filename] = {
        // 写入新文件的内容
        source: function () {
          return content
        },
        // 新文件大小(给 webapck 输出展示用)
        size: function () {
          return content.length
        },
      }

      // 执行回调,让 webpack 继续执行
      cb()
    })
  }
}

module.exports = FileListPlugin
复制代码

手写插件 2:去除注释

开发一个插件能够去除打包后代码的注释,这样我们的 bundle.js 将更容易阅读

思路:

  1. 通过 compiler.hooks.emit.tap() 来触发生成文件后的钩子
  2. 通过 compilation.assets 拿到生产后的文件,然后去遍历各个文件
  3. 通过 .source() 获取构建产物的文本,然后用正则去 replace 调注释的代码
  4. 更新构建产物对象
  5. 执行回调,让 webpack 继续执行
class RemoveCommentPlugin {
  constructor(options) {
    this.options = options
  }
  apply(compiler) {
    // 去除注释正则
    const reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)|(\/\*\*\*\*\*\*\/)/g

    compiler.hooks.emit.tap('RemoveComment', (compilation) => {
      // 遍历构建产物,.assets中包含构建产物的文件名
      Object.keys(compilation.assets).forEach((item) => {
        // .source()是获取构建产物的文本
        let content = compilation.assets[item].source()
        content = content.replace(reg, function (word) {
          // 去除注释后的文本
          return /^\/{2,}/.test(word) || /^\/\*!/.test(word) || /^\/\*{3,}\//.test(word) ? '' : word
        })
        // 更新构建产物对象
        compilation.assets[item] = {
          source: () => content,
          size: () => content.length,
        }
      })
    })
  }
}

module.exports = RemoveCommentPlugin
复制代码

原文地址:https://blog.csdn.net/More_Action/article/details/112257740

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


一准备工作umi为react的一个封装比较nice的脚手架。优点当然就是安装方便,开箱即用,集成了诸多好用功能,详见官网。当然缺点就是无法暴露原始的webpack.config.json文件,只能按照官方文档上特定的方法进行修改,而文档却并不完善。项目顺利进行大家笑嘻嘻,一旦遇到偏难怪需求,
webpack在引入两个依赖的包时,可能需要使用shimming,意思是处理代码上的兼容1、在main.js中引入jqueryimport$from'jquery'importappendfrom'./append'//下面的给页面添加元素文件append()2、append.js使用main.js的$向页面中添加元素functionappDomFunc(){
  Happypack (plugin)多线程使用loader编译文件treeshaking删除无用JS代码,依赖ESM规范source-map生产环境一定去掉process.env.XXX配置环境变量,区分各种编译环境splitchunks 代码分离去重DynamicImport动态加载js文件Hot-module-replacement开发环境热更新w
一目录结构├──build//构建相关├──config//配置相关├──src//源代码│├──api//所有请求│├──assets//主题字体等静态资源│
接着第一节的demo现在我们尝试整合一些其他资源,比如图像,看看webpack如何处理。在webpack出现之前,前端开发人员会使用grunt和gulp等工具来处理资源,并将它们从 /src 文件夹移动到 /dist 或 /build 目录中。同样方式也被用于JavaScript模块,但是,像webpack这样的工
webpack的打包原理识别入口文件通过逐层识别模块依赖(Commonjs、amd或者es6的import,webpack都会对其进行分析,来获取代码的依赖)webpack做的就是分析代码,转换代码,编译代码,输出代码最终形成打包后的代码什么是loaderloader是文件加载器,能够加载资源文件,并对这些文件进行
上一篇文章我们在一个demo中见到了webpack的身影,如果从未接触过webpack的同学学完了上一篇文章可能会觉得webpack只是一个“翻译官”,但事实上webpack可不仅仅可以‘翻译’代码,事实上,webpack是一个‘打包’工具,‘打包‘才是webpack的核心任务。打开webpack的官网:webpack中
注:本章将配合Vue详细讲解webpack前提条件1.在开始之前,请确保安装了Node.js和npm的最新版本。使用旧版本,你可能遇到各种问题,因为它们可能缺少webpack功能以及/或者缺少相关package包。在cmd中node-v,npm-v可查看对应的版本和安装情况。2.这里会用到ES6的模块化,如果你
前言、之前我认为对于项目的优化无非是从代码上去优化一些东西,比如循环呀函数式调用呀让你的代码看起来更加的简洁容易懂后来我在面试过程中不断有面试官不断地问了我这些问题所以自己就去研究了一下发现并不是我之前想的那样,一个好的webapck优化的胜过于你在整体代码上
##一、组件component###1.什么是组件?组件(Component)是Vue.js最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码组件是自定义元素(对象)###2.定义组件的方式方式1:先创建组件构造器,然后由组件构造器创建组件方式2:直接创建组件###3.组件的分
 #webpack5概述>webpack是一个现代javascript应用程序的**静态模块打包器(modulebundler)**>>vue-cli脚手架环境,集成了webpack,所以才能对各类文件进行打包处理[webpack官网](https://webpack.js.org/) ##webpack能做什么webpack是一个静态模块打包
//这里导入webpack配置,我用对象的形式表示,当然entry属性上定义的文件要有//constconfig=require("./webpack.config");constconfig={entry:'./src/index.js'};const{join,dirname}=require("path");const{readFileSync,writeFileSync}=requi
安装cnpminstallwebpack-bundle-analyzer-D 修改vue.config.jsmodule.exports={chainWebpack:config=>{if(process.env.use_analyzer){//分析config.plugin('webpack-bundle-analyzer')
webpack打包html插件html-webpack-plugin的使用1.创建这样的测试目录结构1)index.html是即将被打包的文件,你可以随便写一些内容2)index.js是打包的入口文件,你可以写或不写内容2.webpack.config.js的代码如下:/***压缩html需要插件:html-webpack=plugin*插件使用方法:
vueinitwebpacktabbar创建vue项目时出现问题vue:无法加载文件C:\Users\dengqian\AppData\Roaming\npm\vue.ps1,因为在此系统上禁止运行脚本。C:\Windows\System32\WindowsPowerShell\v1.0,找到如上路径,以管理员身份运行powershell.exe即可输入set-ExecutionPolicyRemo
在webpack中使用ECharts【官网教程】1、使用如下命令通过npm 安装EChartsnpminstallecharts--save2、全局引用在main.jsimportechartsfrom'echarts'Vue.prototype.$echarts=echarts;3、运用<divstyle="width:60vw;height:280px;"ref="chart"&
如果没有看过我的上一篇文章,请先移步去看一下哈!时隔一年多,之前写的文章感觉大家还挺喜欢的,一直说要更新下一篇。但是一直没有机会,没时间,还有就是,感觉自己的技术,可能不足以支持我继续往下走。但是经过这个一年多的历练,感觉很多东西考虑的会更周全,不负期待!废话说一堆,步入正题
dist文件配置对应的路由名展示这样的效果可以创建一个本地的服务还可以进行代理项目开发是在src里进行代码逻辑编写./表示本地路径/表示绝对路径(nginx需要配置)
 1、用命令创建webpack模板项目vueinitwebpack 项目名,需要选是否的统一选否,所以的依赖手动添加  2、安装依赖vue-router,elementui,sass-loader,node-sass,axios插件安装路由:npminstallvue-router安装elementu
最近想把蘑菇博客部署到k8s上,作为一名java搬砖工,搬砖人不讲码德,biu一下就把后端各模块的dockerfile和chart包copy过来了,很快啊!接下来就可以愉快的使用helm管理蘑菇后端服务部署了。部署完后端服务后,准备制作前端镜像,发现前端打包后,无法动态读取系统环境变量,这很头疼,难不