我把 Webpack 和 Echart 放在一起学, 是因为 Echart 的官方教程和实例代码是基于 Webpack 的啊… Orz

Webpack 是一个模块打包器。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。

Echart 是百度推出的一款个纯 Javascript 的图表库, 可以流畅的运行在 PC 和移动设备上, 兼容当前绝大部分浏览器. 最主要的还是它提供了看起来很炫酷可交互的图表类型.

Hint: 这是一篇学习笔记. 大部分贴的是代码, 很没有意思…

Webpack 的特点

Webpack

代码拆分

Webpack 有两种组织模块依赖的方式,同步和异步。异步依赖作为分割点,形成一个新的块。在优化了依赖树后,每一个异步区块都作为一个文件被打包。

Loader

Webpack 本身只能处理原生的 JavaScript 模块,但是 loader 转换器可以将各种类型的资源转换成 JavaScript 模块。这样,任何资源都可以成为 Webpack 可以处理的模块。

智能解析

Webpack 有一个智能解析器,几乎可以处理任何第三方库,无论它们的模块形式是 CommonJS、 AMD 还是普通的 JS 文件。甚至在加载依赖的时候,允许使用动态表达式 require(“./templates/“ + name + “.jade”)。

插件系统

Webpack 还有一个功能丰富的插件系统。大多数内容功能都是基于这个插件系统运行的,还可以开发和使用开源的 Webpack 插件,来满足各式各样的需求。

快速运行

Webpack 使用异步 I/O 和多级缓存提高运行效率,这使得 Webpack 能够以令人难以置信的速度快速增量编译。

Webpack 安装与使用

安装

首先需要安装 Node.js, 然后使用 npm 安装 Webpack:

1
$ npm install webpack -g

将 Webpack 安装到项目的依赖中:

1
$ npm install webpack --save-dev

使用

在项目中创建一个静态页面 index.html 和一个 JS 入口文件 entry.js:

1
2
3
4
5
6
7
8
9
<!-- index.html -->
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
1
2
// entry.js
document.write('It works.')

然后编译 entry.js 并打包到 bundle.js:

1
$ webpack entry.js bundle.js

打包过程会显示日志:

1
2
3
4
5
6
Hash: 71820986fc674176394e
Version: webpack 1.14.0
Time: 39ms
Asset Size Chunks Chunk Names
bundle.js 1.42 kB 0 [emitted] main
[0] ./entry.js 27 bytes {0} [built]

用浏览器打开 index.html 将会看到 It works. 。

接下来添加一个模块 module.js 并修改入口 entry.js:

1
2
// module.js
module.exports = 'It works from module.js.'
1
2
3
// entry.js
document.write('It works.')
document.write(require('./module.js')) // 添加模块

重新打包 webpack entry.js bundle.js 后刷新页面看到变化 It works.It works from module.js.

1
2
3
4
5
6
7
Hash: 279c7601d5d08396e751
Version: webpack 1.14.0
Time: 48ms
Asset Size Chunks Chunk Names
bundle.js 1.57 kB 0 [emitted] main
[0] ./entry.js 66 bytes {0} [built]
[1] ./module.js 43 bytes {0} [built]

Webpack 会分析入口文件,解析包含依赖关系的各个文件。这些文件(模块)都打包到 bundle.js Webpack 会给每个模块分配一个唯一的 id 并通过这个 id 索引和访问模块。在页面启动时,会先执行 entry.js 中的代码,其它模块会在运行 require 的时候再执行。

Loader

Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。

Loader 的特性

  • Loader 可以通过管道方式链式调用,每个 loader 可以把资源转换成任意格式并传递给下一个loader ,但是最后一个 loader 必须返回 JavaScript。
  • Loader 可以同步或异步执行。
  • Loader 运行在 node.js 环境中,所以可以做任何可能的事情。
  • Loader 可以接受参数,以此来传递配置项给 loader。
  • Loader 可以通过文件扩展名(或正则表达式)绑定给不同类型的文件。
  • Loader 可以通过 npm 发布和安装。
  • 除了通过 package.json 的 main 指定,通常的模块也可以导出一个 loader 来使用。
  • Loader 可以访问配置。
  • 插件可以让 loader 拥有更多特性。
  • Loader 可以分发出附加的任意文件。

