webpack使用详解

摘要:webpack作为一款主流的构建工具,对比后来者Vite虽然存在一些缺点,例如启动慢,配置复杂等。在很多项目中使用依然基于webpack构建,有必要掌握其概念、构建流程和配置方法。

1 webpack概述

1.1 基本概念

        webpack 是一个用于现代JavaScript 应用程序的静态模块打包工具。当webpack处理应用程序时,它会在内部从一个或多个入口点构建一个依赖图,然后将你项目中所需的每一个模块组合成一个或多个bundles,它们均为静态资源,用于展示你的内容。

        这里官方给出的的webpack定义涉及如下关键字:

        静态模块: 这里的“静态模块”指的是在开发阶段可以直接被Webpack引用的资源,这些资源可以直接被获取并打包进最终的输出文件(如bundle.js)。静态模块可以包括JavaScript代码、CSS样式表、图片和其他类型的文件。Webpack通过处理应用程序,在内部构建一个依赖图,此依赖图对应映射到项目所需的每个模块,并生成一个或多个bundle。这些bundle是静态资源,用于展示应用程序的内容。

        打包: 是指将多个模块和资源组合成一个或多个bundle(包)的过程。Webpack 通过分析应用程序的依赖图,将所有必要的模块和资源打包成一个或多个文件,这些文件可以被浏览器加载和执行。 其目的是简化部署和优化加载性能。通过打包,可以减少 HTTP 请求的数量,合并多个资源,以及在加载前对资源进行优化(如压缩、合并等)。打包过程是高度可配置的,允许开发者根据需要定制输出的 bundle 结构,包括代码分割(将应用程序分割成多个chunk,按需加载)、优化资源(如压缩 JavaScript 和 CSS)、提取共享依赖等。

        依赖图 (dependency graph) 是指在Webpack构建过程中,Webpack会将所有模块及其依赖关系组织成一个图形结构。每当一个文件依赖于另一个文件时,Webpack就会将这两者视为直接依赖关系。其他概念

概念解释
入口(entry)入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
输出(output)output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。
loaderwebpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。
插件(plugin)loader 用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。包括:打包优化,资源管理,注入环境变量
模式(mode)通过选择 development, production 或 none 之中的一个,来设置 mode 参数,你可以启用 webpack 内置在相应环境下的优化。其默认值为 production

1.2 构建流程

        webpack 构建的核心任务是完成内容转化和资源合并。其完整构建流程主要包含以下 3 个阶段:

  1. 初始化阶段

    初始化参数:从配置文件、配置对象和Shell参数中读取并与默认参数进行合并,组合成最终使用的参数。

    创建编译对象:用上一步得到的参数创建 Compiler 对象。

    初始化编译环境:包括注入内置插件、注册各种模块工厂、初始化 RuleSet 集合、加载配置的插件等。

  2. 构建阶段

    开始编译:执行Compiler对象的run方法,创建Compilation对象。

    确认编译入口:进入entryOption阶段,读取配置的Entries,递归遍历所有的入口文件,调用 Compilation.addEntry 将入口文件转换为 Dependency 对象。

    编译模块(make): 调用 normalModule 中的 build 开启构建,从 entry 文件开始,调用 loader 对模块进行转译处理,然后调用 JS 解释器(acorn)将内容转化为 AST 对象,然后递归分析依赖,依次处理全部文件。

    完成模块编译:在上一步处理好所有模块后,得到模块编译产物和依赖关系图。

  3. 生成阶段

    输出资源(seal):根据入口和模块之间的依赖关系,组装成多个包含多个模块的 Chunk,再把每个 Chunk 转换成一个 Asset 加入到输出列表,这步是可以修改输出内容的最后机会。

    写入文件系统(emitAssets):确定好输出内容后,根据配置的 output 将内容写入文件系统。

        构建阶段围绕 module 展开,生成阶段则围绕 chunks 展开。经过构建阶段之后,webpack 得到足够的模块内容与模块关系信息,之后通过 Compilation.seal 函数生成最终资源。[1]

1.3 webpack loader

        webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,以供应用程序使用,以及被添加到依赖图中。

        loader本质上是一个函数,将接收到的内容进行转换并返回转换后的结果。通过loader将其他类型的文件转换成有效的webpack modules(如 ESmodule、CommonJS、AMD),这样webpack就可以消费这些模块,并将其添加到依赖关系图中。

        常见的loader有(具体用法后文将有介绍):

  • raw-loader:加载文件原始内容,webpack 5中已经废弃。
  • file-loader:将引用文件输出到目标文件夹中,在代码中通过相对路径引用输出的文件。
  • url-loader:和file-loader类似,但是能在文件很小的情况下以 base64 的方式将文件内容注入到代码中。
  • babel-loader:将较新版本的ES语法转换为浏览器兼容的语法。它结合了同步和异步转换的优势:当启用缓存时,babel-loader 采用异步模式以提高构建效率;反之,则默认使用同步模式以确保转换的准确性。
  • style-loader:将CSS代码注入到JavaScript中,通过DOM操作加载CSS。
  • css-loader:加载CSS,支持模块化、压缩、文件导入等特性。

        区别:style-loader负责将样式插入到DOM中,使样式对页面生效。css-loader主要负责处理import、url路径等外部引用。style-loader会先于css-loader执行。

        使用 loader 的方式主要有两种:

  1. 配置方式(推荐):在 webpack.config.js 文件中指定 loader,通过在 module.rules 中使用 test 匹配要转换的文件类型,使用 use 指定要使用的 loader。[2]
