标签搜索

webpack学习笔记

指针原来是套娃的
2025-03-21 / 0 评论 / 14 阅读 / 正在检测是否收录...

一、webpack安装与配置

官网:https://www.webpackjs.com/

npm i webpack webpack-cli
// 运行
npx webpack

基本配置
默认情况下,webpack会读取webpack.config.js文件作为配置文件,也可以通过CLI参数--config来指定某个配置文件


// webpack的基本配置
module.exports = {
  // 编译模式 "development" | "production"
  mode: "development",
  // 配置源码地图 具体配置:https://www.webpackjs.com/configuration/devtool/
  devtool: "source-map",
  // 入口 webpack从入口开始依赖解析
  entry:{
       // 属性名:chunk的名称, 属性值:入口模块(启动模块)
        main: "./src/index.js"
    },
  // 出口配置
  output: {
    // 公用的公共路径 /
    publicPath: "/",
    // 输出目录为 dist 默认为dist
    path: path.resolve(__dirname, "dist"),
    // js 输出到 dist/js/xxx
    filename: "js/[name].[chunkhash:5].js" 
  },
  // loader配置 运行时从从右到左,从下到上
  module: {
    // 模块的匹配规则
    rules: [
      // 规则1
      {
        // 正则表达式,匹配模块的路径
        test: /index\.js$/,
        // 匹配到了之后,使用哪些加载器
        use: ["./loaders/loader1", "./loaders/loader2"]
      },
      // 规则2
      {
        // 正则表达式,匹配模块的路径
        test: /\.js$/,
        // 匹配到了之后,使用哪些加载器
        use: ["./loaders/loader3", "./loaders/loader4"]
      }
    ]
  },
  // 插件配置 运行顺序看内部生命周期钩子触发顺序,监听相同生命周期时从上到下(从左到右)
  plugins: [
    new MyPlugin({
      // 插件内部配置
    })
  ],
  // 开发处理器配置 需要安装webpack-dev-server 只在开发环境有效
  devServer: {
    port: 8080,
    // 自动打开网页
    open: true,
    // 开启热替换HMR 需要在代码里写入 module.hot.accept(); 接受热更新
    hot:true,
    // 代理规则
    proxy: {
      "/api": {
        target: "http://blog.imtwa.com",
        //更改请求头中的host和origin
        changeOrigin: true 
      }
    }
  },
  // 优化配置
  optimization: {
    splitChunks: {
      // 分包配置
      chunks: "all",
      // 缓存组配置
      cacheGroups:{
        
      }
    },
  },
  resolve: {
    alias: {
      // 别名 @ = src目录
      "@": path.resolve(__dirname, "src"),
      // 别名 _ = 工程根目录
      _: __dirname,
    },
  },
  stats: {
    // 打包时使用不同的颜色区分信息
    colors: true,
    // 打包时不显示具体模块信息
    modules: false,
    // 打包时不显示入口模块信息
    entrypoints: false, 
    // 打包时不显示子模块信息
    children: false, 
  }
};

二、webpack打包流程

webpacke是实现模块化的重要构建工具,它是将开发时态的代码转成运行时态代码,所以webpack不会影响代码的开发和运行,只会在打包时影响打包过程。所以webpack是运行在node环境下,与浏览器环境无关。
m8iehj2a.png

初始化

此阶段,webpack会将CLI参数配置文件默认配置进行融合,形成一个最终的配置对象。

编译

1、创建chunk
chunk是webpack在内部构建过程中的一个概念,译为,它表示通过某个入口找到的所有依赖的统称。
根据入口模块(默认为./src/index.js)创建一个chunk
每个chunk都有至少两个属性:

  • name:默认为main
  • id:唯一编号,开发环境和name相同,生产环境是一个数字,从0开始

2、构建所有依赖模块
webpack只能分析js代码,将js代码读取后进行AST抽象语法树分析,找到每个代码的依赖,并转换代码。
loader在做AST前生效,处理源代码。

AST在线测试工具:https://astexplorer.net/

