webpack 配置与近期项目总结

最近使用 webpack 打包工具,配合 nginx 做了个活动页。记录下本次项目中的问题,就当笔记用了。

一、webpack 配置

本次项目采用传统前后端分离模式,就是后端提供接口,前端写静态、查询接口就可以了,然后是基于微信公众号开发的 h5 活动页。话说在使用决定使用 webpack 之前,我对 webpack 的期望就只是能够完成前端代码的打包就可以,并且以前使用 webpack 也只是在 vue 全家桶中使用的,具体涉及到配置什么的完全没有关心过,因为是拿来即用不需要考虑特别多。然而啊,出来混迟早是要还的,用了还想不学会简直是想的美。

废话不多说,接下来直接介绍下我一步一步构建起来的配置。在此强调一点,任何不懂请详细看官方文档,请详细看官方文档!有些文档是英文,可以借助谷歌翻译、有道翻译等,这样能避免百度出来的各种不靠谱答案。亲测!!

这次我们直接使用 webpack 的最新版本 4.12.0 ,并搭配 webpack-cli 使用,版本为 3.0.6 。

1、项目初始化

新建一个文件夹 webpack 并在当前文件夹下初始化项目,打开终端,依次执行 npm initnpm install --save-dev webpack-cli webpack,以上依赖包安装完成后,增加项目目录如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|-- webpack
|-- node_modules // 各种依赖包
|-- libs // 各个页面公用的插件
|-- utils // 各个页面通用的一些方法汇总
|-- assets // 各个页面通用资源
|-- css
|-- reset.css
|-- img
|-- xxx.png
|-- src // 页面资源
|-- index // 首页
|-- css
|-- index.less
|-- js
|-- index.js
|-- img
|-- xxx.png
|-- index.html
|-- award // 奖励
|-- login // 登录
|-- register // 注册
|-- package.json // 项目构建信息的文件
|-- README.md

2、增加 webpack 配置文件

在项目根目录增加文件 webpack.config.js ,基本配置如下:

1
2
3
4
5
6
7
8
9
const path = require('path');

module.exports = {
entry:'./src/index/js/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'dist')
}
}

以上,入口文件为 src/index/js/index.js ,最后会打包输出到根目录下 dist 文件目录下的 bundle.js 。此时在 package.json 文件中的 scripts 中添加一条指令 "build" : "webpack",保存后在终端运行指令 npm run build,此时会在根目录生成 dist 目录并打包出文件 bundle.js 。 ok ,至此,我们已经能够完美实现 js 打包。

3、html 页面打包

在第二步的操作中,虽然我们完成了 js 源码的打包,但是打包到的目录中并没有 html 文件,也就是我们的视图层并没有完成一并打包。此处,我们需要安装 html-webpack-plugin ,在终端执行 npm install --save-dev html-webpack-plugin ,在 webpack-config.js 中引入此依赖,增加代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入依赖包

module.exports = {
entry:'./src/index/js/index.js',
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'dist')
},
plugins:[ // 引入的依赖包在此进行配置
new HtmlWebpackPlugin({
filename:'index.html', // 输出的文件名称
template:'./src/index/index.html', // 打包的文件地址
minify:true, // 是否压缩,默认值为 false
chunks:[] // 引入其他插件,数组形式
})
]
}

删除 dist 目录,在终端执行 npm run build 则会在 dist 目录下生成 bundle.js 文件和 index.html ,并且 index.html 中默认引入 bundle.js 文件。

执行到这个步骤,我这边其实是报错了,但是一直无法找到其中的原因是因为什么,所以直接删除了 node_modules 目录然后重新安装了依赖,显然是解决了问题。

4、清理 dist 目录

每次文件打包都会打包到 dist 目录下,最好在每次打包前能够删除 dist 然后重新构建生成 dist 目录下资源。此时就使用到 clean-webpack-plugin 插件,首先在命令行输入:

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

然后在 webpack.config.js 中引入:

1
2
3
4
5
6
7
8
9
10
....
const CleanWebpackPlugin = require('clean-webpack-plugin');
....
plugins:[
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
...
})
]
....

