webpack是一个模块打包工具,通过依赖处理模块,并生成那些模块静态资源。
观察上图,webpack把所有的资源(js,css和图片等)都当做是模块——webpack中可以引用css,css中可以嵌入图片dataUrl,对于不同类型的资源,webpack有对应的模块加载器。webpack模块打包器会分析模块间的依赖关系,最后 生成了优化且合并后的静态资源。
当今网站正在变成一个web应用:一个页面包含的javascript代码越来越多,结果客户端存在着大量的代码。通过模块系统划分代码到一个个小模块中,是管理大量代码的有效方式。目前有好几种方式用于实现模块和依赖关系定义:
标签
window
,需要通过全局对象来访问依赖的模块。实例:
<script src="module1.js">script>
<script src="module2.js">script>
<script src="libraryA.js">script>
<script src="module3.js">script>
CommonJS
exports
对象绑定属性或者给module.exports
赋值来定义输出,实例:
require("module");
require("../file.js");
exports.doStuff = function() {};
module.exports = someValue;
AMD
实例:
require(["module", "../file"], function(module, file) { /* ... */ });
define("mymodule", ["dep1", "dep2"], function(d1, d2) {
return someExportedValue;
});
ES6
实例:
import "jquery";
export function doStuff() {}
module "localModule" {}
模块确定了,要在客户端执行,必须从服务器传输到客户端。以下是两种极端的传输方式:
上面两种模块加载方式比较极端,我们需要一个能够更加灵活地传输模块的方法。当在编译所有模块的时候,将整个系统划分成多个更小的模块集。各个模块集不会一开始就加载进来,只有在需要的时候才加载,所以初始化的时候并没有包含所有的模块代码。模块集的划分点取决于开发者,例如按功能划分,将该模块依赖的一个个模块当做一个模块集,当访问系统某项功能时,一次请求加载该项功能的模块集。
前面讨论完关于模块的定义与加载,我们发现为什么只提到了Javascript的模块系统,而前段还有许多其他的静态资源需要加载,如:样式表、图片、字体和html模板等。我们能不能像javascript一样使用require来加载指定的静态资源,例如:
require("./style.css");
require("./style.less");
require("./template.jade");
require("./image.png");
一个大点的项目,都会遇到上面的问题——模块划分和静态资源,目前已有的模块打包工具已经不适用了。而webpack作者曾尝试扩展已有的模块打包工具,但也是无法实现所有的构建目标。
webpack的目标:
webpack的特性
npm install webpack -g
,如果有将npm全局安装路径加入path,则可以直接在命令行输入webpack命令了npm install webpack --save-d
,本地安装后的命令行工具在./node_modules/.bin/webpack.cmd
,也可以在npm package.json里的scripts里引用webpack除了在命令行界面使使用webpack外,也可以直接在代码中使用webpack。
webpack --entry entry.js --output bundle.js
webpack --config webpack.config.js
webpack --config webpack.config.js -d
:开发模式webpack --config webpack.config.js -w
:观察模式webpack --config webpack.config.js -p
:生成模式node.js-api
简单示例
var webpack = require("webpack");
// returns a Compiler instance
webpack({
// configuration
}, function(err, stats) {
// ...
});
复杂示例
var webpack = require("webpack");
// returns a Compiler instance
var compiler = webpack({
// configuration
});
compiler.run(function(err, stats) {
// ...
});
// or
compiler.watch({ // watch options:
aggregateTimeout: 300, // wait so long for more changes
poll: true // use polling instead of native watchers
// pass a number to set the polling interval
}, function(err, stats) {
// ...
});
常见问题:
--confgi
选项是做什么的? --config
用来指定一个配置文件,代替命令行中的选项,从而简化命令。如果直接执行webpack
的话,webpack会在当前目录查找名为webpack.config.js
的文件。本例展示Hello World!
,源代码如下:
index.html
<html>
<head>
<meta charset="utf-8">
head>
<body>
<script type="text/javascript" src="bundle.js" charset="utf-8">script>
body>
html>
entry.js
document.write(require("./content.js"));
content.js
module.exports = "Hello World.";
模块打包及运行:
webpack ./entry.js bundle.js
,该命令会编译entry.js和content.js,并合并生成一个bundle.js。index.html
,显示:Hello World!.
。除了在webpack命令选项中指定模块入口文件和模块打包生成文件外,可以创建webpack.config.js
来配置,具体配置如下代码所示。配置好后,在命令行中直接输入webpack
(默认查找配置文件webpack.config.js
)或者webpack --config webpack.config.js
,这样效果同上面的命令webpack ./entry.js bundle.js
。
module.exports = {
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js"
}
};
更多关于webpack.config.js
的配置见下文。
webpack同时支持CommonJS和AMD规范的模块编写方式, Hello World
实例已经简单地演示了模块的定义和加载,下面我们开始学习webpack模块相关的api。
module.exports
实例:
module.exports = function doSomething() {
// Do something
};
exports
实例:
exports.someValue = 42;
exports.anObject = {
x: 123
};
exports.aFunction = function doSomething() {
// Do something
};
export label
实例:
export: var answer = 42;
export: function method(value) {
// Do something
};
define
define(value: !Function)
define([name: String], [dependencies: String[]], factoryMethod: function(...))
示例
define with value
define({
answer: 42
});
define with factory
define(["jquery", "my-module"], function($, myModule) {
// Do something with $ and myModule.
// Export a function
return function doSomething() {
// Do something
};
});
require(dependency: String)
document.write(require("./content.js"));
,这便是一个commonJS规范的require。这里有两个模块:entry.js和content.js,执行webpack却只生成了一个文件bundle.js。require: "dependency"
require AMD
require(dependencies: String[], [callback: function(...)])
实例:
// in file.js
var a = require("a");
var d = require("d);
require(["b", "d"], function(b, d) {
var c = require("c");
});
/* This results in:
* entry chunk
- file.js
- a
- d
* anonymous chunk
- b
- c
*/
webpack file.js bundle.js
生成bundle.js和1.chunk.js,前者包含模块file.js、a.js和d.js,后者包含模块b.js和c.js。关于require AMD模块划分规则,如下所示: require.ensure
实例:
简单示例:
// in file.js
var a = require("a");
require.ensure(["b"], function(require) {
var c = require("c");
});
require.ensure(["d"], function() {
var e = require("e");
}, "my chunk");
require.ensure([], function() {
var f = require("f");
}, "my chunk");
/* This results in:
* entry chunk
- file.js
- a
* anonymous chunk
- b
- c
* "my chunk"
- d
- e
- f
*/
require.include
require.include(dependency: String)
实例:
// in file.js
require.include("a");
require.ensure(["a", "b"], function(require) {
// Do something
});
require.ensure(["a", "c"], function(require) {
// Do something
});
/* This results in:
* entry chunk
- file.js
- a
* anonymous chunk
- b
* anonymous chunk
- c
Without require.include "a" would be in both anonymous chunks.
The runtime behavior isn't changed.
*/
webpack命令提供了一系列的命令选项用于控制模块打包——可通过webpack --help
查看这些选项。当需要配置较多的选项时,会使webpack命令变得非常臃肿,如:webpack --entry entry.js --output-path ./build --ouput-file bundle.js
。所以webpack提供了一个配置对象来简化项目模块打包,在运行webpack命令的时候,只要指定这个配置对象即可——webpack --config webpack.config.js
,webpack.config.js
会输出配置对象,例如:
module.exports = {
entry: "./entry.js,
output: {
path: "./build",
filename: "bundle.js"
}
};
观察上面的配置对象,可以发现配置对象本身就是一个模块,而不是JSON,我们可以在配置对象中随意的使用Javascript代码。除了在命令行界面中直接使用webpack,也可以在js代码中使用webpack api,例如:
var webpack = require("webpack");
webpack({
entry: "./entry.js,
output: {
path: "./build",
filename: "bundle.js"
}, callback);
webpack配置对象:
process.cmd()
,即webpack.config.js
文件所在路径entry:
实例:
{
entry: {
page1: "./page1",
page2: ["./entry1", "./entry2"]
},
output: {
// Make sure to use [name] or [id] in output.filename
// when using multiple entry points
filename: "[name].bundle.js",
chunkFilename: "[id].bundle.js"
}
}
注:不得不说webpack的强大,提供如此之多的配置选项,由于精力有限,后续再慢慢维护完善各个配置选项的说明。
加载器在webpack中作为资源(不只是javascript,包含其他静态资源)转换器,它本身是个可以运行在node.js环境中的函数。加载器根据参数对源文件进行处理,返回新的源文件,例如:在webpack中使用加载器处理CoffeeScrip和JSX——CoffeeScript是JavaScript的转译语言,JSX是Javascript的超集,都不能直接运行在浏览器中,但加载器会将他们编译成普通的javascript代码。
加载器的特性:
简介中提到了加载器是发布在npm上,作为一个模块使用——输出一个转换函数。所以我们可以通过npm来安装管理加载器,例如: npm install xxx-loader --save-dev
或者 npm install xxx-loader --save
作为约定,加载器的命名通常为xxx-loader
——xxx是加载器的名字,例如:json-loader。我们既可以使用xxx-loader
来引用加载器,也可以直接使用xxx
。
在webpack中使用加载器有三种方式:
1. 直接在require中引用——不推荐
2. 在webpack.config.js中配置——推荐
3. 通过命令选项配置——不推荐
除了一些特殊情况在require中引用加载器外,我们推荐使用第二种方式来使用加载器——不需要解释,在后面的使用中很容易体会到。
下面简单介绍下这三种方式的用法:
require
!
分割实例:
require("./loader!./dir/file.txt");
// => uses the file "loader.js" in the current directory to transform
// "file.txt" in the folder "dir".
require("jade!./template.jade");
// => uses the "jade-loader" (that is installed from npm to "node_modules")
// to transform the file "template.jade"
require("!style!css!less!bootstrap/less/bootstrap.less");
// => the file "bootstrap.less" in the folder "less" in the "bootstrap"
// module (that is installed from github to "node_modules") is
// transformed by the "less-loader". The result is transformed by the
// "css-loader" and then by the "style-loader".
./loader.js
;第二个require使用了npm安装的加载器jade-loader
;第三个加载器使用了加载器的链接特性,首先会使用less-loader
处理源文件,处理后的源文件交给css-loader
处理,最后交给style-loader
——less-loader
转换less源文件为普通的css文件,css-loader
处理css文件中的url(由于webpack构建的代码会合并统一放在一个路径下,所以要处理下css引用的资源,否则在实际运行时,无法正确找到资源的位置),style-loader
会处理css文件,通过js代码将插入到html页面中配置文件
实例:
{
module: {
loaders: [
{ test: /\.jade$/, loader: "jade" },
// => "jade" loader is used for ".jade" files
{ test: /\.css$/, loader: "style!css" },
// => "style" and "css" loader is used for ".css" files
// Alternative syntax:
{ test: /\.css$/, loaders: ["style", "css"] },
]
}
}
webpack --module-bind jade --module-bind 'css=style!css'
加载器提供了一些参数来控制加载器对资源的转换,添加参数的方式类似于url链接中的查询参数。
- require require("url-loader?mimetype=image/png!./file.png");
- 配置
`{ test: /\.png$/, loader: "url-loader?mimetype=image/png" }`
或者
```
{
test: /\.png$/,
loader: "url-loader",
query: { mimetype: "image/png" }
}
```
webpack --module-bind "png=url-loader?mimetype=image/png"
webpack有着丰富的加载器实习,可以参考list-of-loaders来查找你需要的加载器。
略
webpack有丰富的插件接口,使得webpack变得更加灵活。webpack内部集成了一些插件,另外在npm上也有发布一些第三方的插件,当然你可以自己根据webpack的接口,开发属于自己的插件。
webpack本身提供了一些插件供我们使用,如webpack.optimize.DedupePlugin
——查找相等或近似的模块,避免在最终生成的文件中出现重复的模块。下面就以webpack.optimize.DedupePlugin
为例展示webpack内部插件的使用:
webpack.config.js
var webpack = require("webpack");
var path = require("path");
module.exports = {
entry: "./example.js",
output: {
path: path.join(__dirname, "js"),
filename: "output.js"
},
plugins: [
new webpack.optimize.DedupePlugin()
]
}
example.js
var a = require("./a");
var b = require("./b");
a.x !== b.x;
a.y !== b.y;
a/index.js
module.exports = {
x: require("./x"),
y: require("./y"),
z: require("../z")
}
module.exports = {"this is": "x"};
module.exports = {"this is": "y", "but in": "a"};
b/index.js
module.exports = {
x: require("./x"),
y: require("./y"),
z: require("../z")
}
module.exports = {"this is": "x"};
module.exports = {"this is": "y", "but in": "b"};
module.exports = {"this is": "z"};
webpack example.js output.js
,最终的生成文件会有两个模块x。再观察上面的webpack配置文件webpack.config.js,最后增加了plugins: [new webpack.optimize.DedupePlugin()]
,如果使用这个配置运行webpack构建,可以发现最后的生成文件js/ouput.js
里只有个模块x,这个就是插件webpack.optimize.DedupePlugin的作用。更多内建插件的使用请参考LIST OF PLUGINS
除了内建插件外,还有一些webpack插件发布在npm上了,我们可以通过npm命令来安装,例如: npm install --save-dev extract-text-webpack-plugin
——extract-text-webpack-plugin
是用于将某些类型的模块,如css,单独提取到文件的插件,具体如何使用该插件可以参考该项目的主页extract-text-webpack-plugin。
如果想要查找更多的webpack插件,可以在npm上利用webpack-plugin
关键字来搜索,即webpack-plugin。
Plugins-api
webpack-dev-server是一个轻量级的node.js服务器,同webpack,webpack-dev-server也可以分析模块的依赖关系进行打包——webpack命令的所有选项对webpack-dev-server命令都有效。但不同的是,webpack-dev-server不会在本地目录下生成目标文件,而是放在内存里,通过访问服务器相应地址获取。另外,webpack-dev-server自带有一个轻量级的runtime,浏览器可以通过Socket.IO与服务器连接,服务器向客户端浏览器送编译状态信息,然后客户端/浏览器根据这些信息做相应的处理,如:刷新,模块热替换等。
module.exports = {
entry: {
app: ["./app/main.js"]
},
output: {
path: "./build",
publicPath: "/assets/",
filename: "bundle.js"
}
};
document.write("Hello World!")
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Documenttitle>
head>
<body>
<script src="assets/bundle.js">script>
body>
html>
webpack-dev-server --content-base test/
localhost:8080/
,观察运行结果webpack-dev-server --content-base test/
中的--content-base
指定html等静态资源的位置webpack.config.js
中的output.publicPath指定webpack模块打包后在服务器的路径所有的webpack命令选项对webpack-dev-server命令都有效,但是没有默认的output参数。除此之外,webpack-dev-server还有一些额外的选项:
- --content-base
: 可以是文件、目录或者url,表示静态资源的基础路径
- --quiet
: 布尔值,控制是否要在控制台输出所有信息
- --colors
: 为控制台输出信息增加一些颜色
- --no-info
: 禁止输出一些无聊的信息?
- --host
: 主机名或ip地址
- –port: 端口号
- –inline: 布尔值,表示是否要将webpack-dev-server runtime集成到模块打包文件里,可以实现浏览器与服务器的通信
- --hot
: 布尔值,表示增加HotModuleReplacementPlugin插件,且将服务器切换到热模式中。注意点:不要再额外添加HotModuleReplacementPlugin
- --https
: 布尔值,表示webpack-dev-server是否开启HTTPS协议
http://webpack.github.io/docs/webpack-dev-server.html#api
在真实的项目开发中,前端需要与后台服务器交互——传输业务数据,这里通常会有一个后台服务器来向前端提供业务数据接口,我们也是在浏览器中访问这个后台服务器来使用功能的。这样的话,我们就需要将webpack生成的静态资源拷贝到我们项目的后台服务器下。如果是在最后发布的话,这样没有什么问题,但在实际开发中,每次调整代码都需要这么做,开发人员在就放弃使用webpack了。
当然,webpack已经为我们解决了这个问题,我们只需要同时运行后台服务器和webpack-dev-server——后台服务器的HTML页面包含指向webpack-dev-server静态资源的script标签,而webpack-dev-server只是充当前端静态资源的服务器。
示例:
- webpack.config.js
module.exports = {
entry: {
app: ["./app/main.js"]
},
output: {
path: "./build",
publicPath: "http://localhost:9090/",
filename: "bundle.js"
}
};
webpack-dev-server --config webapck.config.js --host localhost --port 9090 --inline
http://localhost:8080
,则在浏览器中访问: http://localhost:8080/index.html
注意点:
- 为了保证总是(为了非入口模块)向webpack-dev-server发送请求来获得静态资源,必须向output.publicPath提供一个完整的URL——如果只是个目录路径的话,非入口模块静态资源的请求会向后台服务器发送,而不是webpack-dev-server
- 为了连接webpacl-dev-server和它的runtime最好在webpack-dev-server命令后加上选项--inline
webpack-dev-middleware
koa-webpack-dev
Todo