webpack 基础篇(一)

1.什么是Webpack?

webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler),当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle

使用Webpack作为前端构建工具:

  • 代码转换:TypeScript 编译成 JavaScript、SCSS 编译成 CSS 等。
  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
  • 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载。
  • 模块合并:在采用模块化的项目里会有很多个模块和文件,需要构建功能把模块分类合并成一个文件。
  • 自动刷新:监听本地源代码的变化,自动重新构建、刷新浏览器。
  • 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
  • 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。

webpack应用中有两个核心:

  • 1) 模块转换器,用于把模块原内容按照需求转换成新内容,可以加载非 JS 模块
  • 2) 扩展插件,在 Webpack 构建流程中的特定时机注入扩展逻辑来改变构建结果或做你想要的事情。

2.webpack快速上手

2.1 安装(4.X)

1
2
npm init -y
npm install webpack webpack-cli --save-dev (--save-dev表示是一个开发环境)

这里webpack-cli的作用是 可以解析用户传递的参数,将解析好的参数传递给webpack来进行打包。

2.2 初始化项目

1
2
3
├── src   # 源码目录
│   ├── a-module.js
│   └── index.js

编写 a-module.js

1
module.exports = 'hello';

编写 index.js

1
2
3
4
// webpack 默认支持 模块的写法:
// commonjs规范 node esmodule规范 es6
let a = require('./a-module');
console.log(a);

这里我们使用CommonJS模块的方式引入,这种方式(在node中可以直接运行)默认在浏览器上是无法运行的,所以我们希望通过 webpack 来进行打包!

2.3 打包配置

这里可以使用npx webpack,npx 是 5.2版本之后npm提供的命令可以执行/module/.bin下的(命令)可执行文件

我们可以发现已经产生了dist目录,此目录为最终打包出的结果。dist/main.js可以在html中直接引用,这里还提示我们默认modeproduction
npx webpack –mode development | npx webpack –mode production

也可以配置 script 脚本

webpack默认支持0配置,配置scripts脚本

1
2
3
4
5
6
7
"scripts": {
"build": "webpack"
}
// 或者
"scripts": {
"build": "webpack --mode production"
}

npm run 会执行 script中的命令, 执行npm run build,默认会调用 node_modules/.bin下的webpack命令,内部会调用webpack-cli解析用户参数进行打包。默认会以 src/index.js 作为入口文件。

2.4 webpack.config.js

我们打包时一般不会采用0配置,webpack在打包时默认会查找当前目录下的 webpack.config.js or webpack.file.js 文件。

通过配置文件进行打包
新建 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
// webpack 是基于nodejs(所以可以用node的所有模块) 所以语法规范是commonjs
// 默认导出的是配置对象
const path = require('path');
module.exports = {
mode: 'development', // 当前是开发模式,在这里配置就不需要在script中配置了
// 入口 出口
entry: path.resolve(__dirname,'./src/index.js'), // 写路径都采用绝对路径
output:{ // 出口的配置
filename: 'bundle.js',
path: path.resolve(__dirname,'dist')
}
}

2.5 配置打包的mode

我们需要在打包时提供mode属性来区分是开发环境还是生产环境,来实现配置文件的拆分

新建build/ 文件夹

1
2
3
4
├── build
│   ├── webpack.base.js
│   ├── webpack.dev.js
│   └── webpack.prod.js

我们可以通过指定不同的文件来进行打包

配置scripts脚本

1
2
3
4
"scripts": {
"build": "webpack --config ./build/webpack.prod",
"dev": "webpack --config ./build/webpack.dev"
}

可以通过 --config 参数指定,使用哪个配置文件来进行打包

通过env参数区分

1
2
3
4
"scripts": {
"build": "webpack --env.production --config ./build/webpack.base",
"dev": "webpack --env.development --config ./build/webpack.base"
}

改造webpack.base文件默认导出函数,会将环境变量传入到函数的参数中

1
2
3
module.exports = (env)=>{
console.log(env); // { development: true }
}

合并配置文件

我们可以判断当前环境是否是开发环境来加载不同的配置,这里我们需要做配置合并
安装webpack-merge:

1
npm install webpack-merge --save-dev

webpack.dev配置

1
2
3
module.exports = {
mode:'development'
}

webpack.prod配置

1
2
3
module.exports = {
mode:'production'
}

webpack.base配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const path = require('path');
const merge = require('webpack-merge');
// 开发环境
const dev = require('./webpack.dev');
// 生产环境
const prod = require('./webpack.prod');
module.exports = (env) =>{
const base = { // 基础配置
entry:'./src/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'../dist')
}
}
if(env.development){
return merge(base,dev);
}else{
return merge(base,prod)
}
}
// 函数会返回配置文件,没返回会采用默认配置

后续的开发中,我们会将公共的逻辑放到base中,开发和生产对的配置也分别进行存放!

3.webpack-dev-server

配置开发服务器,它是在内存中打包的,不会产生实体文件,并且自动启动服务

1
npm install webpack-dev-server --save-dev
1
2
3
4
"scripts": {
"build": "webpack --env.production --config ./build/webpack.base",
"dev": "webpack-dev-server --env.development --config ./build/webpack.base"
}

通过执行npm run dev来启启动开发环境

默认会在当前根目录下启动服务

配置开发服务的配置

1
2
3
4
5
6
7
8
9
10
const path = require('path')
module.exports = {
mode:'development',
devServer:{
// 更改静态文件目录位置(默认是放在根目录下)
contentBase:path.resolve(__dirname,'../dist'), // 表示webpack启动服务会在dist目录下
compress:true, // 开启gzip 可以提升页面返回的速度
port:3000, // 更改端口号
}
}

