webpack笔记

158 阅读8分钟

1.   Webpack 核心工作过程中的关键环节

Webpack CLI 启动打包流程; 

载入 Webpack 核心模块,创建 Compiler 对象;

使用 Compiler 对象开始编译整个项目; 

从入口文件开始,解析模块依赖,形成依赖关系树;

递归依赖树,将每个模块交给对应的 Loader 处理; 

合并 Loader 处理完的结果,将打包结果输出到 dist 目录。

(1)webpack-cli

        yargs解析命令行参数 ---->  判断是否指定配置文件  ---->  按照配置文件配置 ----> 调用webpack核心模块

(2)创建compiler对象

        判断options是数组还是对象(多路or单路打包)创建 ----> 注册配置插件plugins ----> 触发特定hook(complier) ----> 开始构建compiler.run() ---->  编译整个项目(compile 方法内部主要就是创建了一个 Compilation 对象,可以理解为一次构建过程中的上下文对象,里面包含了这次构建中全部的资源和信息)---->  触发make 钩子

(3)make阶段(事件触发机制)

        make 阶段主体的目标就是:根据 entry 配置找到入口模块,开始依次递归出所有依赖,形成依赖关系树,然后将递归到的每个模块交给不同的 Loader 处理;

        make.tap()  注册make事件; tapable库

        流程:

        SingleEntryPlugin 中调用了 Compilation 对象的 addEntry 方法,开始解析入口; addEntry 方法中又调用了 _addModuleChain 方法,将入口模块添加到模块依赖列表中; 紧接着通过 Compilation 对象的 buildModule 方法进行模块构建; buildModule 方法中执行具体的 Loader,处理特殊资源加载; build 完成过后,通过 acorn 库生成模块代码的 AST 语法树; 根据语法树分析这个模块是否还有依赖的模块,如果有则继续循环 build 每个依赖; 所有依赖解析完成,build 阶段结束; 最后合并生成需要输出的 bundle.js 写入 dist 目录。

2.  两大核心机制

通过 Loader 处理特殊类型资源的加载,例如加载样式、图片; 

通过 Plugin 实现各种自动化的构建任务,例如自动压缩、自动发布。

3. 常用loader和plugin

        css-loader、style-loader、 vue-loader、 babel-loader、 url-loader

        html-webpack-plugin、 clean-webpack-plugin、copy-webpack-plugin

4. webpack-dev-serve(提升开发者的开发体验,帮助开发者更快更高效的完成开发工作)

        提供了一个开发服务器,并且将自动编译和自动刷新浏览器等一系列对开发友好的功能全部集成在了一起

        运行 webpack-dev-server 这个命令时,它内部会启动一个 HTTP Server,为打包的结果提供静态文件服务,并且自动使用 Webpack 打包我们的应用,然后监听源代码的变化,一旦文件发生变化,它会立即重新打包,大致流程如下

        需要注意的是,webpack-dev-server 为了提高工作速率,它并没有将打包结果写入到磁盘中,而是暂时存放在内存中,内部的 HTTP Server 也是从内存中读取这些文件的。这样一来,就会减少很多不必要的磁盘读写操作,大大提高了整体的构建效率

配置:

        devServer: {

                contentBase: 'public',  //指定额外的静态资源路径

                hot: true,  //热更新

                proxy:  {

                       '/api': { 

                                 target: 'api.github.com' ,

                                 pathRewrite: { 

                                         '^/api': '' // 替换掉代理地址中的 /api

                                 }, 

                                 changeOrigin: true, // 确保请求 GitHub 的主机名就是:api.github.com,而不是localhost:8080/

                        }

                }

        }

5. sourceMap

开发过程中(开发环境)

我会选择 cheap-module-eval-source-map,原因有以下三点:

 我使用框架的情况会比较多,以 React 和 Vue.js 为例,无论是 JSX 还是 vue 单文件组件,Loader 转换后差别都很大,我需要调试 Loader 转换前的源代码。 一般情况下,我编写的代码每行不会超过 80 个字符,对我而言能够定位到行到位置就够了,而且省略列信息还可以提升构建速度。 虽然在这种模式下启动打包会比较慢,但大多数时间内我使用的 webpack-dev-server 都是在监视模式下重新打包,它重新打包的速度非常快。 综上所述,开发环境下我会选择 cheap-module-eval-source-map。 

发布前的打包,也就是生产环境的打包,我选择 none,它不会生成 Source Map。

