精益 React 學習指南 (Lean React)- 2.2 webpack

書籍完整目錄javascript

2.2 webpack

圖片描述

這一節中咱們將系統的講解 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

2.2.1 webpack 介紹

webpack 是什麼

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 對於業務代碼的要求可能有:

  1. 代碼能夠分塊,實現按需加載

  2. 首屏加載時間要儘可能減小

  3. 須要集成一些第三方庫

對於模塊打包工具,單一的支持 CommonJs 的打包在大型項目中是不夠用的,爲了知足一個大型項目的前端需求,那麼一個打包工具應該包含一些這些功能:

  1. 支持多個 bundler 輸出 -> 解決代碼分塊問題

  2. 異步加載 -> 按需加載,優化首屏加載時間

  3. 可定製化 -> 能夠集成第三方庫,能夠定製化打包過程

  4. 其餘資源也能夠定義爲模塊

webpack 的出現正式爲了解決這些問題,在 webpack 中,提供了一下這些功能:

  1. 代碼分塊: webpack 有兩種類型的模塊依賴,一種是同步的,一種是異步的。在打包的過程當中能夠將代碼輸出爲代碼塊(chunk),代碼塊能夠實現按需加載。 異步加載的代碼塊經過分割點(spliting point)來肯定。

  2. Loaders: Webpack 自己只會處理 Javascript,爲了實現將其餘資源也定義爲模塊,並轉化爲 Javascript, Webpack 定義 loaders , 不一樣的 loader 能夠將對應的資源轉化爲 Javascript 模塊。

  3. 智能的模塊解析: webpack 能夠很容易將第三方庫轉化爲模塊集成到項目代碼中,模塊的依賴能夠用表達式的方式(這在其餘打包工具中是沒有支持的),這種模塊依賴叫作動態模塊依賴。

  4. 插件系統: webpack 的可定製化在於其插件系統,其自己的不少功能也是經過插件的方式實現,插件系統造成了 webpack 的生態,是的可使用不少開源的第三方插件。

webpack 核心思想

webpack 的三個核心:

  1. 萬物皆模塊:在 webpack 的世界中,除了 Javascript,其餘任何資源均可以當作模塊的方式引用

  2. 按需加載: webapp 的優化關鍵在於代碼體積,當應用體積增大,實現代碼的按需加載是剛需,這也是 webpack 出現的根本緣由

  3. 可定製化: 任何一個工具都不可能解決全部問題,提供解決方案纔是最可行的,webpack 基於可定製化的理念構建,經過插件系統,配置文件,能夠實現大型項目的定製需求。

2.2.2 安裝配置

第一步:Node.js

webpack 是 Node 實現,首先須要到 http://nodejs.org/ 下載安裝最新版本的 Node.js

第二步:webpack-cli

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');

第四步:在項目中安裝 webpack

// 初始化 package.json,  根據提示填寫 package.json 的相關信息
$ npm init

// 下載 webpack 依賴 
// --save-dev 表示將依賴添加到 package.json 中的 'devDependencies' 對象中
$  npm install webpack --save-dev

* 第五步:Develop Server 工具 (可選)

dev server 能夠實現一個基於 node + express 的前端 server

$ npm install webpack-dev-server --save-dev

2.2.3 webpack 使用

命令行調用

在以前建立的目錄下執行:

$ 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

會和經過命令執行有一樣的輸出

2.2.4 webpack 配置

entry 和 output

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 。

2.2.5 webpack 支持 Jsx

如今咱們已經可使用 webpack 來打包基於 CommonJs 的 Javascript 模塊了,可是還無法解析 JSX 語法和 Es6 語法。下面咱們將利用 Babel 讓 webpack 可以解析 Es6 和 Babel

第一步:npm install 依賴模塊

// 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

第二步:webpack.config.js 中添加 babel loader 配置

{
    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']
            }
        }]
    }
}

第三步: 修改 index.js 爲 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

$ 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

2.2.6 webpack loaders

在配置 JSX 的過程當中,使用到了 loader, 前面已經介紹過 webpack 的核心功能包含 loader,經過 loader 能夠將任意資源轉化爲 javascript 模塊。

loader 定義

Loaders are transformations that are applied on a resource file of your app.
(Loaders 是應用中源碼文件的編譯轉換器)

clipboard.png

也就是說在 webpack 中,經過 loader 能夠實現 JSX 、Es六、CoffeeScript 等的轉換,

loader 功能

  1. loader 管道:在同一種類型的源文件上,能夠同時執行多個 loader , loader 的執行方式能夠相似管道的方式,管道執行的方向爲從右到左。

  2. loader 能夠支持同步和異步

  3. loader 能夠接收配置參數

  4. loader 能夠經過正則表達式或者文件後綴指定特定類型的源文件

  5. 插件能夠提供給 loader 更多功能

  6. 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

第一步: 安裝

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 的使用方式有多種

  1. 在配置文件中配置

  2. 顯示的經過 require 調用

  3. 命令行調用

顯示的調用 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");

編譯結果相同。

2.2.7 webpack 開發環境與生產環境

前端開發環境一般分爲兩種,開發環境和生成環境,在開發環境中,可能咱們須要日誌輸出,sourcemap ,錯誤報告等功能,在生成環境中,須要作代碼壓縮,hash 值生成。兩種環境在其餘的一些配置上也可能不一樣。

因此爲了區分,咱們能夠建立兩個文件:

  • webpack.config.js // 開發環境

  • webpack.config.prod.js // 生產環境

生產環境 build 用以下命令:

webpack --config webpack.config.prod.js

在本章深刻 webpack 小節中會更多的介紹生產環境中的優化

2.2.8 webpack 插件

webpack 提供插件機制,能夠對每次 build 的結果進行處理。配置 plugin 的方法爲在 webpack.config.js 中添加:

{
  plugins: [
   new BellOnBundlerErrorPlugin()
  ]
}

plugin 也是一個 npm 模塊,安裝一個 plugin :

$ npm install bell-on-bundler-error-plugin --save-dev

2.2.9 webpack 分割 vendor 代碼和應用業務代碼

在上面的 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

2.2.10 webpack develop server

在前端開發的過程當中,一般須要啓動一個服務器,把開發打包好的前端代碼放在服務器上,經過訪問服務器訪問並測試(由於能夠有些狀況須要 ajax 請求)。 webpack 提供了一個機遇 node.js Express 的服務器 - webpack-dev-server 來幫助咱們簡化服務器的搭建,並提供服務器資源訪問的一些簡單配置。

安裝 webpack-dev-server

$ npm install  webpack-dev-server -g

啓動 webpack-dev-server

$ 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 配置參數

  • 沒有頂部信息提示條,提示信息在控制檯中展示

熱加載 (hot module replacement)

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 中配置 webpack develop server

修改 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 還提供了其餘的一些功能, 如:

  1. 配置 proxy

  2. 訪問 node.js API

  3. 和現有的 node 服務集成

基於這些功能能夠實現不少自定義的功能。

相關文章
相關標籤/搜索