基础
安装
首先要安装 Node.js, Node.js 自带了软件包管理器 npm。用 npm 全局安装 Webpack:
$ npm install webpack -g
通常我们会将 Webpack 安装到项目的依赖中,这样就可以使用项目本地版本的 Webpack。
# 进入项目目录,初始化,创建 package.json。
# 若存在 package.json 文件,则不运行。
$ npm init
# 确定已经有 package.json
# 安装 webpack 依赖
$ npm install webpack --save-dev
如果需要使用 Webpack 开发工具,要单独安装:
$ npm install webpack-dev-server --save-dev
使用
首先创建一个静态页面 index.html
和一个 JS 入口文件 entry.js
:
<!-- index.html -->
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
// entry.js
document.write('It works.')
然后编译 entry.js
并打包到 bundle.js
:
$ webpack entry.js bundle.js
用浏览器打开 index.html
将会看到
It works.
最终目录结构如下:
.
├── entry.js
├── index.html
├── package.json
├── node_modules
接下来添加一个模块 module.js
并修改入口 entry.js
:
// module.js
module.exports = 'It works from module.js.'
// entry.js
document.write('It works.')
document.write(require('./module.js')) // 添加模块
重新打包 webpack entry.js bundle.js
后刷新页面看到变化
It works.It works from module.js.
最终目录结构如下:
.
├── bundle.js
├── entry.js
├── index.html
├── module.js
├── package.json
├── node_modules
进阶
使用 Loader
Webpack 本身只能处理 JavaScript 模块,如果要处理其他类型的文件,就需要使用 loader 进行转换。Loader 可以理解为是模块和资源的转换器,它本身是一个函数,接受源文件作为参数,返回转换的结果。这样,我们就可以通过 require 来加载任何类型的模块或文件,比如 CoffeeScript、 JSX、 LESS 或图片。
先来看看 loader 有哪些特性?
-
Loader 可以通过管道方式链式调用,每个 loader 可以把资源转换成任意格式并传递给下一个 loader ,但是最后一个 loader 必须返回 JavaScript。
-
Loader 可以同步或异步执行。
-
Loader 运行在 node.js 环境中,所以可以做任何可能的事情。
-
Loader 可以接受参数,以此来传递配置项给 loader。
-
Loader 可以通过文件扩展名(或正则表达式)绑定给不同类型的文件。
-
Loader 可以通过 npm 发布和安装。
-
除了通过 package.json 的 main 指定,通常的模块也可以导出一个 loader 来使用。
-
Loader 可以访问配置。
-
插件可以让 loader 拥有更多特性。
-
Loader 可以分发出附加的任意文件。
Loader 本身也是运行在 node.js 环境中的 JavaScript 模块,它通常会返回一个函数。大多数情况下,我们通过 npm 来管理 loader,但是你也可以在项目中自己写 loader 模块。
按照惯例,而非必须,loader 一般以 xxx-loader
的方式命名,xxx
代表了这个 loader 要做的转换功能,比如 json-loader
。
除了npm安装模块的时候以外,在任何场景下,loader名字都是可以简写的。例如:安装时必须用全名,即:npm install json-loader
,而在引用 loader 的时候可以使用全名 json-loader
,也可以使用短名 json
。这个命名规则和搜索优先级顺序在 webpack 的 resolveLoader.moduleTemplates
api 中定义。
Default: ["*-webpack-loader", "*-web-loader", "*-loader", "*"]
Loader 可以在 require()
引用模块的时候添加,也可以在 webpack 全局配置中进行绑定,还可以通过命令行的方式使用。
loader是可以串联使用的,也就是说,一个文件可以先经过A-loader再经过B-loader最后再经过C-loader处理。而在经过所有的loader处理之前,webpack会先取到文件内容交给第一个loader。
接上一节的例子,我们要在页面中引入一个 CSS 文件 style.css
,首先将 style.css
也看成是一个模块,然后用 css-loader
来读取处理(路径处理、import处理等),然后经过 style-loader
处理(包装成JS文件,运行的时候直接将样式插入DOM中)。
/* style.css */
body { background: yellow; }
修改 entry.js
:
// entry.js
require("!style!css!./style.css") // 载入 style.css
document.write('It works.')
document.write(require('./module.js'))
安装 loader:
# css-loader:读取 css 文件
# style-loader:将 css 文件插入页面
$ npm install css-loader style-loader --save-dev
重新编译打包,刷新页面,就可以看到黄色的页面背景了。
如果每次 require CSS 文件的时候都要写 loader 前缀,是一件很繁琐的事情。我们可以根据模块类型(扩展名)来自动绑定需要的 loader。
将 entry.js
中的 require("!style!css!./style.css")
修改为 require("./style.css")
,然后执行:
$ webpack entry.js bundle.js --module-bind 'css=style!css'
显然,这两种使用 loader 的方式,效果是一样的。最终的目录结构如下:
.
├── bundle.js
├── entry.js
├── index.html
├── module.js
├── node_modules
├── package.json
├── style.css
loader还可以接受参数,不同的参数可以让loader有不同的行为(前提是loader确实支持不同的行为),具体每个loader支持什么样的参数可以参考loader的文档。loader的使用有三种方法,分别是:
-
在require中显式指定,如:
-
在命令行中指定,如:
$ webpack entry.js output.js --module-bind 'css=style!css'
-
在配置项(webpack.config.js)中指定,如:
第一种显式指定,即在 JS
文件中指定:
require('style!css!./style.css');`
第二种在命令行中指定参数的用法用得较少,可以这样写:
$ webpack --module-bind jade --module-bind 'css=style!css'
使用 --module-bind
指定loader,如果后缀和loader一样,直接写就好了,比如jade表示.jade文件用jade-loader处理,如果不一样,则需要显示指定,如 css=style!css
表示分别使用 css-loader
和 style-loader
处理 .css
文件。
第三种在配置项中指定是最灵活的方式,它的指定方式是这样:
module: {
// loaders是一个数组,每个元素都用来指定loader
loaders: [{
test: /.jade$/, //test值为正则表达式,当文件路径匹配时启用
loader: 'jade', //指定使用什么loader,可以用字符串,也可以用数组
exclude: /regexp/, //可以使用exclude来排除一部分文件
//可以使用query来指定参数,也可以在loader中用和require一样的用法指定参数,如`jade?p1=1`
query: {
p1:'1'
}
},
{
test: /.css$/,
loader: 'style!css' //loader可以和require用法一样串联
},
{
test: /.css$/,
loaders: ['style', 'css'] //也可以用数组指定loader
}]
}
注意: 用数组指定串联loader时,配置文件中要写 loaders
,而非 loader
。
配置文件
Webpack 在执行的时候,除了在命令行传入参数,还可以通过指定的配置文件来执行。默认情况下,会搜索当前目录的 webpack.config.js 文件,这个文件是一个 node.js 模块,返回一个 json 格式的配置信息对象,或者通过 --config 选项来指定配置文件。
继续我们的案例,创建配置文件 webpack.config.js
:
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 加载方式:
require('./style.css')
最后运行 webpack
,可以看到 webpack 通过配置文件执行的结果和上一节通过命令行 webpack entry.js bundle.js --module-bind 'css=style!css'
执行的结果是一样的。
插件
插件可以完成更多 loader 不能完成的功能。
插件的使用一般是在 webpack 的配置信息 plugins 选项中指定。
Webpack 本身内置了一些常用的插件,还可以通过 npm 安装第三方插件。
接下来,我们利用一个最简单的 BannerPlugin 内置插件来实践插件的配置和运行,这个插件的作用是给输出的文件头部添加注释信息。
修改 webpack.config.js,添加 plugins:
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,可以看到文件头部出现了我们指定的注释信息:
/*! This file is created by zhaoda */
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
// 后面代码省略
参数详解
entry
entry参数定义了打包后的入口文件,可以是个字符串或数组或者是对象;如果是数组,数组中的所有文件会打包生成一个filename文件;如果是对象,可以将不同的文件构建成不同的文件:
{
entry: {
page1: "./page1",
//支持数组形式,将加载数组中的所有模块,但以最后一个模块作为输出
page2: ["./entry1", "./entry2"],
// 如果想保持目录结构,则直接按照目录结构命名
'subapp1/page': './app/subapp1/page.js',
'subapp2/page': './app/subapp2/page.js',
},
output: {
path: "dist/js/page",
publicPath: "/output/",
filename: "[name].bundle.js"
}
}
该段代码最终会在 ./dist/js/page
文件夹下生成如下结构:
│ page1.bundle.js
│ page2.bundle.js
│
├─subapp1
│ page.bundle.js
│
└─ssubapp2
page.bundle.js
保持目录结构命名的方式,在构架大型应用中非常有用。
output
output参数是个对象,定义了输出文件的位置及名字:
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: 'http://localhost:3000/static/',
filename: "js/[name].bundle.js"
}
-
path
: 打包文件存放的绝对路径 -
publicPath
: 网站运行时的访问路径 -
filename
:打包后的文件名
当我们在entry
中定义构建多个文件时,filename
可以对应的更改为[name].js
用于定义不同文件构建后的名字。
如下 'http://localhost:3000/static/'
一般我们做调试时的路径,如果我们要在网页中引用 js 文件,html 文件中的路径写为:http://localhost:3000/static/js/<name>.bundle.js
,
即 <publicPath>
+<filename>
。
module
在webpack中JavaScript,CSS,LESS,TypeScript,JSX,CoffeeScript,图片等静态文件都是模块,不同模块的加载是通过模块加载器(webpack-loader)来统一管理的。loaders之间是可以串联的,一个加载器的输出可以作为下一个加载器的输入,最终返回到JavaScript上:
module: {
//加载器配置
loaders: [
//.css 文件使用 style-loader 和 css-loader 来处理
{ test: /.css$/, loader: 'style-loader!css-loader' },
//.js 文件使用 jsx-loader 来编译处理
{ test: /.js$/, loader: 'jsx-loader?harmony' },
//.scss 文件使用 style-loader、css-loader 和 sass-loader 来编译处理
{ test: /.scss$/, loader: 'style!css!sass?sourceMap'},
//图片文件使用 url-loader 来处理,小于8kb的直接转为base64
{ test: /.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
}
字段 | 说明 |
---|---|
test |
表示匹配的资源类型 |
loader 或 loaders |
表示用来加载这种类型的资源的loader |
! |
定义loader的串联关系,多个loader之间用“!”连接起来 |
此外,还可以添加用来定义png、jpg这样的图片资源在小于10k时自动处理为base64图片的加载器:
{ test: /.(png|jpg)$/,loader: 'url-loader?limit=10000'}
给css和less还有图片添加了loader之后,我们不仅可以像在node中那样 require()
js文件了,我们还可以 require()
css、less甚至图片文件:
require('./bootstrap.css');
require('./myapp.less');
var img = document.createElement('img');
img.src = require('./glyph.png');
注意,require()
还支持在资源path前面指定loader,即 require(![loaders list]![source path])
形式:
require("!style!css!less!bootstrap/less/bootstrap.less");
// “bootstrap.less”这个资源会先被"less-loader"处理,
// 其结果又会被"css-loader"处理,接着是"style-loader"
// 可类比pipe操作
require()
时指定的loader会覆盖配置文件里对应的loader配置项。
resolve
webpack在构建包的时候会按目录的进行文件的查找,resolve
属性中的 extensions
数组中用于配置程序可以自行补全哪些文件后缀:
resolve: {
//查找module的话从这里开始查找
root: '/pomy/github/flux-example/src', //绝对路径
//自动扩展文件后缀名,意味着我们require模块可以省略不写后缀名
extensions: ['', '.js', '.json', '.scss'],
//模块别名定义,方便后续直接引用别名,无须多写长长的地址
alias: {
AppStore : 'js/stores/AppStores.js',//后续直接 require('AppStore') 即可
ActionType : 'js/actions/ActionType.js',
AppAction : 'js/actions/AppAction.js'
}
}
然后我们想要加载一个js文件时,只要 require('common')
就可以加载 common.js
文件了。
注意一下, extensions
第一个是空字符串! 对应不需要后缀的情况.
plugin
webpack提供了[丰富的组件]用来满足不同的需求,当然我们也可以自行实现一个组件来满足自己的需求:
plugins: [
//your plugins list
]
在webpack中编写js文件时,可以通过require的方式引入其他的静态资源,可通过loader对文件自动解析并打包文件。通常会将js文件打包合并,css文件会在页面的header中嵌入style的方式载入页面。但开发过程中我们并不想将样式打在脚本中,最好可以独立生成css文件,以外链的形式加载。这时 extract-text-webpack-plugin
插件可以帮我们达到想要的效果。需要使用npm的方式加载插件,然后参见下面的配置,就可以将js中的css文件提取,并以指定的文件名来进行加载。
npm install extract-text-webpack-plugin –-save-dev
plugins: [
new ExtractTextPlugin('styles.css')
]
externals
当我们想在项目中require一些其他的类库或者API,而又不想让这些类库的源码被构建到运行时文件中,这在实际开发中很有必要。此时我们就可以通过配置externals参数来解决这个问题:
externals: {
"jquery": "jQuery"
}
这样我们就可以放心的在项目中使用这些API了:var jQuery = require(“jquery”)
;
context
当我们在require一个模块的时候,如果在require中包含变量,像这样:
require("./mods/" + name + ".js");
那么在编译的时候我们是不能知道具体的模块的。但这个时候,webpack也会为我们做些分析工作:
1.分析目录:’./mods’; 2.提取正则表达式:’/^.*.js$/’;
于是这个时候为了更好地配合wenpack进行编译,我们可以给它指明路径,像在cake-webpack-config中所做的那样(我们在这里先忽略abcoption的作用):
var currentBase = process.cwd();
var context = abcOptions.options.context ? abcOptions.options.context :
path.isAbsolute(entryDir) ? entryDir : path.join(currentBase, entryDir);
关于 webpack.config.js 更详尽的配置可以参考 Webpack Configuration。
开发环境
当项目逐渐变大,webpack 的编译时间会变长,可以通过参数让编译的输出内容带有进度和颜色。
$ webpack --progress --colors
如果不想每次修改模块后都重新编译,那么可以启动监听模式。开启监听模式后,没有变化的模块会在编译后缓存到内存中,而不会每次都被重新编译,所以监听模式的整体速度是很快的。
$ webpack --progress --colors --watch
当然,使用 webpack-dev-server
开发服务是一个更好的选择。它将在 localhost:8080 启动一个 express 静态资源 web 服务器,并且会以监听模式自动运行 webpack,在浏览器打开 http://localhost:8080/ 或 http://localhost:8080/webpack... 可以浏览项目中的页面和编译后的资源输出,并且通过一个 socket.io 服务实时监听它们的变化并自动刷新页面。
# 安装
$ npm install webpack-dev-server -g
# 运行
$ webpack-dev-server --progress --colors
React 开发环境的配置
Webpack相关:
$ npm install webpack -g
$ npm install webpack-dev-server -g
# 安装必要的 loader:
## 编译 JSX
$ npm install --save-dev babel-loader
## CSS 文件处理
$ npm install --save-dev css-loader style-loader
## React
$ npm install --save-dev react-hot-loader
Babel 相关:
$ npm install --save-dev babel-core
# 添加 ES6 支持
$ npm install --save-dev babel-preset-es2015
$ npm install --save-dev babel-react
var webpack = require('webpack');
module.exports = {
entry: [
'webpack/hot/only-dev-server',
"./js/app.js"
],
output: {
path: './build',
filename: "bundle.js"
},
module: {
loaders: [
{ test: /.js?$/, loaders: ['react-hot', 'babel'], exclude: /node_modules/ },
{ test: /.js$/, exclude: /node_modules/, loader: 'babel-loader'},
{ test: /.css$/, loader: "style!css" }
]
},
resolve:{
extensions:['','.js','.json']
},
plugins: [
new webpack.NoErrorsPlugin()
]
};
参考资料:
webpack-dev-server
webpack-dev-server 是一个基于 Node.js Express 框架的开发服务器,它是一个静态资源 Web 服务器,对于简单静态页面或者仅依赖于独立服务的前端页面,都可以直接使用这个开发服务器进行开发。在开发过程中,开发服务器会监听每一个文件的变化,进行实时打包,并且可以推送通知前端页面代码发生了变化,从而可以实现页面的自动刷新。
简单来说,webpack-dev-server就是一个小型的静态文件服务器。使用它,可以为webpack打包生成的资源文件提供Web服务。
webpack-dev-server有两种模式支持自动刷新——iframe模式和inline模式。
iframe模式
在iframe模式下:页面是嵌套在一个iframe下的,在代码发生改动的时候,这个iframe会重新加载。使用iframe模式无需额外的配置,只需在浏览器输入:
http://localhost:8080/webpack-dev-server/index.html
inline模式
在inline模式下:一个小型的webpack-dev-server客户端会作为入口文件打包,这个客户端会在后端代码改变的时候刷新页面。使用inline模式有两种方式:命令行方式和Node.js API。
命令行方式比较简单,只需加入--line选项即可。例如:
webpack-dev-server --inline
使用--inline
选项会自动把webpack-dev-server客户端加到webpack的入口文件配置中。
注意:默认配置文件名称为:webpack.config.js
,若要更改需要在命令行中指明。例如,
webpack-dev-server --inline --config webpack.config.dev.js。
若用Node.js API方式,因为webpack-dev-server没有inline:true这个配置项,所以需要手动把
webpack-dev-server/client?http://localhost:8080
加到配置文件的入口文件配置处。
模块热替换
webpac-dev-server 支持 Hot Module Replacement,即模块热替换,在前端代码变动的时候无需整个刷新页面,只把变化的部分替换掉。使用HMR功能也有两种方式:命令行方式和Node.js API。
命令行方式同样比较简单,只需加入--line --hot
选项。--hot
这个选项干了一件事情,它把webpack/hot/dev-server
入口点加入到了webpack配置文件中。这时访问浏览器,你会看见控制台的log信息:
[HMR] Waiting for update signal from WDS...
[WDS] Hot Module Replacement enabled.
HMR前缀
的信息由webpack/hot/dev-server模块产生,WDS前缀
的信息由webpack-dev-server客户端产生。
Node.js API方式需要做三个配置:
-
把
webpack/hot/dev-server
加入到webpack配置文件的entry
项; -
把
new webpack.HotModuleReplacementPlugin()
加入到webpack配置文件的plugins
项; -
把
hot:true
加入到 Webpack 配置文件的webpack-dev-server
的配置项里面。
devServer:{
hot:true
}
注意:要使HMR功能生效,还需要做一件事情,就是要在应用热替换的模块或者根模块里面加入允许热替换的代码。否则,热替换不会生效,还是会重刷整个页面。
if(module.hot)
module.hot.accept();
也可以使用一些插件去完成这个工作,例如webpack-module-hot-accept插件。不过,webpack-dev-server HMR结合react-hot-loader使用的时候,react-hot-loader会去做这个工作。综合上述,使用wepack-dev-server辅助开发,使得开发者在开发前端代码的过程中无需频繁手动刷新页面,使用HMR甚至不用等待页面刷新,确实可以给开发者带来很好的体验。
但是,问题又来了。我要进行前后端联调的时候怎么办呢?毕竟webpack-dev-server只是一个静态文件服务器,不具备动态处理的能力。这个时候就需要将后端服务器与webpack-dev-server结合使用了。webpack-dev-server只用来为webpack打包生成的资源文件提供服务,比如js文件、图片文件、css文件等;后端服务器除提供API接口外,还提供入口HTML。
要将webpack-dev-server与后端服务器结合使用,需要做三件事情。
第一 首页HTML文件是从后端服务器发出的,这时页面的根地址变成了后端服务器地址,怎么使得webpack产生的资源文件在请求资源的时候是向web-dev-server请求而不是后端服务器请求?只需在webpack配置文件中的 output.publicPath
配置项写上绝对URL地址,例如output.publicPath = "http://localhost:8080/assets/"
。这时,webpack打包产生的资源文件里面的url地址都会是绝对地址,而不是相对地址。
第二 后端服务器产生的入口HTML文件要向webpack-dev-server请求资源文件,这个简单,只需在HTML文件中加入资源文件的绝对地址,例如:<script src="http://localhost:8080/assets/bundle.js">
第三 要使webpack-dev-server和它的运行时程序连接起来。这个简单,只需要使用iline模式即可。
提取公共代码与压缩
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
... ...
// plugins 项配置中增加
plugins: [
... ...
// 提取公共代码
commonsPlugin,
//压缩
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
}
参见:
WEBPACK DEV SERVER
webpack-dev-server 官方文档
前端模块加载工具——webpack(二)