在终端执行 npm run build 指令会先删除 dist 目录及其目录下资源,然后重新生成打包文件。

5、资源管理

常见的资源如 CSS 、 IMG 、Font 、数据等,这里所说的资源管理就是 webpack 通过各种 **-loader 将其所对应的资源打包入我们的 dist 目录中。

5.1 加载 CSS

为了从 Javascript 模块中 import 一个 CSS 文件,我们需要在 module 配置中安装并添加 style-loadercss-loader
在终端输入指令:

1
npm install --save-dev style-loader css-loader

在 webpack.congfig.js 中修改配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
output:{},
module:{
rules:[
{
test:/\.css$/,
use:[
'style-loader',
'css-loader'
]
}
]
}
...

这使你可以在依赖于此样式的文件中 import './style.css'。现在,当该模块运行时,含有 CSS 字符串的 <style> 标签,将被插入到 html文件的 <head> 中。

在终端输入 npm run build 后,打开生成的 dist 目录下的 index.html 文件。运行到这里发现代码出现了问题,静态页面和 js 是被打包并生成了,但是在浏览器中打开页面发现这二者并没有什么关联,也就是说 bundle.js 并没有被引入到 index.html 中。又查了下 html-wabpck-plugin 的配置说明,修改 webpack.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
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
entry:{
'index':'./src/index/js/index.js' // 此处做了修改,为引入多文件做准备
},
output:{
filename:'bundle.js',
path:path.resolve(__dirname,'dist')
},
plugins:[
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
filename:'index.html',
template:'./src/index/index.html',
minify:true,
chunks:['index'] // 引入的 js
})
],
module:{
rules:[
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
}

在 ./src/index/js/index.js 中执行代码:

1
2
import './../css/index.css';
alert(1);

在 ./src/index/css/index.less 的代码如下:

1
2
3
4
5
body{
background: rgb(222,222,222);
font-size: 14px;
line-height: 1.5;
}

在终端执行 npm run build 后将 dist 目录下 index.html 在浏览器中打开就可以看到效果。

5.2 加载 img

首先添加依赖包 npm install --save-dev file-loader,然后修改 webpack.config.js ,添加如下代码:

1
2
3
4
5
6
7
8
9
10
...
rules:[
...,
{
test:/\.(png|jpg|svg|gif)$/,
use:[
'file-loader'
]
}
]

此外,在 ./src/index/js/index.js 中增加如下代码:

1
2
3
4
5
6
import logo from './../img/logo.png'

let img = document.createElement('img');
img.src = logo;

document.getElementsByTagName('body')[0].appendChild(img);

在终端输入指令 npm run build ,可见图片资源已经被打包到 dist 目录中。

5.2 字体、数据等资源

因为这些资源在本项目中使用较少,在此就不进行过多的介绍了。详情请查案官方文档-资源管理;

6、增加各个页面内容

至此,只需要完成各个静态页面布局基本上能够完成整个项目,并修改 webpack.congfig.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
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
entry:{
'index':'./src/index/js/index.js',
'award':'./src/award/js/award.js',
'login':'./src/award/js/award.js'
},
output:{
filename:'[name].bundle.js',
path:path.resolve(__dirname,'dist')
},
plugins:[
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
filename:'index.html',
template:'./src/index/index.html',
minify:true,
chunks:['index']
}),
new HtmlWebpackPlugin({
filename:'award.html',
template:'./src/award/index.html',
minify:true,
chunks:['award']
}),
new HtmlWebpackPlugin({
filename:'login.html',
template:'./src/login/index.html',
minify:true,
chunks:['login']
})
],
module:{
rules:[
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test:/\.less$/,
use:[
{
loader:'style-loader'
},
{
loader: 'css-loader'
},
{
loader: 'less-loader'
}
]
},
{
test:/\.(png|svg|jpg|gif)$/,
use:[
'file-loader'
]
}
]
}
}

至此,项目基本搭建完成,接下来进行一步一步优化。

6.1 使用 source-map

