用途
- 转义代码(ES6 转换为 ES5 )。
- 代码压缩。
- 代码分析。
- 构建发布项目。
如何安装
全局安装
npm install -g webpack webpack-cli
查看所有webpack 版本命令
npm info webpack
注意:安装 webpack 时最好不要全局安装,因为一些旧的项目用到的 webpack 版本是不一样的。
起步
创建空目录并进入
mkdir webpack-demo-1 && cd webpack-demo-1
初始化webpack
npm init -y 或者 yarn init
本地安装webpack
npm install webpack webpack-cli --save-dev
yarn add webpack webpack-cli
初体验
在目录下新建一个
index.js文件
mkdir src && cd src
touch index.js
给
index.js文件添加内容
console.log('hello webpack')
运行webpack命令
npx webpack
为什么使用 npx?
npx可以自动去到node_modules/.bin/webpack找到webpack并运行。
运行之后发现控制台抛出一个警告,如图:
意思是没有设置
mode选项 ,需要新建一个webpack.config.js文件进行设置:
// webpack.config.js 内容
module.exports = {
mode: 'development' // development 是开发; production 是生产
};
然后再运行 npx webpack 则就不会有警告抛出。
上述的代码我们可以知道 mode 是用于切换是开发模式还是生产模式进行打包。
配置入口(entry)
可以在 webpack.config.js 中配置入口文件,配置入口之后,webpack 会从这个入口处进行处理。如下代码:
module.exports = {
mode: 'development',
entry: './src/index.js'
};
配置出口(output)
output告诉webpack在什么地方输出创建的目录,以及如何命名这些文件,默认是 ./dist 。如果我们指定了输出的配置,基本上所有的程序都会输出到我们所配置的目录当中,如下代码:
// webpack.config.js 内容
var path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, './dist'), // 打包后存放的文件存放的地方
filename: 'main.js' // 这里表示webpack打包后我所创建的 index.js 最终生成为 main.js
}
};
注:
__dirname是node.js中的一个全局变量,它指向当前执行脚本所在的目录。
当然在输出时为了防止文件名称进行 HTTP 缓存,需要在 webpack 中设置一些随机文件名,配置如下:
// webpack.config.js 内容
var path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].[conenthash].js' // contenthsh 表示随机字符串 name 表示是会生成一个 main 文件名称,默认的。
}
};
// 所以最终生成的文件名如下
// main.ca0549f95b112594dcbb.js
上述代码写了之后会造成一个问题,就是默认打包的 dist 目录文件会越来越多,所以呢我们必须在打包之前先删除 dist 然后再打包。修改 package.json 文件如下:
{
"name": "webpack-demo-1",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "rm -rf dist && webpack",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"description": "",
"dependencies": {
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11"
}
}
在配置了 scripts 中的 build 后就可以在命令行输入: yarn build 或者 npm run build进行打包。当然更简单的方法则是在webpacl.config.js中配置即可:
module.exports = {
mode: "development",
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "main.js",
clean: true,
}
}
配置 clean选项后则每次打包出的目录前,会把之前的目录覆盖掉。
配置 source map
// webpack.config.js 内容
module.exports = {
mode: 'development',
entry: './src/index.js',
devtool: "inline-source-map",
output: {
filename: '[name].[conenthash].js'
}
};
使用webpack构建本地服务器
如果想让浏览器监听代码的变化,并自动刷新显示修改后的结果,webpack 为我们提供了一个本地开发服务器,这个本地服务器是基于 node.js 构建。我们需要在 webpack 中单独安装。
yarn add --dev webpack-dev-server
需要在 webpack.config.js 中进行配置,它的配置项是 devserver 其中比较重要的配置项如下:(更多配置 )
| devserer 配置 | 功能描述 |
|---|---|
| static | 默认 webpack-dev-server 会为根文件夹提供本地服务器,如果想为另外一个目录下文件提供本地服务器,应该在这设置起所在的目录(我们使用的是'dist' 目录) |
| port | 设置默认监听的端口,默认为 “8080” |
| inline | 设置为 true,当源文件改变时会自动刷新页面 |
| historyApiFallback | 如果设置为 true,所有的跳转将指向 index.html |
devServer: {
static: "./dist", // 本地服务器所加载的页面所在的目录
historyApiFallback: true, // 不跳转
inline: true // 实时刷新
}
在 package.json 中的 scripts 对象中添加如下命令,用以开启本地服务器:
"scripts": {
"start": "webpack-dev-server",
"build": "rm -rf dist && webpack"
}
启动时可以直接使用 yarn start 。如果想自动启动浏览器的话,则可以加上 --open :
"scripts": {
"start": "webpack-dev-server --open",
"build": "rm -rf dist && webpack"
}
webpack 打包资源文件
在 JavaScript 中引入的资源文件,也可以单独打包,引入如下:
import butterman from "./assets/butterman.png"
const img = document.createElement('img')
img.src = butterman
document.body.appendChild(img)
使用asset/resource打包,导出一个单独的文件,并生成一个 URL:
const path = require("path")
module.exports = {
mode: "development",
entry: "./src/index.js",
devtool: "inline-source-map",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "main.js",
clean: true,
assetModuleFilename: "images/[contenthash][ext]" // contenthash 表示生成 hash 值的资源名称,ext 表示使用资源本身的扩展名
},
module: {
rules: [
{
test: /\.png$/,
type: "asset/resource",
generator: {
filename: "images/[contenthash][ext]" // 优先级比 assetModuleFilename 高
}
}
]
}
}
使用 asset/inline打包资源,导出的是资源的data URI:
const path = require("path")
module.exports = {
mode: "development",
entry: "./src/index.js",
devtool: "inline-source-map",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "main.js",
clean: true,
assetModuleFilename: "images/[contenthash][ext]" // 生成hash值的资源名称,ext 表示使用资源扩展名
},
module: {
rules: [
{
test: /\.png$/,
type: "asset/resource",
generator: {
filename: "images/[contenthash][ext]" // 优先级比 assetModuleFilename 高
}
},
{
test: /\.svg/,
type: "asset/inline"
}
]
}
}
在生成的目录中不会存在此文件,反应到页面中则是一串base64的字符串。
使用 asset/source打包,导出资源的源代码:
const path = require("path")
module.exports = {
mode: "development",
entry: "./src/index.js",
devtool: "inline-source-map",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "main.js",
clean: true,
assetModuleFilename: "images/[contenthash][ext]" // 生成hash值的资源名称,ext 表示使用资源扩展名
},
module: {
rules: [
{
test: /\.png$/,
type: "asset/resource",
generator: {
filename: "images/[contenthash][ext]" // 优先级比 assetModuleFilename 高
}
},
{
test: /\.svg/,
type: "asset/inline"
},
{
test: /\.txt/,
type: "asset/sorce"
}
]
}
}
使用通用的资源类型进行打包,webpack 将按照默认条件,自动地在resource和inline之间进行选择:小于 8kb 的文件,将会视为inline模块类型,否则会被视为resource模块类型。
const path = require("path")
module.exports = {
mode: "development",
entry: "./src/index.js",
devtool: "inline-source-map",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "main.js",
clean: true,
assetModuleFilename: "images/[contenthash][ext]" // 生成hash值的资源名称,ext 表示使用资源扩展名
},
module: {
rules: [
{
test: /\.png$/,
type: "asset/resource",
generator: {
filename: "images/[contenthash][ext]" // 优先级比 assetModuleFilename 高
}
},
{
test: /\.svg/,
type: "asset/inline"
},
{
test: /\.jpg/,
type: "asset"
}
]
}
}
可以通过:rules.parser.dataUrlCondition.maxSize 设置来定义超过多大的时,
自动使用 resource:
const path = require("path")
module.exports = {
mode: "development",
entry: "./src/index.js",
devtool: "inline-source-map",
output: {
path: path.resolve(__dirname, "./dist"),
filename: "main.js",
clean: true,
assetModuleFilename: "images/[contenthash][ext]" // 生成hash值的资源名称,ext 表示使用资源扩展名
},
module: {
rules: [
{
test: /\.png$/,
type: "asset/resource",
generator: {
filename: "images/[contenthash][ext]" // 优先级比 assetModuleFilename 高
}
},
{
test: /\.svg/,
type: "asset/inline"
},
{
test: /\.jpg/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024 // 超过 4M 自动选择 asset/resource 模式
}
}
}
]
}
}
webpack 生成 HTML
安装插件
html-webpack-plugin。
yarn add --dev html-webpack-plugin
npm install html-webpack-plugin -D
webpack.config.js 配置引入插件。
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js',
clean: true // 表示每次都会清理上一次打包出来的目录
},
plugins: [
new HtmlWebpackPlugin()
]
}
运行
yarn build生成一个dist/index.html,包含如下内容:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script type="text/javascript" src="main.c96a8b8962a66c4b8d11.js"></script></body>
</html>
如果需要引入静态模板的话,则可以使用如下命令:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js'
},
plugins: [
new HtmlWebpackPlugin({
title: '小鱼儿的快乐生活', // 配置生成的html的title
template: 'src/assess/test.html', // 引入静态模板
inject: "body" // 表示 script 会在body中,默认是在 head 中
})
]
}
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<!--这里的title生成的是webpack中配置的title-->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<h1>这是我需要打包的html文件</h1>
</body>
</html>
webpack 打包 CSS
webpack 提供了两个工具用来处理 CSS,分别是, css-loader 与 style-loader 。二者对于处理 css 来讲有不同的任务分工, css-loader 的作用是能够使用类型 @import 和 url(...) 的方法实现require() 的功能,而 style-loader 是将所有的计算后的样式加入到页面当中,也就是是说在 HTML 头部中会多出一个 style 的标签来。二者的结合就使得我们把样式表嵌入到 webpack 打包后的 JS 中。
首先,安装
css-loader与style-loader
yarn add --dev style-loader
yarn add --dev css-loader
在webpack.config.js 中引入
var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: '[name].[contenthash].js'
},
plugins: [new HtmlWebpackPlugin({
title: '小鱼儿的快乐生活',
template: 'src/assets/test.html',
})],
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
}
};
上面的 module 中引入了 css 两个插件。运行打包命令后预览页面即可看到我们所有的样式都加在了页面头部的style 标签中。
注意:
style-loader一定是写在前面的。
webpack 单独抽取css文件
安装
mini-css-extract-plugin插件
yarn add --dev mini-css-extract-plugin
配置 plugins
plugins: [
new HtmlWebpackPlugin({
title: '小鱼儿的快乐生活',
template: 'src/assets/test.html',
}),
new MiniCssExtractPlugin({
// 默认生成main.xxxx.css
filename: '[name].[contenthash].css',
chunkFilename: '[id].[contenthash].css'
})
]
配置 module
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: (resourcePath, context) => {
return path.relative(path.dirname(resourcePath), context) + '/';
},
},
},
'css-loader',
],
}
]
}
压缩打包的 css
安装
css-minimizer-webpack-plugin
npm install css-minimizer-webpack-plugin -D
webpack.config.js 配置
const MiniMizerWebpackPlugin = require("css-minimizer-webpack-plugin")
module.exports = {
mode: "production",
...
optimization: {
minimizer: [
new MiniMizerWebpackPlugin()
]
}
}
webpack 打包 scss
需要安装
sass-loader与dart-sass。
yarn add --dev sass-loader dart-sass
webpack.config.js 配置
module: {
rules: [
{
test: /\.scss$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
{
loader: 'sass-loader',
options: {
// Prefer `dart-sass`
implementation: require('dart-sass'),
},
},
],
},
]
}
webpack 打包 less
安装
less-loader与less
yarn add --dev less-loader less
配置 webpack.config.js
module: {
rules: [
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"]
}
]
}
webpack 打包 stylus
安装
stylus-loader与stylus
yarn add --dev stylus-loader stylus
配置 module
module: {
rules: [
{
test: /\.styl/,
use: ["style-loader", "css-loader", "stylus-loader"]
}
]
}
webpack 打包图片
打包图片的核心是使用一个叫 file-loader 的loader,它的作用是把文件转换为一个文件路径。
安装
file-loader
yarn add --dev file-loader
配置
module
module: {
relus: [
{
test: /\.(png|svg|jpg|jpeg|gif)$/,
use: ["file-loader"]
}
]
}
使用 babel-loader
- 安装
npm install babel-loader @babel/core @babel/preset-env -D
- webpack.config.js 中配置,使用babel-loader。
module: {
relus: [
{
test: /\.js$/,
exclude: /node_modules/, // 排除 node_modules 中的文件
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
}
]
}
- 使用 regeneratorRuntime 插件(async 与 await 使用时用到此插件)。
npm install @babel/runtime -D
npm install @babel/plugin-transform-runtime -D
module: {
relus: [
{
test: /\.js$/,
exclude: /node_modules/, // 排除 node_modules 中的文件
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
plugins: [
["@babel/plugin-transform-runtime"]
]
}
}
}
]
}
代码分离
代码分离就是通过 webpack 把代码分离到不同的文件中,通过按需加载的方式去引入。 常用的分离方法:
- 入口起点。使用
entry配置手动分离代码。 - 使用
Entry dependencies或SplitChunksPlugin去重分离代码。 - 动态导入。
入口配置
入口配置,就是在入口处(entry)配置多个入口,打包出多个文件,但是这样有一个很大的弊端:就是会重复打包。我们如下配置:
module.exports = {
mode: "development",
entry: {
index: "./src/index.js",
other: "./src/other.js"
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "[name].main.js",
clean: true
}
}
如上代码中,我们配置了两个入口,分别是:index.js与other.js,在other.js中引入lodash.js在index.js中保持原封不动:
// other.js
import _ from 'lodash'
const str = _.join(['a', 'b', 'c'], '-')
console.log(str)
// index.js
import helloWorld from './helloWorld'
helloWorld()
那么打包出的结果如下:
可以看到打包出的文件
index.main.js目前是78.8kb。other.main.js大小是1.38Mib。我们做一个动作,在index.js中引lodash.js看看会不会重复打包:
// index.js
import helloWorld from './helloWorld'
import _ from "lodash"
console.log(_.join(["e", "f", "d"], "-"))
helloWorld()
打包出结果如下:
可以看出
index.main.js大小变为了1.44MiB了,就是因为配置了多入口导致引入的lodash.js被重复打包了,所以此代码分离不可取。
防止重复的 entry
- 可以通过
entry选项来抽离公共的模块:
module.exports = {
mode: "development",
entry: {
index: {
import: "./src/index.js",
dependOn: "shared"
},
other: {
import: "./src/other.js",
dependOn: "shared"
},
shared: "lodash" // 公共的模块
}
}
打包如下:
可以看到多出一个
shared.main.js就是抽离的第三方的lodash模块。
- 配置
splitChunks自动抽离第三方代码
module.exports = {
...
entry: {
index: "./src/index.js",
other: "./src/other.js"
},
...
optimization: {
splitChunks: {
chunks: "all" // 配置之后自动抽离
}
}
}
动态导入
动态导入是借助 ES6 的 import 来实现的:
// asyncModule.js
function getComponent() {
return import('lodash') // 动态引入 lodash
.then(({default: _}) => {
const element = document.createElement('div')
element.innerHTML = _.join(['hello', 'webpack'], '-')
return element
})
}
getComponent().then((element) => {
document.body.appendChild(element)
})
在 index.js中引入:
import './asyncModule.js'
执行打包后的结果:
可以看到多出一个
vendors-node_....js这个文件就是我们动态引入的文件。
动态导入应用-懒加载
- 在项目中新建一个
math.js文件,定义两个方法:
export const add = (x, y) => {
return x + y
}
export const minus = (x, y) => {
return x - y
}
- 在
index.js中通过动态导入的方法引入:
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)
可以看到,上述的代码是在页面中创建了一个button元素,其中/*webpackChunkName: 'math'*/的含义则是在打包时给math.js的打包出的文件命名。
- 执行打包后,如下:
dist目录中多出了一个math.main.js文件。此文件只有在点击页面中的「相加按钮」时才会引入,从而达到了一个懒加载的效果。
动态引入-预加载/预获取
Webpack v4.6.0+的版本中开始支持。
在使用动态导入时,可以使用 webpack 内置的一些指令来实现预加载和预获取:
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 */会生成一个 <link rel="prefetch" href="math.js"/>并追加到页面头部,指示着浏览器在闲置时间预取 math.js 文件。
缓存
缓存我们业务代码的话,只需每次打包时加上hash值即可,所以配置一下output即可:
module.exports = {
output: {
path: path.resolve(__dirname, "./dist"),
filename: "[name].[contenthash].js"
}
}
第三方包缓存的话则是通过 webpack 自带的功能进行缓存:
module.exports = {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: "all"
}
}
}
}
上述代码的话就是把node_modules中所有的包进行缓存打包为一个叫 vendors.xxx.js的文件。
拆分生产环境与开发环境
为什么需要拆分?因为环境不同,用到的配置选项也不同,比如:测试环境不需要压缩代码、不需要配置
publicPath、无需设置缓存等;而生产环境则不需要设置sourceMap等,当然有的项目还是需要配置的,具体看业务需求。所以就需要单独拆分,那么如何拆分呢?步骤如下:
1. 公用配置
命名为base.config.js,开发与生产环境都可以用到的配置项,单独提取一个文件出来:
const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
module.exports = {
entry: {
index: "./src/index.js"
},
output: {
path: path.resolve(__dirname, "../dist"),
clean: true,
assetModuleFilename: "images/[contenthash][ext]",
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html",
inject: "body"
}),
new MiniCssExtractPlugin({
filename: "styles/[name].[contenthash].css"
})
],
module: {
rules: [
{
test: /\.png$/,
type: "asset/resource",
generator: {
filename: "images/[contenthash][ext]"
}
},
{
test: /\.svg$/,
type: "asset/inline"
},
{
test: /\.txt$/,
type: "asset/source"
},
{
test: /\.jpg$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024
}
}
},
{
test: /\.(css|less)$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"]
},
{
test: /\.ttf$/,
type: "asset/resource"
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
plugins: [
["@babel/plugin-transform-runtime"]
]
}
}
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
chunks: "all"
}
}
}
}
}
2. 开发配置
// dev.config.js
module.exports = {
mode: "development",
devtool: "inline-source-map",
output: {
filename: "scripts/[name].js"
},
devServer: {
static: "./dist"
}
}
3. 生产配置
// pro.config.js
const MiniMizerWebpackPlugin = require("css-minimizer-webpack-plugin")
const TerserPlugin = require("terser-webpack-plugin")
module.exports = {
mode: "production",
output: {
filename: "scripts/[name].[contenthash].js",
publicPath: "http://localhost:8080/"
},
optimization: {
minimizer: [
new MiniMizerWebpackPlugin(),
new TerserPlugin()
]
}
}
4. 合并
- 安装
webpack-merge插件。
npm install webpack-merge -D
- 新建
webpack.config.js文件。
const { merge } = require("webpack-merge")
const baseConfig = require("./base.condig.js")
const devConfig = require("./dev.config.js")
const proConfig = require("./pro.config.js")
module.exports = (env, arg) => {
switch (arg.mode) {
case "development":
return merge(baseConfig, devConfig)
case "production":
return merge(baseConfig, proConfig)
default:
return baseConfig
}
}
5. package.json 中配置脚本
{
"scripts": {
"start": "webpack serve -c ./webpack/webpack.config.js --mode=development",
"build": "webpack -c ./webpack/webpack.config.js --mode=production"
}
}
扩展
webpack 中 loader 与 plugin 的区别
loader是一个加载器,如:style-loadercss-loader;plguin是插件,如:mini-css-extract-plugin。- 加载器是用来加载文件的,如:
babel-loader是webpack内置的,可以将我们的高版本的 JS 转换为低版本的 JS 以换取浏览器的兼容性;再比如:style-loader是为了把 css 变为一个style标签放入在页面上,方便加载 css。 - 插件的作用就是为了加强功能。为了扩展
webpack的功能。如:html-webpack-plugin主要是用来生成 HTML 文件;mini-css-extract-plugin是把 css 单独抽成一个文件的。
loader 的一些配置属性
test用于匹配 loaders 所处理文件的拓展名的正则表达式。- 如:
test: /\.less$/表示文件是以.less文件也就是说匹配所有的less文件。(必填) loaderloader 的名称。(必填)include手动添加必须处理的文件(文件夹)。exclude屏蔽不需要处理的文件(文件夹)(可选)query为 loaders 提供额外的设置选项。(可选)
- 如: