书籍完整目录javascript
这一节中咱们将系统的讲解 webpack ,包括:css
webpack 介绍html
webpack 是什么前端
为何引入新的打包工具java
webpack 核心思想node
webpack 安装react
webpack 使用webpack
命令行调用web
配置文件ajax
webpack 配置参数
entry 和 output
单一入口
多个入口
多个打包目标
webpack 支持 Jsx 和 Es6
webpack loaders
loader 定义
loader 功能
loader 配置
使用 loader
webpack 开发环境与生产环境
webpack 分割 vendor 代码和应用业务代码
webpack develop server
安装 webpack-dev-server
启动 webpack-dev-server
代码监控
自动刷新
热加载 (hot module replacement)
在 webpack.config.js 中配置 webpack develop server
webpack is a module bundler. webpack takes modules with dependencies and generates static assets representing those modules
webpack 是一个模块打包工具,输入为包含依赖关系的模块集,输出为打包合并的前端静态资源。在上一节的前端工程化中,已经介绍过,webpack 是同时支持 AMD 和 CommonJs 的模块定义方式,不只如此,webpack 能够将任何前端资源视为模块,如 css,图片,文本。
在 webpack 出现以前,已经有了一些打包工具,如 Browserify, 那为何不优化这些工具,而是重复造轮子?
webpack 以前的打包工具工具功能单一,只能完成特定的任务,然而 web 前端工程是复杂的,一个 webapp 对于业务代码的要求可能有:
代码能够分块,实现按需加载
首屏加载时间要尽可能减小
须要集成一些第三方库
对于模块打包工具,单一的支持 CommonJs 的打包在大型项目中是不够用的,为了知足一个大型项目的前端需求,那么一个打包工具应该包含一些这些功能:
支持多个 bundler 输出 -> 解决代码分块问题
异步加载 -> 按需加载,优化首屏加载时间
可定制化 -> 能够集成第三方库,能够定制化打包过程
其余资源也能够定义为模块
webpack 的出现正式为了解决这些问题,在 webpack 中,提供了一下这些功能:
代码分块: webpack 有两种类型的模块依赖,一种是同步的,一种是异步的。在打包的过程当中能够将代码输出为代码块(chunk),代码块能够实现按需加载。 异步加载的代码块经过分割点(spliting point)来肯定。
Loaders: Webpack 自己只会处理 Javascript,为了实现将其余资源也定义为模块,并转化为 Javascript, Webpack 定义 loaders , 不一样的 loader 能够将对应的资源转化为 Javascript 模块。
智能的模块解析: webpack 能够很容易将第三方库转化为模块集成到项目代码中,模块的依赖能够用表达式的方式(这在其余打包工具中是没有支持的),这种模块依赖叫作动态模块依赖。
插件系统: webpack 的可定制化在于其插件系统,其自己的不少功能也是经过插件的方式实现,插件系统造成了 webpack 的生态,是的可使用不少开源的第三方插件。
webpack 的三个核心:
万物皆模块:在 webpack 的世界中,除了 Javascript,其余任何资源均可以当作模块的方式引用
按需加载: webapp 的优化关键在于代码体积,当应用体积增大,实现代码的按需加载是刚需,这也是 webpack 出现的根本缘由
可定制化: 任何一个工具都不可能解决全部问题,提供解决方案才是最可行的,webpack 基于可定制化的理念构建,经过插件系统,配置文件,能够实现大型项目的定制需求。
webpack 是 Node 实现,首先须要到 http://nodejs.org/ 下载安装最新版本的 Node.js
Node.js 安装好事后,打开命令行终端,经过 npm 命令安装:
// -g 参数表示全局安装 $ npm install webpack -g
为了使用 webpack,先新建一个空前端项目,建立一个目录,目录结构以下:
. ├── index.html // 入口 HTML ├── dist // dist 目录放置编译事后的文件文件 └── src // src 目录放置源文件 └── index.js // 入口 js
其中 html 内容:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Hello React!</title> </head> <body> <div id="AppRoot"></div> <script src="dist/index.js"></script> </body> </html>
index.js 内容为:
alert('hello world webpack');
// 初始化 package.json, 根据提示填写 package.json 的相关信息 $ npm init // 下载 webpack 依赖 // --save-dev 表示将依赖添加到 package.json 中的 'devDependencies' 对象中 $ npm install webpack --save-dev
dev server 能够实现一个基于 node + express 的前端 server
$ npm install webpack-dev-server --save-dev
在以前建立的目录下执行:
$ webpack src/index.js dist/index.js
执行成功事后会出现以下信息:
Hash: 9a8e7e83864a07c0842f Version: webpack 1.13.1 Time: 37ms Asset Size Chunks Chunk Names index.js 1.42 kB 0 [emitted] main [0] ./src/index.js 29 bytes {0} [built]
能够查看 dist/index.js 的编译结果:
/******/ (function(modules) { // webpackBootstrap // .......... UMD 定义内容 /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ function(module, exports) { // index.js 的内容被打包进来 alert('hello world webpack'); /***/ } /******/ ]);
在浏览器中打开 index.html :
以命令执行的方式须要填写很长的参数,因此 webpack 提供了经过配置的方式执行,在项目目录下建立 webpack.config.js 以下:
var webpack = require('webpack') module.exports = { entry: './src/index.js', output: { path: './dist/', filename: 'index.js' } }
执行:
$ webpack
会和经过命令执行有一样的输出
webpack 的配置中主要的两个配置 key 是,entry 和 output。
{ entry: [String | Array | Object], // 入口模块 output: { path: String, // 输出路径 filename: String // 输出名称或名称 pattern publicPath: String // 指定静态资源的位置 ... // 其余配置 } }
若是只有一个入口文件,能够有以下几种配置方式
// 第一种 String { entry: './src/index.js', output: { path: './dist/', filename: 'index.js' } } // 第二种 Array { entry: ['./src/index.js'], output: { path: './dist/', filename: 'index.js' } } // 第三种 Object { entry: { index: './src/index.js', }, output: { path: './dist/', filename: 'index.js' } }
当存在多个入口时 ,可使用 Array 的方式,好比依赖第三方库 bootstrap ,最终 bootstrap 会被追加到打包好的 index.js 中,数组中的最后一个会被 export。
{ entry: ['./src/index.js', './vendor/bootstrap.min.js'], output: { path: './dist', filename: "index.js" } }
最终的输出结果如:
/******/ ([ /* 0 */ /***/ function(module, exports, __webpack_require__) { __webpack_require__(1); // export 最后一个 module.exports = __webpack_require__(2); /***/ }, /* 1 */ /***/ function(module, exports) { alert('hello world webpack'); /***/ }, /* 2 */ /***/ function(module, exports) { // bootstrap 的内容被追加到模块中 console.log('bootstrap file'); /***/ } /******/ ])
上面的例子中都是打包出一个 index.js 文件,若是项目有多个页面,那么须要打包出多个文件,webpack 能够用对象的方式配置多个打包文件
{ entry: { index: './src/index.js', a: './src/a.js' }, output: { path: './dist/', filename: '[name].js' } }
最终会打包出:
. ├── a.js └── index.js
文件名称 pattern
[name]
entry 对应的名称
[hash]
webpack 命令执行结果显示的 Hash 值
[chunkhash]
chunk 的 hash
为了让编译的结果名称是惟一的,能够利用 hash 。
如今咱们已经可使用 webpack 来打包基于 CommonJs 的 Javascript 模块了,可是还无法解析 JSX 语法和 Es6 语法。下面咱们将利用 Babel 让 webpack 可以解析 Es6 和 Babel
// babel 相关的模块 $ npm install babel-loader babel-preset-es2015 babel-preset-stage-0 babel-preset-react babel-polyfill --save-dev // react 相关的模块 $ npm install react react-dom --save
{ entry: { index: './src/index.js', a: './src/a.js' }, output: { path: './dist/', filename: '[name].js' }, module: { loaders: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel', query: { presets: ['es2015', 'stage-0', 'react'] } }] } }
src/index.js 内容改成:
Es6 的知识在后面的章节中讲解,目前咱们暂时以 Es5 的方式来写,可是配置已经支持了 Es6 的编译,熟悉 Es6 的读者也能够直接写 Es6
// 经过 require 的方式依赖 React,ReactDOM var React = require('react'); var ReactDOM = require('react-dom'); var Hello = React.createClass({ render: function render() { return <div>Hello {this.props.name}</div>; } }); ReactDOM.render( <Hello name="World" />, document.getElementById('AppRoot') );
$ webpack
执行结果:
Hash: ae2a037c191c18195b6a Version: webpack 1.13.1 Time: 1016ms Asset Size Chunks Chunk Names a.js 1.42 kB 0 [emitted] a index.js 700 kB 1 [emitted] index + 169 hidden modules
浏览器中打开 index.html 会显示 Hello World
在配置 JSX 的过程当中,使用到了 loader, 前面已经介绍过 webpack 的核心功能包含 loader,经过 loader 能够将任意资源转化为 javascript 模块。
Loaders are transformations that are applied on a resource file of your app.
(Loaders 是应用中源码文件的编译转换器)
也就是说在 webpack 中,经过 loader 能够实现 JSX 、Es六、CoffeeScript 等的转换,
loader 管道:在同一种类型的源文件上,能够同时执行多个 loader , loader 的执行方式能够相似管道的方式,管道执行的方向为从右到左。
loader 能够支持同步和异步
loader 能够接收配置参数
loader 能够经过正则表达式或者文件后缀指定特定类型的源文件
插件能够提供给 loader 更多功能
loader 除了作文件转换之外,还能够建立额外的文件
新增 loader 能够在 webpack.config.js 的 module.loaders
数组中新增一个 loader 配置。
一个 loader 的配置为:
{ // 经过扩展名称和正则表达式来匹配资源文件 test: String , // 匹配到的资源会应用 loader, loader 能够为 string 也能够为数组 loader: String | Array }
感叹号和数组能够定义 loader 管道:
{ module: { loaders: [ { test: /\.jade$/, loader: "jade" }, // => .jade 文件应用 "jade" loader { test: /\.css$/, loader: "style!css" }, { test: /\.css$/, loaders: ["style", "css"] }, // => .css 文件应用 "style" 和 "css" loader ] } }
loader 能够配置参数
{ module: { loaders: [ // => url-loader 配置 mimetype=image/png 参数 { test: /\.png$/, loader: "url-loader?mimetype=image/png" }, { test: /\.png$/, loader: "url-loader", query: { mimetype: "image/png" } } ] } }
loader 和 webpack 同样都是 Node.js 实现,发布到 npm 当中,须要使用 loader 的时候,只须要
$ npm install xx-loader --save-dev // eg css loader $ npm install css-loader style-loader --save-dev
{ entry: { index: './src/index.js', a: './src/a.js' }, output: { path: './dist/', filename: '[name].js' }, module: { loaders: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel', query: { presets: ['es2015', 'stage-0', 'react'] } }, { test: /\.css$/, loader: "style-loader!css-loader" }] } }
前面咱们已经使用过 jsx loader 了, loader 的使用方式有多种
在配置文件中配置
显示的经过 require 调用
命令行调用
显示的调用 require 会增长模块的耦合度,应尽可能避免这种方式
以 css-loader 为例子,在项目 src 下面建立一个 css
src/style.css
body { background: red; color: white; }
修改 webpack 配置 entry 添加
entry: { index: ['./src/index.js', './src/style.css'] }
执行 webpack 命令而后打开 index.html 会看到页面背景被改成红色。
最终的编译结果为:
.... function(module, exports, __webpack_require__) { exports = module.exports = __webpack_require__(171)(); exports.push([module.id, "\nbody {\n background: red;\n color: white;\n}\n", ""]); } ....
能够看到 css 被转化为了 javascript, 在页面中并不是调用 <link rel="stylesheet" href="">
的方式, 而是使用 inline 的 <style>.....</style>
另一种方法是直接 require, 修改 src/index.js:
var css = require("css!./style.css");
编译结果相同。
前端开发环境一般分为两种,开发环境和生成环境,在开发环境中,可能咱们须要日志输出,sourcemap ,错误报告等功能,在生成环境中,须要作代码压缩,hash 值生成。两种环境在其余的一些配置上也可能不一样。
因此为了区分,咱们能够建立两个文件:
webpack.config.js // 开发环境
webpack.config.prod.js // 生产环境
生产环境 build 用以下命令:
webpack --config webpack.config.prod.js
在本章深刻 webpack 小节中会更多的介绍生产环境中的优化
webpack 提供插件机制,能够对每次 build 的结果进行处理。配置 plugin 的方法为在 webpack.config.js 中添加:
{ plugins: [ new BellOnBundlerErrorPlugin() ] }
plugin 也是一个 npm 模块,安装一个 plugin :
$ npm install bell-on-bundler-error-plugin --save-dev
在上面的 jsx 配置中,咱们将 React 和 ReactDOM 一块儿打包进了项目代码。为了实现业务代码和第三方代码的分离,咱们能够利用
CommonsChunkPlugin 插件.
修改 webpack.config.js
{ entry: { index: './src/index.js', a: './src/a.js', // 第三方包 vendor: [ 'react', 'react-dom' ] }, output: { path: './dist/', filename: '[name].js' }, module: { loaders: [{ test: /\.js$/, exclude: /node_modules/, loader: 'babel', query: { presets: ['es2015', 'stage-0', 'react'] } }, { test: /\.css$/, loader: "style-loader!css-loader" }] }, plugins: [ new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.js") ] }
执行 webpack 命令,输出日志:
Hash: f1256dc00b9d4bde8f7f Version: webpack 1.13.1 Time: 1459ms Asset Size Chunks Chunk Names a.js 109 bytes 0 [emitted] a index.js 10.9 kB 1 [emitted] index vendor.bundle.js 702 kB 2 [emitted] vendor [0] multi vendor 40 bytes {2} [built] [0] multi index 40 bytes {1} [built] + 173 hidden modules
index.js 体积变小了,多出了 vendor.bundle.js
在前端开发的过程当中,一般须要启动一个服务器,把开发打包好的前端代码放在服务器上,经过访问服务器访问并测试(由于能够有些状况须要 ajax 请求)。 webpack 提供了一个机遇 node.js Express 的服务器 - webpack-dev-server 来帮助咱们简化服务器的搭建,并提供服务器资源访问的一些简单配置。
$ npm install webpack-dev-server -g
$ webpack-dev-server --content-base ./
--content-base ./
参数表示将当前目录做为 server 根目录。 命令启动事后,会在 8080 端口启动一个 http 服务,经过访问 http://localhost:8080/index.html
能够访问 index.html 内容。
若是访问提示报错:
Uncaught ReferenceError: webpackJsonp is not defined
缘由是 html 中没有引用 vendor.bundle.js
, 修改 html :
<!-- vendor 必须先于 index.js --> <script src="dist/vendor.bundle.js"></script> <script src="dist/index.js"></script>
修改 index.html 事后能够看到正确结果
webpack-dev-server 除了提供 server 服务之外, 还会监控源文件的修改,若是源文件改变了,会调用 webpack 从新打包
修改 style.css 中的内容为:
body { background: whitesmoke; color: #333; font-size: 100px; }
能够看到输出如下日志:
[168] ./~/react/lib/renderSubtreeIntoContainer.js 466 bytes {2} [built] webpack: bundle is now VALID. webpack: bundle is now INVALID. Hash: cc7d7720b1a0fcbef972 Version: webpack 1.13.0 Time: 76ms chunk {0} a.js (a) 32 bytes {2} + 1 hidden modules chunk {1} index.js (index) 10.3 kB {2} [170] ./~/css-loader!./src/style.css 230 bytes {1} [built] + 5 hidden modules chunk {2} vendor.bundle.js (vendor) 665 kB + 168 hidden modules webpack: bundle is now VALID.
这个时候说明代码已经修改了,可是这个时候刷新浏览器事后,背景是没有改变的,缘由是 webpack-dev-server 的打包结果是放在内存的,查看 dist/index.js 的内容其实是没有改变的,那如何访问内存中的打包内容呢?
修改 webpack.config.js 的 output.publicPath:
output: { path: './dist/', filename: '[name].js', publicPath: '/dist' // webpack-dev-server 启动目录是 `/`, `/dist` 目录是打包的目标目录相对于启动目录的路径 },
从新启动
$ ctrl + c 结束进程 $ webpack-dev-server
修改 style.css 再刷新页面,修改的内容会反映出来。
上面的配置已经能作到自动监控代码,每次修改完代码,刷新浏览器就能够看到最新结果,可是 webpack-dev-server 还提供了自动刷新功能,有两种模式。
Iframe 模式
修改访问的路径: http://localhost:8080/index.html
-> http://localhost:8080/webpack-dev-server/index.html
。这个时候每次修改代码,打包完成事后都会自动刷新页面。
不须要额外配置,只用修改路径
应用被嵌入了一个 iframe 内部,页面顶部能够展现打包进度信息
由于 iframe 的关系,若是应用有多个页面,没法看到当前应用的 url 信息
inline 模式
启动 webpack-dev-server 的时候添加 --inline
参数
须要添加 --inline
配置参数
没有顶部信息提示条,提示信息在控制台中展示
webpack-dev-server 还提供了模块热加载的方式,在不刷新浏览器的条件下,应用最新的代码更新,启动 webpack-dev-server 的时候添加 --inline --hot
参数就能够体验。
$ webpack-dev-server --inline --hot
修改代码在浏览器控制台中会看到这样的日志输出:
[HMR] Waiting for update signal from WDS... vendor.bundle.js:670 [WDS] Hot Module Replacement enabled. 2vendor.bundle.js:673 [WDS] App updated. Recompiling... vendor.bundle.js:738 [WDS] App hot update... vendor.bundle.js:8152 [HMR] Checking for updates on the server... vendor.bundle.js:8186 [HMR] Updated modules: vendor.bundle.js:8188 [HMR] - 245 vendor.bundle.js:8138 [HMR] App is up to date.
修改 webpack.config.js 添加:
plugins: [ new webpack.optimize.CommonsChunkPlugin( /* chunkName= */"vendor", /* filename= */"vendor.bundle.js", Infinity), // 须要手动添加 HotModuleReplacementPlugin , 命令行的方式会自动添加 new webpack.HotModuleReplacementPlugin() ], devServer: { hot: true, inline: true }
不加参数直接执行 webpack-dev-server
$ webpack-dev-server
webpack-dev-server 还提供了其余的一些功能, 如:
配置 proxy
访问 node.js API
和现有的 node 服务集成
基于这些功能能够实现不少自定义的功能。