为了更容易地追踪错误和警告,JavaScript 提供了 source map 功能,将编译后的代码映射回原始源代码。如果一个错误来自于 b.js,source map 就会明确的告诉你。

具体配置请看代码:
webpack.config.js:

1
2
3
4
5
...
entry:{},
devtool:'inline-source-map',
plugins:[]
...

6.2 使用 webpack-dev-server

每次编译代码时,都需要在终端运行 npm run build ,这样就变得很麻烦。在 webpack 中提供了几种不同的选项,如:

  • webpack’s watch mode
  • webpack-dev-erver
  • webpack-dev-middleware

此处只介绍下 webpack-dev-server ,关于其他两项,请查阅官方文档

首先安装 webpack-dev-server 依赖:

1
npm install --save-dev webpack-dev-server

然后修改 webpak.config.js 配置文件:

1
2
3
4
5
6
7
8
9
10
...
entry:{...},
devtool:'...',
devServer:{
contentBase : './dist'
}
plugins:[
...
]
...

以上配置告知 webpack-dev-server ,在 localhost:8080 下建立服务,将 dist 目录下的文件,作为可访问文件。

接下来添加一个 script 脚本,可以直接运行开发服务器(dev-server):

package.json:

1
2
3
4
5
6
7
...
scripts:{
...
"start":"webpack-dev-server --open"
...
}
...

OK ,在终端输入 npm run start ,就会看到浏览器自动加载页面。如果现在修改和保存任意源文件,web 服务器就会自动重新加载编译后的代码。

6.3 页面内静态资源无法打包

index.html 中通过 img 引入图片资源,执行 npm run start 后发现图页面内图片资源无法打包输出。 官方文档推荐使用 html-loader,按照文档的操作以及相关资料的查询,我始终没能够成功完成页面内图片资源的打包,最后使用的是 html-withimg-loader 完成图片打包,在终端输入指令:

1
npm install --save-dev html-withimg-loader

webpack.config.js :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
...
entry:{},
plugin:[...],
modules:{
rules:[
...
{
test: /\.(html|htm|)$/,
use: [
'html-withimg-loader'
]
}
]
},
output:{
...
}
...

6.4 jquery 依赖安装配置

整个前端页面是依赖 jquery 完成开发的,所有需要引入 jquery 依赖。终端输入指令:

1
npm install --save-dev jquery

webpack.config.js 增加如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
entry:{},
plugin:[
new webpack.ProvidePlugin({
$:"jquery",
jQuery:"jquery",
"window.jQuery":"jquery"
})
],
output:{
...
}
...

6.5 打包代码分类

目前所有资源包括 **.html 、 css 、 js 、 img 都在 dist 目录下,没有进行资源分类,所以接下来就是整合资源。

js

将 js 代码打包至 dist 目录下 js 文件加只需修改 webpack.config.js 的 output 即可:

1
2
3
4
5
6
...
output:{
filename:'js/[name].bundle.js', // 此处增加一层 js 目录
path:path.resolve(__dirname,'dist')
}
...

img

将图片打包至 images 目录下,需要修改下 file-loader 的使用规则:
webpack.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
...
entry:{}
output:{},
plugins:[],
modules:{
rules:[
...
{
test:/\.(png|svg|jpg|gif)$/,
use:[
{
loader:'file-loader',
options:{
name: '[name][hash].[ext]',
outputPath: 'images/' // 此处修改输出路径
}
}

]
}
...
]
}
...

css

css 打包到相同目录就有点复杂了,需要使用 extract-text-webpack-plugin 插件,需要说明的是在 webpack 4.0 中,需要安装这个插件的 beta 版本。在终端输入指令:

1
npm install --save-dev extract-text-webpack-plugin@next

webpack.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
...
const ExtractTextPlugin = require('extract-text-webpack-plugin');
...
entry:{},
output:{},
plugins:[
...
new ExtractTextPlugin({
filename: 'css/[name].css' // 此处规定输出目录
})
...
],
modules:{
rules:[
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{
loader:"css-loader",
options:{
url: false,
minimize: true,
sourceMap: true
}
}
]
})
}
...
]
}
...