3、产生chunk assets
在第二步完成后,chunk中会产生一个模块列表,列表中包含了模块id模块转换后的代码
接下来,webpack会根据配置为chunk生成一个资源列表,即chunk assets,资源列表可以理解为是生成到最终文件的文件名和文件内容
4、合并chunk assets
将多个chunk的assets合并到一起,并产生一个总的hash

输出

webpack将利用node中的fs模块,根据编译产生的总的assets,生成相应的文件。
涉及术语

  1. module:模块,分割的代码单元,webpack中的模块可以是任何内容的文件,不仅限于JS
  2. chunk:webpack内部构建模块的块,一个chunk中包含多个模块,这些模块是从入口模块通过依赖分析得来的
  3. bundle:chunk构建好模块后会生成chunk的资源清单,清单中的每一项就是一个bundle,可以认为bundle就是最终生成的文件
  4. hash:最终的资源清单所有内容联合生成的hash值
  5. chunkhash:chunk生成的资源清单内容联合生成的hash值
  6. chunkname:chunk的名称,如果没有配置则使用main
  7. id:通常指chunk的唯一编号,如果在开发环境下构建,和chunkname相同;如果是生产环境下构建,则使用一个从0开始的数字进行编号

三、loader

loader处理在读取到文件内容之后,发生在AST抽象语法树之前。loader的功能定位是转换代码,因为webpack只能对js代码进行抽象语法树分析,所以需要在loader阶段引入对图片、css等处理成js代码。
也可以对源代码进行修改,如babel-loader等。
loader执行顺序是从下到上、从右到左,上一个loader处理后再交给下一个loader。

常用的loader

// 使用 postcss 处理 css https://postcss.org/
postcss-loader
// 解析 CSS 文件中的 import 和 url()
css-loader
// 将 CSS 插入到 DOM 中
style-loader
// css 样式打包成一个文件
// 该 loader 负责记录要生成的 css 文件的内容
// 同时导出开启 css-module 后的样式对象
mini-css-extract-plugin.loader
// 各种图片、字体文件,均交给 url-loader 处理
url-loader
// 转换js代码,用于适配各版本浏览器 https://www.babeljs.cn/
babel-loader

四、plugin

plugin的本质是一个带有apply方法的对象,嵌入到webpack的编译流程中的节点进行处理。
执行顺序看节点,同节点的按配置中从上到下、从左到右。
apply函数会在初始化阶段,创建好Compiler对象后运行。compiler对象是在初始化阶段构建的,整个webpack打包期间只有一个compiler对象,后续完成打包工作的是compiler对象内部创建的compilation。
apply方法会在创建好compiler对象后调用,并向方法传入一个compiler对象
compiler对象提供了大量的钩子函数(hooks,可以理解为事件),plugin的开发者可以注册这些钩子函数,参与webpack编译和生成。

class MyPlugin{
    apply(compiler){
        compiler.hooks.事件名称.事件类型(name, function(compilation){
            //事件处理函数
        })
    }
}

事件名称
即要监听的事件名,即钩子名,所有的钩子:https://www.webpackjs.com/api/compiler-hooks

事件类型
这一部分使用的是 Tapable API,这个小型的库是一个专门用于钩子函数监听的库。
它提供了一些事件类型:

  • tap:注册一个同步的钩子函数,函数运行完毕则表示事件处理结束
  • tapAsync:注册一个基于回调的异步的钩子函数,函数通过调用一个回调表示事件处理结束
  • tapPromise:注册一个基于Promise的异步的钩子函数,函数通过返回的Promise进入已决状态表示事件处理结束

处理函数
处理函数有一个事件参数compilation

常用的plugin

// 清除输出目录文件
clean-webpack-plugin
// 自动生成html文件 并引入打包后的js
html-webpack-plugin
// 复制静态资源到输出目录
copy-webpack-plugin
// 将css文件打包成一个文件
// 和mini-css-extract-plugin.loader合用
mini-css-extract-plugin

webpack内置插件

