为什么要使用webpack?

现今的很多网页其实可以看做是功能丰富的应用,它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度,前端社区涌现出了很多好的实践方法:

  • 模块化,让我们可以把复杂的程序细化为小的文件;
  • 类似于TypeScript这种在JavaScript基础上拓展的开发语言:使我们能够实现目前版本的JavaScript不能直接使用的特性,并且之后还能能装换为JavaScript文件使浏览器可以识别;
  • Scss,less等CSS预处理器

这些改进确实大大的提高了我们的开发效率,但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常反锁的,这就为webpack类的工具的出现提供了需求。

什么是webpack

webpack可以看做是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。

webpack和Grunt以及Gulp相比有什么特性

其实,webpack和另外两个并没有太多的可比性,Gulp/Grunt是一种能够优化前端的开发流程的工具,而webpack是一种模块化的解决方案,不过webpack的优点使得webpack可以替代Gulp/Grunt类的工具。

Grunt和Gulp的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,这个工具之后可以自动替你完成这些任务。
Alt text

webpack的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack将从这个文件开始找到你的项目的所有依赖文件,使用loaders处理它们,最后打包为一个浏览器可识别的JavaScript文件。
Alt text

如果实在要把二者进行比较,webpack的处理速度更快更直接,能打包更多不同类型的文件。

开始使用webpack

安装

可以使用npm安装,新建一个空的练习文件夹(此处命名为webpack sample progect),在终端中转到该文件夹后执行下述指令就可以完成安装。

1
2
3
4
//全局安装
$ npm install -g webpack
//安装到你的项目目录
$ npm install --save-dev webpack

正式使用webpack前的准备

在上述练习文件夹中创建一个package.json文件,这是一个标准的npm说明文件,里面蕴含了丰富的信息,包括当前项目的依赖模块,自定义的脚本任务等等。在终端中使用npm init命令可以自动创建这个package.json文件。

1
$ npm init

输入这个命令后,终端会问你一系列诸如项目名称,项目描述,作者等信息,不过不用担心,如果你不准备在npm中发布你的模块,这些问题的答案都不重要,回车默认即可。

package.json文件已经就绪,我们在本项目中安装Webpack作为依赖包

1
2
//安装到你的项目目录
$ npm install --save-dev webpack

下面来说一说webpack.config.js中对webpack的主要配置:

entry(入口)

用来指明webpack构建的唯一入口文件。

1
entry: __dirname + '/src/main'

注:“__dirname”是node.js中的一个全局变量,它指向当前执行脚本所在的目录。

output

1
2
3
4
output: {
path: path.join(__dirname,'./dist'),//打包后的文件存放的地方
filename: '[name]-[hash].js'//打包后输出文件的文件名
}

现在如果你需要打包文件只需要在终端里你运行webpack(非全局安装需使用node_modules/.bin/webpack)命令就可以了,这条命令会自动参考webpack.config.js文件中的配置选项打包你的项目,输出结果如下:
Alt text

又学会了一种使用Webpack的方法,而且不用管那烦人的命令行参数了,有没有感觉很爽。有没有想过如果可以连webpack(非全局安装需使用node_modules/.bin/webpack)这条命令都可以不用,那种感觉会不会更爽~,继续看下文。

Webpack的强大功能

devtool(生成Source Maps(使调试更容易))

devServer(使用webpack构建本地服务器)

想不想让你的浏览器监测你都代码的修改,并自动刷新修改后的结果,其实Webpack提供一个可选的本地开发服务器,这个本地服务器基于node.js构建,可以实现你想要的这些功能,不过它是一个单独的组件,在webpack中进行配置之前需要单独安装它作为项目依赖

1
2
//安装到你的项目目录
$ npm install --save-dev webpack-dev-server

devserver作为webpack配置选项中的一项:

1
2
3
4
5
6
devServer: {
contentBase: "./public",//本地服务器所加载的页面所在的目录(默认webpack-dev-server会为根文件夹提供本地服务器,如果想为另外一个目录下的文件提供本地服务器,应该在这里设置其所在目录(本例设置到“public"目录))
colors: true,//终端中输出结果为彩色
historyApiFallback: true,//不跳转
inline: true//实时刷新
}

Loaders(在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个)