以上大概就能基本完成整个项目的打包。还有两点需要说明:

  • 将生产和开发的配置文件拆开来使用,然后使用 webpack-merge 来实现代码合并
  • 生产的代码需要删除 console.log 等控制台信息,可使用 uglifyjs-webpack-plugin 来完成

以上两项的配置不再过多说明,详情请参考 webpack 官方文档或者我的github

二、其他问题

1、ES6 语法不兼容

本次项目中使用了大量的 ES6 语法,实践证明在某些 IOS 机中对 ES6 语法并不支持( 根据手机型号、系统版本都有关系 ),暂时改用 ES5 语法进行开发。 webpack 应该有 loader 可以完成语法转换,后续再更新吧。


我又回来更新啦。这次更新一下 ES6 转 ES5 语法。

1) 依赖安装

最开始我是按照官网的步骤安装依赖并且修改配置的,然而并没有实现 ES6 转 ES5 。请看安装的依赖:
依赖目录
在终端输入以下指令完成 babel-loader 相关依赖的安装:

1
npm install --save-dev babel-loader babel-core babel-preset-env

其中:

  • babel-loader 是 webpack 的 loader 的一种,作用同其他 loader 一样,实现对特定文件类型的处理。

loaderwebpack 能够去处理那些非 JavaScript 文件( webpack 自身只理解 JavaScript )。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack的打包能力,对它们进行处理。

  • 虽然 webpack 本身就能够处理 .js 文件,但无法对 ES2015+ 的语法进行转换, babel-loader 的作用正是实现对使用了 ES2015+ 语法的 .js 文件进行处理。
  • babel-core 的作用在于提供一系列 api 。这便是说,当 webpack 使用 babel-loader 处理文件时,babel-loader 实际上调用了 babel-coreapi ,因此也必须安装 babel-core
  • babel-preset-env 的作用是告诉 babel 使用哪种转码规则进行文件处理。事实上,babel 有几种规则都可以实现对 ES6 语法的转码,如 babel-preset-es2015babel-preset-latestbabel-preset-env ,不过官方现已建议采用 babel-preset-env ,本文也将采用 babel-preset-env
2) babel 规则配置

在项目根目录下新建 .babelrc 文件,代码如下:

1
{ "presets": [ "env" ] }

3) webpack.config.js 配置

修改 webpack.base.config.js 配置文件,此处只需要增加 modules -> rules 规则即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
...
modules:{
rules:[
...
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
}
...
]
}
...

这就告诉 webpack 打包时,一旦匹配到 .js 文件就使用 babel-loader 进行处理,如前文所述, babel-loader 调用 babel-core 的 api 使用 bable-preset-env 的规则进行转码。这里并没有使用 entry 、 output 这样的参数,这是 webpack4.x有默认的入口和出口,本项目无须改变,因此便不必进行设置。

完成以上配置,即可在终端输入指令 npm run build ,可以查看打包输出的代码已经将 let 转变成 var

2、判断是否在微信中打开

1
var isInWeiXin = navigator.userAgent.toLowerCase().indexOf('micromessagenger') != -1 ? true : false;

3、IOS 系统 300ms 延迟

本次活动页面同时部署到了 app 内,在使用过程中发现很多地方在点击后并不是立刻执行了绑定事件,主要是由于 IOS 内部存在 300ms 的延迟。有一部分历史原因,详情查看《ios的300ms点击延时问题》

根据以上博客,我采用了 fastclick 来解决这个问题。在终端输入:

1
npm install --save-dev fastclick

然后在各个页面的 js 文件头部引入此插件,如果使用了 jQuery 的话,在 $(function(){})中执行:fastclick.attach()
./src/index/js/index.js:

1
2
3
4
5
var fastClick = require('fastclick');
$(function(){
fastclick.attach(document.body);
.....
})

其他页面按照这个方法引入即可。


参考:《webpack4.x下babel的安装、配置及使用》
推荐:大公司里怎样开发和部署前端代码?
推荐:关于 webpack 的面试题有哪些?


代码地址: https://github.com/HappyJeannie/webpack-init

本文为个人原创,转载请注明出处!