webpack学习笔记

webpack可以解决什么问题

基本使用方法

安装

配置package.json文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"name": "vis",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
},
"devDependencies": {
"uglifyjs-webpack-plugin": "^2.1.2",
"webpack": "^4.37.0",
"webpack-bundle-analyzer": "^3.4.1",
"webpack-cli": "^3.3.6",
"webpack-dev-server": "^3.2.1"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "npm run webpack-dev",
"prod": "npm run webpack-prod && node src/server/api.js",
"build": "npm run webpack-prod",
"webpack-dev": "webpack-dev-server --config webpack.config.dev.js --progress --colors",
"webpack-prod": "webpack --config webpack.config.prod.js --progress --colors"
}
}

执行npm install安装。

配置

通用基础配置

放在webpack.config.base.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
/**
* 通用的基础配置
*/
const path = require("path");
const webpack = require('webpack')

module.exports = {
mode: 'production',
devtool: 'cheap-module-source-map',
entry: {
index: './src/index.js',
edit: './src/edit/edit.js'
}, //入口文件
output: { //输出文件路径设置
path: __dirname,
filename: './dist/[name].js'
},

devServer: {
compress: false,
hot: true,
inline: true,
watchOptions: {
ignored: /node_modules/
}
},
module: {
rules: [{
test: /\.(js)$/,
include: path.resolve(__dirname, "src"),
loader: 'babel-loader',
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader']
}, {
test: /\.svg$/,
use: ['file-loader']
}]
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
}

环境差异化配置

比如开发环境,会新建一个webpack.config.dev.js文件,里面可以这样配置:

1
2
3
4
5
6
7
8
9
10
11
/**
* 本地开发环境的个性化配置
*/
const webpack = require("webpack")
const config = require("./webpack.config.base")
config.mode = 'development'
config.plugins.push(new webpack.DefinePlugin({
__DEV__ : true
}))

module.exports = config

压缩文件

由于uglifyjs不兼容ES6语法,所以一般采用terser-webpack-plugin这个插件来执行压缩操作。

注意:如果你webpack的版本是4.X,那么terser-webpack-plugin也需要改为4.X的版本,否则如果用了高版本的terser-webpack-plugin,会报错。

如果是webpac5,则默认就已经有terser-webpack-plugin了,无需重新安装。

另外entry配置默认不支持通过正则表达式读取多个文件,可以引入glob这个包来扩展支持。

下面这个例子我自己添加了文件的读写功能,目的是将多个js文件整合到一个js文件中,进行打包压缩。之所以不通过glob直接设置,是因为我发现单个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
const path = require('path');
const uglify = require('terser-webpack-plugin');
const glob = require("glob")
const fs = require('fs')

const dir = './src/src/js/s2';
const tempFile = dir + '/temp.js';
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
}
const files = fs.readdirSync(dir)
files.forEach((item, index) => {
const filePath = dir + '/' + item;
if (!fs.lstatSync(filePath).isDirectory()) {
fs.appendFileSync(tempFile, '\r\n' + fs.readFileSync(filePath))
}
})

module.exports = {
entry: {
main: tempFile
// main: glob.sync('./src/src/js/s2/bar*1.js')
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'src/app/js/s2')
},
plugins: [
new uglify()
]
};

去掉console信息

这也是通过terser-webpack-plugin实现的,代码如下:

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
const path = require('path');
const webpack = require('webpack');
const TerserPlugin = require('terser-webpack-plugin');

module.exports = {
mode: 'production',
// mode: 'development',
devtool: 'source-map',
// devtool: 'cheap-module-source-map',
// productionSourceMap: true,
entry: {
'dynamic-chart.2.3.0.min': './src/index.js',
}, //入口文件
output: {
//输出文件路径设置
path: __dirname,
// filename: './dist/js/[name].js'
filename: './dist/js/dynamic-chart.2.3.0.min.js',
// filename: './dist/js/dynamic-histogram.0.0.1.js'
},

devServer: {
compress: false,
hot: true,
inline: true,
watchOptions: {
ignored: /node_modules/,
},
},
module: {
rules: [
{
test: /\.(js)$/,
include: path.resolve(__dirname, 'src'),
loader: 'babel-loader',
options: {
presets: ['@babel/env'],
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.svg$/,
use: ['file-loader'],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
//打包环境去掉console.log
new TerserPlugin({
cache: true,
sourceMap: false,
// 多进程
parallel: true,
terserOptions: {
ecma: undefined,
warnings: false,
parse: {},
compress: {
drop_console: true,
drop_debugger: false,
pure_funcs: ['console.log'], // 移除console
},
},
}),
],
};

常见问题

如何设置服务启动的端口号?

默认的端口号是8080,是在node_modules/webpack-dev-server/lib/utils/defaultPort.js这个文件里面配置的。

如果我们实际应用中想修改端口号,可以在项目的webpack.config.xx.js中进行调整。比如我们想将开发环境的端口号改为8888,那么就应该在webpack.config.dev.js中做如下设置:

1
2
3
4
devServer: {
port: 8888,
// 此处略去其他配置
}

如何分文件独立打包?

entry里面设置多个文件,然后output的filename采用内置变量来设置最终打包好的文件名即可:

1
2
3
4
5
6
7
8
9
10
module.exports = {
entry: {
bundle1: './addr.js',
bundle2: './backward.js'
.......
},
output: {
filename: '[name].js'
}
};

如何给不同环境设置不同的接口地址?

通过在webback.config.xxx.js中,设置常量来标注当前环境:

1
2
3
config.plugins.push(new webpack.DefinePlugin({
__DEV__ : true
}))

然后程序中根据该常量确定使用哪个接口地址:

1
2
3
4
5
6
let DATA_URL
if (__DEV__) {
DATA_URL = "http://localhost:3000/get";
} else {
DATA_URL = "http://172.19.80.196/get";
}

配置文件能否继承?

可以继承,通过引入配置文件,然后修改其属性的方式来实现。

NODE_ENV的值是怎么确定的?

build的时候,如何不压缩JS代码?

如果你设置mode=production,那么webpack打包时,是默认会调用uglifyjs-webpack-plugin插件压缩代码的。

只需要设置mode=development,build后生成的JS文件就不会被压缩了。

mode=production

不会启动服务,而是生成最终的静态文件。

为什么定义的全局变量,在控制台提示是未定义?

我在js文件中定义了一个全局变量:

1
let var1 = 0

但是在Chrome控制台输出这个变量,却提示未定义:

1
Uncaught ReferenceError: var1 is not defined

原因是webpack打包时,会自动给所有js代码加上一个闭包,因此这个全局变量实际上就不是全局变量了:

1
2
3
(function(module, exports) {
let var1 = 0;
})

解决方案就是直接将需要暴露的全局变量,手动挂载到window对象下:

1
window.var1 = 0

如何不给打包的文件生成hash码?

加上filenameHashing: false即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
module.exports = {
lintOnSave: false,
css: {
loaderOptions: {
less: {
javascriptEnabled: true
}
}
},
// 文件名不加hash码
filenameHashing: false,
configureWebpack: {

},
chainWebpack: (config) => {

},
};