Loaders是webpack中最让人激动人心的功能之一了。通过使用不同的loader,webpack通过调用外部的脚本或工具可以对各种各样的格式的文件进行处理,比如说分析JSON文件并把它转换为JavaScript文件,或者说把下一代的JS文件(ES6,ES7)转换为现代浏览器可以识别的JS文件。或者说对React的开发而言,合适的Loaders可以把React的JSX文件转换为JS文件。

Loaders需要单独安装并且需要在webpack.config.js下的module关键字下进行配置,Loaders的配置选项包括以下几方面:

  • test:一个匹配loaders所处理的文件的拓展名的正则表达式(必须)
  • loader:loader的名称(必须)
  • include/exclude:手动添加必须处理的文件(文件夹)或屏蔽不需要处理的文件(文件夹)(可选);
  • query:为loaders提供额外的设置选项(可选)

Babel
Babel其实是一个编译JavaScript的平台,它的强大之处表现在可以通过编译帮你达到以下目的:

  • 下一代的JavaScript标准(ES6,ES7),这些标准目前并未被当前的浏览器完全的支持;
  • 使用基于JavaScript进行了拓展的语言,比如React的JSX

Babel的安装与配置
Babel其实是几个模块化的包,其核心功能位于称为babel-core的npm包中,不过webpack把它们整合在一起使用,但是对于每一个你需要的功能或拓展,你都需要安装单独的包(用得最多的是解析Es6的babel-preset-es2015包和解析JSX的babel-preset-react包)。
我们先来一次性安装这些依赖包:

1
2
3
//安装到你的项目目录
// npm一次性安装多个依赖模块,模块之间用空格隔开
$ npm install --save-dev babel-core babel-loader babel-preset-es2015

在webpack中配置Babel的方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module: {// 添加 module 模块,把 loaders 配置在此模块中
loaders: [
// 解析.vue文件
{ test: /\.vue$/, loader: 'vue' },
// 转化ES6的语法,除了/node_modules/目录下的
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,//排除该目录
query: { // loader 的其他选项,babel 的配置项
presets: ['es2015']
}
},
// 编译css并自动添加css前缀
// { test: /\.css$/, loader: 'style-loader!css-loader!autoprefixer'},
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader','css-loader')},//'style-loader!css-loader!autoprefixer'
// 图片转化,小于8K自动转化为base64的编码
{ test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192'},
// html模板编译?
{ test: /\.(html|tpl)$/, loader: 'html-loader' },
]
}

Babel其实可以完全在webpack.config.js中进行配置,但是考虑到babel具有非常多的配置选项,在单一的webpack.config.js文件中进行配置往往使得这个文件显得太复杂,因此一些开发者支持把babel的配置选项放在一个单独的名为 “.babelrc” 的配置文件中。我们现在的babel的配置并不算复杂,不过之后我们会再加一些东西,因此现在我们就提取出相关部分,分两个配置文件进行配置(webpack会自动调用.babelrc里的babel配置选项),如下:

1
2
3
4
5
6
7
8
9
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel'
}
]
}

.babelrc:

1
2
3
4
//.babelrc
{
"presets": ["es2015"]
}

到目前为止,我们已经知道了,对于模块,Webpack能提供非常强大的处理功能,那哪些是模块呢?
一切皆模块!!!!Webpack有一个不可不说的优点,它把所有的文件都可以当做模块处理,包括你的JavaScript代码,也包括CSS和fonts以及图片等等等,只有通过合适的loaders,它们都可以被当做模块被处理。

CSS
webpack提供两个工具处理样式表,css-loader 和 style-loader,二者处理的任务不同,css-loader使你能够使用类似@import 和 url(…)的方法实现 require()的功能,style-loader将所有的计算后的样式加入页面中,二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。

1
2
//安装到你的项目目录
$ npm install --save-dev style-loader css-loader

配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module: {// 添加 module 模块,把 loaders 配置在此模块中
loaders: [
// 解析.vue文件
{ test: /\.vue$/, loader: 'vue' },
// 转化ES6的语法,除了/node_modules/目录下的
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,//排除该目录
query: { // loader 的其他选项,babel 的配置项
presets: ['es2015']
}
},
// 编译css并自动添加css前缀
{ test: /\.css$/, loader: 'style-loader!css-loader!autoprefixer'},
// 图片转化,小于8K自动转化为base64的编码
{ test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192'},
// html模板编译?
{ test: /\.(html|tpl)$/, loader: 'html-loader' },
]
}

