一、为什么需要webpack
1.为什么使用webpack
①传统的书写方式,加载太多脚本会导致网络瓶颈,如不小心改变JavaScript文件加载顺序,项目会崩溃,还会导致作用域问题、js文件太大无法做到按需加载、可读性和可维护性太低的问题。
②当今JavaScript、css的语法规范不断更新,但是浏览器的兼容性却不能同步的更新,开发者可以通过 webpack 预处理器进行编译,自由的使用 JS、CSS 等语言的新语法。
③Vue 脚手架vue-cli、React 脚手架create-react-app、Taro 脚手架taro-cli都是使用webpack,开发者掌握 webpack后,可以自由配置脚手架,根据项目需要,去调整 webpack 配置,以提高项目性能。
④拥有依赖管理、动态打包、代码分离、按需加载、代码压缩、静态资源压缩、缓存等配置;扩展性强,插件机制完善,开发者可自定义插件、loader;webpack 社区庞大,更新速度快,轮子丰富;
1.由于浏览器解析html的顺序是从上至下,引入第三方库时便会存在先后顺序问题;作用域是由于存在多个JS文件时,window下有可能挂载多个相同或不同变量
→解决方案:使用立即调用函数表达式 IIFE 来创建独立作用域
2.js文件过于庞大时无法做到按需加载,如引用第三方工具库lodash等
→解决:
①(依赖nodeJS实现)使用commonJs的module.exports来抛出模块代码,使用require来引入模块
②(不依赖nodeJS实现)可以使用browserify、requireJS等打包工具实现
- 在没有各个 webpack 搭建的脚手架(create-react-app、vue-cli 等等)之前,我们通过在 HTML5 文件里引入一个个 Javascript 文件来进行开发,这就可能导致并行请求数量过多、存在重复代码等问题。
- 而通过 webpack,我们可以使用 import、require 来进行模块化开发。
- 在 webpack 中一切皆模块,js、css、图片、字体都是模块,而且支持静态解析、按需打包、动态加载、代码分离等功能,帮助我们优化代码,提升性能。
2.什么是webpack?
Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
- Webpack是一个模块打包器。
- 在Webpack中会将前端的所有资源文件(js/json/css/img/less/...)都作为模块处理。
- 它将根据模块的依赖关系进行分析,生成对应的资源
3.五个核心概念:
- 【入口(entry)】:指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
- 【输出(output)】:在哪里输出文件,以及如何命名这些文件。
- 【Loader】:处理那些非 JavaScript 文件(webpack 自身只能解析 JavaScript和json)。
- 【插件(plugins)】执行范围更广的任务,从打包到优化都可以实现。
- 【模式(mode)】,有生产模式production和开发模式development
- 对loader的理解:webpack 本身只能处理JS、JSON模块,如果要加载其他类型的文件(模块),就需要使用对应的loader 。它本身是一个函数,接受源文件作为参数,返回转换的结果。loader 一般以 xxx-loader 的方式命名,xxx 代表了这个 loader 要做的转换功能,比如 css-loader。
- 对plugins的理解:插件可以完成一些loader不能完成的功能。
4.配置文件
- webpack.config.js : 用于存储webpack配置信息。
二、webpack与竞品
Webpack :为处理资源管理和分割代码而生,可以包含任何类型的文件。灵活,插件 多。
Parcel :是 0 配置工具, 用户一般无需再做其他配置即可开箱即用。
Rollup:用标准化的格式(es6)来写代码,通过减少死代码尽可能地缩小包体积。 一般只用来打包JS。
构建一个简单的应用并让它快速运行起来?使用 Parcel。
构建一个类库只需要导入很少第三方库?使用 Rollup。
构建一个复杂的应用,需要集成很多第三方库?需要代码分拆,使用静态资源文件, 还有 CommonJS 依赖?使用 webpack。
Vite 将成 为 Vue 的现代标配。甚至最近新推出的 Petite Vue 从开发、编译、发布、Demo几 乎全都是使用 Vite 完成。Webpack、Vite 作为前端热门的工程化构建工具,它们都有各自的适用场景。
三、安装webpack
1.本地安转
bash
npm install --save-dev webpack
# 或指定版本
npm install --save-dev webpack@<version>
如果你使用 webpack v4+ 版本,并且想要在命令行中调用 webpack,你还需要安装 CLI。
bash
npm install --save-dev webpack-cli
**对于大多数项目,我们建议本地安装。这可以在引入重大更新(breaking change)版本时,更容易分别升级项目。**通常会通过运行一个或多个 npm scripts 以在本地 node_modules 目录中查找安装的 webpack, 来运行 webpack:
javascript
"scripts": {
"build": "webpack --config webpack.config.js"
}
2.全局安装
bash
npm install --global webpack
不推荐 全局安装 webpack。这会将你项目中的 webpack 锁定到指定版本,并且在使用不同的 webpack 版本的项目中, 可能会导致构建失败。
- 安装node.js https://nodejs.org/zh-cn/ 最新LTS版本
- 验证node版本 node -v
- 验证npm版本 npm -v
- (可选)执行 npm i webpack webpack -cli --global (全局)安装webapck
- 在当前文件目录下执行 webpack -v 验证版本
- npm init -y 初始化package配置文件
- npm install webpack webpack-cli --save-dev 本地安装webpack及webpack-cli
四、运行webpack
- 在终端中输入 webpack 回车会生成dist文件夹即打包后的文件 (使用全局webpack)
- webpack --status detailed 查看详细打包信息(使用全局webpack)
- npx webpack 使用当前目录webpack
进入项目目录,运行webpack,执行指令:
bash
npx webpack
生成了一个main.js文件
五、自定义webpack配置
实际上, webpack-cli 给我们提供了丰富的终端命令行指令,可以通过npx webpack --help 查看帮助信息。
- npx webpack --entry ./src/index.js --mode production 设置入口文件
可是命令行不方便也不直观,而且还不利于保存配置的内容,可以采取配置config的方式。因此,webpack 还给我们提供了通过配置文件,来自定义配置参数的能力。
- 可以在当前目录下新建webpack.config.js文件,用commonJs写法抛出配置模块:
javascript
const { resolve } = require('path'); //node内置核心模块,用来设置路径。
module.exports = {
//入口是指依赖关系图的开始,从入口开始寻找依赖,打包构建。webpack 允许一个或多个入口配置。
entry: './src/js/app.js', // 入口文件配置(精简写法)
/*完整写法:
entry:{
main:'./src/js/app.js'
}
*/
//输出则是用于配置 webpack 构建打包的出口,如打包的位置,打包的文件名等等。
output: { //输出配置
clean: true, //打包前清理 dist 文件夹,在每次构建前清理 /dist文件夹,这样只会生成用到的文件。
filename: 'bundle.js',//输出文件名
path: resolve(__dirname, './dist')//输出文件路径(绝对路径),require的path为nodeJS自带,__dirname可直接匹配当前目录
},
mode: 'development' //开发环境(二选一) webpack编译模式,区分生产测试环境
//mode: 'production' //生产环境(二选一)
// 文件监视改动,自动产出bundle.js
devtool: 'inline-source-map'//精准定位代码行数,便于查看。在开发模式下追踪代码
watch: true //观察模式,自动检测变化,但需要手动刷新浏览器,如果其中一个文件被更新,代码将被重新编译,所以不必再去手动运行整个构建。
};
使用 webpack-dev-server
**webpack-dev-server支持文件打包后的本地代码实时更新,提高webpack开发效率。**提供了一个基本的 web server,并且具有 live reloading(实时重新加载) 功能。可以自动检测文件变化,从而重新编译,帮助我们自动实现浏览器的刷新。
先安装:
javascript
npm install --save-dev webpack-dev-server
也可以通过npx webpack命令后缀添加--watch 实现编译时自动检测文件变化(需要手动刷新浏览器)
本地安装: npm install webpack-dev-server -D
修改配置文件,告知 dev server,从什么位置查找文件:
javascript
module.exports={
...
devServer:{
static:'./dist'
},
}
以上配置告知 webpack-dev-server,将 dist 目录下的文件作为 web 服务的根目录。
执行命令:
bash
npx webpack serve --open
- 执行 npx webpack --dev-server --watch
- 也可以 npx webpack serve
webpack-dev-server 在编译之后不会写入到任何输出文件。而是将 bundle 文件保留在内存中,然后将它们 serve 到 server 中,就好像它们是挂载在 server 根路径上的真实文件一样。
六、自动引入资源
1.什么是插件?
随着应用程 序增长,如果继续手动管理,就会变得困难起来。然而,webpack在打包流程中产生的编译过程,可以借助插件实现某些功能,通过一些插件可以使这个过程更容易管控。
插件是 webpack 的核心功能。插件可以用于执行一些特定的任务,包括:打包优化,资源管理,注入环境变量等。Webpack自身也是构建于你在webpack 配置中用到的相同的插件系统之上。
想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。 多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而 多次使用同一个插件,这时需要通过使用 new 操作符来创建一个插件实例。
2.使用html-webpack-plugin
本地安装:
javascript
npm install html-webpack-plugin -D
config中添加配置:
javascript
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports={
entry:'./src/index.js',
output:{
// 打包前清理 dist 文件夹
clean: true,
filename:'bundle.js',
path:path.resolve(__dirname,'./dist')
},
mode:'development',
devtool: 'inline-source-map' ,
//插件配置集合
plugins:[
new HtmlWebpackPlugin()
],
}
new HtmlWebpackPlugin() 中也可以添加配置项
javascript
plugins:[
...
new HtmlWebpackPlugin({
template:'./index.html',//指向的html
filename:'app.html',//被打包后的html文件名
inject:'body'// js打包后的生成位置
}),
]
七、引入资源
1.Resource-发送单独文件并导出Url
修改webpack.config.js配置:
javascript
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//...
// 配置资源文件
module: {
rules: [{
test: /\.png/,
type: 'asset/resource'
}]
},
//...
}
在module中设置文件类型及资源类型,output端中增加打包后的资源文件夹配置:
javascript
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'./dist'),
clean:true,
assetModuleFilename:'images/[contenthash][ext]',
},
2.inline-导出资源的Data Url
javascript
module:{
rules:[
...
{
test:/\.svg$/,
type:'asset/inline'
}
]
}
实际展示出的文件的url为base64格式
3.source-导出资源的源代码
javascript
module:{
rules:[
...
{
test:/\.txt$/,
type:'asset/source'
}
]
}
4.asset-通用资源类型-自动选择data url或源文件
javascript
module:{
rules:[
...
{
test:/\.jpg$/,
type:'asset',
parser:{
dataUrlCondition:{
maxSize:4 * 1024 * 1024 //当图片大小大于4M时生成资源文件,否则为base64 url
}
}
}
]
}
可以通过配置临界值实现自动切换资源格式,更加灵活可控(默认大小为8kb)
八、管理资源
除了以上四个资源模块,我们还可以通过webpack-loader引入其他类型的文件。webpack只能解析JS、JSON文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效模块,通过loader可以实现在js上加载css等其他文件。
在 webpack 的配置中,loader 有两个属性:
-
- test 属性,识别出哪些文件会被转换。
-
- use 属性,定义出在进行转换时,应该使用哪个 loader。
javascript
const path = require('path');
module.exports = {
output: {
filename: 'my-first-webpack.bundle.js',
},
module: {
rules: [{ test: /\.txt$/, use: 'raw-loader' }],
},
};
//以上配置中,对一个单独的 module 对象定义了 rules 属性,里面包含两个必须属性:
//test 和 use。这告诉 webpack 编译器(compiler) 如下信息:
//"嘿,webpack 编译器,当你碰到「在 require()/import 语句中被解析为'.txt' 的路径」时,
//在你对它打包之前,先 use(使用) raw-loader 转换一下。
1.加载CSS
- 本地安装css-loader及style-loader npm install css-loader -D
- 并在规则中添加css
javascript
module:{
rules:[
...
{
test:/\.css$/,
use:['style-loader','css-loader']
}
]
}
应保证 loader 的先后顺序: 'style loader' 在前,而 'css loader' 在后。如果 不遵守此约定,webpack 可能会抛出错误。
2.抽离和压缩CSS
在多数情况下,我们也可以进行压缩CSS,以便在生产环境中节省加载时间,同时还可以将CSS文件抽离成一个单独的文件。
抽离
实现这个功能,需要mini-css-extract-plugin这个插件来帮忙,本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
- 本地安装mini-css-extract-plugin : npm install mini-css-extract-plugin -D
- 基于webpack5构建,需要webpack5环境下才能正常使用
- 和style-loader不同,MiniCssExtractPlugin会生成link标签从而引入外部css
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial scale=1.0">
<title>Webpack5学习指南</title>
<link href="main.css" rel="stylesheet"></head>
<body>
<script defer src="bundle.js"></script></body>
</html>
将 loader 与 plugin 添加到你的 webpack 配置文件中。通过require引入后在plugins里添加引用,同时在原有css规则中替换掉style-loader,npx webpack打包后生效。
javascript
plugins:[
new HtmlWebpackPlugin({
template:'./index.html',
filename:'app.html',
inject:'body'
}),
new MiniCssExtractPlugin()
]
javascript
module:{
rules:[
...
{
test:/\.css$/,
use:[MiniCssExtractPlugin.loader,'css-loader']
}
]
}
在plugins里调用MiniCssExtractPlugin时也可以添加filename来自定义生成目录及文件名。
contenthash会随机生成字符串的文件名:
javascript
plugins:[
...
new MiniCssExtractPlugin({
filename:'styles/[contenthash].css'
}),
],
压缩
- 本地安装css-minimizer-webpack-plugin: npm install css-minimizer-webpack-plugin -D
- 和其他插件不同,需在optimization中添加配置,并且mode要切换为production
javascript
//优化配置
optimization:{
minimizer:[
new CssMinimizerPlugin(),
],
}
javascript
//生产模式
mode:'production'
3.加载images图像
假如,现在我们正在下载 CSS,但是像 background 和 icon 这样的图像,要如何处理呢?在 webpack 5 中,可以使用内置的 Asset Modules,可以将这些内容混入我们的系统中,在 css文件里也可以直接引用文件,修改style.css 和入口 index.js,依赖于资源模块
javascript
module:{
rules:[
...
{
test:/\.png$/,
type:'asset/resource',
generator:{
filename:'images/[contenthash][ext]'
}
}
]
}
打包后:
javascript
import './style.css'
4.加载fonts字体
?使用 Asset Modules 可以接收并加载任 何文件,然后将其输出到构建目录。这就是说,我们可以将它们用于任何类型的文 件,也包括字体。
javascript
module:{
rules:[
...
{
test:/\.(woff|woff2|eot|ttf|otf)$/,
type:'asset/resource'
}
]
}
在css中引用并创建字体图标库
javascript
@font-face {
font-family: 'iconfont';
src: url('../icon/iconfont.ttf') format('truetype');
}
5.加载数据
可以加载的有用资源还有数据,如 JSON 文件,CSV、TSV 和 XML。类似于 NodeJS,JSON 支持实际上是内置的,也就是说 import Data from loader 和 './data.json' 默认将正常运行。
- 根据数据格式安装不同的loader模块,比如要导入 CSV、TSV 和 XML,可以使用csv-loader和xml-loader。
- 本地安装 csv-loader xml-loader : npm install csv-loader xml-loader -D
- 在项目中引入csv、xml等数据格式时会自动转化为对象、数组等JS数据格式
javascript
module:{
rules:[
...
{
test:/\.(csv|tsv)$/,
use:'csv-loader'
},
{
test:/\.xml$/,
use:'xml-loader'
}
]
}
//现在,你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,
//所导入的 Data 变量,将包含可直接使用的已解析 JSON
6.自定义JSON模块parser
通过使用自定义 parser 替代特定的webpack loader,可以将任何toml、yaml或json5文件作为JSON导入。
本地安装 toml yaml json5 :npm install toml yaml json5 -D
引入模块:
javascript
const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')
添加规则:
javascript
module:{
rules:[
...
{
test:/\.xml$/,
use:'xml-loader'
},
{
test:/\.toml$/,
type:'json',
parser:{
parse:toml.parse
}
},
{
test:/\.yaml$/,
type:'json',
parser:{
parse:yaml.parse
}
},
{
test:/\.json5$/,
type:'json',
parser:{
parse:json5.parse
}
}
]
}
7.使用babel-loader
webpack 自身可以自动加载JS文件,就像加载JSON文件一样,无需任何 loader。但加载的JS文件会原样输出,即使你的JS文件里包含ES6+的代码,也不会做任何转化。这时我们就需要Babel来帮忙。Babel 是一个 JavaScript 编译器,可以将 ES6+转化成ES5,将js代码转化以提供兼容度支持。在Webpack里使用Babel,需要使用babel-loader。
- babel-loader: 在webpack里应用 babel 解析ES6的桥梁
- @babel/core: babel核心模块
- @babel/preset-env: babel预设,一组 babel 插件的集合
本地安装: babel-loader @babel/core @babel/preset-env : npm install babel-loader @babel/core @babel/preset-env -D
在 webpack 配置中,添加babel-loader到module中:
javascript
module:{
rules:[
...
{
test:/\.js$/,
exclude:/node_modules/,
use:{
loader: 'babel-loader',
options:{
presets:['@babel/preset-env']
}
}
}
]
}
注意:
- 需在exclude中排除node_modules包
- 如需兼容async/await语法则还需要添加regeneratorRuntime模块。regeneratorRuntime是webpack打包生成的全局辅助函数,由babel生成,用于兼容async/await的语法。
- npm install --save @babel/runtime【这个包中包含了regeneratorRuntime,运行时需要】
- npm install --save-dev @babel/plugin-transform-runtime【这个插件会在需要regeneratorRuntime的地方自动require导包,编译时需要】
javascript
module:{
rules:[
...
{
test:/\.js$/,
exclude:/node_modules/,
use:{
loader: 'babel-loader',
options:{
presets:['@babel/preset-env'],
plugins:[
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
}
九、代码分离
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。项目中若存在多个入口文件时,则需要代码分离;若存在多个模块共用的代码时,也需要分离代码来防止重复打包。
代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。常用的代码分离方法有三种:
- 入口起点:使用 entry 配置手动地分离代码。
- 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。
- 动态导入:通过模块的内联函数调用来分离代码。
1.入口起点
javascript
module.exports = {
entry: {
//将其他包命名在entry中
index: './src/index.js',
another: './src/another-module.js',
},
output: {
//并在output输出端中直接配置[name]对应entry中的key
filename: '[name].bundle.js'
//...
},
//...}
执行npx webpack后便会提示相应包名被分别打包且都被引入
存在一些隐患:
- 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。
- 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。
2.防止重复
Entry dependencies
入口依赖,当图中两个模块共有lodash时,会抽离出来并取名为lodash。配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块:
javascript
entry:{
index:{
import:'./src/index.js',
dependOn:'shared'
},
another:{
import:'./src/another-module.js',
dependOn:'shared'
},
shared:'lodash'
index:'./src/index.js',
another:'./src/another-module.js'
},
打包后多出的shared.budle.js即为定义中的模块,此时lodash是共用的,做到了模块的去重和分离
SplitChunksPlugin
可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。
配置时依旧可以采用独立命名
javascript
entry:{
index:'./src/index.js',
another:'./src/another-module.js'
},
在 optimization 优化配置项中添加 splitChunks
javascript
optimization:{
...
splitChunks:{
chunks:'all'
}
},
执行之后可以发现,使用 optimization.splitChunks 配置选项之后,index.bundle.js 和 another.bundle.js 中已经移除了重复的依赖模块。注意,插件将 lodash分离到单独的chunk,并且将其从main bundle 中移除,减轻了大小。
3.动态导入
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。
- 第一种,也是推荐选择的方式是,使用符合ECMAScript提案的import()语法来实现动态导入,且不影响其他模块抽离方式。
async-module.js 假设为功能模块:
javascript// async-module.js function getComponent(){ return import('lodash').then(({default:_})=>{ const element = document.createElement('div') element.innerHTML = _.join(['hello','webpack'],' ') return element }) } getComponent().then((element)=>{ document.body.appendChild(element) })
在入口文件中导入:
javascriptimport './async-module'
- 第二种,则是webpack的遗留功能,使用 webpack 特定的 require.ensure。
4.懒加载
懒加载或按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。
依旧通过import直接引入模块,区别在于:何时调用何时加载模块
javascript
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
import(/* webpackChunkName: 'math' */ './math.js').then(({ add
}) => {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
加上注释 webpackChunkName:'模块名' 后,可以定义打包后的模块名
javascript
import(/* webpackChunkName:'math' */'./math.js')
第一次加载完页面, math.bundle.js不会加载,当点击按钮后,才加载 math.bundle.js文件。
5.预获取/预加载模块
在声明import时,使用下面这些内置指令,可以让webpack输出"resource hint(资源提示)",来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源 ,即在浏览器网络空闲时再获取资源
- preload(预加载):当前导航下可能需要资源,和懒加载效果类似
prefetch
依旧在import引入时的注释中添加
javascript
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click',()=>{
import(/* webpackChunkName:'math',webpackPrefetch:true */'./math.js').then(({add})=>{
console.log(add(4,5));
})
})
document.body.appendChild(button)
加上注释 webpackPrefetch: true 后,可以告诉 webpack 执行预获取。会生成<link rel="prefetch" href="main.js">并追加到页面头部,指示着浏览器在闲置时间预取 math.js 文件。
我们发现,在还没有点击按钮时,math.bundle.js就已经下载下来了。同时,在 app.html可以看到,点击按钮,会立即调用已经下载好的 math.bundle.js文件中的 add 方法。
preload
- 与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
十、缓存
打包后的dist文件部署到服务器以后便能被浏览器客户端所访问,因浏览器的解析特性会优先选择缓存资源,我们要确保文件发生更新时浏览器能识别到,所以需要对输出文件的文件名做处理。
1.输出文件的文件名
我们可以通过替换 output.filename 中的 substitutions 设置,来定义输出文件的名称。webpack提供了一种使用 **substitution (可替换模板字符串)**的方式,通过带括号字符串来模板化文件名,其中,[contenthash] substitution 将根据资源内容创建出唯一的hash。当资源内容发生变化时, [contenthash] 也会发生变化。
javascript
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
};
2.缓存第三方库
将第三方库 (library) (例如 lodash) 提取到单独的 vendor chunk 文件中,是比较推荐的做法,因为第三方库文件很少像本地源码那样频繁修改,所以我们可以利用client的长效缓存机制,命中缓存来消除请求,并减少向server 获取资源,同时保证client和server的代码一致。
在 optimization.splitChunks 添加cacheGroups 参数并构建:
javascript
optimization:{
...
splitChunks:{
...
cacheGroups:{
vendor:{
test:/[\\/]node_modules[\\/]/,
name:'vendors',
chunks:'all'
}
},
}
}
3.将js文件放到一个文件夹中
目前,全部 js 文件都在 dist文件夹根目录下,我们尝试把它们放到一个文件夹中, 这个其实也简单,在输出配置中修改 filename,即output.filename加上scripts前缀即可汇总js文件至指定文件夹下生成。
javascript
output:{
filename:'scripts/[name].[contenthash].js',
...
}
十一、拆分开发环境和生产环境配置
1.公共路径
publicPath 配置选项在各种场景中都非常有用,我们可以通过它来指定应用程序中所有资源的基础路径。
javascript
output:{
filename:'scripts/[name].[contenthash].js',
path:path.resolve(__dirname,'./dist'),
clean:true,
assetModuleFilename:'images/[contenthash][ext]',
publicPath:'http://localhost:8080/'
},
- 基于环境设置
在开发环境中,我们通常有一个assets/ 文件夹,它与索引页面位于同一级 别。这没太大问题,但是,如果我们将所有静态资源托管至 CDN,然后想在生产环境中使用呢? 想要解决这个问题,可以直接使用一个 environment variable(环境变量)。假设我们有一个变量 ASSET_PATH:
javascript
import webpack from 'webpack';
// 尝试使用环境变量,否则使用根路径
const ASSET_PATH = process.env.ASSET_PATH || '/';
export default {
output: {
publicPath: ASSET_PATH,
},
plugins: [
// 这可以帮助我们在代码中安全地使用环境变量
new webpack.DefinePlugin({
'process.env.ASSET_PATH': JSON.stringify(ASSET_PATH),
}),
],
};
- Automatic publicPath
有可能你事先不知道 publicPath 是什么,webpack 会自动根据 import.meta.url、 document.currentScript、 script.src 或者 self.location 变量设置 publicPath。你需要做的是将 output.publicPath 设为 ' auto':
javascript
module.exports = {
output: {
publicPath: 'auto',
},
};
注意:在某些情况下不支持 document.currentScript,例如:IE 浏览器,不得不引入一个 polyfill,例如 currentScript Polyfill。
2.环境变量
想要消除 webpack.config.js 在开发环境和生产环境之间的差异,需要环境变量(environment variable)。webpack命令行环境配置的 --env 参数,可以允许传入任意数量的环境变量。而webpack.config.js 中可以访问到这些环境变量。例如,--env production 或--env goal=local。
- npx webpack --env production 可以通过命令传入变量区分环境
对于我们的 webpack 配置,有一个必须要修改之处。通常,module.exports 指向配置对象。要使用 env 变量,你必须将 module.exports(抛出对象)转换为函数后即可动态传参
javascript
//...
module.exports = (env) => {
return {
//...
// 根据命令行参数 env 来设置不同环境的 mode
mode: env.production ? 'production' : 'development',
//...
}
}
3.拆分配置文件
生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到不同的配置文件中。如 webpack.config.dev.js(开发环境配置)和 webpack.config.prod.js(生产环境配置)。在项目根目录下创建一个配置文件夹config 来存放他们。
- 开发环境:执行 npx webpack serve -c config文件路径
- 生产环境:执行 npx webpack -c config文件路径
4.npm命令
每次打包或启动服务时,都需要在命令行里输入一长串的命令。我们将父目录的 package.json、 node_modules 与 package-lock.json拷贝到当前目录下,
可以在package.json中配置命令来方便使用,配置 npm 脚本来简化命令行的输入。
javascript
"scripts": {
"start": "webpack serve -c ./config/webpack.config.js --env development",
"build": "webpack -c ./config/webpack.config.js --env production"
},
- 开发环境运行脚本:npm run start
- 生产环境运行脚本:npm run build
关闭生产环境打包的文件过大警告提示:
javascript
performance:{
hints:false
}
5.提取公共配置
拆分出的配置文件存在大量重复配置代码时,可以单独创建一个公共配置文件,手动的将这些重复的代码单独提取到一个文件里。创建 webpack.config.common.js,配置公共的内容。
6.合并配置文件
配置文件拆分好后,我们又需要保证配置合并没用的问题,这时候就需要webpack-merge这个工具。
- 安装 npm install webpack-merge -D
- 创建新的config文件用以合并
javascript
const {merge} = require('webpack-merge')
const commonConfig = require('./webpack.config.common')
const productionConfig = require('./webpack.config.prod')
const developmentConfig = require('./webpack.config.dev')
module.exports=((env)=>{
switch(true){
case env.development:
return merge(commonConfig,developmentConfig)
case env.production:
return merge(commonConfig,productionConfig)
default:
return new Error('No matching configuration was found')
}
})