安装 Loader

1
npm install css-loader style-loader

使用 Loader

在页面中引入一个 CSS 文件 style.css, 然后用 css-loader 来读取它:

1
2
/* style.css */
body { background: yellow; }

修改 entry.js:

1
2
3
require("!style!css!./style.css") // 载入 style.css
document.write('It works.')
document.write(require('./module.js'))

如果每次 require CSS 文件的时候都要写 loader 前缀,是一件很繁琐的事情。我们可以根据模块类型(扩展名)来自动绑定需要的 loader。

将 entry.js 中的 require(“!style!css!./style.css”) 修改为 require(“./style.css”) ,然后执行:

1
2
3
4
$ webpack entry.js bundle.js --module-bind 'css=style!css'
# 有些环境下可能需要使用双引号
$ webpack entry.js bundle.js --module-bind "css=style!css"

配置文件

Webpack 在执行的时候,除了在命令行传入参数,还可以通过指定的配置文件来执行。默认情况下,会搜索当前目录的 webpack.config.js 文件,这个文件是一个 node.js 模块,返回一个 json 格式的配置信息对象,或者通过 –config 选项来指定配置文件。

在根目录创建 package.json 来添加 webpack 需要的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"name": "webpack-example",
"version": "1.0.0",
"description": "A simple webpack example.",
"main": "bundle.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"webpack"
],
"author": "zhaoda",
"license": "MIT",
"devDependencies": {
"css-loader": "^0.21.0",
"style-loader": "^0.13.0",
"webpack": "^1.12.2"
}
}

别忘了运行 npm install。

然后创建一个配置文件 webpack.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var webpack = require('webpack')
module.exports = {
entry: './entry.js',
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
loaders: [
{test: /\.css$/, loader: 'style!css'}
]
}
}

同时简化 entry.js 中的 style.css 加载方式:

1
require('./style.css')

最后运行 webpack.

插件

插件可以完成更多 loader 不能完成的功能。插件的使用一般是在 webpack 的配置信息 plugins 选项中指定。Webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。

修改 webpack.config.js,添加 plugins:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var webpack = require('webpack')
module.exports = {
entry: './entry.js',
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
loaders: [
{test: /\.css$/, loader: 'style!css'}
]
},
plugins: [
new webpack.BannerPlugin('This file is created by zhaoda')
]
}

然后运行 webpack,打开 bundle.js,可以看到文件头部出现了我们指定的注释信息:

1
2
3
4
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
// 后面代码省略

开发环境

当项目逐渐变大,webpack 的编译时间会变长,可以通过参数让编译的输出内容带有进度和颜色。

1
$ webpack --progress --colors

如果不想每次修改模块后都重新编译,那么可以启动监听模式。开启监听模式后,没有变化的模块会在编译后缓存到内存中,而不会每次都被重新编译,所以监听模式的整体速度是很快的。

1
$ webpack --progress --colors --watch

当然,使用 webpack-dev-server 开发服务是一个更好的选择。它将在 localhost:8080 启动一个express 静态资源 web 服务器,并且会以监听模式自动运行 webpack,在浏览器打开 http://localhost:8080/http://localhost:8080/webpack-dev-server/ 可以浏览项目中的页面和编译后的资源输出,并且通过一个 socket.io 服务实时监听它们的变化并自动刷新页面。

1
2
3
4
5
# 安装
$ npm install webpack-dev-server -g
# 运行
$ webpack-dev-server --progress --colors

然后就可以通过 http://localhost:8080/webpack-dev-server/ 打开测试页面了:

测试页面

故障处理

Webpack 的配置比较复杂,很容出现错误,下面是一些通常的故障处理手段。