注:感叹号的作用在于使同一文件能够使用不同类型的loader

接下来,在src->static->css文件夹里创建一个名字为”common.css”等的文件,对一些元素设置样式。你还记得吗?webpack只有单一的入口,其它的模块需要通过 import, require, url等导入相关位置,为了让webpack能找到”common.css“等文件,我们把它导入”main.js “中,如下:

1
2
3
4
5
import './static/css/common.css'
import './static/css/vux.css'
import './static/css/swiper.min.css'
import './static/css/my-job.css'
import './static/css/joblist.css'

通常情况下,css会和js打包到同一个文件中,并不会打包为一个单独的css文件,不过通过合适的配置webpack也可以把css打包为单独的文件的。

postcss(css预处理器)
Sass 和 Less之类的预处理器是对原生CSS的拓展,它们允许你使用类似于variables, nesting, mixins, inheritance等不存在于CSS中的特性来写CSS,CSS预处理器可以这些特殊类型的语句转化为浏览器可识别的CSS语句,你现在可能都已经熟悉了,在webpack里使用相关loaders进行配置就可以使用了,以下是常用的CSS 处理loaders

  • Less Loader
  • Sass Loader
  • Stylus Loader

不过其实也存在一个CSS的处理平台-PostCSS,它可以帮助你的CSS实现更多的功能,在其CSS官方文档可了解更多相关知识。
举例来说如何使用PostCSS,我们使用PostCSS来为CSS代码自动添加适应不同浏览器的CSS前缀。
首先安装postcss-loader 和 autoprefixer(自动添加前缀的插件)

1
2
//安装到你的项目目录
$ npm install --save-dev postcss-loader autoprefixer

接下来,在webpack配置文件中进行设置,只需要新建一个postcss关键字,并在里面申明依赖的插件,如下,现在你写的css会自动根据Can i use里的数据添加不同前缀了。

1
2
3
postcss: [
require('autoprefixer')//调用autoprefixer插件
]

到现在,本文已经涉及到处理JS的Babel和处理CSS的PostCSS,它们其实也是两个单独的平台,配合Webpack可以很好的发挥它们的作用(ExtractTextPlugin分离CSS和JS文件)。接下来介绍Webpack中另一个非常重要的功能-Plugins

plugins(插件,插件并不直接操作单个文件,它直接对整个构建过程起作用)

插件(Plugins)是用来拓展Webpack功能的,它们会在整个构建过程中生效,执行相关的任务。Loaders和Plugins常常被弄混,但是他们其实是完全不同的东西,可以这么来说,loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个,插件并不直接操作单个文件,它直接对整个构建过程其作用。
Webpack有很多内置插件,同时也有很多第三方插件,可以让我们完成更加丰富的功能。

使用插件的方法

