書籍完整目錄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 服務集成
基於這些功能能夠實現不少自定義的功能。