4.打包Html插件

4.1 单入口打包

自动产生html,并引入打包后的js文件

新建 /public/index.html

1
2
3
4
5
6
7
8
9
10
11
12
<!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="root"></div>
</body>
</html>

配置插件,在打包结束后会将打包的结果自动引进来 并且产生文件到当前的dist/下

编辑webpack.base文件

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
const path = require('path');
const merge = require('webpack-merge');
const dev = require('./webpack.dev');
const prod = require('./webpack.prod');
// html插件
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = (env) =>{
let isDev = env.development;
const base = {
entry:'./src/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'../dist')
},
plugins:[
new HtmlWebpackPlugin({
filename:'index.html', // 打包出来的文件名
template:path.resolve(__dirname,'../public/index.html'),// 以这个文件为模板
minify: !isDev && { // 压缩
collapseWhitespace: true, // dist下产生的html折叠, 显示一行 删除空白符与换行符
removeComments: true, // 移除HTML中的注释
removeRedundantAttributes: true, //删除多余的属性
removeScriptTypeAttributes: true, //删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false
removeStyleLinkTypeAttributes: true, //删除style的类型属性, type="text/css" 同上
useShortDoctype: true

}
})
]
}
if(isDev){
return merge(base,dev);
}else{
return merge(base,prod)
}
}
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
{
//是否对大小写敏感,默认false
caseSensitive: true,

//是否简写boolean格式的属性如:disabled="disabled" 简写为disabled 默认false
collapseBooleanAttributes: true,

//是否去除空格,默认false
collapseWhitespace: true,

//是否压缩html里的css(使用clean-css进行的压缩) 默认值false;
minifyCSS: true,

//是否压缩html里的js(使用uglify-js进行的压缩)
minifyJS: true,

//Prevents the escaping of the values of attributes
preventAttributesEscaping: true,

//是否移除属性的引号 默认false
removeAttributeQuotes: true,

//是否移除注释 默认false
removeComments: true,

//从脚本和样式删除的注释 默认false
removeCommentsFromCDATA: true,

//是否删除空属性,默认false
removeEmptyAttributes: true,

// 若开启此项,生成的html中没有 body 和 head,html也未闭合
removeOptionalTags: false,

//删除多余的属性
removeRedundantAttributes: true,

//删除script的类型属性,在h5下面script的type默认值:text/javascript 默认值false
removeScriptTypeAttributes: true,

//删除style的类型属性, type="text/css" 同上
removeStyleLinkTypeAttributes: true,

//使用短的文档类型,默认false
useShortDoctype: true,
}
  • 运行 npm run dev 可以看到生成了一个index.html文件,但是是在内存中的看不到,可以访问 http://localhost:3000/ 看到有上面的console.log打印 a-module.js中的内容。

  • 运行 npm run build 可以看到dist/下生成了 index.html 、bundle.js,并且 html中自动引入了这个打包后的文件。

4.2 多入口打包

根据不同入口 生成多个js文件,引入到不同html中

1
2
3
── src
├── entry-1.js
└── entry-2.js

多入口需要配置多个entry

1
2
3
4
5
6
7
8
9
10
// entry有三种写法 字符串 数组 对象
entry:{
jquery:['jquery'], // 打包jquery
entry1:path.resolve(__dirname,'../src/entry-1.js'),
entry2:path.resolve(__dirname,'../src/entry-2.js')
},
output:{
filename:'[name].js', // 这里也要改成动态的名字,多出口,生成jquery.js, entry1.js,entry2.js
path:path.resolve(__dirname,'../dist')
},

产生多个Html文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
new HtmlWebpackPlugin({
filename:'index.html',
template:path.resolve(__dirname,'../public/template.html'),
hash:true,
minify:{
removeAttributeQuotes:true
},
chunks:['jquery','entry1'], // 引入的代码块chunk 有jquery,entry
}),
new HtmlWebpackPlugin({
filename:'login.html',
template:path.resolve(__dirname,'../public/template.html'),
hash:true,
minify:{
removeAttributeQuotes:true
},
inject:false, // inject 为false表示不注入js文件
chunksSortMode:'manual', // 代码块顺序:手动配置
chunks:['entry2','jquery'] // 这样打包生成的页面中引入的模块的顺序就是这个数组里写的顺序
})
1
2
3
4
5
6
注入选项。有四个选项值 true, body, head, false.

true:默认值,script标签位于html文件的 body 底部
body:script标签位于html文件的 body 底部(同 true
head:script 标签位于 head 标签内
false:不插入生成的 js 文件,只是单纯的生成一个 html 文件

以上的方式不是很优雅,每次都需要手动添加HtmlPlugin应该动态产生html文件,像这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let htmlPlugins = [
{
entry: "entry1",
html: "index.html"
},
{
entry: "entry2",
html: "login.html"
}
].map(
item =>
new HtmlWebpackPlugin({
filename: item.html,
template: path.resolve(__dirname, "../public/template.html"),
hash: true,
minify: {
removeAttributeQuotes: true
},
chunks: ["jquery", item.entry]
})
);
plugins: [...htmlPlugins]

5.清空打包结果

可以使用clean-webpack-plugin手动清除某个文件夹内容:

安装

1
npm install --save-dev clean-webpack-plugin

放到 webpack.prod.js 中

1
2
3
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
// 每次打包之前 先清空dist目录下的文件
new CleanWebpackPlugin()

这样就可以清空指定的目录了,我们可以看到webpack插件的基本用法就是 new Plugin并且放到plugins