超详细webpack的plugin讲解,看不懂算我输,案例>原理>实践

2023-11-15

前言

本篇文章为webpack系列文章的第三篇,主要内容是对webpack的plugin进行详细的讲解,从使用,到原理,再到自己开发一个plugin,对每个过程都会进行详细的分析介绍。如果你对webpack了解的还比较少,建议你先阅读以下往期文章。

如何使用webpack实现模块化打包

webpack的核心机制之loader的秘密

介绍

plugin是webpack的一个插件机制,它为项目的构建提供了更加广泛的能力,loader的作用只是实现各种资源的转换,使得任何资源都可以模块化的被打包。而plugin可以解决其他的更多的自动化打包工作,plugin的范围更大,作用也更强。

  • 可以自动打包生成html文件,并自动引入打包后的结果
  • 打包前清除原dist文件中的内容
  • 可以将我们需要的但是并没有引入静态资源一同打包到dist文件中
  • 对打包的结果进行特殊的处理
  • 压缩打包后的内容,对打包结果可以进行更细的自定义操作

  • plugin的作用还远不止这些,可以看出plugin的重要性,下面我们来看几个常用的plugin,然后去深入它的原理,最后自己写一个简单的plugin。

情景再现

情景1

通过往期的学习我们知道,每次打包后的内容默认会输出到dist文件夹中,如果在原dist文件夹中存在相同文件名,则会覆盖原文件,使得打包结果始终为最新的内容。但是有没有想过这样的场景,如果原dist中存在某些文件已经被我们遗弃,也就是说后面我们不需要这些文件了,但是他们还是存在于dist中,这种情况下我们还要去提取出来哪些使我们需要的文件,这个过程显然是很繁琐的。

那如何解决这个问题呢,如果我们在打包输出前清除掉dist中原来的内容,那么打包后的内容必然都是我们需要的。我们每次手动的去清理也比较麻烦,如果有个插件可以在我们打包前自动帮我们清理就好了。

clean-webpack-plugin这个插件就可以实现这个功能,它是一个第三方npm包,我们只需要安装就可以使用。

npm install clean-webpack-plugin

在webpack.config.js中引入,我们只需要用到它其中的CleanWebpackPlugin就行。

const {CleanWebpackPlugin} = require('clean-webpack-plugin')

使用插件是在plugins属性中,它是一个数组,表示可以使用多个插件,我们只需要将上面的对象实例化后写在数组中即可。

module.exports = {
    mode: 'none',
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
    },
    plugins:[
        new CleanWebpackPlugin(),
    ]
}

现在我们执行打包命令

可以看到在原dist中含有很多个文件,我这里打包后会自动的清理掉。

情景2

在我们打包完成后会生成结果文件,我们需要手动的在index.html中去引入,如果结果文件名改变了,还需要手动的去修改引入的文件名。如果有个插件可以在打包完成后自动的生成一个html文件,并自动引入script标签就好了。其实这些功能我们可以使用html-webpack-plugin插件来实现。

npm install clean-webpack-plugin

引入

const HtmlWebpackPlugin =  require('html-webpack-plugin')

配置

...
     plugins:[
        new HtmlWebpackPlugin(),
     ]
...

我们可以在实例化时传入配置参数,这个插件提供了很多灵活的配置项。

  • title:标题
  • meta:meta标签
  • filename:输出的文件名
  • template:使用已有模板,有时在index.html中含有一些其他的内容,可以选用该html文件为模板。
  • minify:压缩html文件
  • favicon:favicon的路径

  • 这里我只列出了其中的部分配置,更多详情可以去查阅该插件的说明文档。
new HtmlWebpackPlugin({
    title: '测试标题',
    meta:{
        keywords: 'webpack,plugin'
    },
    filename: 'webpack.html',
    template: 'index.html',
    minify:{   //压缩html文件
        caseSensitive: true,   //是否对大小写敏感,默认false         
        collapseBooleanAttributes: true,   //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled  默认false           
        collapseWhitespace: true,  //是否去除空格,默认false           
        minifyCSS: true,  //是否压缩html里的css(使用clean-css进行的压缩) 默认值false;
        minifyJS: true,  //是否压缩html里的js(使用uglify-js进行的压缩)           
        preventAttributesEscaping: true,  //Prevents the escaping of the values of attributes            
        removeAttributeQuotes: true,  //是否移除属性的引号 默认false           
        removeComments: true,  //是否移除注释 默认false           
        removeCommentsFromCDATA: true,  //从脚本和样式删除的注释 默认false           
        removeEmptyAttributes: true,  //是否删除空属性,默认false           
        removeOptionalTags: false,  //  若开启此项,生成的html中没有 body 和 head,html也未闭合           
        removeRedundantAttributes: true,   //删除多余的属性      
        removeScriptTypeAttributes: true,  //删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false        
        removeStyleLinkTypeAttributes: true,   //删除style的类型属性, type="text/css" 同上
        useShortDoctype: true,  //使用短的文档类型,默认false
    },
    favicon: './public/logo.ico'
}),