一般情况下,webpack 如果出问题,会打印一些简单的错误信息,比如模块没有找到。我们还可以通过参数 –display-error-details 来打印错误详情。

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
$ webpack --display-error-details
Hash: a40fbc6d852c51fceadb
Version: webpack 1.12.2
Time: 586ms
Asset Size Chunks Chunk Names
bundle.js 12.1 kB 0 [emitted] main
[0] ./entry.js 153 bytes {0} [built] [1 error]
[5] ./module.js 43 bytes {0} [built]
+ 4 hidden modules
ERROR in ./entry.js
Module not found: Error: Cannot resolve 'file' or 'directory' ./badpathmodule in /Users/zhaoda/data/projects/webpack-handbook/examples
resolve file
/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule doesn't exist
/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule.webpack.js doesn't exist
/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule.js doesn't exist
/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule.web.js doesn't exist
/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule.json doesn't exist
resolve directory
/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule doesn't exist (directory default file)
/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule/package.json doesn't exist (directory description file)
[/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule]
[/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule.webpack.js]
[/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule.js]
[/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule.web.js]
[/Users/zhaoda/data/projects/webpack-handbook/examples/badpathmodule.json]
@ ./entry.js 3:0-26

Webpack 的配置提供了 resolve 和 resolveLoader 参数来设置模块解析的处理细节,resolve 用来配
置应用层的模块(要被打包的模块)解析,resolveLoader 用来配置 loader 模块的解析。

当引入通过 npm 安装的 node.js 模块时,可能出现找不到依赖的错误。Node.js 模块的依赖解析算法很简单,是通过查看模块的每一层父目录中的 node_modules 文件夹来查询依赖的。当出现 Node.js 模块依赖查找失败的时候,可以尝试设置 resolve.fallback 和 resolveLoader.fallback 来解决问题。

1
2
3
4
module.exports = {
resolve: { fallback: path.join(__dirname, "node_modules") },
resolveLoader: { fallback: path.join(__dirname, "node_modules") }
};

Webpack 中涉及路径配置最好使用绝对路径,建议通过 path.resolve(dirname, “app/folder”) 或 path.join(dirname, “app”, “folder”) 的方式来配置,以兼容 Windows 环境。

使用 Webpack 展示 Echart 图表

将 index.html 修改为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>echart</title>
</head>
<body>
<div id='root'>
</div>
<div id="main" style="width: 600px;height:400px;"></div>
<script src="bundle.js"></script>
</body>
</html>

将 entry.js 的代码修改为如下:

1
2
3
4
// entry.js
var greeter = require('./module.js');
document.getElementById('root').appendChild(greeter());

将 module.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
// module.js
module.exports = function() {
var greet = document.createElement('div');
greet.textContent = "webpack 的眼里一切都是模块";
return greet;
};
var echarts = require('echarts');
var myChart = echarts.init(document.getElementById('main'));
// 绘制图表
myChart.setOption({
title: { text: 'ECharts 入门示例' },
tooltip: {},
xAxis: {
data: ["衬衫","羊毛衫","雪纺衫","裤子","高跟鞋","袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
});

然后我们运行 webpack, 就可以得到如下事例图片:

事例图片

我们再多试一个 Echart 官网提供的事例, 可以得到如下的图片:

事例2

遇到的问题

在执行 webpack 的时候报错: FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - process out of memory

解决方法: 这个肯定是你尝试次数太多了, bundle 内容太多了, 使用 webpack -p 删除所有无作用的代码:

1
webpack -p

参考文档

Webpack 中文指南: http://webpackdoc.com/index.html
Echart 官方文档: http://echarts.baidu.com/tutorial.html#ECharts%20%E7%89%B9%E6%80%A7%E4%BB%8B%E7%BB%8D
使用 Webpack 使用 Echarts3 简单入门: http://blog.csdn.net/a82793510/article/details/52240286?locationNum=13&fps=1