阅读 1296

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编译流程

  1. 解析 webpack 配置参数,合并从 shell 传入和 webpack.config.js 文件里配置的参数,进行错误检查,生产最后的配置结果。

  2. 注册所有配置的插件和初始化 complier,好让插件监听 webpack 构建生命周期的事件节点,以做出对应的反应。(利用 tapable 将 plugin 挂载在特定的钩子上 hook)

  3. 开始编译主入口,从配置的 entry 入口文件开始解析文件构建 AST 语法树,找出每个文件所依赖的文件,递归下去。

  4. 在解析文件递归的过程中根据文件类型和 loader 配置找出合适的 loader 用来对文件进行转换。

  5. module 优化,为 module 增加ID。

  6. 递归完后得到每个文件的最终结果,根据 entry 配置生成代码块 chunk。

  7. 输出所有 chunk 到文件系统。

模块加载

  • 核心方法

    1. webpack_modules:所有的模块标记使用和被使用used、unused(production环境下或sideEffect:true,会被去掉)

    2. webpack_module_cache:缓存执行过的模块

    3. webapck_require:以 moduleId 为参数,调用时先从 cache 中查找,如果找到则直接返回(return cachedModule.exports),如果找不到则创建一个新模块放入缓存并执行这个模块方法,返回模块的导出。__webpack_require__所在的闭包能访问外层变量modules和缓存installedModules。这个很关键,因为modules是 webpack 打包后立即执行函数传入的参数。

  • 同步加载

    直接通过 webpack_require 加载模块

  • 异步加载

    require.ensure()和import()本质上都是动态创建一个script

    1. 将import()的模块打包成单独的 chunk

    2. 入口文件执行,为全局的 self[webpackJsonp]构造 push,即当下载完成后执行:webpackJsonpCallback(①将 module 保存进全局当 modules ②遍历 chunkIds,设置 installedChunks 标志已下载)

    3. 执行 import(),构造 script,利用 jsonP 下载,返回一个 promise包裹的结果,调用__webpack_require__,拿到保存在全局 modules 中的模块,执行 then()方法。

loader解析

  • 构造ruleSet实例:

    1. 合并用户配置、内置的规则,在后面匹配起到过滤作用.

  • 分类处理:

    1. 有内联路径则处理字符串,生成内联的loader数组,通过 resolveRequestsArray 递归解析 loader,完成回调 continueCallback。

    2. 无内联路径则直接解析文件路径,完成回调 continueCallback。

  • loader处理module:

    1. module 创建——>开始 build——>创建 loaderContext 上下文——>runloader——>parse module content

    2. 核心在于 runLoader,pitch 向下,达到最后一个 loader,相当于是一个拦截器功能,再回溯向上执行loader

chunk生成

  • 准备工作:

    1. 以入口文件建立一个 chunkGroup

    2. 建立 chunk 和 chunkGroup 关系

    3. 建立 module 和 chunk 关系

  • visitModule:

    1. 根据 module graph 建立 chunk graph

  • 优化chunk graph:

    1. 剔除重复的依赖

Webpack性能优化

  • 提高构建速度

    1. 通过 exclude、include 缩小搜索范围

    2. 配置 resolve.modules:[path.resolve(__dirname,'node_modules')]避免层层查找

    3. 设置 resolve.alias,使 webpack 直接使用库的 min 文件,避免库内解析,利用UnsafeCachePlugin缓存,并且减少文件路径的查找

    4. 设置 resolve.extensions ,减少文件查找,设置 resolve.extensions 中的值,列表值尽量少,频率高的文件类型的后缀写在前面。['js','less']

  • 缓存之前构建过的js

    1. 将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

    1. 通常来讲,同一种类型的文件只能由一个 loader 处理,那么正常来讲的逻辑应该是,比如我是一个 css 文件,那么我匹配到 test 为 css 后缀的 loader 我就应该立即执行了,但是事实是,虽然匹配到了,但是还是会遍历完整一遍再进行解析,这样来讲效果明显就更低了。
      而 Oneof 语法就是解决这个问题的,使文件一旦匹配上 loader 之后就立即解析,省去了全盘遍历这个不必要的过程。

  • 压缩打包体积

    1. 使用 TreeShaking 删除无用代码

    2. splitChunks 将体积较大的包提取出来

  • sourceMap提高调试体验

    1. 在开发环境下可以提升构建速度和调试速度

    2. 在生产环境可以减小代码体积

 伪原创工具 SEO网站优化  https://www.237it.com/ 

作者:CJTCJT
链接:https://juejin.cn/post/7036277620580810789

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