在配置中我选用了自己的index.html作为模板,原有的index.html内容如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>wepack-plugins</title>
</head>
<body>
    <div>
        <span>我是原html中的内容</span>
    </div>
</body>
</html>

在index.js中动态加入了一个标签

var dom = document.createElement('h2')
dom.innerHTML = '我是外部新增的内容'
document.body.append(dom)

执行打包,可以在dist下面出现一个webpack.html文件,这是因为我们在配置中配置了输出文件名为webpack.html。在配置中还对html进行了压缩配置,所以看到的是被压缩后的结果。

我们取消html压缩配置,重新打包

可以看到我们的配置全部生效,包括标题、meta等信息。在浏览器中预览结果完全符合我们的预期。

情景3

最后再来介绍一个插件叫做copy-webpack-plugin,看名字就知道它与复制有关。我们在项目中会用到很多静态资源,但有些可能并没有被直接的使用,我们希望这些资源可以一同的被打包到dist中。

新建一个public文件夹,假如说这个文件夹中的所有内容我们都希望被打包。为了测试结果,在里面我们新建一个static.txt文件,这个文件我们没有在任何地方使用到。

它接收的是一个数组,因为可能存在多个不同的目录

const CopyWebpackPlugin = require('copy-webpack-plugin')
...
new CopyWebpackPlugin(['./public']),

执行打包

通过上面几个案例,是不是可以看出plugin的强大能力和它的重要地位了,总有一个插件可以满足我们的需求。有人会说了,万一我就是没有找到适合我的需求的插件呢?

这个世界上本是没有轮子的,没有轮子我们就要学会去造轮子,下面我们就来造一个自己的轮子。

要学会造轮子

在造轮子之前我们必须要知道它的原理,plugin相比loader还有一点很大的不同,loader只工作于模块的加载环节,而plugin即可可以作用于打包过程的每一个环节,有点像vue中的生命周期,我们可以在一个合适的周期进行相应的操作。webpack的插件机制就是我们常说的钩子机制,整个打包过程可以分为多个环节,为了便于插件的扩展,webpack机会在每个环节都提供了一个钩子,我们就可以利用这些钩子来造轮子。

webpack为我们提供了哪些hooks呢?

  • entry-option 初始化 option
  • run 开始编译
  • compile 真正开始的编译,在创建 compilation 对象之前
  • compilation 生成好了 compilation 对象
  • make 从 entry 开始递归分析依赖,准备对每个模块进行 build
  • after-compile 编译 build 过程结束
  • emit 在将内存中 assets 内容写到磁盘文件夹之前
  • after-emit 在将内存中 assets 内容写到磁盘文件夹之后
  • done 完成所有的编译过程
  • failed 编译失败的时候

  • 更多hooks请参考官方文档:https://www.webpackjs.com/api/compiler-hooks/
    现在我们知道了生产线,我们在生产线的不同阶段做不同的事情。但是我们还是不知道怎么造轮子啊。

    webpack要求我们的插件必须是一个函数,或者是一个包含apply的对象。一般来说我们都会定义一个类型,然后在这个类型中定义apply方法,最后再通过这个类型来创建一个实例对象去使用这个插件。
const pluginName = 'myplugin'
module.exports =  class myplugin {
    apply(){}
}

这个apply方法接收一个叫compiler的参数对象,这个对象是webpack工作中最核心的对象,包含了此次打包构建的所有配置信息,我们就可以通过这个对象去注册钩子函数。

const pluginName = 'myplugin'
module.exports =  class myplugin {
    apply(compiler){
        compiler.hooks.run.tap(pluginName, () =>{
            {
                console.log('开始编译');
            }
        })
    }
}

我们想在run阶段输出‘开始编译’这句话,在webpack.config.js中引入并配置

const myplugin = require('./myplugin')
...
plugins:[
  new myplugin()
]
...

在这里插入图片描述
在控制台可以看到在开始阶段输出了内容,说明我们的plugin生效了,这只是测试,接下来我们就来实现点功能。

我们在使用node或者development模式下,打包后的js文件中,前面会有许多这样的注释符,看起来很不舒服。

