Webpack知识点整理(webpack菜鸟教程)
为什么要使用Webpack?
要理解 webpack 是什么,我们先记住这两个词:- 模块- 打包
模块化理想的方式是在页面中引入一个 JS 入口文件,其余用到的模块可以通过代码控制
按需加载
模块化的方式划分出来的模块文件过多,而前端应用又运行在浏览器中,每一个文件都需要单独从服务器请求回来,零散的模块文件必然会导致浏览器的频繁发送网络请求,影响应用的工作效率。那是不是我把所有 JavaScript 文件合成一个文件就好了呢?没错,这样就减少了 http 请求数量,让我们的页面加载和显示更快,但是如果在开发过程就合并这些文件,将导致项目难以维护,在开发后完成的这个合并的过程就是
打包
为什么Webpack要在JS中加载其他资源?
假设在开发页面上的某个局部功能,需要用到一个样式模块和一个图片文件,如果你还是将这些资源文件单独引入到 HTML 中,然后再到JS中添加对应的逻辑代码,试想如果后期这个局部功能不用了,你就需要同时删除JS中的代码和 HTML 中的资源文件引入,也就是需要同时维护两条线
。而如果你遵照 Webpack 的这种设计,所有资源的加载都是由JS 代码控制,后期也就只需要维护 JS 代码这一条线。
常用的loader
css-loader:Webpack 是用 JS 写的,运行在 node 环境,所以默认 Webpack 打包的时候只会处理 JS 之间的依赖关系,如果在 JS 中导入了 css,那么就需要使用 css-loader 来识别这个模块,通过特定的语法规则进行转换内容最后导出 css-loader 会处理 import / require() / @import / url 引入的内容。
style-loader:css-loader 只会把 css 模块加载到 JS 代码中,并不会使用这个模块,因为 css-loader 处理之后导出的是个数组,页面是无法直接使用,这时我们需要用到零外一个 style-loader 来处理,style-loader 是通过一个 JS 脚本创建一个 style 标签,里面包含一些样式。
file-loader:在css文件中定义 background 的属性或者在html中引入 image 的src,我们知道在 webpack 打包后这些图片会打包至定义好的一个文件夹下,和开发时候的相对路径会不一样,这就会导致导入图片路径的错误。而 file-loader 正是为了解决此类问题而产生的,他修改打包后图片的储存路径,再根据配置修改我们引用的路径,使之对应引入。
url-loader:如果页面图片较多,发很多http请求,会降低页面性能。这个问题可以通过 url-loader 解决。url-loader 会将引入的图片编码,生成 dataURl 并将其打包到文件中,最终只需要引入这个 dataURL 就能访问图片了。当然,如果图片较大,编码会消耗性能。因此 url-loader 提供了一个limit参数,小于 limit 字节的文件会被转为 DataURl,大于 limit 的还会使用 file-loader 进行copy。url-loader 内置了 file-loader。
babel-loader:如今 ES6 语法在开发中已经非常普及,甚至也有许多开发人员用上了 ES7 或 ES8 语法。然而,浏览器对这些高级语法的支持性并不是非常好。因此为了让我们的新语法能在浏览器中都能顺利运行,Babel 应运而生。Babel是一个 JavaScript 编译器,能够让我们放心的使用新一代JS语法。
常用的plugin
clean-webpack-plugin:每次打包后自动清理dist目录。
html-webpack-plugin:相比于之前写死HTML文件的方式,自动生成HTML的优势在于: 1.HTML也输出到dist目录中了,上线时只需要把dist目录发布出去。
2.HTML中的script标签是自动引入的,所以可以确保资源文件的路径是正常的。DefinePlugin:这是一个定义全局变量的插件,定义的变量可以在webpack打包范围内任意 javascript 环境内访问,使用字符串或 JSON.stringify() 转换值。
MiniCssExtractPlugin:将 css 单独打包成一个文件的插件,它为每个包含 css 的 js 文件都创建一个 css 文件,结合 html-webpack-plugin,以 link 的形式插入到 html 文件中。它支持 css 和 sourceMaps 的按需加载。目前只有在 webpack V4 版本才支持使用该插件。这个插件应该只在
生产环境构建
中使用,并且在 loader链中不应该有style-loader
,特别是我们在开发模式中使用 HMR 时。此插件不支持 HMR,若修改了样式文件,是不能即时在浏览器中显示出来的,需要手动刷新页面。HotModuleReplacementPlugin:HMR 作为一个 Webpack 内置的功能,可以通过 --hot 或者 HotModuleReplacementPlugin 开启。具体介绍热更新请看之前有关热更新讲解的文章。
loader和plugin的区别
loader是
转换器
,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中。1.处理一个文件可以使用多个loader,loader 的执行顺序和配置中的顺序是相反的,即最后一个loader最先执行,第一个 loader 最后执行。
2.第一个执行的 loader 接收源文件内容作为参数,其它 loader 接收前一个执行的 loader 的返回值作为参数,最后执行的 loader 会返回此模块的 JavaScript 源码 。plugin 是
插件扩展器
,在 webpack 运行的生命周期中会广播出许多事件,plugin 可以监听这些事件,在合适的时机通过 webpack 提供的 API 改变输出果。针对webpack打包的过程,它不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些事件钩子,执行任务。plugin 比 loader 强大,通过plugin 可以访问 compliler 和 compilation 过程,通过钩子拦截 webpack 的执行。
Webpack编译流程
解析 webpack 配置参数,合并从 shell 传入和 webpack.config.js 文件里配置的参数,进行错误检查,生产最后的配置结果。
注册所有配置的插件和初始化 complier,好让插件监听 webpack 构建生命周期的事件节点,以做出对应的反应。(利用 tapable 将 plugin 挂载在特定的钩子上 hook)
开始编译主入口,从配置的 entry 入口文件开始解析文件构建 AST 语法树,找出每个文件所依赖的文件,递归下去。
在解析文件递归的过程中根据文件类型和 loader 配置找出合适的 loader 用来对文件进行转换。
module 优化,为 module 增加ID。
递归完后得到每个文件的最终结果,根据 entry 配置生成代码块 chunk。
输出所有 chunk 到文件系统。
模块加载
核心方法
webpack_modules:所有的模块标记使用和被使用used、unused(production环境下或sideEffect:true,会被去掉)
webpack_module_cache:缓存执行过的模块
webapck_require:以 moduleId 为参数,调用时先从 cache 中查找,如果找到则直接返回(return cachedModule.exports),如果找不到则创建一个新模块放入缓存并执行这个模块方法,返回模块的导出。
__webpack_require__
所在的闭包能访问外层变量modules
和缓存installedModules
。这个很关键,因为modules
是 webpack 打包后立即执行函数传入的参数。同步加载
直接通过 webpack_require 加载模块
异步加载
require.ensure()和import()本质上都是动态创建一个script
将import()的模块打包成单独的 chunk
入口文件执行,为全局的 self[webpackJsonp]构造 push,即当下载完成后执行:webpackJsonpCallback(①将 module 保存进全局当 modules ②遍历 chunkIds,设置 installedChunks 标志已下载)
执行 import(),构造 script,利用 jsonP 下载,返回一个 promise包裹的结果,调用__webpack_require__,拿到保存在全局 modules 中的模块,执行 then()方法。
loader解析
构造ruleSet实例:
合并用户配置、内置的规则,在后面匹配起到过滤作用.
分类处理:
有内联路径则处理字符串,生成内联的loader数组,通过 resolveRequestsArray 递归解析 loader,完成回调 continueCallback。
无内联路径则直接解析文件路径,完成回调 continueCallback。
loader处理module:
module 创建——>开始 build——>创建 loaderContext 上下文——>runloader——>parse module content
核心在于 runLoader,pitch 向下,达到最后一个 loader,相当于是一个拦截器功能,再回溯向上执行loader
chunk生成
准备工作:
以入口文件建立一个 chunkGroup
建立 chunk 和 chunkGroup 关系
建立 module 和 chunk 关系
visitModule:
根据 module graph 建立 chunk graph
优化chunk graph:
剔除重复的依赖
Webpack性能优化
提高构建速度
通过 exclude、include 缩小搜索范围
配置 resolve.modules:[path.resolve(__dirname,'node_modules')]避免层层查找
设置 resolve.alias,使 webpack 直接使用库的 min 文件,避免库内解析,利用UnsafeCachePlugin缓存,并且减少文件路径的查找
设置 resolve.extensions ,减少文件查找,设置 resolve.extensions 中的值,列表值尽量少,频率高的文件类型的后缀写在前面。['js','less']
缓存之前构建过的js
将Babel编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间。
loader:'babel-loader?cacheDirectory=true'
并行构建:
受限于 Node 是单线程运行的,所以 Webpack 在打包的过程中也是单线程的,特别是在执行 Loader 的时候,长时间编译的任务很多,这样就会导致等待的情况。HappyPack和ThreadLoader作用是一样的,都是同时执行多个进程,从而加快构建速度。而Thread-Loader是webpack4提出的。
采用HappyPack开启多进程Loader
使用方式
npm i happypack -D // webpack.config.json const path = require('path'); const HappyPack = require('happypack'); module.exports = { //... module:{ rules:[{ test:/.js$/, // 这里的id对应下面的id use:['happypack/loader?id=babel'] exclude:path.resolve(__dirname, 'node_modules') },{ test:/.css/, use:['happypack/loader?id=css'] }], plugins:[ new HappyPack({ id:'babel', loaders:['babel-loader?cacheDirectory'] }), ] } }复制代码
ThreadLoader开启多进程
使用方式
module.exports = { { test:/.js$/, exclude:/node_modules/, use:[ { loader:'thread-loader', options:{ wordkers:2 // 进程2个 } }, ... ] }复制代码
需要注意的是,开启多进程的意义在于需要构建的文件的体积足够大,如果不够大,不然效果不够明显,明显有高射炮打小苍蝇的感觉。因为进程开启需要时间,进程之间的通信也需要时间,如果这点时间都不能忽略不计,那么意义不大。
采用Oneof
通常来讲,同一种类型的文件只能由一个 loader 处理,那么正常来讲的逻辑应该是,比如我是一个 css 文件,那么我匹配到 test 为 css 后缀的 loader 我就应该立即执行了,但是事实是,虽然匹配到了,但是还是会遍历完整一遍再进行解析,这样来讲效果明显就更低了。
而 Oneof 语法就是解决这个问题的,使文件一旦匹配上 loader 之后就立即解析,省去了全盘遍历这个不必要的过程。压缩打包体积
使用 TreeShaking 删除无用代码
splitChunks 将体积较大的包提取出来
sourceMap提高调试体验
在开发环境下可以提升构建速度和调试速度
在生产环境可以减小代码体积
伪原创工具 SEO网站优化 https://www.237it.com/
作者:CJTCJT
链接:https://juejin.cn/post/7036277620580810789