原因很简单: 首先,Source Map 会暴露我的源代码到生产环境。如果没有控制 Source Map 文件访问权限的话,但凡是有点技术的人都可以很容易的复原项目中涉及的绝大多数源代码,这非常不合理也不安全,我想很多人可能都忽略了这个问题。 其次,调试应该是开发阶段的事情,你应该在开发阶段就尽可能找到所有问题和隐患,而不是到了生产环境中再去全民公测。如果你对自己的代码实在没有信心,我建议你选择 nosources-source-map 模式,这样出现错误可以定位到源码位置,也不至于暴露源码。

webpack优化

webpack 做过哪些优化,开发效率方面、打包策略方面等等
1)优化 Webpack 的构建速度

使用高版本的 Webpack (使用webpack4)
多线程/多实例构建:HappyPack(不维护了)、thread-loader
缩小打包作用域:

exclude/include (确定 loader 规则范围)
resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
resolve.extensions 尽可能减少后缀尝试的可能性
noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 importrequire、define 等模块化语句)
IgnorePlugin (完全排除模块)
合理使用alias


充分利用缓存提升二次构建速度:

babel-loader 开启缓存
terser-webpack-plugin 开启缓存
使用 cache-loader 或者 hard-source-webpack-plugin
注意:thread-loader 和 cache-loader 兩個要一起使用的話,請先放 cache-loader 接著是 thread-loader 最後才是 heavy-loader


DLL:

使用 DllPlugin 进行分包,使用 DllReferencePlugin(索引链接) 对 manifest.json 引用,让一些基本不会改动的代码先打包成静态资源,避免反复编译浪费时间。
2)使用webpack4-优化原因


(a)V8带来的优化(for of替代forEach、MapSet替代Object、includes替代indexOf)
(b)默认使用更快的md4 hash算法
(c)webpacks AST可以直接从loader传递给AST,减少解析时间
(d)使用字符串方法替代正则表达式
①noParse
不去解析某个库内部的依赖关系
比如jquery 这个库是独立的, 则不去解析这个库内部依赖的其他的东西
在独立库的时候可以使用

module.exports = {
  module: {
    noParse: /jquery/,
    rules:[]
  }
}
复制代码
②IgnorePlugin

忽略掉某些内容 不去解析依赖库内部引用的某些内容
从moment中引用 ./locol 则忽略掉
如果要用local的话 则必须在项目中必须手动引入

import 'moment/locale/zh-cn'
module.exports = {
    plugins: [
        new Webpack.IgnorePlugin(/./local/, /moment/),
    ]
}
复制代码
③dllPlugin

不会多次打包, 优化打包时间
先把依赖的不变的库打包
生成 manifest.json文件
然后在webpack.config中引入
webpack.DllPlugin Webpack.DllReferencePlugin
④happypack -> thread-loader
大项目的时候开启多线程打包
影响前端发布速度的有两个方面,一个是构建,一个就是压缩,把这两个东西优化起来,可以减少很多发布的时间。
⑤thread-loader
thread-loader 会将您的 loader 放置在一个 worker 池里面运行,以达到多线程构建。
把这个 loader 放置在其他 loader 之前(如下图 example 的位置), 放置在这个 loader 之后的 loader 就会在一个单独的 worker 池(worker pool)中运行。

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /.js$/,
        include: path.resolve("src"),
        use: [
          "thread-loader",
          // 你的高开销的loader放置在此 (e.g babel-loader)
        ]
      }
    ]
  }
}
复制代码
每个 worker 都是一个单独的有 600ms 限制的 node.js 进程。同时跨进程的数据交换也会被限制。请在高开销的loader中使用,否则效果不佳
⑥压缩加速——开启多线程压缩

不推荐使用 webpack-paralle-uglify-plugin,项目基本处于没人维护的阶段,issue 没人处理,pr没人合并。
Webpack 4.0以前:uglifyjs-webpack-plugin,parallel参数

module.exports = {
  optimization: {
    minimizer: [
      new UglifyJsPlugin({
        parallel: true,
      }),
    ],
  },};
复制代码

推荐使用 terser-webpack-plugin

module.exports = {
  optimization: {
    minimizer: [new TerserPlugin(
      parallel: true   // 多线程
    )],
  },
};
复制代码
2)优化 Webpack 的打包体积

压缩代码
提取页面公共资源:
Tree shaking
Scope hoisting
图片压缩
动态Polyfill
3)speed-measure-webpack-plugin
简称 SMP,分析出 Webpack 打包过程中 LoaderPlugin 的耗时,有助于找到构建过程中的性能瓶颈。
开发阶段

开启多核压缩
插件:** terser-webpack-plugin **
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
    optimization: {
        minimizer: [
            new TerserPlugin({
                parallel: true,
                terserOptions: {
                    ecma: 6,
                },
            }),
        ]
    }
}
复制代码
传送门 ☞# 工程化专题作者:Gaby链接:https://juejin.cn/post/7016593221815910408来源:稀土掘金著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。