# 核心概念
# 一、Webpack入门
# 1、安装webpack
项目目录下安装webpack,npm install webpack webpack-cli --save-dev
。webpack-cli的作用是支持在命令行里面使用webpack命令。
命令行方式执行打包:npx webpack ./index.js (4.x方式)
。npx 就是调用项目内部安装的模块,让项目内部安装的模块用起来更方便。npx 的原理很简单,就是运行的时候,会到node_modules/.bin路径和环境变量$PATH里面,检查命令是否存在。
由于 npx 会检查环境变量$PATH,所以系统命令也可以调用。另外命令行才需要使用npx,如果是在项目内的script里,就可以直接用webpack,不需要加npx。
不建议使用全局安装的方式,因为不同项目,webpack的版本可能不一样。
# 2、几个命令
// 查看webpack的历史版本
npm info webpack
// 安装指定版本的webpack
npm install webpack@4.16.5 -D
2
3
4
# 3、使用配置文件
项目根目录下新建 webpack.config.js,即webpack的默认配置文件,默认配置文件名就是 webpack.config.js。
// webpack.config.js
const path = require('path')
module.exports = {
entry: './index.js', // 入口文件
//entry: { 等价于下面这个main
// main:'./index.js', // 入口文件
// },
output: {
filename: 'bundle.js', // 打包后的文件的名字
path: path.resolve(__dirname, 'bundle') // 打包后的目录,必须使用绝对路径,要用到path.resolve
}
}
2
3
4
5
6
7
8
9
10
11
12
执行打包npx webpack
,webpack会自动用webpack.config.js作为配置文件。
如果配置文件的文件名不是webpack.config.js,而是webpackconfig.js,那么执行打包命令就需要用 --config参数了,指定配置文件的名字。
npx webpack --config webpackconfig.js
还有一种打包就是使用npm script
// package.json
{
"name": "learn-webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack"
},
"author": "",
"license": "ISC",
"dependencies": {},
"devDependencies": {
"webpack": "^4.39.3",
"webpack-cli": "^3.3.7"
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
打包用 npm run start
。
# 二、使用Loader打包静态资源
# 1、打包图片
# (1)file-loader
配置webpack,并安装file-loader模块,npm i file-loader -D
。
// 配置相关loader
module: {
rules: [{
test: /\.(jpg|png|jpeg)/,
use:{
loader:'file-loader',
options: {
name: '[name].[ext]', // 配置打包后图片的名字,[name]就是图片的原名字,[ext]是原后缀
outputPath:'images/', // 将打包后的图片放在images目录下,不配置则会放在根目录下
}
}
}]
}
2
3
4
5
6
7
8
9
10
11
12
13
如果不配置 options.name,那么打包后图片名字格式如下:e3b63a0b1b4f4ca82cf2a5c686d58ae1.png
需要注意的是,引入一张图片,会返回图片打包后的地址,如下
import logo from "./images/logo.png";
var img = new Image();
img.src = logo;
document.body.append(img);
2
3
4
# (2)url-loader
url-loader 功能类似于 file-loader,但是在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。即base64 url。
module: {
rules: [{
test: /\.(jpg|png|jpeg)$/,
use: {
loader: 'url-loader', // 使用 url-loader 打包,在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。
options: {
name: '[name].[ext]',// 配置打包后图片的名字,[name]就是图片的原名字,[ext]是原后缀
outputPath: 'images/', // 将打包后的图片放在images目录下,不配置则会放在根目录下
limit: 2048 // 图片小于2048字节就会打包成base64
}
}
}]
}
2
3
4
5
6
7
8
9
10
11
12
13
# 2、打包样式
# (1)支持普通css
安装这两个loader,npm i style-loader css-loader -D
。
style-loader能够在需要载入的html中创建一个<style></style>
标签,标签里的内容就是CSS内容。
css-loader是允许在js中import一个css文件,会将css文件当成一个模块引入到js文件中。
# (2)支持sass语法
如果需要支持sass语法,则需要再安装 sass-loader 和 node-sass。
npm i sass-loader node-sass -D
// webpack配置
{
test: /\.(css|scss)$/,
use: ['style-loader','css-loader','sass-loader']
}
2
3
4
5
要注意这3个loader的顺序,是从右到左解析的,即先把scss翻译成css,然后插入到html的style标签里。
# (3)css厂商前缀
使用postcss-loader可以自动补充厂商前缀,npm i postcss-loader -D
。
根目录下新增postcss.config.js文件,里面是postcss相关配置。
module.exports = {
plugins: [
require('autoprefixer')({
// browsers: ["last 5 versions"]
overrideBrowserslist: ["last 5 versions"]
})
]
}
2
3
4
5
6
7
8
需要安装autoprefixer插件,npm i autoprefixer -D
。
webpack配置:
{
test: /\.(css|scss)$/,
use: ['style-loader','css-loader','sass-loader','postcss-loader']
}
2
3
4
# (4)scss文件支持@import语法和模块化css
可以给css-loader加上具体的配置
{
test: /\.(css|scss)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders:2, // scss支持@import语法
modules:true // css支持模块化
}
}
,'sass-loader',
'postcss-loader'
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- importLoaders:2
作用是支持在scss文件里面使用@import语法引入其它scss文件。 - modules:true
作用是将css模块化,避免造成css全局污染。
//import './style/index.scss'; 全局引入css
import style from './style/index.scss'; // 模块化引入css
var img = new Image();
img.src = logo;
// img.classList.add('avatar'); 全局引入css时的写法
img.classList.add(style.avatar); // 模块化引入css时的写法
document.body.append(img);
2
3
4
5
6
7
# 3、打包字体
{
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader' // 使用 file-loader 打包
}
}
2
3
4
5
6
# 三、使用plugins插件打包
plugins会在webpack打包时做一些事情。
# 1、html-webpack-plugin
html-webpack-plugin会在打包结束后,自动生成一个html文件,并把打包生成的js自动引入到这个html文件里面。
安装 html-webpack-plugin
npm i html-webpack-plugin -D
webpack配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 这里省略其它配置
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
2
3
4
5
6
7
8
# 2、clean-webpack-plugin
清空打包后的目录。安装 clean-webpack-plugin,npm i clean-webpack-plugin -D
。
webpack配置
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 这里省略其它配置
plugins: [
new CleanWebpackPlugin()
]
2
3
4
5
6
# 四、多入口配置和sourcemap
# 1、多入口配置
webpack配置
entry: {
main: './src/index.js', // 第一个入口文件
sub: './src/index.js', // 第二个入口文件
},
// 打包出口
output: {
publicPath: 'http://cdn.com/', // 在打包后生成的js地址前面加上域名,即http://cdn.com/main.js
filename: '[name].js', // 打包后的文件的名字,[name]是占位符,是上面的entry里的main或者sub
path: path.resolve(__dirname, 'dist') // 打包后的目录,必须使用绝对路径,要用到path.resolve
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(),
]
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
打包后会生成main.js和sub.js,并且会插入到同一个html文件里。
<!DOCTYPE html>
<html lang="en">
<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>Document</title>
</head>
<body>
<div id="head"></div>
<div id="footer"></div>
<script type="text/javascript" src="main.js"></script><script type="text/javascript" src="sub.js"></script></body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
# 2、sourcemap
sourceMap 它是一个映射关系,它知道dist目录下main.js文件xx行实际上对应的是src目录下index.js文件中的第xx行。
- webpack配置(关闭sourcemap):
mode: 'development', // 开发者模式
devtool:'none', // 关闭sourcemap
2
关闭sourcemap后,如果源代码有错误。那么就只能在打包后的js文件里面找到错误,无法在源代码里定位错误。
- webpack配置(开启sourcemap):
mode: 'development', // 开发者模式
devtool:'source-map', // 开启sourcemap
2
开启sourcemap后,如果源代码有错误,那么可以直接定位到源代码里的位置。打包后会生成main.js和main.js.map。其中main.js.map就是一个存储映射关系的文件。
sourcemap参数
1、source-map
会生成map格式的文件,里面包含映射关系的代码.
2、inline-source-map
不会生成map格式的文件,包含映射关系的代码会放在打包后生成的代码中.
3、inline-cheap-source-map
cheap有两种作用:一是将错误只定位到行,不定位到列。二是映射业务代码,不映射loader和第三方库等。会提升打包构建的速度。
4、inline-cheap-module-source-map
module会映射loader和第三方库.
5、eval
用eval的方式生成映射关系代码,效率和性能最佳。但是当代码复杂时,提示信息可能不精确。
sourcemap最佳实战
mode: 'development', // 开发模式
devtool:'cheap-module-eval-source-map', // 开启sourcemap
2
速度最快,打包错误提示最全。
mode: 'development', // 生产模式
devtool:'cheap-module-source-map', // 开启sourcemap,或者直接去掉
2
# 五、自动编译,优先使用webpackDevServer
每次要编译代码时,手动运行 npm run build
就会变得很麻烦。webpack 中有几个不同的选项,可以帮助你在代码发生变化后自动编译代码:
- webpack Watch Mode
- webpack-dev-server
- webpack-dev-middleware
# 1、--watch参数
webpack --watch
,加--watch参数,那么webpack监听到了src目录下的文件变化就会自动重新打包,但不会自动刷新页面。
# 2、webpackDevServer
不但会监听文件变化自动重新打包,也会自动刷新页面,也可以自动打开浏览器,还可以支持跨域。安装 npm i webpack-dev-server -D
。
配置:
// webpack.config.js
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(),
],
devServer: { // 配置devServer
contentBase: './dist', // 打包后起服务的目录名
open:true, // 自动打开浏览器
proxy: {
'/api': 'http://localhost:3000' // 跨域代理转发
},
port: 8090 // 指定端口号
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// package.json
"scripts": {
"server": "webpack-dev-server", // 配置webpack-dev-server
"watch": "webpack --watch"
},
2
3
4
5
这样就会起一个服务,端口默认是8080。
# 3、利用node来实现
安装express和webpack-dev-middleware,npm i express webpack-dev-middleware -D
。
配置webpack
// webpack.config.js
output: {
publicPath: '/', // ******在打包后生成的js地址前面加上 / 根路径,确保打包生成的文件路径不会有问题******
filename: '[name].js', // 打包后的文件的名字,[name]是占位符,是上面的entry里的main或者sub
path: path.resolve(__dirname, 'dist') // 打包后的目录,必须使用绝对路径,要用到path.resolve
}
2
3
4
5
6
// package.json
"scripts": {
"middware": "node server.js"
},
2
3
4
加 publicPath: '/'
publicPath 也会在服务器脚本用到,以确保文件资源能够在 http://localhost:3000
下正确访问。
// server.js
const express = require('express');
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');
const complier = webpack(config);
const app = express();
app.use(webpackDevMiddleware(complier, {
publicPath: config.output.publicPath
}));
app.listen(3000, () => {
console.log('server is running')
})
2
3
4
5
6
7
8
9
10
11
12
13
14
# 六、模块热替换(HMR)
模块热替换(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新。
# webpack配置
// webpack.config.js
const webpack = require('webpack'); // 引入webpack
// 省略其它配置
// 配置devServer
devServer: { // 配置devServer
contentBase: './dist', // 打包后起服务的目录名
open: true, // 自动打开浏览器
proxy: {
'/api': 'http://localhost:3000' // 跨域代理转发
},
port: 8090, // 指定端口号
hot: true, // ******开启热模块替换******
hotOnly:false // ******如果热模块替换没有生效,则让浏览器自动刷新******
}
// 配置plugins
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(),
new webpack.NamedModulesPlugin(), // 更容易查看要修补(patch)的依赖
new webpack.HotModuleReplacementPlugin() // 使用热模块替换插件
],
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
注意,我们还添加了 NamedModulesPlugin,以便更容易查看要修补(patch)的依赖。
对于js文件的改变,需要我们使用 module.hot.accept
手动更新,因为js文件默认没有实现HMR。
// index.js
import number from './number';
number();
if (module.hot) {
module.hot.accept('./number.js', function () {
console.log('Accepting the updated printMe module!');
number();
})
}
// number.js
export default ()=>{
console.log('test')
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
对于css文件,因为css-loader底层实现了HMR,所以就会自动刷新。
# 七、使用babel处理ES6
安装babel,npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/polyfill
。
babel-loader:将webpack和loader打通桥梁,不会转义ES6。
@babel/core: babel的核心库。
@babel/preset-env:将ES6转义成ES5。
@babel/polyfill:低版本浏览器打补丁,比如补充Promise这些东西,还有map方法这些东西,低版本浏览器比如ie就没有内置这些东西。
# 配置webpack
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"]
}
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
// index.js
import "@babel/polyfill"; // 引入polyfill,兼容低版本浏览器
const arr=[
new Promise(()=>{}),
new Promise(()=>{})
]
arr.map(item=>{
console.log(item);
})
2
3
4
5
6
7
8
9
10
11
# babel/polyfil按需引入
@babel/polyfil会自动加入所有兼容的代码,如果需要按需引入兼容的代码,则需要安装core-js,
npm install --save core-js@3
。
webpack配置:
// webpack.config.js
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
presets: [['@babel/preset-env', {
useBuiltIns: 'usage',
corejs: "3", // 声明corejs版本
}]]
}
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
源码中不需要引入@babel/polyfill
// index.js
// import "@babel/polyfill"; // 不需要引入polyfill,因为已经按需引入了
const arr=[
new Promise(()=>{}),
new Promise(()=>{})
]
arr.map(item=>{
console.log(item);
})
2
3
4
5
6
7
8
9
10
11
# 根据浏览器版本决定是否要将ES6转义成ES5
因为高版本浏览器已经支持了ES6,所以需要配置在针对高版本不需要转义。
options: {
presets: [
['@babel/preset-env', {
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns: 'usage',
corejs: "3", // 声明corejs版本
}]]
}
2
3
4
5
6
7
8
9
10
11
12
13
# 开发第三方模块时options的写法
如果我们写的是业务代码时,则使用上面的options即可,如果我们写的是第三方模块或者库代码时,则options要用下面的方案。因为上面的代码会污染全局变量,而下面的代码不会。安装相关模块,npm install --save-dev @babel/plugin-transform-runtime @babel/runtime @babel/runtime-corejs2
。
webpack配置:
options: {
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
使用.babelrc,即把options里面的配置写到.babelrc文件里面。
.babelrc文件:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
},
"useBuiltIns": "usage",
"corejs": "3", // 声明corejs版本
}
]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 八、支持React的打包
安装相关模块,npm install --save-dev @babel/preset-react react react-dom
。
配置 .babelrc
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1",
},
"useBuiltIns": "usage",
"corejs": "3", // 声明corejs版本
}
],
[
"@babel/preset-react" // 支持react的相关语法
]
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
完整的 webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const webpack = require('webpack');
module.exports = {
//entry: './src/index.js', // 入口文件,等价于下面的main
entry: {
main: './src/index.js', // 第一个入口文件
sub: './src/index.js', // 第二个入口文件
},
// 打包出口
output: {
//publicPath: '/', webpackDevServer时会用到
// publicPath: 'http://cdn.com/', // 在打包后生成的js地址前面加上域名,即http://cdn.com/main.js
filename: '[name].js', // 打包后的文件的名字,[name]是占位符,是上面的entry里的main或者sub
path: path.resolve(__dirname, 'dist') // 打包后的目录,必须使用绝对路径,要用到path.resolve
},
mode: 'development', // 开发者模式
//devtool:'none', // 关闭sourcemap,无法定位语法错误发生在源代码里第几行
//devtool: 'source-map',
devtool: 'eval-source-map',
// 配置相关loader
module: {
rules: [
{
test: /\.(jpg|png|jpeg)$/,
use: {
// loader: 'file-loader', // 使用 file-loader 打包
// options: {
// name: '[name].[ext]',// 配置打包后图片的名字,[name]就是图片的原名字,[ext]是原后缀
// outputPath: 'images/', // 将打包后的图片放在images目录下,不配置则会放在根目录下
// }
loader: 'url-loader', // 使用 url-loader 打包,在文件大小(单位 byte)低于指定的限制时,可以返回一个 DataURL。
options: {
name: '[name].[ext]',// 配置打包后图片的名字,[name]就是图片的原名字,[ext]是原后缀
outputPath: 'images/', // 将打包后的图片放在images目录下,不配置则会放在根目录下
limit: 2048 // 图片小于2048字节就会打包成base64
}
}
},
{
test: /\.(css|scss)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: true
}
},
'sass-loader',
'postcss-loader'
]
},
{
test: /\.(eot|ttf|svg)$/,
use: {
loader: 'file-loader' // 使用 file-loader 打包
}
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader",
options: {
// 开发业务代码时的写法
/*
//presets: ["@babel/preset-env"]
presets: [['@babel/preset-env', {
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns: 'usage',
corejs: "2", // 声明corejs版本
}]]
*/
// 开发第三方模块时的写法
/*
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"absoluteRuntime": false,
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}
]
]
*/
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.HotModuleReplacementPlugin()
],
devServer: { // 配置devServer
contentBase: './dist', // 打包后起服务的目录名
open: true, // 自动打开浏览器
proxy: {
'/api': 'http://localhost:3000' // 跨域代理转发
},
port: 8090, // 指定端口号
hot: true, // 开启热模块替换
hotOnly: true // 即便热模块替换没有生效,不让浏览器自动刷新
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118