我们可以自己来造个轮子让webpack打包后的内容中没有这些东西。

第一步,我们要找到合适的环节执行我们要进行的操作。通过查看API文档,可以找到emit这个hooks很符合我们的场景,它在生成资源到output目录之前执行,也就是在还没有输出打包文件时执行。

我们的思路是这样的,在即将输出文件的前面,获取到要输出的文件内容,找到以js为后缀的文件,然后去掉里面的注释符,最后再重新将处理后的内容替换原来要输出的内容。

./remove-comments-plugin

const pulginName = 'RemoveCommentsPlugin'
module.exports = class RemoveCommentsPlugin {
    apply(compiler) {
        compiler.hooks.emit.tap(pulginName, (compilation) => {
            for (const name in compilation.assets) {
                if (name.endsWith('js')) {
                    let contents = compilation.assets[name].source()
                    let noComments = contents.replace(/\/\*{2,}\/\s?/g, '')
                    compilation.assets[name] = {
                        source: () => noComments,
                        size: () => noComments.length
                    }
                }
            }
        })
    }
}

它接收一个叫 compilation 的参数对象,这个对象可以理解为本次运行打包的上下文,它包含了所有打包过程中产生的结果。它的assets属性中包含的就是所有打包后将要输出的文件,source()方法可以获取到文件内的内容。

这里我们遍历所有即将要输出的文件,使用endsWith(‘js’)方法匹配到js文件(这是ES6的一个方法),然后使用正则将所有的注释符替换为空字符串,最后将修改后的内容覆盖掉原来的内容。这里要注意一点,在改变文件内容后需要重新计算文件的大小,否则size的值可能会与实际值不匹配。

const RemoveCommentsPlugin from './remove-comments-plugin'
...
plugins:[
  new RemoveCommentsPlugin()
]
...

查看打包后的结果,已经不存在注释符。

对于有些插件需要传递配置参数,这个也很简单,我们只需要在构造函数中进行接收即可。

const pulginName = 'RemoveCommentsPlugin'
module.exports = class RemoveCommentsPlugin {
    constructor(params){
        this.config = {}
        for (const key in params) {
            if (this.config.hasOwnProperty(key)) {
                this.config[key] = params[key]
            }
        }
    }
    apply(compiler) {
        compiler.hooks.emit.tap(pulginName, (compilation, params) => {
            for (const name in compilation.assets) {
                if (name.endsWith('js')) {
                    let contents = compilation.assets[name].source()
                    let noComments = contents.replace(/\/\*{2,}\/\s?/g, '')
                    compilation.assets[name] = {
                        source: () => noComments,
                        size: () => noComments.length
                    }
                }
            }
        })
    }
}

每次都不知道怎么写结语,这都不重要,重要到的可以学到东西,学以致用就够了。
最后觉得我的文章对你有所帮助的话,希望可以收藏、点赞、关注,你们的支持是我最大的动力

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

