花了一个多月的时间捣鼓 webpack 的各种东西,终于把毕业设计搞出来了,看起来很糟糕的工作流程。然后就是拖了一个多月时间的总结咯,会记录 webpack 的使用以及自己对 webpack 和 FIS3 的一些想法,然后插件开发就留着下次好了。

介绍

webpack is a bundler for modules. The main purpose is to bundle JavaScript files for usage in a browser, yet it is also capable of transforming, bundling, or packaging just about any resource or asset.

webpack 是一个模块打包工具,和 Grunt、Gulp 之类的任务管理工具有本质上的区别。根据其设计理念,开发过程中接触到的所有文件资源都是模块。不同类型的文件能够被 webpack 转换为 js 模块并被 js 代码直接引用,同时在打包过程中能够静态分析模块间的依赖关系。

简单的 demo

demo0 bundle

// main.js
document.body.innerHTML += '<p>main.js works</p>';
require('./style.scss');
document.body.innerHTML += '<p>style.scss works</p>';
require('./foo.js');
// webpack.config.js
module.exports = {
entry: './src/main.js',
output: {
path: 'build/',
filename: 'bundle.js'
},
module: {
loaders: [{
test: /\.scss$/,
loader: 'style!css!sass'
}]
}
};

目录下执行 webpack, 构建结果如下,输出了 bundle.js。由于 main.js, foo.js, style.scss 都被打包到 bundle.js, 因此 index.html 只需要加载 bundle.js 就可以了。

demo0

webpack 就做了这么些东西。

DEMO 代码仓库

entry & output

entry: The entry point for the bundle.
output: Options affecting the output of the compilation.

简单理解,entry 是页面的入口文件,有点“启动器”的感觉;output 是输出文件的一些配置,包括文件输出路径,文件命名,CDN 之类的。以之前的 demo 为例:

{
// 入口文件
entry: './src/main.js',
// 输出配置
output: {
// 输出的路径
path: 'build/',
// 输出文件名,相对路径
filename: 'bundle.js'
},
...
}

Loader & Plugin

Loader

Loaders are transformations that are applied on a resource file of your app

webpack 通过配置 loader (加载器)实现对任意类型文件的打包。还是以之前的 demo 为例:

{
...
module: {
loaders: [{
test: /\.scss$/,
loader: 'style!css!sass'
}]
}
}

test 通过正则表达式匹配命中的文件,loader 指定该文件被哪些 loader 处理。

这里匹配的是 .scss 文件;! 作为分隔符,实际上指定了 style-loader, css-loader, sass-loader 三个加载器,等价于 ['style', 'css', 'sass']。每一个文件依序被 sass-loader, css-loader, style-loader 处理,最终转换为 js 模块被 main.js 直接引用,并在执行过程中向页面插入 style 标签,以此嵌入样式。

使用 webpack 还会经常见到类似 style!css?sourceMap!sass?sourceMap 的写法,为 loader 提供配置参数。把他当做 URL query 看待就舒服多了。

瞄一下简单的 html-loader 能够更好地理解 loader 是如何工作。

Plugin

Use plugins to add functionality typically related to bundles in webpack.

webpack 中的插件比 loader 有更多能力,能够在构建过程中的各个阶段对文件资源进行操作。常见的配置方式如下:

{
module: {
...
},
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new webpack.optimize.CommonsChunkPlugin('common.js')
]
}

这里使用的 webpack 自带的 optimize.UglifyJsPlugin,直接将其实例作为参数就能够根据默认配置选项对代码进行压缩混淆;optimize.CommonsChunkPlugin 则将公共代码模块提取出来独立打包。同时还有其他常用的插件还有 extract-text-plugin, html-webpack-plugin 等。

由于 webpack 的 plugin 的配置没有规范,感觉就和 Grunt 差不多然后导致了要用什么插件就去找什么文档,所以就不继续巴拉咯。

复杂的 demo

列举一些要实现的功能,然后通过 webpack 实现。

  1. es2015,sass 等支持
  2. 各种模块规范支持
  3. 代码校验压缩混淆
  4. 维护资源依赖关系
  5. 资源定位和输出
  6. 合并公共模块
  7. 文件名添加 MD5 戳和长缓存
  8. 添加 CDN 地址

考虑到上面的东西,webpack.config.js 代码如下