module.exports = {module: {rules: [{ test: /\.ts$/, use: "ts-loader" }],},
};
  1. 内联方式:在每个import语句中显式指定 loader。通过为内联import语句添加前缀,可以覆盖配置中的所有 loader, preLoader 和 postLoader
import Styles from "style-loader!css-loader?modules!./styles.css";

2 基本配置

2.1 webpack安装

        wepack正常运行必须依赖node环境,在node环境中须使用npm工具管理node中各种依赖的包,所以安装webpack之前需要安装Node.js。

npm i webpack webpack-cli --save-dev

        Webpack-CLI则是用于在命令行中执行Webpack相关操作的工具,通常和Webpack一起使用,以便更好地管理和构建前端项目。

        Webpack的配置一般要配置common、dev、pro三个环境,配置多个环境。会在不同的环境使用不同的配置文件进行构建。例如,npm run dev命令会使用build/webpack.dev.js 配置文件

"scripts": {"test": "echo \"Error: no test specified\" && exit 1","devBuild": "webpack --config build-optimization/webpack.dev.js","dev": "webpack serve --config build/webpack.dev.js","build": "webpack --config build-optimization/webpack.prod.js"},

        dev和pro环境通过merge引入common配置文件

//const webpackCommonConf = require('./webpack.common.js')
const { merge } = require('webpack-merge')

        注:也可以借助 cross-env (跨平台通用)包命令,设置参数区分环境。但是要注意cross-env 设置的只在Node.js环境生效,前端代码无法访问process.env.NODE_ENV。需要借助Webpack内置的 DefinePlugin插件。在编译时,将前端代码中匹配的变量名,替换为值或表达式

2.2 开发环境和生产环境的配置

        下面通过webpack 配置对象,展示 Webpack 如何构建应用程序,其基本组成部分如下(逐行功能参考注释):

module.exports = {entry: path.join(srcPath, 'index'),  //配置入口module: {rules: [  //开始定义模块处理规则,通过rules数组来配置不同文件类型的加载器。{ // 对于所有以.js结尾的文件,使用babel-loader进行转译,只处理srcPath目录下的文件,排除node_modulestest: /\.js$/,loader: ['babel-loader'],include: srcPath,exclude: /node_modules/},// {//     test: /\.vue$/,  // 匹配所有以 .vue 结尾的文件//     loader: ['vue-loader'],  // 使用 vue-loader 来处理 Vue 单文件组件//     include: srcPath// },// {  //     test: /\.css$/,//     // loader 的执行顺序是:从后往前(知识点)//     loader: ['style-loader', 'css-loader']// },{ // 对于.css文件,使用style-loader、css-loader和postcss-loader,注意加载器是从后往前执行的test: /\.css$/,// loader 的执行顺序是:从后往前loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss},{  // 处理.less文件,使用style-loader、css-loader和less-loader,同样遵循加载器的执行顺序test: /\.less$/,// 增加 'less-loader' ,注意顺序loader: ['style-loader', 'css-loader', 'less-loader']}]},plugins: [// 使用HtmlWebpackPlugin生成index.html文件,自动将打包后的资源插入其中,template指定了模板文件的位置new HtmlWebpackPlugin({template: path.join(srcPath, 'index.html'),  // 指定了被打包HTML模板文件的位置。filename: 'index.html'  // filename:指定了生成的HTML文件的名称})]
}

        开发环境Dev

module.exports = smart(webpackCommonConf, {  // 使用smart函数与webpackCommonConf进行合并,这样可以继承通用配置mode: 'development',  // 设置Webpack的模式为development,启用适合开发的优化,比如更快的构建速度和更友好的调试信息module: {rules: [// 对于所有以.png、.jpg、.jpeg和.gif结尾的图片文件,使用file-loader来处理。这会将图片文件复制到输出目录,并返回它们的路径{test: /\.(png|jpg|jpeg|gif)$/,use: 'file-loader'}]},plugins: [// 使用DefinePlugin来定义全局常量ENV,值为'development',便于在代码中根据环境做不同的处理new webpack.DefinePlugin({// window.ENV = 'development'       //定义环境ENV: JSON.stringify('development')})],devServer: {                //启动本地服务,需安装webpack-dev-serveport: 8080,  // 配置devServer,指定本地开发服务器的端口为8080progress: true,  // 显示打包的进度条contentBase: distPath,  // 根目录open: true,  // 自动打开浏览器compress: true,  // 启动gzip压缩// 设置代理proxy: {      //解决本地和服务端请求的跨域问题// 将本地/api/xxx代理到localhost:3000/api/xxx'/api': 'http://localhost:3000',// 将本地/api2/xxx代理到localhost:3000/xxx'/api2': {target: 'http://localhost:3000',pathRewrite: {'/api2': ''}}}}
})

        插件DefinePlugin允许你定义全局常量,这些常量会在编译时被替换。这样可以在代码中使用这些常量,而不需要在运行时进行判断。

        生产环境Pro