超详细webpack的plugin讲解,看不懂算我输,案例>原理>实践 的相关文章

  • 使用 JavaScript 格式化日期

    JavaScript 中的日期格式有问题 这是我的函数代码 originalDate 2016 03 02 09 12 14 989522 var d new Date originalDate month d getMonth 1 day
  • 如何使用 JavaScript 刷新页面?

    如何使用 JavaScript 刷新页面 Use location reload https developer mozilla org en US docs Web API Location reload 例如 每当元素带有以下内容时重新
  • jQuery UI Datepicker 奇怪的行为

    我在使用 jqueryUI 使用简单的日期选择器时遇到了一个奇怪的问题 我只想显示两个月的日历 包括上个月和当前月份 我使用了这段代码 function picker datepicker numberOfMonths 2 showCurr
  • Dialogflow Fulfillment Webhook 调用失败

    I am new to dialogflow fulfillment and I am trying to retrieve news from news API based on user questions I followed doc
  • 需要参数的addEventListener(和removeEventListener)函数

    我需要向 8 个对象 手掌 添加一些侦听器 这些对象是相同的 但行为必须根据它们的位置而改变 我有以下 丑陋的 代码 root palmsStatus B B B B B B B B if root palmsStatus 0 N root
  • 在自动完成上添加 jQuery 延迟

    我正在尝试为应用程序创建 jQuery 自动完成 search input on keyup function search this val autocomplete div autocomplete get ajax search se
  • 从对象中取出具有无效(NaN、空白等)值的键的最佳方法?

    我有一个供用户填写的简短搜索表单 将有多个搜索查询进入 MongoDB 该表单创建一个名为的变量searchParams可能看起来像这样 var searchParams city Springfield bedrooms 3 bathro
  • Pjax动画

    我终于让 pjax 工作了 但我还有另一个问题 如何添加一些 jquery 动画 如淡出 幻灯片旧内容和淡入 幻灯片新内容 默认情况下 pjax 只是更改内容 没有任何好看的效果 任何帮助将非常感激 此致 基本上 你有一堆事件 https
  • fadeOut() 和slideUp() 同时进行?

    我已经发现jQuery 淡出然后滑动 https stackoverflow com questions 734554 jquery fadeout then slideup这很好 但不是那个 我怎么能够fadeOut and slideU
  • Javascript:我应该隐藏我的实现吗?

    作为一名 C 程序员 我有一个习惯 将可以而且应该私有的东西设为私有 当 JS 类型向我公开其所有私有部分时 我总是有一种奇怪的感觉 而且这种感觉并没有被 唤起 假设我有一个类型draw方法 内部调用drawBackground and d
  • 如何在具有相同值的下拉菜单上触发 jQuery 更改事件

    即使用户选择相同的值 如何每次都触发 jQuery 更改事件 我需要刷新效果 例如如果用户选择Lawyer它会发出警报hello然后用户再次选择Lawyer从下拉菜单中 它应该发出警报hello 我怎样才能实现它 以下是代码 jQuery
  • 如何按 Angular 表中的属性(该属性具有单个 rownspan)进行分组?

    我没有找到这个问题的合适标题 我的问题是 例如 我有一个包含两列的表 列汽车品牌和列汽车型号 我希望表是 like in this picture 换句话说 品牌名称只会出现 1 次 我的输入数组采用以下 json 格式 brand Aud
  • 嵌套 DIV 的类似斑马的 CSS 样式

    我嵌套了 DIV 元素 但我不知道嵌套的级别 我需要每个都有与其父级不同的背景 创建类似斑马的颜色 我只使用两种背景 深色和白色 效果需要类似于在容器中设置奇数和偶数子级的样式 但在我的例子中 子级是嵌套的 我可以使用每个嵌套元素的规则来做
  • 如何使用 GreaseMonkey 让浏览器恢复“/”键?

    Lots of web pages seem to use the key for searching I d like to disable that because 100 of the time I want to use to se
  • jQuery 中什么函数相当于 .SelectMany()?

    让我解释一下 我们知道 jQuery 中的映射函数充当 Select 如 LINQ 中 tr map function return this children first returns 20 tds 现在的问题是我们如何在 jQuery
  • 使 Bootstrap Popover 在悬停而不是单击时出现/消失

    我正在使用 Bootstrap 构建一个网站Popover http twitter github com bootstrap javascript html popovers我不知道如何使弹出窗口出现在悬停而不是单击时 我想做的就是当有人
  • 在javascript中创建图像的缩略图方块(不丢失纵横比)

    我正在制作一个客户端拖放文件上传脚本作为书签 在上传之前 我使用 File API 将图像读取为 base64 格式并将其显示为缩略图 This is how my thumbnails look like I want them to l
  • 将两个数字相加将它们连接起来而不是计算总和

    我将两个数字相加 但没有得到正确的值 例如 做1 2返回 12 而不是 3 我在这段代码中做错了什么 function myFunction var y document getElementById txt1 value var z do
  • 使用 File API polyfill 读取数据 URL

    我正在尝试使用文件 API 库 https github com mailru FileAPI https github com mailru FileAPI 作为不支持文件 API 的浏览器的后备 以便将文件作为数据 URL 读取并将其传
  • Jquery 以编程方式更改

    文本

    编辑 解决方案是将其添加到个人资料页面而不是性别页面 profile live pageinit function event p pTest text localStorage getItem gender 我在列表视图中有一个带有一些文