// 全局常量定义插件,使用该插件通常定义一些常量值
DefinePlugin
// 可以为每个chunk生成的文件头部添加一行注释
// 一般用于添加作者、公司、版权等信息
BannerPlugin
// 自动加载模块,不用手动import 或 require 
// 写法
/*
new webpack.ProvidePlugin({
  $: 'jquery',
  _: 'lodash'
})
*/
ProvidePlugin

五、性能优化

性能优化可以从三个方向入手

  • 构建性能
  • 传输性能
  • 运行性能

构建性能

这里所说的构建性能,是指在开发阶段的构建性能,降低从打包开始,到代码效果呈现所经过的时间。

1、减少模块解析
配置module.noParse,对第三方或打包好的模块不进行解析。
2、优化loader性能
有一些库不用经过特定的loader解析,如配置exclude: /lodash/,在使用babel-loader时不解析lodash库。

module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /lodash/,
        use: "babel-loader"
      }
    ]
  }
}

3、开启热替换
默认情况下,webpack-dev-server不管是否开启了热更新,当重新打包后,都会调用location.reload刷新页面。
但如果运行了module.hot.accept(),将改变这一行为。
module.hot.accept()的作用是让webpack-dev-server通过socket管道,把服务器更新的内容发送到浏览器。
浏览器将不在刷新重新请求整个模块,而是只请求发送变化的模块。

devServer:{
    hot:true // 开启HMR
}
// 并在js中加入下面代码
if(module.hot){ // 是否开启了热更新
  module.hot.accept() // 接受热更新
}

传输性能

传输性能是指,打包后的JS代码传输到浏览器经过的时间。
在优化传输性能时要考虑到:

  1. 总传输量:所有需要传输的JS文件的内容加起来,就是总传输量,重复代码越少,总传输量越少。
  2. 文件数量:当访问页面时,需要传输的JS文件数量,文件数量越多,http请求越多,响应速度越慢。
  3. 浏览器缓存:JS文件会被浏览器缓存,被缓存的文件不会再进行传输。

1、手动分包
将不依赖其他模块的文件打包成公共模块,公共模块会被打包成为动态链接库(dll Dynamic Link Library),并生成资源清单。后续打包时webpack分析到引用的模块在资源清单中,就不会打包该模块。
打包公共模块是一个独立的打包过程,在打包时利用DllPlugin生成资源清单。

module.exports = {
  mode: "production",
  entry: {
    jquery: ["jquery"],
    lodash: ["lodash"]
  },
  output: {
    filename: "dll/[name].js",
    library: "[name]" // 每个bundle暴露的全局变量名
  },
  plugins: [
    new webpack.DllPlugin({
      // 这里不参与打包后的代码运行,不用放到输出目录中
      path: path.resolve(__dirname, "dll", "[name].manifest.json"),
      name: "[name]"
    })
  ]
};

打包完成后,需要在页面手动引入公共模块的js文件。
并在主代码打包时引入打包好的资源清单

new webpack.DllReferencePlugin({
  manifest: require("./dll/jquery.manifest.json")
}),
new webpack.DllReferencePlugin({
  manifest: require("./dll/lodash.manifest.json")
})
手动打包的过程:
1. 开启`output.library`暴露公共模块
2. 用`DllPlugin`创建资源清单
3. 用`DllReferencePlugin`使用资源清单
手动打包的注意事项:
1. 资源清单不参与运行,可以不放到打包目录中
2. 记得手动引入公共JS,以及避免被删除
3. 不要对小型的公共JS库使用
优点:
1. 极大提升自身模块的打包速度
2. 极大的缩小了自身文件体积
3. 有利于浏览器缓存第三方库的公共代码
缺点:
1. 使用非常繁琐
2. 如果第三方库中包含重复代码,则效果不太理想

2、自动分包
配置webpack分包策略,webpack可以根据分包策略自动进行分包。
webpack提供了optimization配置项,用于配置一些优化信息,其中splitChunks是分包策略的配置。

module.exports = {
  optimization: {
    splitChunks: {
      // 分包策略
      chunks: "all"
    }
  }
}

chunks:从chunk中分离chunk,实现分包。

  • all: 对于所有的chunk都要应用分包策略
  • async:【默认】仅针对异步chunk应用分包策略
  • initial:仅针对普通chunk应用分包策略