// webpack.config.js
'use strict';
var webpack = require('webpack'),
HtmlWebpackPlugin = require('html-webpack-plugin'),
ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
// 基本目录(绝对路径),entry 配置项会基于 context 定位文件
context: __dirname + '/app',
// 入口文件的配置,这里每个 page 配置一个入口 js
entry: {
page0: './page0/page0.js',
page1: './page1/page1.js',
// 这里配合 commonPlugin 实现JS库的独立打包,实现长缓存
vendor: ['./lib/webpack-zepto']
},
// 输出配置
output: {
// 输出路径,默认是 process.cwd()
path: 'build/',
// 输出的文件名,name 对应 `entry` 配置的键名
filename: 'assets/[name].entry.[chunkhash:6].js',
// 发布地址
publicPath: '/'
},
module: {
// 在 loader 处理之前处理匹配模块
preLoaders: [
{
test: /\.js$/,
loader: 'jshint-loader',
exclude: /node_modules|lib/
}
],
loaders: [{
// babel-loader 的配置,排除 node_modules
test: /\.js$/,
loader: 'babel?presets=es2015',
exclude: /node_modules/
}, {
// css-loader 和 sass-loader,同时配置 extract-text-plugin 输出独立的样式文件
test: /\.(css|scss)$/,
loader: ExtractTextPlugin.extract(['css', 'sass'])
}, {
// url-loader,配置图片输出命名和内联大小限制
test: /\.(png|jpg)$/,
loader: 'url',
query: {
name: 'assets/images/[name].[hash:6].[ext]',
limit: 8192
}
}, {
// html-loader,解决 img 标签的图片资源定位
test: /\.html$/,
loader: 'html?-minimize'
}]
},
plugins: [
// html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'page0.html',
template: 'page0/page0.html',
excludeChunks: ['page1']
}),
new HtmlWebpackPlugin({
filename: 'page1.html',
template: 'page1/page1.html',
excludeChunks: ['page0']
}),
// 提取公共模块
new webpack.optimize.CommonsChunkPlugin('commons', 'assets/common.[hash:6].js', ['page0', 'page1']),
// 提取公共库,vendor 见 `entry` 配置
new webpack.optimize.CommonsChunkPlugin('vendor', 'assets/vendor.[chunkhash:6].js'),
// 输出独立样式文件,配置文件命名
new ExtractTextPlugin('assets/[name].[chunkhash:6].css', {
allChunks: true
}),
// 代码混淆压缩
new webpack.optimize.UglifyJsPlugin()
],
resolve: {
// 增加解析根路径,可直接 require 到 lib 里面的模块
root: [process.cwd() + '/app/lib'],
alias: {
zepto: 'webpack-zepto'
}
},
// jshint 配置
jshint: {
esnext: true
}
};

然后执行打包,结果如下

demo1_shell

demo1_build

注意到 entry 配置变成了一个 Object, 这里 page0 和 page1 分别是两个不同页面的入口文件,分开打包依赖文件;vendor 则是为了实现公共库的独立打包,而与 page0 和 page1 区分开。

还有这里提到的都是生产环境的打包配置,开发过程中需要 sourceMap, 不需要打 hash 和压缩混淆,还有愉悦的 HMR 虽然看起来很爽但也有很多东西要注意所以拉出来单独巴拉,这里就没有啦。

DEMO 代码仓库

webpack & FIS3

一开始接触的 FIS3, 发现它确实把我们团队所需要的东西都给考虑进去了,用起来也十分顺手,但总有点不温不火的感觉。考虑了一下 webpack 跟 FIS3 的区别,有以下几点

  1. 定位不同。webpack 作为模块打包工具,FIS3 作为解决方案。
  2. 如果说 FIS3 的亮点在于建立资源依赖表,但其实 webpack 也进行了静态资源分析并维护了 compilation.module, 两者的差别在于 FIS3 核心高度模块化之后提供的资源依赖表简洁清晰、能够轻易被其它前后端的构建框架接受,而 webpack 好像没这方面考虑,提供了 webpack-stats-plugin
  3. webpack 的生态比 FIS3 健康很多,包括插件数量和问题解决。FIS3 官方维护的插件有限,第三方维护的插件质量难以保障,发现了问题很难找到相应的内容,这是用 FIS3 少有的不开心的地方。
  4. FIS 的所谓代码入侵性应该是指 __url 之类的标记的注入,对应的 webpack 通过将各种文件资源编译成 JS 模块并通过模块管理工具直接 require 引用。
  5. webpack 配置文件复杂,插件的使用缺少规范,学习成本高;FIS3 则是类 CSS 风格的配置语法,容易理解和接受。后者比前者在配置方面要灵活很多。webpack 难以在不同项目中复用配置,但 FIS3 就轻松很多了。
  6. webpack 在 Web Component 和 css module 方面的支持通过 loader 实现,但 FIS3 在这两方面似乎还没有出现解决方法。
  7. webpack 有 HMR, FIS3 有简单的 LiveReload。
  8. webpack 插件开发的学习成本高,很难找到相关的文档,只能听作者的话啃源码,相比 FIS3 就低很多了,更加清爽。

小结

webpack 用起来还是很吸引人的,但是配置文件的维护在短周期项目大量存在的情况下会增加使用成本;FIS3 在这类项目中使用很让人放心,偶尔遇到问题了就偶尔纠结一下。看项目类型需要咯~