随机推荐

  • FileSystemObject组件新建/读取/添加/修改/删除功能实例

  • MAC快捷键对照表

    Mac 键盘符号说明 Command Shift Caps Lock Option Control Return Enter Delete 向前删除键 Fn Delete 上箭头 下箭头 左箭头 右箭头 Page Up Fn Page Do
  • matlab kl散度,基于KL散度的面向对象遥感变化检测

    3 2 实验过程与结果 3 2 1 面向对象的遥感影像分割 利用ENVI软件的Segment Only Feature Extraction功能模块对实验数据进行面向对象分割操作 该方法采用的是Full Lambda Schedule分割算
  • 多益2018春招前端技术面试

    2018春招 多益网络内推 前端开发工程师 技术面问题 据我了解 从一月到三月份年 多益一共启动了至少三批春招招聘 从同学的反馈来看 一月份考验刚结束的那批春招通过率比较高 至于我个人 参加的是三月份上中旬的春招内推 可惜并没有通过 总体上
  • 虚拟机克隆后无法连接网络

    本文章转载自 Linux 无线图标莫名消失的解决方法 xin1889的博客 CSDN博客 今天我的也是的觉得再弄个虚拟机麻烦 索性就直接克隆了 然后连不上网 就连接原来的可以 连接百度也可以 但是唯独ping自己不行 然后不知道怎么回事 同
  • 智能指针和函数模板

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
  • c++学习笔记-#pragma once

    pragma 预处理宏的一般格式 pragma one one是参数 1 pragma message message 当编译器遇到这条指令时 它会在编译输出窗口中将message打印出来 常用于宏条件条件判断中使用 这样就知道自己的宏条件
  • 串口通信知识点总结

    串口通信小试牛刀 一 串口协议和RS 232标准 一 串口通讯协议 1 简介 2 串口通信的基本原理 二 RS232串口标准 1 简介 2 RS232串行接口的特点 3 RS232串行接口的电气特性 二 RS232电平与TTL电平 一 TT
  • Pytorch中多GPU并行计算教程

    如果不想看文字的 可以在我bilibili上看录制的视频教程 Pytorch多GPU使用教程 本教程所涉及的代码可自行在我的github上下载 https github com WZMIAOMIAO deep learning for im
  • layui添加菜单和动态操作tab

    layui添加菜单和动态操作tab 代码一 使用模板引擎渲染菜单 代码二修改版 代码一 使用模板引擎渲染菜单
  • JQ奇偶选择

    table tr even click function console log 选择奇数行 表示获取一个table 所有的索引为偶数的行 其中索引index 从0开始算起 0算偶数 table tr odd click function
  • Java运行时一个类是什么时候被加载的?

    A question a day get offer every day 未来的魔法师 一个类在什么时候开始被加载 Java虚拟机规范 中并没有进行强制约束 交给了虚拟机自己去自由实现 HotSpot虚拟机是按需加载 在需要用到该类的时候加
  • ThreadLocal源码分析,线程局部变量,内存泄漏?

    ThreadLocal作为线程局部变量 线程级的 单个线程内共享的 一般来说可以有两方面的用途 作为共享变量 在某些方法计算的结果 要共享到其他方法 在使用时 通过threadLocal set 设置值 通过threadLocal get
  • 源码断点分析Spring的占位符(Placeholder)是怎么工作的

    项目中经常需要使用到占位符来满足多环境不同配置信息的需求 比如
  • 国内可用的ntp服务器地址

    ntp sjtu edu cn 202 120 2 101 上海交通大学网络中心NTP服务器地址 s1a time edu cn 北京邮电大学 s1b time edu cn 清华大学 s1c time edu cn 北京大学 s1d ti
  • 程序员常用的计算机cmd指令

    windows cmd 查看command命令帮助说明 calc 计算器 mspaint 图画 notepad 记事本 dir 遍历当前目录 cd 路径名 进入该目录 cd 返回上级目录 netstat ano 查看端口占用 netstat
  • java中获取比毫秒更为精确的时间

    关键词 java 毫秒 微秒 纳秒 System currentTimeMillis 误差 在对新写的超快xml解析器和xpath引擎进行效率测试时 为获取执行时间 开始也没多想就用了System currentTimeMillis 来做的
  • 【Metashape精品教程17】导出产品和报告

    Metashape精品教程17 导出产品和报告 文章目录 Metashape精品教程17 导出产品和报告 前言 一 导出空三 二 导出DEM 三 导出DOM 四 导出点云 五 生成报告 前言 本章是整套教程的终结 简单介绍一下Metasha
  • 声学测试软件手机版_最新手机性能排名:小米84万分拿到第一,iQOO5Pro第五,华为?...

    华为Mate40 Pro首发麒麟9000处理器 安兔兔跑分高达69 是今年最强旗舰 不过在此之前还是以骁龙865 麒麟990 5G为主 鲁大师发布了2020年Q3季度手机性能排行榜 第一名的跑分高达84万 第一名 小米10至尊纪念版 84
  • 超详细webpack的plugin讲解,看不懂算我输,案例>原理>实践

    前言 本篇文章为webpack系列文章的第三篇 主要内容是对webpack的plugin进行详细的讲解 从使用 到原理 再到自己开发一个plugin 对每个过程都会进行详细的分析介绍 如果你对webpack了解的还比较少 建议你先阅读以下往