module.exports = smart(webpackCommonConf, {mode: 'production',output: {filename: 'bundle.[contenthash:8].js',  // 打包代码时,加上hash戳path: distPath,// publicPath: 'http://cdn.abc.com'  // 修改所有静态文件url的前缀(如cdn域名),这里暂时用不到},module: {rules: [// 图片-考虑 base64 编码的情况{test: /\.(png|jpg|jpeg|gif)$/,use: {loader: 'url-loader',options: {// 小于 5kb 的图片用 base64 格式产出// 否则,依然延用 file-loader 的形式,产出 url 格式limit: 5 * 1024,// 打包到 img 目录下outputPath: '/img1/',// 设置图片的 cdn 地址(也可以统一在外面的output中设置,那将作用于所有静态资源)// publicPath: 'http://cdn.abc.com'}}},]},plugins: [new CleanWebpackPlugin(), // 会默认清空output.path文件夹new webpack.DefinePlugin({// window.ENV = 'production'ENV: JSON.stringify('production')})]
})

        dev-server

devServer: {       // dev 环境port: 8080,progress: true,  // 显示打包的进度条contentBase: distPath,  // 根目录open: true,  // 自动打开浏览器compress: true,  // 启动 gzip 压缩// 设置代理proxy: {// 将本地 /api/xxx 代理到 localhost:3000/api/xxx'/api': 'http://localhost:3000',// 将本地 /api2/xxx 代理到 localhost:3000/xxx'/api2': {target: 'http://localhost:3000',pathRewrite: {'/api2': ''}}}
}
2.2.1 解析ES6 babel-loader
rules: [         // 使用babel解析conmmon.js中处理{test: /\.js$/,           //验证规则,处理.js结尾的文件loader: ['babel-loader'],include: srcPath,           //处理的文件目录exclude: /node_modules/    //排除此文件夹下的文件},
]

注:配置babel-loader需要配置babellrc文件;

{"presets": ["@babel/preset-env"],    //preset-env中已经包含ES6、ES7、ES8的语法"plugins": []
}

        Babel是一个JavaScript编译器,主要用于将ECMAScript 2015+(ES6+)代码转换为向后兼容的 JavaScript版本,以便在旧版浏览器或环境中运行Babel 的工作原理可以概括为以下几个步骤:

  1. 解析:将现代 JavaScript 源代码解析为抽象语法树(AST)。
  2. 转换:使用插件对 AST 进行转换,处理不同的 JavaScript 特性(如箭头函数、类等),将其转化为兼容的旧语法。
  3. 生成:将转换后的 AST 转换回 JavaScript 代码。
  4. 配置:通过配置文件(如 .babelrc)定制 Babel 的行为,选择使用的插件和预设。
  5. 预设:使用预设(如 @babel/preset-env)自动选择需要的插件,以支持特定的 JavaScript 版本。
  6. 集成:通常与构建工具(如 Webpack)结合使用,在构建过程中自动处理 JavaScript 文件。
2.2.2 解析样式的loaders
rules: [{test: /\.css$/,// loader 的执行顺序是:从后往前loader: ['style-loader', 'css-loader', 'postcss-loader'] // 加了 postcss},{test: /\.less$/,// 增加 'less-loader' ,注意顺序loader: ['style-loader', 'css-loader', 'less-loader']}
]

注: Loader的解析顺序是从右往左的

        postcss-loader处理CSS会抚平浏览器兼容性的问题(需要配置postcss.config.js文件),允许开发者使用 autoprefixer、cssnano 等工具来优化和转换 CSS;

module.exports = {plugins: [require('autoprefixer')]    //autoprefixer是一个增加前缀的插件
}

        css-loader是将文件解析为css文件,将 CSS 文件内容作为模块导入到 JavaScript 中。它支持 @import和url() 语句,能够处理 CSS 依赖;

        style-loader将CSS作为 <style> 标签插入到DOM中。它通常与css-loader一起使用,可以在运行时动态加载 CSS;

        sass-loader将Sass文件(.scss 或 .sass)编译成CSS。需要 node-sass 或 dart-sass 作为依赖。

        less-loader将Less文件编译成CSS。需要安装less作为依赖

        stylus-loader将Stylus文件编译成CSS,支持Stylus的各种特性

        less-loader和scss-loader是一样的,将less语法解析为css语法,再解析成css文件,再style插入HTML中;;上述配置在开发环境没有问题,在生产环境需要处理下;

2.2.3 解析图片文件url-loader

        在开发环境,是利用file-loader直接引入url;但是对生产环境不太友好(可能会导致过多的HTTP请求);

rules: [// 直接引入图片 url{test: /\.(png|jpg|jpeg|gif)$/,use: 'file-loader'}
]

        生产环境使用url-loader,会限制小于5KB的图片通过base64输出;

rules: [// 图片 - 考虑 base64 编码的情况{test: /\.(png|jpg|jpeg|gif)$/,use: {loader: 'url-loader',options: {// 此处,小于5kb的图片用base64格式产出;否则,依然延用file-loader的形式,产出url格式// **生成图片临界值默认为 8KB **limit: 5 * 1024,// 大的图片文件打包到 img 目录下outputPath: '/img1/',// 设置图片的 cdn 地址(也可以统一在外面的 output 中设置,那将作用于所有静态资源)// publicPath: 'http://cdn.abc.com'}}},
]

        注:通过增加hash戳,当重新打包(文件有变化),还文件的hash戳改变,会请求新的文件;文件没有变化会直接命中缓存,更快启动页面。

output: {filename: 'bundle.[contenthash:8].js',  // 打包代码时,加上hash戳path: distPath,// publicPath: 'http://cdn.abc.com'  // 修改所有静态文件url的前缀(如cdn域名),这里暂时用不到
},

        Webpack5 内置资源模块(字体,图片等)打包,无需额外 loader,无需下载包。Webpack添加如下配置 将拥有打包图片功能 ,规则的作用是匹配以 .png、.jpg、.jpeg、.gif 结尾的文件,并使用 asset 模块类型进行处理。

        asset 模块类型是 webpack 5 引入的一种处理资源的方式,它会根据文件大小,自动决定是将文件转换为 Data URL (DataURL),还是将其复制到输出目录。[3]

  1. 占位符 【hash】对模块内容做算法计算,得到映射的数字字母组合的字符串
  2. 占位符 【ext】使用当前模块原本的占位符,例如:.png / .jpg 等字符串
  3. 占位符 【query】保留引入文件时代码中查询参数(只有 URL 下生效)
module: {rules: [...{test: /\.(png|jpg|jpeg|gif)$/i, // 匹配所有以.png、.jpg、.jpeg和.gif 结尾的文件,`i` 表示不区分大小写type: 'asset', // 指定资源类型为 asset,这会自动处理图像文件generator: { // 配置生成文件的方式filename: 'assets/[hash][ext][query]' // 指定输出文件的路径和名称}}],
},

2.2.4 插件HtmlWebpackPlugin

        这里遇到一个webpack插件HtmlWebpackPlugin,其作用主要是自动化地创建 HTML 文件,并将 Webpack 打包后的 JavaScript 和 CSS 文件注入到这个 HTML 文件中。安装

npm install --save-dev html-webpack-plugin

        主要功能如下:

  • 自动生成 HTML 文件:HtmlWebpackPlugin 可以自动创建一个 HTML 文件,不必手动创建一个 HTML 文件来引用打包后的JavaScript和CSS文件
  • 注入资源:插件会自动将Webpack打包的输出文件(如 JavaScript bundle)作为<script>标签注入到 HTML文件中。若使用了CSS提取插件(如 MiniCssExtractPlugin),生成的CSS文件也会被注入;
  • 模板支持:你可以指定一个 HTML 模板文件,HtmlWebpackPlugin 会使用这个模板来生成最终的 HTML 文件。模板可以是任意格式,如 EJS、HBS、Jade 等,但需要相应的 loader 来处理;
  • 缓存管理:通过为注入的资源文件添加版本哈希,防止浏览器缓存旧版本的文件,确保用户总是加载到最新的资源;
  • 配置灵活性:插件提供了多种配置选项,如设置生成的 HTML 文件的标题、指定 favicon、定义 meta 标签、控制资源文件的注入位置等 。
  • 支持多入口:当 webpack 配置中有多个入口点,HtmlWebpackPlugin 可以为每个入口点生成一个对应的 HTML 文件,也可以配置插件来生成多个 HTML 文件 ;
  • 压缩 HTML:HtmlWebpackPlugin 还支持压缩生成的 HTML 文件,移除多余的空格和注释,减小文件体积
// 其他配置...
plugins: [new HtmlWebpackPlugin({title: 'My App', // 页面标题template: 'src/index.html', // 指定模板文件filename: 'index.html', // 输出的 HTML 文件名minify: { // 压缩选项removeComments: true,collapseWhitespace: true,},}),
],

        loader和plugin的区别

  • 作用:loader是文件加载器,能够加载资源文件,并对进行一些处理,诸如编译、压缩等,最终打包到指定的文件中;plugin用于在 Webpack 构建过程中扩展其功能,可以影响 Webpack 的构建流程,例如打包优化、资源管理、环境变量注入等,常用于解决 loader无法实现的其他事。
  • 应用时机:在运行时机上,loader 运行在打包文件之前;plugin则是在整个编译周期都起作用。 loader处理单个文件,plugin则用于全局的构建流程。
  • 使用方式:在配置上,loader在module.rules中配置,作为模块的解析规则,类型为数组。每一项都是一个 Object,内部包含了 test(类型文件)、loader、options (参数)等属性;plugin在 plugins中单独配置,类型为数组,每一项是一个 plugin 的实例,参数都通过构造函数传入。

3 webpack高级配置

3.1 配置多入口

  1. 例如,Common.js文件中入口两个
entry: {index: path.join(srcPath, 'index.js'),other: path.join(srcPath, 'other.js')
},
  1. Common.js中的plugins中生成HTML文件时,配置多个入口:
plugins: [// 多入口 - 生成 index.htmlnew HtmlWebpackPlugin({  //每个入口都要new HtmlWebpackPlugin的插件实例template: path.join(srcPath, 'index.html'),filename: 'index.html',       //  可自定义名称// chunks 表示该页面要引用哪些chunk(即上面entry的index和other),默认全部引用chunks: ['index']  // 只引用 index.js}),// 多入口 - 生成other.htmlnew HtmlWebpackPlugin({template: path.join(srcPath, 'other.html'),filename: 'other.html',chunks: ['other']  // 只引用 other.js})
]
  1. Prod.js中output的filename增加name作为多入口entry的key;
output: {// filename: 'bundle.[contenthash:8].js',  // 打包代码时,加上hash戳filename: '[name].[contenthash:8].js', // name即多入口时entry的keypath: distPath,// publicPath: 'http://cdn.abc.com'  // 修改所有静态文件url的前缀(如cdn域名),这里暂时用不到
},

        注:Prod.js中 引入CleanWebpackPlugin插件会在每次打包时,默认清空output.path(即dist目录)文件;

plugins: [new CleanWebpackPlugin(), // 会默认清空output.path文件夹new webpack.DefinePlugin({// window.ENV = 'production'ENV: JSON.stringify('production')})
]

3.2 MiniCssExtractPlugin.loader抽离和CssMinimizerPlugin压缩css

        MiniCssExtractPlugin.loader的主要作用是将CSS提取到单独的文件中,而不是将其嵌入到JavaScript 中,这在生产环境中css 文件可以被浏览器缓存,减少 js 文件体积非常有用。主要优点如下;

  • 性能优化: 提升页面加载性能,因为浏览器可以并行下载CSS文件,而不是等到JavaScript执行完成后才加载CSS。 减少JavaScript bundle 的大小( 代码不再内联在 JavaScript 文件中), 代码不再内联在 JavaScript 文件中。减少页面渲染的时间,提高用户体验。
  • 缓存利用: 独立的CSS文件可以被浏览器缓存,当页面重新加载时,如果CSS没有变化,就不需要再次下载。 对于没有服务端渲染(SSR)的应用,独立的 CSS 文件可以确保样式在页面加载时就已经可用,避免了样式闪烁的问题。
  • 代码组织: 将CSS提取到单独的文件中,使得项目结构更加清晰,便于维护。 方便进行CSS源码映射(source maps)的生成,有助于开发调试。
  • 兼容性: 独立的CSS文件可以在不支持JavaScript或者JavaScript被禁用的情况下仍然正常加载样式。

1. 抽离css和less,使用MiniCSSExtractPlugin.loader抽离成单独文件

module: {rules: [// 抽离 css{test: /\.css$/,loader: [MiniCssExtractPlugin.loader,// 注意这里不再用style-loader'css-loader','postcss-loader']},// 抽离 less --> css{test: /\.less$/,loader: [MiniCssExtractPlugin.loader,// 注意这里不再用style-loader'css-loader','less-loader','postcss-loader']}]
},

2. 在plugins文件中,增加了抽离文件插件MiniCssExtractPlugin的配置,并设置了抽离文件名称;

plugins: [// 抽离 css 文件new MiniCssExtractPlugin({filename: 'css/main.[contenthash:8].css',chunkFilename: '[id].[contenthash:8].css' // 定义动态加载的 CSS 文件的名称格式。})
],

3. 压缩CSS文件,此处webpack5的压缩要使用,css-minimizer-webpack-plugin

optimization: {// 压缩css使用CssMinimizerPlugin; TerserJSPlugin用于压缩和优化JavaScript代码,减少文件体积,提升加载速度minimizer: [new TerserJSPlugin({}), // JavaScript压缩,可设置压缩选项terserOptions: {compress: true,mangle: true,}new CssMinimizerPlugin({}), // CSS压缩,可设置压缩选项minimizerOptions: {preset: 'default',},new OptimizeCSSAssetsPlugin({})],
}

注:在 Webpack 5 中,TerserJSPlugin是默认启用,上述几个插件是需要安装后。使用时引入:

const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const TerserJSPlugin = require('terser-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')

3.3 抽离公共代码

        在生产环境第三方模块,希望其单独打包;公共引用的部分,打包时也希望被拆分。

以下是prod.js中增加的配置,开发环境没有必要这么处理(代码还要修改调试,这么优化会增加打包时间)

optimization: {// 压缩 cssminimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],// **分割代码块(新增)**splitChunks: {chunks: 'all',/***  initial入口chunk,对于异步导入的文件不处理async异步chunk,只对异步导入的文件处理all全部chunk*/// 缓存分组cacheGroups: {// 1. 第三方模块vendor: {name: 'vendor', // chunk 名称priority: 1, // 权限更高,优先抽离,重要!**值越大,优先级越高**test: /node_modules/,  //模块路径在不在node_modulesminSize: 3kb,  // 大小限制,**当文件过小时,单独拆封文件意义不大**;minChunks: 1  // 最少复用过几次,**会被拆分**},// 2. 公共的模块common: {name: 'common', // chunk 名称priority: 0, // 优先级minSize: 0,  // 公共模块的大小限制minChunks: 2  // 公共模块最少复用过几次,**复用达两次的才会拆分**}}}
}

        Common.js中的配置做出相应改动

plugins: [// 多入口 - 生成 index.htmlnew HtmlWebpackPlugin({template: path.join(srcPath, 'index.html'),filename: 'index.html',// chunks 表示该页面要引用哪些chunk(即上面的index和other),默认全部引用chunks: ['index', 'vendor', 'common']  // chunk引用,要考虑代码分割(**新增当前入口下,需要的chunk)**}),// 多入口 - 生成 other.htmlnew HtmlWebpackPlugin({template: path.join(srcPath, 'other.html'),filename: 'other.html',chunks: ['other', 'common']  // 考虑代码分割**(新增当前入口下,需要的chunk)**})
]

3.4 懒加载

        不需要增加配置,webpack默认支持这种语法;

        import本身是懒加载模式,但是使用时有限制必须放在第一行 ,import()可以在任意位置使用。例如,

import('./dynamic-data.js');
import(/* webpackChunkName: "header" */'./header.js').then(res => {console.log(res);
})

相当于定义一个chunk返回一个promise。异步模块是按需加载

3.5 处理react 和 vue

        处理JSX,需要babelIrc中preset配置如下:

"presets": ["@babel/preset-react"],

        Vue则使用vue-loader;

npm  install  vue-loader;

        并在common.js的module.rules中增加配置:

{test:/\.vue$/,loader:['vue-loader'],include:srcPath
},

module chunk bundle的区别

  • module(模块) – 各个源码文件,webpack中任何单独文件皆模块;
  • chunk(代码块) – 多模块合并成的(通过入口文件的分析,引用或者依赖联系起来的),如entry import() splitChunk;类似于还在内存中未输出
  • bundle(捆绑)– 最终输出文件,一个或多个代码块的物理文件。

3.6 优化构建速度(可用于生产)

        优化babel-loader

        利用cacheDirectory开启缓存;当ES6代码没改动时,就不会重新编译

{test: /\.js$/,// ?cacheDirectory是一个查询参数,表示在转换过程中启用缓存,提升编译速度。缓存会将转换结果存储在文件系统中,下次处理相同文件时可以直接使用缓存。loader: ['babel-loader?cacheDirectory'],include: srcPath,// exclude: /node_modules/
},

        include为包含范围,include和exclude两者二选一

        ignorePlugin

        webpack内置插件;这个插件的作用是:指定忽略第三方包指定目录,让这些指定目录不要被打包进去 [4]

plugins: [new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹new webpack.DefinePlugin({// window.ENV = 'production'ENV: JSON.stringify('production')}),// **忽略moment下的/locale目录,忽略默认引用的所有语言包(新增启用IgnorePlugin)**new webpack.IgnorePlugin(/\.\/locale/, /moment/),
],

        noParse

        这是module中的一个属性,作用:指定不去解析属性值代表的库的依赖;可避免重复打包;

module: {// 读完整的‘react.min.js’文件就没有采用模块化的必要// 忽略‘react.min.js’文件的递归解析处理noParse: [/react\.min.js$/],
}

        区别IgnorePlugin和noParse

        IgnorePlugin直接不引入,代码中没有;减少最终包的大小

        noParse引入,但不打包(不进行babel这类编译构建过程);提高构建速度

        happyPack(已经弃用)

        JS单线程,开启多进程打包;提高构建速度(特别是多核CPU);

        Prod.js(也可以写在Common.js,但happyPack会改变babel的配置)中引入happyPack:

const HappyPack = require('happypack')

        改变prod.js下module.rules的配置:

rules: [// js{test: /\.js$/,// 把对.js文件的处理转交给id为babel的HappyPack实例use: ['happypack/loader?id=babel'],include: srcPath,// exclude: /node_modules/},
]

        Plugin的配置

plugins: [new CleanWebpackPlugin(), // 会默认清空 output.path 文件夹new webpack.DefinePlugin({// window.ENV = 'production'ENV: JSON.stringify('production')}),// happyPack 开启多进程打包new HappyPack({// 用唯一的标识符id来代表当前的HappyPack是用来处理一类特定的文件id: 'babel',// 如何处理 .js 文件,用法和 Loader 配置中一样loaders: ['babel-loader?cacheDirectory'],threads: 4, // 指定工作线程数量}),// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
],

        ParallelUglifyPlugin OR TerserWebpackPlugin多进程压缩JS

        ParallelUglifyPlugin是一个用于 Webpack 的插件,可以通过多进程压缩JavaScript文件,从而提高构建速度。尽管这个插件在Webpack 4中非常流行,但在Webpack 5中,通常推荐使用内置的 TerserWebpackPlugin,它已经实现了类似的多线程压缩功能

        Prod.js引用:

const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')

        plugins中配置:

plugins: [new CleanWebpackPlugin(), // 会默认清空output.path文件夹new webpack.DefinePlugin({// window.ENV = 'production'ENV: JSON.stringify('production')}),// **使用 ParallelUglifyPlugin 并行压缩输出的JS代码*****(新增)***new ParallelUglifyPlugin({// 传递给 UglifyJS 的参数// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)uglifyJS: {output: {beautify: false, // 最紧凑的输出comments: false, // 删除所有的注释},compress: {// 删除所有的 `console` 语句,可以兼容ie浏览器drop_console: true,// 内嵌定义了但是只用到一次的变量collapse_vars: true,// 提取出出现多次但是没有定义成变量去引用的静态值reduce_vars: true,}},// 指定进程数量workers: 2, // 根据你的 CPU 核心数调整})
],

        关于开启多进程:项目较大,打包较慢,开启多进程能提高速度;项目较小,打包很快,开启多进程会降低速度(进程开销);建议按需使用

        使用TerserWebpackPlugin(推荐)

        如果你使用的是 Webpack 5,推荐使用内置的 TerserWebpackPlugin,它支持多线程压缩。

安装插件(如果还没有):

npm install terser-webpack-plugin --save-dev  

        配置 Webpack:

const TerserWebpackPlugin = require('terser-webpack-plugin');module.exports = {optimization: {minimize: true,minimizer: [new TerserWebpackPlugin({parallel: true, // 开启多进程terserOptions: {compress: {drop_console: true,},},}),],},
};

3.7 优化构建速度(不可用于生产)

3.7.1 自动刷新

        一般使用不到,配置devServe就默认开启了

3.7.2 热更新

        Dev.js中引用,但不需要安装:

const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');

        dev.js中的entry配置修改:

entry: {// index: path.join(srcPath, 'index.js'),index: [    //index参数配置为数组(新增)'webpack-dev-server/client?http://localhost:8080/','webpack/hot/dev-server',path.join(srcPath, 'index.js')],other: path.join(srcPath, 'other.js')
},plugins: [new webpack.DefinePlugin({// window.ENV = 'production'ENV: JSON.stringify('development')}),new HotModuleReplacementPlugin()   //(新增)
],devServer: {port: 8080,progress: true,  // 显示打包的进度条contentBase: distPath,  // 根目录open: true,  // 自动打开浏览器compress: true,  // 启动gzip压缩hot: true,   //(新增)// 设置代理proxy: {// 将本地 /api/xxx 代理到 localhost:3000/api/xxx'/api': 'http://localhost:3000',// 将本地 /api2/xxx 代理到 localhost:3000/xxx'/api2': {target: 'http://localhost:3000',pathRewrite: {'/api2': ''}}}
},

        热更新的成本,需要自己在开发环境中,注册监听的模块和范围;范围之外还是刷新的方式。

// // module增加,开启热更新之后的代码逻辑
// if (module.hot) {
//     module.hot.accept(['./math'], () => {
//         const sumRes = sum(10, 30)
//         console.log('sumRes in hot', sumRes)
//     })
// }

        热更新与自动刷新的区别

        自动刷新:整个网页全部刷新,速度较慢,且状态会丢失;

        热更新:新代码会生效,网页不刷新,状态不丢失

3.7.3 DLLPlugin动态链接库插件(webpack5已经弃用,方推荐使用Module Federation作为替代方案

        一些不常变化的依赖项打包成动态链接库(DLL),从而减少每次构建时需要重新编译的代码量如vue react。DLLPlugin 和 DLLReferencePlugin 配置将某些模块(如库或框架)打包到一个 DLL 文件中。

DLLPlugin-先打包出dll文件;

DLLReferencePlugin-再使用dll文件;

使用,配置webpack.dll.js文件,使用环境为developmen;

const path = require('path')
const DllPlugin = require('webpack/lib/DllPlugin')  //引用内置插件
const { srcPath, distPath } = require('./paths')module.exports = {mode: 'development',// JS 执行入口文件entry: {// 把 React 相关模块的放到一个单独的动态链接库react: ['react', 'react-dom']},output: {// 输出的动态链接库的文件名称,[name] 代表当前动态链接库的名称,// 也就是 entry 中配置的 react 和 polyfillfilename: '[name].dll.js',// 输出的文件都放到 dist 目录下path: distPath,// 存放动态链接库的全局变量名称,例如对应 react 来说就是 _dll_react// 之所以在前面加上 _dll_ 是为了防止全局变量冲突library: '_dll_[name]',},plugins: [// 接入 DllPluginnew DllPlugin({// 动态链接库的全局变量名称,需要和 output.library 中保持一致// 该字段的值也就是输出的 manifest.json 文件 中 name 字段的值// 例如 react.manifest.json 中就有 "name": "_dll_react"name: '_dll_[name]',// 描述动态链接库的 manifest.json 文件输出时的文件名称path: path.join(distPath, '[name].manifest.json'),}),],
}

        使用 npm run dll运行输出dll文件;

        引用dll文件:首先,index.html文件引用

<script src="./react.dll.js"></script>

        Dev.js中module.plugins配置:

        1.引入插件

// 第一,引入 DllReferencePlugin
const DllReferencePlugin = require('webpack/lib/DllReferencePlugin');

        2.plugins配置

plugins: [new webpack.DefinePlugin({// window.ENV = 'production'ENV: JSON.stringify('development')}),// 第三,告诉 Webpack 使用了哪些动态链接库(新增)new DllReferencePlugin({// 描述 react 动态链接库的文件内容manifest: require(path.join(distPath, 'react.manifest.json')),}),
],

        3.babel-loader中的配置要忽略node-modules(已经打包过了)

rules: [{test: /\.js$/,use: ['babel-loader'],include: srcPath,exclude: /node_modules/ // 第二,不要再转换 node_modules 的代码},
]
3.7.4 Module Federation(没用过c)

        替代方案Module Federation是Webpack 5中的一个强大特性,允许不同的Webpack构建共享和动态加载模块,基本概念:

        Host(主应用):负责加载远程模块的应用。

        Remote(远程模块):提供可以被主应用加载的模块。

[1] https://febook.hzfe.org/awesome-interview/book1/engineer-webpack-workflow

[2] https://febook.hzfe.org/awesome-interview/book3/engineer-webpack-loader#32-babel-loader-分析

[3] 前端构建工具 webpack 笔记-腾讯云开发者社区-腾讯云

[4] 18、webpack优化(3)——IgnorePlugin-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/57934.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于YOLO11/v10/v8/v5深度学习的维修工具检测识别系统设计与实现【python源码+Pyqt5界面+数据集+训练代码】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

Zypher Network:全栈式 Web3 游戏引擎,服务器抽象叙事的引领者

近期&#xff0c;《黑神话&#xff1a;悟空》的爆火不仅让 AAA 游戏重回焦点&#xff0c;也引发了玩家与开发者的热议。Web2 游戏的持续成功导致部分 Web3 玩家们的倒戈&#xff0c;对比之下 Web3 游戏存在生命周期短且商业模式难以明确的问题&#xff0c;尤其在当前加密市场环…

H7-TOOL自制Flash读写保护算法系列,为兆易创新GD32E23X制作使能和解除算法,支持在线烧录和脱机烧录使用(2024-10-29)

说明&#xff1a; 很多IC厂家仅发布了内部Flash算法文件&#xff0c;并没有提供读写保护算法文件&#xff0c;也就是选项字节算法文件&#xff0c;需要我们制作。 实际上当前已经发布的TOOL版本&#xff0c;已经自制很多了。但是依然有些厂家还没自制&#xff0c;所以陆续开始…

flutter 写个简单的界面

起因&#xff0c; 目的: 来源: 客户需求。 着急要&#xff0c;我随便写的&#xff0c;应付一下。 过程: 略&#xff0c;直接看代码&#xff0c;看注释。 代码 1 xxx import package:flutter/material.dart;void main() {runApp(const MyApp()); }// # class MyApp extends…

.NET 8 中 Entity Framework Core 的使用

本文代码&#xff1a;https://download.csdn.net/download/hefeng_aspnet/89935738 概述 Entity Framework Core (EF Core) 已成为 .NET 开发中数据访问的基石工具&#xff0c;为开发人员提供了强大而多功能的解决方案。随着 .NET 8 和 C# 10 中引入的改进&#xff0c;开发人…

推荐一款可视化和检查原始数据的工具:RawDigger

RawDigger是一款强大的工具&#xff0c;旨在可视化和检查相机记录的原始数据。它被称为一种“显微镜”&#xff0c;使用户能够深入分析原始图像数据&#xff0c;而不对其进行任何更改。RawDigger并不是一个原始转换器&#xff0c;而是一个帮助用户查看将由转换器使用的数据的工…

第三十三章 Vue路由进阶路由模块封装

目录 一、引言 二、完整代码 main.js index.js App.vue Find.vue My.vue 一、引言 在上一个章节中&#xff0c;我们将所有的路由配置都堆在main.js中来实现路径组件的路由&#xff0c;这样做的话非常不利于我们后期对项目的维护。因此正确的做法是将路由模块抽离出来&a…

基于java+SpringBoot+Vue的新闻推荐系统设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven mysql5.7或8.0等等组成&#x…

指派问题的求解

实验类型&#xff1a;◆验证性实验 ◇综合性实验 ◇设计性实验 实验目的&#xff1a;学会使用Matlab求解指派问题。 实验内容&#xff1a;利用Matlab编程实现枚举法求解指派问题。 实验例题&#xff1a;有5人分别对应完成5项工作&#xff0c;其各自的耗费如下表所示&#…

下载安装COPT+如何在jupyter中使用(安装心得,windows,最新7.2版本)

目录 1.到杉树科技官网申请下载COPT 2.安装COPT&配置许可文件 3.在jupyter中使用COPT的python接口 最近看到一本和数学建模有关的新书&#xff1a;《数学建模与数学规划&#xff1a;方法、案例及编程实战》&#xff0c;作为数学建模老手&#xff0c;肯定要学习一下&…

基于“互联网+”医养结合的智慧养老实训室建设方案

一、建设背景 根据国家统计局的数据&#xff0c;截至2023年末&#xff0c;我国60岁及以上的老年人口已达到29,697万人&#xff0c;占总人口的21.1%&#xff1b;其中&#xff0c;65岁及以上的人口为21,676万人&#xff0c;占总人口的15.4%。这一数据表明&#xff0c;我国正面临…

为什么需要MQ消息系统,mysql 不能满足需求吗?

大家好&#xff0c;我是锋哥。今天分享关于【为什么需要MQ消息系统&#xff0c;mysql 不能满足需求吗&#xff1f;】面试题&#xff1f;希望对大家有帮助&#xff1b; 为什么需要MQ消息系统&#xff0c;mysql 不能满足需求吗&#xff1f; 1000道 互联网大厂Java工程师 精选面试…

计算机网络-以太网小结

前导码与帧开始分界符有什么区别? 前导码--解决帧同步/时钟同步问题 帧开始分界符-解决帧对界问题 集线器 集线器通过双绞线连接终端, 学校机房的里面就有集线器 这种方式仍然属于共享式以太网, 传播方式依然是广播 网桥: 工作特点: 1.如果转发表中存在数据接收方的端口信息…

C/C++常用编译工具链:GCC,Clang

目录 GNU Compiler Collection GCC的优势 编译产生的中间文件 Clang Clang的特点 什么是LLVM&#xff1f; Clang编译过程中产生的中间表示文件 关于Clang的调试 C 编译工具链中有几个主要的编译工具&#xff0c;包括&#xff1a; GNU Compiler Collection (GCC…

NNLM——预测下一个单词

一、原理篇 NNLM&#xff08;Neural Network Language Model&#xff0c;神经网络语言模型&#xff09;是一种通过神经网络进行语言建模的技术&#xff0c;通常用于预测序列中的下一个词。 NNLM的核心思想是使用词嵌入&#xff08;word embedding&#xff09;将词转换为低维向…

【C++】类和对象(十二):实现日期类

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解C的实现日期类&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1 /!/>/</>/<运算符重载2 /-//-运算符重载(A) 先写&#xff0c;再通过写(B…

KTHREAD--InitialStack和KernelStack和TSS的esp0

InitialStack和TSS.esp0的关系,在这里可以看到 mov ecx, [esi_KTHREAD.InitialStack] ; esi: newthread lea eax, [ecx-210h] ; 越过FPXSAVE指令存储地址 test byte ptr [eax-1Ah], 2 ; 判断efalgs寄存器的VIF位是否为1 jnz short loc_458743 sub eax, 10h…

xlrd.biffh.XLRDError: Excel xlsx file; not supported

文章目录 一、问题报错二、报错原因三、解决思路四、解决方法 一、问题报错 在处理Excel文件时&#xff0c;特别是当我们使用Python的xlrd库来读取.xlsx格式的文件&#xff0c;偶尔会遇到这样一个错误&#xff1a;“xlrd.biffh.XLRDError: Excel xlsx file; not supported”。…

二叉树进阶-二叉搜索树

目录 1.二叉树的概念 2.二叉搜索树的操作 2.1二叉搜索树的结构 2.2实现节点的查找&#xff08;find&#xff09; 2.3实现增加节点&#xff08;insert&#xff09; 2.4实现删除节点&#xff08;erase&#xff09; 2.5析构函数 2.6二叉搜索树的完整实现 3.二叉搜索树的应…

「Mac畅玩鸿蒙与硬件24」UI互动应用篇1 - 灯光控制小项目

本篇将带领你实现一个互动性十足的灯光控制小项目&#xff0c;用户可以通过点击按钮来控制灯光的开关。该项目将涉及状态管理、动态图片加载以及按钮交互&#xff0c;是学习鸿蒙应用开发的重要基础。 关键词 UI互动应用状态管理动态图片加载用户交互 一、功能说明 在这个灯光…