鉴于自己 webpack
知识一过久了就忘记的尿性,今天闲来无事又敲一遍,顺便做个记录
github 链接先放!
众所周知,webpack
以配置麻烦闻名于世,像我这种记忆力越来越差的人想起他就觉得头皮发麻
先连接好水管
- 创建文件夹
npm init -y
- 这个没啥好说…
npm i -D webpack webpack-cli webpack-dev-server
webpack4
之后需要单独安装多一个webpack-cli
webpack-dev-server
这个大佬是起一个本地服务器的,你懂我意思吧
- 创建一个
webpack.config.js
文件,用来写配置(对的,用来写你写完,一个月不写就会全部忘光光的配置!所以你到底零配置了什么…) - 好了,我们的文件开写,先把几个神仙请出来
1
2
3
4
5
6
7
8module.exports = {
entry:'', //入口
output:{}, //出口
module:{}, //处理对应模块
plugins:[], //对应插件
devServer:{}, //开发服务器设置
mode:'development' //模式配置
}entry
就是入口,它霸道的掌握了整个webpack
的门口,什么神仙都是从这里进去的,你可以使用字符串语法或者对象语法来定义入口1
2
3
4
5
6
7
8
9module.exports = {
//一般这么写
entry:'./src/index.js', //单页面入口
entry:{ //多页面应用入口
index:'./src/index.js',
main:'./src/main.js'
},
...
}output
则是webpack
出口,对应入口来说,整个webpack
过程你可以理解成人体消化系统,而入口进去的食物,总将会变成 X 输出,从entry
通向output
module
是指webpack
所处理的模块,比如 ES6、LESS\SASS\CSS、图片等在项目中的种种东西,需要对应的loader
来对正则匹配的文件进行处理,如 转换 ES6 要用到babel-loader
来处理/\.js$/
的文件loader
则是用于对模块的源代码进行转换的小工具。
plugin
插件存在的目的在于解决loader
无法实现的其他事,比如可以用一些插件来对代码进行压缩devServer
这个则是配置一个静态的服务器,它默认自动刷新。mode
告知webpack
使用相应环境的内置优化,一般是development
或production
5. 配一下执行文件先…
- 找到你的
package.json
然后写进去,写完你就可以快乐的在cmd
里面快乐的npm run xxx
- 然后我们创建一个
src
文件夹,创建个index.js
。就随便写句话吧1
2//index.js
console.log('webpack 配置简单') - 然后我们回到
webpack.config.js
将入口,出口配置好,然后npm run build
一下,你就能看到文件夹中出现dist
文件夹,里面有个index.js
辣。就这样我们完成了第一次webpack
之旅1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//webpack.config.js
const path = require('path')
module.exports = {
// entry:'', //入口
entry:'./src/index.js', //单页面入口
// entry:{ //多页面应用入口
// index:'./src/index.js',
// main:'./src/main.js'
// },
output:{
filename: 'index.js', // 打包后文件名
path: path.resolve('dist') // 打包后目录,需为绝对路径
}, //出口
// module:{}, //处理对应模块
// plugins:[], //对应插件
// devServer:{}, //开发服务器设置
// mode:'development' //模式配置
}
到这里大家应该对webpack
有了个小的了解,其实他就是条 ‘管道’ 我们将各种文件都交给他,最后他输出我们配置好的输出文件。
其实比较类似人体的消化系统…你给他吃各种食物,最后输出…你懂我意思吧
给管道添加各类处理程序
webpack
的魅力在于它能处理开发项目中的一切你需要它处理的模块,只要你给他装了相应的模块配置,它就能给你干一切脏活累活。
html
在打包 html 的时候,一般我们在实际开发中都会用一个 html 文件来做一切页面的入口,我们要来实现 html 的打包功能, 需要一个模板来实现,各种引用好路径的 html 。
这里就需要一个很常用的插件 html-webpack-plugin 来帮我们把 html 搞出来
- 装插件,先 npm 装一下
1
npm i html-webpack-plugin -D
- 创建一个index.html 作为模板使用(当然你也可以不用模板)
- 接下来要往
webpack.config.js
管子里装设备了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21const path = require('path')
//首先引入插件,插件是一个类,使用的时候需要先 new
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:'./src/index.js', //单页面入口
output:{
filename: 'index.js', // 打包后文件名
path: path.resolve('dist') // 打包后目录,需为绝对路径
}, //出口
// module:{}, //处理对应模块
plugins:[
new HtmlWebpackPlugin({
template:'./src/index.html', // 打包目标模板
// title:'我被打包了',// 不用 template 时候生效
// filename:'bundle.html', // 换个文件名
})
], //对应插件
// devServer:{}, //开发服务器设置
// mode:'development' //模式配置
} - 写完,打包,一气呵成。不出意外你会得到一个这样的文件
多页面开发配置
1 | // webpack.config.js |
css
处理完 html
我们接着来处理 css
,处理 css
文件,则需要在配置文件中配置 module
,当然需要指定的 loader
来搭配一起辅助。
一般处理 css
模块的 loader
是这两个大佬
css-loader
: 很单纯的处理css
的loader
style-loader
:将处理好的css-loader
的样式代码 插入文件中
东西说完,我们开搞
- 装东西
1
npm i css-loader style-loader -D
- 创建对应文件,写东西,代码引入
1
2
3//index.js
import './css/style-css.css';
console.log('webpack 配置简单')1
2
3
4/* style-css.css */
body{
background-color: #000;
} - 写配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// webpack.config.js
...
//首先引入插件,插件是一个类,使用的时候需要先 new
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...
module:{
rules:[
{
test: /\.css$/, //匹配查找 css 文件
use:['style-loader', 'css-loader'] //从右往左解析 style-loader ← css-loader
}
]
}, //处理对应模块
...
} - 打包查看效果
这样子css
就被打包进去js
文件了,品如跟洪世贤在一起了,这显然不是我们想要的鸭。这时候就需要人来帮我们来挖墙脚。
分手大师
我们现在要用一个插件来实现我们棒打鸳鸯的目的,这个插件可以是 mini-css-extract-plugin
或者 extract-text-webpack-plugin
。
先说 mini-css-extract-plugin
mini-css-extract-plugin
这个插件据说是为了 webpack4
而生的,那我们就来跟跟潮流~
- 先装
1
npm i -D mini-css-extract-plugin
- 再写,这里的原理跟
html-webpack-plugin
是差不多的。将style-loader
替换成MiniCssExtractPlugin.loader
,然后在用插件生成文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23// webpack.config.js
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
...
module:{
rules:[
{
test: /\.css$/, //匹配查找 css 文件
// use:['style-loader', 'css-loader'] //从右往左解析 style-loader ← css-loader
use:[MiniCssExtractPlugin.loader, 'css-loader'] //从右往左解析 style-loader ← css-loader
}
]
}, //处理对应模块
plugins:[
...
new MiniCssExtractPlugin({
filename:'css/[name].css'
})
], //对应插件
...
} - 然后得到
extract-text-webpack-plugin
extract-text-webpack-plugin
则是一直以来 webpack
分拆 js
喝 css
的一个插件
- 装
1
2//@next 版本支持 webpack4
npm i -D extract-text-webpack-plugin@next - 写
1
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// webpack.config.js
...
const ExtractTextWebpackPlugin = require('extract-text-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
//需要装 cross-env 来做跨平台的兼容
// 将package.json 中的 scripts "build": "cross-env NODE_ENV=production webpack"
// 这样代码就能知道我们是处于什么环境打包的
const devMode = process.env.NODE_ENV !== 'production' //判断是否生产环境
const cssLoaders = [
'css-loader'
]
module.exports = {
...
module:{
rules:[
{
test: /\.css$/, //匹配查找 css 文件
// use:['style-loader', 'css-loader'] //从右往左解析 style-loader ← css-loader
// use:[
// // 这个插件暂时不支持 HMR ,开发环境中还是使用 style-loader
// devMode ? 'style-loader' : {
// loader:MiniCssExtractPlugin.loader,
// options:{
// // 这里可以指定一个 publicPath
// // 默认使用 webpackOptions.output中的publicPath
// // publicPath:''
// }
// },
// 'css-loader'
// ] //从右往左解析 MiniCssExtractPlugin.loader ← css-loader
use: devMode ? ['style-loader',...cssLoaders] : ExtractTextWebpackPlugin.extract({
fallback: "style-loader",
use:cssLoaders
})
}
]
}, //处理对应模块
plugins:[
...
// 将所有的CSS提取到一个文件中
// new MiniCssExtractPlugin({
// filename:'css/[name].css'
// }),
new ExtractTextWebpackPlugin({
filename:'css/[name].css'
})
], //对应插件
...
} - 得到
其实两者用着感觉差别并不大,如果有需要分开打包 css 文件的话则后者更好用点,毕竟前者还未支持;前者用着更加简单舒服点
LESS 、SASS 、 postCss 这群小婊砸们怎么融入进来呢?
LESS
处理 LESS
文件,那必须是要使用 less
跟 less-loader
模块啦。什么,你难道觉得不需要咩
- 装
1
npm i -D less less-loader
- 写,这里其实已经万变不离其宗了,需要注意的是
loader
解析方向1
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// webpack.config.js
...
const cssLoaders = [
'css-loader',
'less-loader'
]
module.exports = {
...
module:{
rules:[
...
{
test: /\.less$/, //匹配查找 css 文件
// use:['style-loader', 'css-loader', 'less-loader'] //从右往左解析 style-loader ← css-loader
use:[
devMode ? 'style-loader' : {
loader:MiniCssExtractPlugin.loader,
options:{
}
},
...cssLoaders
] //从右往左解析 MiniCssExtractPlugin.loader ← css-loader ← less-loader
}
]
}, //处理对应模块
...
} - 得到
SASS
SASS
文件的处理其实跟 LESS
差不太多,需要装 sass-loader
和 node-sass
来帮忙解析
- 装
1
npm i -D sass-loader node-sass
- 写,其实跟 LESS 一个套路辣
1
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// webpack.config.js
...
const sassLoaders = [
'css-loader',
'sass-loader'
]
module.exports = {
...
module:{
rules:[
...
{
test: /\.scss$/, //匹配查找 scss 文件
// use:['style-loader', 'css-loader', 'sass-loader'] //从右往左解析 style-loader ← css-loader ← sass-loader
use:[
devMode ? 'style-loader' : {
loader:MiniCssExtractPlugin.loader,
options:{
}
},
...sassLoaders
] //从右往左解析 MiniCssExtractPlugin.loader ← css-loader ← sass-loader
}
]
}, //处理对应模块
...
} - 得到
PostCSS
PostCSS
是 一款使用插件转换 CSS
的工具,里面有很多实用的插件,比如 Autoprefixer
- 装
1
npm i -D postcss-loader autoprefixer
- 写,先添加一个
postcss.config.js
,用来写postcss-loader
的配置,也可以在webpack.config.js
里面用对象方式写,不过一般是单独一个文件写,之后的配置需要注意的问题是postcss-loader
,需要插在css-loader
之前。1
2
3
4
5
6
7
8//postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')({
browsers: ['last 10 Chrome versions', 'last 5 Firefox versions', 'Safari >= 6', 'ie> 8']
})
]
}1
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// webpack.config.js
const sassLoaders = [
'css-loader',
'postcss-loader',//需要插在css-loader之前
'sass-loader'
]
module.exports = {
...
module:{
rules:[
...
{
test: /\.scss$/, //匹配查找 scss 文件
use:[
devMode ? 'style-loader' : {
loader:MiniCssExtractPlugin.loader,
options:{
}
},
...sassLoaders
]
//从右往左解析 MiniCssExtractPlugin.loader ← css-loader ← postcss-loader ← sass-loader
}
]
},
...
} - 得到
到这里 CSS
模块的处理的差不多了,整理一下代码
1 | // webpack.config.js |
js
现在来处理 js
辣,都 2019 年了,现在谁还不认识个 ES6
是的,低版本浏览器就不认识,所以这就需要一个很牛逼的东西来转义我们的ES6
成 ES5
那就是babel(爸爸)!
- 装
1
npm i babel-core babel-loader babel-preset-env babel-preset-stage-0 -D
- 写,这里我们需要写一个配置文件
.babelrc
,然后写下处理的对象1
2
3
4// .babelrc
{
"presets": ["@babel/preset-env"] // 从右向左解析
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// webpack.config.js
...
module.exports = {
...
module:{
rules:[
...
{
test:/\.js$/,
use: 'babel-loader',
include: /src/, // 只转化src目录下的js
exclude: /node_modules/ // 排除掉node_modules,优化打包速度
}
]
},
...
} - 这样配置完,我们就可以在项目里面写
ES6
代码了,webpack
会帮我们转义成ES5
图片文件
上面那些大佬已经被我们安排的明明白白的,目前还有个图片文件处理需要我们来肝。
处理图片需要用到 file-loader
、url-loader
和 html-withimg-loader
- 装
1
npm i file-loader url-loader html-withimg-loader -D
- 写,这里需要注意的是路径问题,需要在打包成文件的时候指定一下路径
1
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// webpack.config.js
const miniLoader = {
loader:MiniCssExtractPlugin.loader,
options:{
publicPath: '../'
}
}
...
module.exports = {
...
module:{
rules:[
...
{
test:/\.(jpe?g|png|gif)$/,
use:[
{
loader: 'url-loader',
options: {
limit: 8192, // 小于8k的图片自动转成base64格式,并且不会存在实体图片
outputPath: 'images/' // 图片打包后存放的目录
}
}
]
},
{
test: /\.(htm|html)$/,
use: 'html-withimg-loader' // html里面所有的img链接
},
{
test: /\.(eot|ttf|woff|svg)$/,
use: 'file-loader'
}
]
},
...
} - 效果
雪碧图
处理雪碧图用的 postcss-sprites
是基于 postcss-loader
的插件
装
1
npm i -D postcss-sprites
写,这里需要一个
postcss-sprites-config.js
文件来写postcss-sprites
的配置,也可以写在postcss-config.js
里面。然后在postcss-config.js
补上我们的配置,这样就能去合成雪碧图了1
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/** postcss-sprites-config.js
* 这里的项目结构是
* —src
* ——icons
* ———icons-@1x
* ———— *.png
* ———icons-@2x
* ———— *.png
* */
var postcss = require('postcss');
// 合法的散列图path 查找 /src/icons/*/*.png
const REG_SPRITES_PATH = /\/icons\/(.*?)\/.*/i
// 合法的retina标识 @*x
const REG_SPRITES_RETINA = /@(\d+)x\./i
// split
const SPLIT = true
const SPLIT_REG = /\?/ig
const RETINA = true
const OUTPUT_PATH = "./dist/sptite"
module.exports = {
spritePath: OUTPUT_PATH,
//过滤 除了 icons 以外 的图片链接
//需返回一个Promise对象
filterBy: (image)=> {
return REG_SPRITES_PATH.test(image.url) ? Promise.resolve() : Promise.reject()
},
//分组 分出 1 / 2 / 3 / n倍 图
groupBy: (image) => {
let groups = null;
let groupName = '';
let module = image.originalUrl.split(/\?/)
image.url = module[0]
if(module.length > 1){
//分模块 ? 跟模块名
groupName = module[1] + '-'
}
if (SPLIT) {
groups = REG_SPRITES_PATH.exec(image.url);
groupName += groups ? groups[1] : 'icons';
} else {
groupName += 'icons';
}
//处理多倍图的情况
if (RETINA) {
image.retina = RETINA;
image.ratio = 1;
let ratio = REG_SPRITES_RETINA.exec(image.url);
if (ratio) {
ratio = ratio[1];
while (ratio > 10) {
ratio = ratio / 10;
}
image.ratio = ratio;
image.groups = image.groups.filter((group) => {
return ('@' + ratio + 'x') !== group;
});
groupName += '@' + ratio + 'x';
}
}
return Promise.resolve(groupName);
},
// 转换百分比定位
hooks: {
onUpdateRule: function(rule, token, image) {
var backgroundSizeX = (image.spriteWidth / image.coords.width) * 100;
var backgroundSizeY = (image.spriteHeight / image.coords.height) * 100;
var backgroundPositionX = (image.coords.x / (image.spriteWidth - image.coords.width)) * 100;
var backgroundPositionY = (image.coords.y / (image.spriteHeight - image.coords.height)) * 100;
backgroundSizeX = isNaN(backgroundSizeX) ? 0 : backgroundSizeX;
backgroundSizeY = isNaN(backgroundSizeY) ? 0 : backgroundSizeY;
backgroundPositionX = isNaN(backgroundPositionX) ? 0 : backgroundPositionX;
backgroundPositionY = isNaN(backgroundPositionY) ? 0 : backgroundPositionY;
var backgroundImage = postcss.decl({
prop: 'background-image',
value: 'url(' + image.spriteUrl + ')'
});
var backgroundSize = postcss.decl({
prop: 'background-size',
value: backgroundSizeX + '% ' + backgroundSizeY + '%'
});
var backgroundPosition = postcss.decl({
prop: 'background-position',
value: backgroundPositionX + '% ' + backgroundPositionY + '%'
});
rule.insertAfter(token, backgroundImage);
rule.insertAfter(backgroundImage, backgroundPosition);
rule.insertAfter(backgroundPosition, backgroundSize);
}
}
}1
2
3
4
5
6
7
8
9
10
11//postcss-config.js
module.exports = {
plugins: [
require('autoprefixer')({
browsers: ['last 10 Chrome versions', 'last 5 Firefox versions', 'Safari >= 6', 'ie> 8']
}),
require('postcss-sprites')(
require('./postcss-sprites.config')
)
]
}效果
静态服务器的开启
webpack4 提供了一个devServer 的选项配合我们的服务器配置
我们只需要在配置中写入
1 | ... |
需要注意的是:需要在指定文件插入这行代码才会进行热更新
1 | if (module.hot) { |
跨域处理
以前我们本地开发跨域需要配置什么apache 什么转发什么的,但是在 webpack4 里面我们可以直接在配置开发服务器这里配一个跨域代理转发,这样岂不是更爽
1 | devServer: { |
解析 resolve
webpack
提供了 resolve
选项供我们配置模块如何被解析。比如我们最常使用到的别名、省略后缀名
1 | ... |
设置之后引入就可以变成这样子,简单来说就是更加快活了,不然你要陷入 ../../../../../
的无限地狱
1 | //index.js |
多页面应用的提取公共代码和单页面应用懒加载
提取公共代码
在开发多页面应用的时候,难免要提取公共代码,在 webpack4
中可以通过配置 optimization
选项中的 splitChunks
来实现公共代码的提取。
我们先来准备一些文件。
1 | //utils.js |
先写一个多页面的入口和出口
1 | ... |
然后我们配置 optimization
1 | ... |
这样配置完成之后其实就已经可以打包出分包的 js
文件了,当你需要输出的 html
中引用它们的时候,则需要在插件 html-webpack-plugin
中指定引用的块
1 | plugins:[ |
提取公共代码之前打包:
提取公共代码之后打包:
懒加载
在 webpack
中除了静态导入还有动态导入模块的方式,可以使用内置的 import()
,写起来大概是这样子的。
1 | import( |
这里需要装一个 @babel/plugin-syntax-dynamic-import
来帮助我们解析
1 | // .babelrc |
在 output
选项中加入 chunkFilename
1 | // webpack.config.js |
这里其实最核心的一点应用其实在于 js懒加载 ,例如我找个元素,然后让它点击之后才会加载,这样子就能把整个 文件瘦身成很多个小块
1 | window.onload = () => { |
懒加载不仅可以用在 js 模块上,css 文件也可以懒加载,只要你想,一切都可以懒
1 | ... |
生产环境跟开发环境
如果你从上面写到这里的话你会发现,整个文件越来越长,越来越大的难以忍受难以看懂,这里为了方便我们开发我们需要把 webpack.config.js
拆分成 webpack.common.js
webpack.dev.js
和 webpack.prod.js
,然后引用个插件来 复合产生两个文件来供我们两个环境使用
1 | cnpm i -D webpack-merge |
这里提取文件的思路是,把公共的代码提取到 webpack.common.js
中,这里一般配置一些常用的东西,比如 entry
,output
,module
,resolve
这些配置,因为这些配置开发环境跟生产环境通用的东西
1 | // webpack.common.js |
提取出 webpack.common.js
之后我们来接着写另外两个
1 | // webpack.dev.js |
1 | // webpack.prod.js |
提取出来后去 package.json
中配置脚本参数
1 | "scripts": { |
这样你就把所有的东西都干完啦。