maxSize:控制包的最大字节数,超过后尽可能分包(因为分包是按模块划分,单一模块超过字节后无法再分)
minChunks:一个模块被多少个chunk使用时,才会进行分包,默认值1

缓存组配置
之前配置的分包策略是全局的,而实际上,分包策略是基于缓存组的。每个缓存组提供一套独有的策略,webpack按照缓存组的优先级依次处理每个缓存组,被缓存组处理过的分包不需要再次分包。
默认情况下,webpack提供了两个缓存组:

module.exports = {
  optimization:{
    splitChunks: {
      //全局配置
      cacheGroups: {
        // 属性名是缓存组名称,会影响到分包的chunk名
        // 属性值是缓存组的配置,缓存组继承所有的全局配置,也有自己特殊的配置
        vendors: {
          // 当匹配到相应模块时,将这些模块进行单独打包
          test: /[\\/]node_modules[\\/]/, 
          // 缓存组优先级,优先级越高,该策略越先进行处理,默认值为0
          priority: -10 
        },
        default: {
          // 覆盖全局配置,将最小chunk引用数改为2
          minChunks: 2, 
          // 优先级
          priority: -20, 
          // 重用已经被分离出去的chunk
          reuseExistingChunk: true 
        }
      }
    }
  }
}

修改缓存组以实现css公共样式抽离

module.exports = {
  optimization: {
    splitChunks: {
      chunks: "all",
      cacheGroups: {
        styles: {
          test: /\.css$/, // 匹配样式模块
          minSize: 0, // 覆盖默认的最小尺寸,这里仅仅是作为测试
          minChunks: 2 // 覆盖默认的最小chunk引用数
        }
      }
    }
  },
  module: {
    rules: [{ test: /\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"] }]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: "./public/index.html",
      chunks: ["index"]
    }),
    new MiniCssExtractPlugin({
      filename: "[name].[hash:5].css",
      // chunkFilename是配置来自于分割chunk的文件名
      chunkFilename: "common.[hash:5].css" 
    })
  ]
}

这两种分包方法所能分的最小包还是一个模块的大小,下面使用代码压缩和tree shaking将会减少模块的体积。
3、代码压缩
webpack自动集成了Terser https://terser.org/,可以减少代码体积;破坏代码的可读性,提升破解成本。
用法:

const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
  optimization: {
    // 是否要启用压缩,默认情况下,生产环境会自动开启
    minimize: true, 
    minimizer: [ // 压缩时使用的插件,可以有多个
      new TerserPlugin(), 
      new OptimizeCSSAssetsPlugin()
    ],
  },
};

4、tree shaking
tree shaking可以通过分析模块间的依赖关系,自动去除没有用到的模块内容,从而大大减少了打包体积。
但是tree shaking的模块分析依赖es6的模块分析,es的import绑定的变量是不可变的,更加稳定,所以在使用第三方库时,需要选择es6版本,如lodash-es
同时,在书写导入语句时,尽量分块导出、按需导入。

  • 使用export xxx导出,而不使用export default {xxx}导出
  • 使用import {xxx} from "xxx"导入,而不使用import xxx from "xxx"导入

webpack2开始内置了tree shaking,在打包生产环境中会自动开启。
5、懒加载
使用import()动态引入一个模块,webpack会将模块打包成异步包,等到运行该语句时动态加载js。
/* webpackChunkName:"lodash" */是修改包的名字。

btn.onclick = async function() {
  //动态加载
  //import 是ES6的草案
  //浏览器会使用JSOP的方式远程去读取一个js模块
  //import()会返回一个promise   (* as obj)
  // const { chunk } = await import(/* webpackChunkName:"lodash" */"lodash-es");
  const result = chunk([3, 5, 6, 7, 87], 2);
  console.log(result);
};

其它优化

1、ESlint
使用ESLint可以检查代码格式与质量,在开发阶段编写优雅的代码。
2、bundle analyzer
bundle-analyzer可以分析打包结果,生成图形化的界面。

4

评论

博主关闭了所有页面的评论