要使用某个插件,我们需要通过npm安装它,然后要做的就是在webpack配置中的plugins关键字部分添加该插件的一个实例(plugins是一个数组)继续看例子,我们添加了一个实现版权声明的插件。

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
//webpack.config.js
"use strict"
let path = require('path')
let webpack = require('webpack')
let ExtractTextPlugin = require('extract-text-webpack-plugin')
let autoprefixer = require('autoprefixer')
let HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main',
output: {
path: path.join(__dirname,'./dist'),
filename: '[name].js',
publicPath: '/'
},
// devServer: {
// historyApiFallback: true,
// hot: false,
// inline: true,
// grogress: true,
// colors: true
// },
module: {// 添加 module 模块,把 loaders 配置在此模块中
loaders: [
// 解析.vue文件
{ test: /\.vue$/, loader: 'vue' },
// 转化ES6的语法,除了/node_modules/目录下的
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,//排除该目录
query: { // loader 的其他选项,babel 的配置项
presets: ['es2015']
}
},
// 编译css并自动添加css前缀
// { test: /\.css$/, loader: 'style-loader!css-loader!autoprefixer'},
{ test: /\.css$/, loader: ExtractTextPlugin.extract('style-loader','css-loader')},//'style-loader!css-loader!autoprefixer'
// 图片转化,小于8K自动转化为base64的编码
{ test: /\.(png|jpg|gif)$/, loader: 'url-loader?limit=8192'},
// html模板编译?
{ test: /\.(html|tpl)$/, loader: 'html-loader' },
]
},
plugins: [
new webpack.BannerPlugin("Copyright Flying Unicorns inc."),//在这个数组中new一个就可以了
new ExtractTextPlugin("[name].[contenthash].css",{
allChunks: true,
}),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
title: 'My App',
filename: 'index.html',
template: 'index.html'
})
],
// postcss: [autoprefixer()],
// vue: {
// loaders: {
// css: 'style!css!autoprefixer',
// }
// },
// 转化成es5的语法
babel: {
presets: ['es2015'],
plugins: ['transform-runtime']
},
resolve: {
// require时省略的扩展名,如:require('module') 不需要module.js
extensions: ['', '.js', '.vue'],
// 别名,可以直接使用别名来代表设定的路径以及其他
alias: {
filter: path.join(__dirname, './src/filters'),
components: path.join(__dirname, './src/components')
}
}//,
// 开启source-map,webpack有多种source-map,在官网文档可以查到
// devtool: 'eval-source-map'
}

通过这个插件,打包后的JS文件显示如下:
Alt text

知道Webpack中的插件如何使用了,下面给大家推荐几个常用的插件

常用插件

HtmlWebpackPlugin

Hot Module Replacement

产品阶段的构建

目前为止,我们已经使用webpack构建了一个完整的开发环境。但是在产品阶段,可能还需要对打包的文件进行额外的处理,比如说优化,压缩,缓存以及分离CSS和JS。

对于复杂的项目来说,需要复杂的配置,这时候分解配置文件为多个小的文件可以使得事情井井有条,以上面的例子来说,我们创建一个“webpack.production.config.js”的文件,在里面加上基本的配置,它和原始的webpack.config.js很像,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
```
package.json:
```javascript
{
"name": "webpack-sample-project",
"version": "1.0.0",
"description": "Sample webpack project",
"scripts": {
"start": "webpack-dev-server --progress",
"build": "NODE_ENV=production webpack --config ./webpack.production.config.js --progress"
},
"author": "Cássio Zen",
"license": "ISC",
"devDependencies": {...},
"dependencies": {...}
}

优化插件

webpack提供了一些在发布阶段非常有用的优化插件,它们大多来自于webpack社区,可以通过npm安装,通过以下插件可以完成产品发布阶段所需的功能

  • OccurenceOrderPlugin :为组件分配ID,通过这个插件webpack可以分析和优先考虑使用最多的模块,并为它们分配最小的ID
  • UglifyJsPlugin:压缩JS代码;
  • ExtractTextPlugin:分离CSS和JS文件

我们继续用例子来看看如何添加它们,OccurenceOrder 和 UglifyJS plugins 都是内置插件,你需要做的只是安装它们:

1
$ npm install --save-dev extract-text-webpack-plugin

在配置文件的plugins后引用它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
plugins: [
new webpack.BannerPlugin("Copyright Hulk Lv."),//在这个数组中new一个就可以了
new ExtractTextPlugin("[name].[contenthash].css",{//分离css和js
allChunks: true,
}),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new HtmlWebpackPlugin({//这个插件可以帮助生成 HTML 文件,在 body 元素中,使用 script 来包含所有你的 webpack bundles,只需要在你的 webpack 配置文件中如下配置:
title: 'My App',//用来生成页面的 title 元素
filename: 'index.html',//输出的 HTML 文件名,默认是 index.html, 也可以直接配置带有子目录
template: 'index.html'//模板文件路径
})
]

缓存

缓存无处不在,使用缓存的最好方法是保证你的文件名和文件内容是匹配的(内容改变,名称相应改变)

webpack可以把一个哈希值添加到打包的文件名中,使用方法如下,添加特殊的字符串混合体([name], [id] and [hash])到输出文件名前:

1
2
3
4
output: {
path: __dirname + "/build",
filename: "[name]-[hash].js"
}

构建之后的目录:
Alt text

现在用户会有合理的缓存了。