(三)webpack入門——webpack功能集合的demo

此篇文章來源於https://blog.madewithlove.be/post/webpack-your-bags/ ,翻譯的不許確的地方請見諒。
 
到目前爲止你也許聽過這個新的很酷的稱爲webpack的工具。若是你瞭解得很少,你可能被一些認爲webpack跟構建工具Gulp同樣的人以及一些認爲webpack和打包工具Browserify同樣的人給誤導了。另一個方面,若是你去看了看webpack,你仍然可能被迷惑了,由於主頁呈現webpack是二者的結合,即Gulp和Browserify的結合。
 
老實說,鼠標第一次劃過「what Webpack is」的時候,我是挫敗的,而後關掉了tab頁。畢竟我已經有一個構建系統,並且很是滿意。若是你密切關注快速發展的Javascript場景,就像我同樣,你或許快速的投入到發展的潮流中。如今對webpack有更多的經驗,我感受我有必要爲那些仍然保持中立態度的人寫一篇文章解釋清楚什麼是webpack,更重要的是它是如此的強大以致於獲得了不少關注。
 
什麼是webpack?
經過介紹讓咱們立刻來回答這個問題:Webpack是一個構建系統或者模塊打包工具?更恰當的說,它二者都是——我並是否是說他作了二者的事,個人意思是說它將二者結合了。Webpack不去構建你的資源,而且它將你的模塊分開打包,它認爲你的資源是模塊自己。
更準確的說webpack不是構建你全部的Sass文件,優化你全部的全部圖片,全部模塊都打包,而後一股腦的都加載到頁面。你能夠像下面這樣作:
import stylesheet from 'styles/my-styles.scss';
import logo from 'img/my-logo.svg';
import someTemplate from 'html/some-template.html';
 
console.log(stylesheet); // "body{font-size:12px}"
console.log(logo); // "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5[...]"
console.log(someTemplate) // "<html><body><h1>Hello</h1></body></html>"
你全部的資源都被看做是一個個的模塊,它們能夠被包含導入、修改、維護,最終被打包進你的打包文件。
爲了使它工做起來,你須要在Webpack配置文件中註冊loaders。Loaders都是很小的插件,他們最基本的功能都是:當你遇到這種類型的文件,用這種方式去處理。下面是一些loaders的例子:
{
    // When you import a .ts file, parse it with Typescript
    test: /\.ts/,
    loader: 'typescript',
},
{
    // When you encounter images, compress them with image-webpack (wrapper around imagemin)
    // and then inline them as data64 URLs
    test: /\.(png|jpg|svg)/,
    loaders: ['url', 'image-webpack'],
},
{
    // When you encounter SCSS files, parse them with node-sass, then pass autoprefixer on them
    // then return the results as a string of CSS
    test: /\.scss/,
    loaders: ['css', 'autoprefixer', 'sass'],
}
最終全部的loaders鏈返回的都是string類型的類容,這樣就容許Webpack把返回結果包裹成Javascript模塊。每一個例子中的Sass文件都被loaders轉譯了,轉譯後的內容也許看起來是這個樣子:
export default 'body{font-size:12px}';
 
爲何要這麼作呢?
一旦你懂了Webpack是什麼你腦海中可能會出現第二個問題:這種方法有什麼好處?好處體如今images和css文件中或者JS文件中?考慮這個:在很長的一段時間咱們被教導和訓練將全部的東西都放進一個單個的文件中,只是爲了減小HTTP請求。
 
如今不少人都是將全部的資源打包到單文件例如app.js文件中,而且全部頁面都引用這個文件,這會致使一個很大的問題。這意味着不少時候不管加載那個頁面都會加載不少不須要的資源文件。如何你那麼作,你可能在肯定頁面手動引入資源文件而不是其餘的方式,這將致使維護和跟蹤一個很大的依賴樹,在這些頁面這些已經加載的依賴是須要的嗎?哪些頁面的樣式A和B有影響嗎?
 
無論這些方法是對的或者錯的.將Webpack當作兩面的——它不只僅是一個構建系統工具或者是打包工具,它說一個只能的模塊打包系統。一旦正確配置了,它甚至比你都更瞭解你的堆棧,它比你更清楚怎麼去最好的優化你的堆棧。
 
讓咱們一塊兒構建一個小的app
爲了便於你更好的瞭解Webpack帶來的好處,咱們將會構建一個很是小的app並將資源文件打包。在這個教程中我推薦基於Node4或Node5和NPM3來進行開發,這樣就避免在使用Webpack工做的過程當中出現一系列頭疼的問題。若是你使用的不是NPM3,你能夠經過 npm install npm@3 -g 命令來安裝它。
$ node --version
v5.7.1
$ npm --version
3.6.0
基礎的引導
讓咱們開始建立咱們的項目並安裝Webpack,咱們也會拉取jQuery稍後作展現用。
$ npm init -y
$ npm install jquery --save
$ npm install webpack --save-dev
如今讓咱們建立app的入口文件,用ES5的語法:
src/index.js
var $ = require('jquery');
$('body').html('Hello');
而後在webpac.config.js文件中建立Webpack配置。Webpack配置就是Javascript,須要導入一個對象。
module.exports = {
    entry: './src',
    output: {
        path: 'builds',
        filename: 'bundle.js',
    },
};
entry 告訴Webpack在你的應用中哪些文件是入口文件。這些就是最主要的文件,位於依賴樹的最上層。而後咱們告訴它編譯的打包文件放到builds文件夾下而且命名爲bundle.js。讓咱們來設置相應的index.html頁面:
<!DOCTYPE html>
<html>
 <head></head>
 <body> 
  <h1>My title</h1> 
  <a>Click me</a> 
  <script src="builds/bundle.js"></script>  
 </body>
</html>
咱們來運行Webpack命令,咱們獲得了一些信息,它告訴咱們bundle.js編譯正確:
$ webpack
Hash: d41fc61f5b9d72c13744
Version: webpack 1.12.14
Time: 301ms
    Asset    Size  Chunks             Chunk Names
bundle.js  268 kB       0  [emitted]  main
   [0] ./src/index.js 53 bytes {0} [built]
    + 1 hidden modules
這裏你能夠看到Webpack告訴你bundle.js包含了你的入口文件(index.js)以及有一個隱藏模塊。這個隱藏模塊就是jQuery,Webpack默認隱藏不是你的模塊。爲了查看全部被Webpack編譯以後隱藏的模塊,咱們能夠加上 --display-modules標記:
$ webpack --display-modules
bundle.js  268 kB       0  [emitted]  main
   [0] ./src/index.js 53 bytes {0} [built]
   [1] ./~/jquery/dist/jquery.js 259 kB {0} [built]
你也能夠運行webpack --watch命令區自動監控你的文件的改變,若是有文件改變會自動從新編譯。
 
設置咱們第一個loader
如今你還記得咱們講過Webpack是怎麼導入CSS、HTML以及其餘各種型的文件的嗎?在過去幾年裏,若是你已經適應了大規模向web組件轉化的趨勢(Angular 2, Vue, React, Polymer, X-Tag等),你也許聽過這樣一個idea,你的app不是一個相互聯繫的總體的UI,而是一系列能夠維護可重用自包含的UI:web組件化(我在這裏簡化,可是你已經明白了)。如今爲了讓組件真正的自包含,你須要將它所依賴的都包含進來。想象一下一個button組件:它有HTML模版,也有交互用的JS,也許還有一些樣式。若是在咱們須要的時候這些都被加載,這是很是好的,對嗎?當咱們僅僅導入Button組件的同時,咱們也會獲得它全部關聯的資源。
 
讓咱們寫咱們的button組件;首先,我猜測大多數人都仍是習慣ES2015語法的,所以咱們加入咱們的第一個loader:Babel. 爲了在Webpack中安裝一個loader你須要作兩件事情:用 npm install {whatever}-loader命令安裝loader,而後將它加入到 Webpack配置文件中的 module.loaders部分,因此讓咱們用這種方式來加入babel到咱們的項目中來吧:
$ npm install babel-loader --save-dev
咱們須要安裝Babel自己,由於在這種狀況下babel-loader不會安裝Babel插件自己。咱們須要安裝 babel-core 包和 es2015 預調裝置
$ npm install babel-core babel-preset-es2015 --save-dev
接下來咱們將會建立一個命名爲 .babelrc 的文件,這個文件用來告訴Babel使用預調裝置。 它是一個簡單的JSON文件告訴Babel轉譯成能夠運行的代碼——在咱們的案例中咱們使用 es2015 預調裝置。
.babelrc
{ "presets": ["es2015"] }
如今Babel已經安裝和配置了咱們須要更新咱們的配置文件:咱們想要作什麼?咱們想要Babel去運行全部以.js 結尾的文件,可是Webpack遍歷全部的依賴,咱們但願避免執行第三方代碼好比像jQuery,因此咱們能夠過濾它。Loaders有 include 和 exclude 兩個規則。它能夠配置成一個字符串、正則表達式、一個或調或者其餘你但願的,咱們但願Babel只執行咱們本身的文件,因此咱們僅 include 咱們本身的資源文件夾:
module.exports = {  
entry: './src',
    output: {
        path: 'builds',
        filename: 'bundle.js',
    },
module: {
        loaders: [{
            test: /\.js/,
            loader: 'babel',
            include: /src/,
        }],
    }
};
Babel已經安裝配置完成,咱們能夠用ES6語法重寫 index.js 文件。從如今開始全部的例子都用ES6語法。
import $ from 'jquery';
$('body').html('Hello');
寫一個小的組件
如今讓咱們寫一個小Button組件,它將會有SCSS樣式,HTML模板,和一些行爲。因此咱們要安裝一些咱們須要的東西。首先咱們須要安裝Mustache,它是一個很是輕量級的模板包,可是咱們也須要用來加載Sass和HTML的loaders。結果是從一個加載器傳送到另一個加載器,咱們須要一個CSS加載器來處理Sass 加載器處理的結果。如今,一旦咱們有了CSS就有不少方式去處理他們了,目前爲止咱們將會使用稱做 style-loader 的 加載器來處理CSS,  style-loader 將CSS動態的注入到頁面。
$ npm install mustache --save
$ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev
如今爲了告訴Webpack傳送數據是從一個加載器到另外一個加載器,咱們簡單的用感嘆號「!」來將一些列的加載器從右到左分隔開。你也能夠給loaders  屬性配置一個array值來替代 loader 屬性配置:
{
    test: /\.js/,
    loader: 'babel',
    include: /src/,
},
{
    test: /\.scss/,
    //loader: 'style!css!sass',
    // Or
    loaders: ['style', 'css', 'sass'],
},
{
    test: /\.html/,
    loader: 'html',
}
如今咱們的loaders配置好了,開始寫咱們的button組件:
 
src/Components/Button.scss
.button{                                                                                                                                                  background:tomato;                                                                                                                                    color:white;                                                                                                                                      }    
src/Components/Button.html
<a class="button"href="{{link}}">{{text}}</a>
src/Components/Button.js
 
import $ from 'jquery';
import template from './Button.html';
import Mustache from 'mustache';
import './Button.scss';
 
export default class Button {
        constructor(link) {
            this.link = link;
        }
 
        onClick(event) {
            event.preventDefault();
            alert(this.link);
        }
 
        render(node) {
            const text = $(node).text();
 
            // Render our button
            $(node).html(Mustache.render(template, {
                text
            }));
 
            // Attach our listeners
            $('.button').click(this.onClick.bind(this));
        }
    }
如今你的 Button.js是百分百自包含的,不管什麼時候被導入,在哪一種上下文環境中,它都是能夠運行的,它是能夠被正確使用和渲染的。如今咱們只須要將Button渲染在咱們的頁面:
src/index.js
import Button from './Components/Button';
const button = new Button('google.com');
button.render('a'); 
運行webpack,而後刷行頁面,你就能夠點擊咱們的按鈕了。
 
你如今已經學會了怎麼設置loader是以及在你的應用中如何定義每一個部分的依賴了.這看起來好像並無多少東西,來讓咱們進一步推動咱們的例子。
 
代碼分離
這個例子好像很好,還包含了不少東西,可是也許咱們不是老是須要咱們的button。也許在一些頁面上根本就沒有一個 a 標籤來供 button渲染在上面,在這個例子中,咱們也許不會導入全部的button樣式、模版、Mustache或者其餘的,對嗎? 因此代碼分離就上場了。Webpack代碼分離處理了打包到一個文件和手動引入文件不可維護的問題。在你的代碼中定義你的想法,你的代碼很容易的分隔到單個文件中,而後按需加載。語法很簡單:
import $ from 'jquery';
 
// This is a split point
require.ensure([], () = >{
    // All the code in here, and everything that is imported
    // will be in a separate file
    const library = require('some-big-library');
    $('foo').click(() = >library.doSomething());
});
在 require.ensure 的回調函數裏面的內容將會被分離進一個chunk中——即單獨打包並經過Ajax按需加載。這意味着咱們基本會這樣:
bundle.js
|- jquery.js
|- index.js // our main file
chunk1.js
|- some-big-libray.js
|- index-chunk.js // the code in the callback
在任何地方你並不須要導入或者加載 chunk1.js 文件。 在須要它的時候Webpack會自動加載。這意味着你能夠用各個部分的邏輯來包裹分塊的代碼,咱們例子中就是這麼作的。在頁面有 a 標籤的時候咱們才須要咱們的Button組件:
src/index.js
if (document.querySelectorAll('a').length) {
    require.ensure([], () = >{
        const Button = require('./Components/Button').default;
        const button = new Button('google.com');
 
        button.render('a');
    });
}
注意,當你使用 require 的時候 你想獲得默認的export須要加上 .default 屬性手動得到。因爲require不能處理默認和正常的exports的緣由因此你不得不明確指定返回值。 而 import  是會處理的,因此不須要額外手動得到對應的module。
 
所以Webpack的輸出如今應該是不同的。讓咱們加上 --display-chunks 標誌來運行webpack命令,看看哪些模塊是在chuns中:
$ webpack --display-modules --display-chunks
Hash: 43b51e6cec5eb6572608
Version: webpack 1.12.14
Time: 1185ms
      Asset     Size  Chunks             Chunk Names
  bundle.js  3.82 kB       0  [emitted]  main
1.bundle.js   300 kB       1  [emitted]
chunk    {0} bundle.js (main) 235 bytes [rendered]
    [0] ./src/index.js 235 bytes {0} [built]
chunk    {1} 1.bundle.js 290 kB {0} [rendered]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {1} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {1} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {1} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {1} [built]
正如你所看到的同樣,你的入口文件(bundle.js)如今僅僅包含一些Webpack的邏輯,其它的像jQuery、Mustache、Button都在 1.bundle.js 分組中,只有咱們在頁面嵌入1.bundle.js,它纔會加載。當加載Ajax的時候,爲了讓Webpack知道去哪兒尋找chunks,咱們不得不加一點內容到咱們的配置文件中:
path: 'builds',
filename: 'bundle.js',
publicPath: 'builds/',
output.publicPath 選項告訴Webpack在頁面視圖中去哪兒尋找構建了的資源(在咱們的例子中就是 builds/).如今咱們若是訪問咱們的也頁面將會看到一切正常工做,但咱們發現更重要的是,咱們的也買年有一個錨,Webpack才能正確加載chunk:
 
 
若是頁面沒有加錨,只會加載bundle.js .這容許在你的應用程序中明智的將複雜的邏輯分離,而且讓每一個頁面僅僅加載它們真正須要的內容。注意,咱們命名咱們的分離文件名,將1.bundle.js替換成更有意義的chunk名。你僅僅給 require.ensure 加第三個參數以及在webpack配置文件中配置 chunkFilename 就能夠作到:
src/index.js
require.ensure([], () = >{
    const Button = require('./Components/Button').default;
    const button = new Button('google.com');
 
    button.render('a');
},
'button');
webpack.config.js
output: {
        path:     'builds',
        filename: 'bundle.js',
        chunkFilename: '[name].bundle.js',
        publicPath: 'builds/',
},
生成的 button.bundle.js 文件將會替代 1.bundle.js 文件。
 
增長第二個組件
如今一切都已經很完美了,可是咱們要增長第二個組件來看看它是怎麼工做的:
src/Components/Header.scss
.header {
    font-size: 3rem;
}
src/Components/Header.html
<headerclass="header">{{text}}</header>
src/Components/Header.js
import $ from 'jquery';
import Mustache from 'mustache';
import template from './Header.html';
import './Header.scss';
export default class Header {
        render(node) {
            const text = $(node).text();
            $(node).html(Mustache.render(template, {
                text
            }));
        }
    }

而後將它渲染在咱們的頁面css

// If we have an anchor, render the Button component on it
if (document.querySelectorAll('a').length) {
    require.ensure([], () = >{
        const Button = require('./Components/Button').default;
        const button = new Button('google.com');

        button.render('a');
    });
}

// If we have a title, render the Header component on it
if (document.querySelectorAll('h1').length) {
    require.ensure([], () = >{
        const Header = require('./Components/Header').default;

        new Header().render('h1');
    });
}
如今能夠看看webpack 命令加上  --display-modules  --display-chunks 標誌後的輸出內容:

$ webpack --display-modules --display-chunks
Hash: 178b46d1d1570ff8bceb
Version: webpack 1.12.14
Time: 1548ms
      Asset     Size  Chunks             Chunk Names
  bundle.js  4.16 kB       0  [emitted]  main
1.bundle.js   300 kB       1  [emitted]
2.bundle.js   299 kB       2  [emitted]
chunk    {0} bundle.js (main) 550 bytes [rendered]
    [0] ./src/index.js 550 bytes {0} [built]
chunk    {1} 1.bundle.js 290 kB {0} [rendered]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {1} {2} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built]
chunk    {2} 2.bundle.js 290 kB {0} [rendered]
    [2] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {1} {2} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built]
    [9] ./src/Components/Header.js 1.62 kB {2} [built]
   [10] ./src/Components/Header.html 64 bytes {2} [built]
   [11] ./src/Components/Header.scss 1.05 kB {2} [built]
   [12] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]
也許你看到了一個至關嚴重的問題: 咱們的兩個組件都須要jQuery 和 Mustache , 這意味着這些依賴在咱們的chunks中是重複的,這並非咱們想要的。默認狀況下Webpack會執行很小的優化。可是它包含了不少很牛掰的東西幫你扭轉乾坤,它們在插件列表中。
 
功能上,plugins 不一樣於加載器,plugins不是僅僅的執行一系列的特定文件,也不是成爲一個管道,它們執行全部的文件以及更高級的操做,不必定須要關聯的去轉換。Webpack將一些插件用來執行各類各樣的優化。其中令咱們感興趣的一個插件是CommonChunksPlugin: 它分析全部的循環依賴,在某個地方提取它們。它能夠是一個徹底獨立的文件(就像vendor.js)或許它能夠成爲你的主要文件。
 
在咱們的例子中咱們更喜歡將普通的依賴移到咱們的入口文件中,由於若是全部的頁面都須要jQuery 和 Mustache, 咱們不妨移入進去。 因此讓咱們來更新咱們的配置:
var webpack = require('webpack');
 
module.exports = {
    entry: './src',
    output: {
        // ...
    },
    plugins: [new webpack.optimize.CommonsChunkPlugin({
        name: 'main',
        // Move dependencies to our main file
        children: true,
        // Look for common dependencies in all children,
        minChunks: 2,
        // How many times a dependency must come up before being extracted
    }), ],
    module: {
        // ...
    }
};
若是咱們再次運行webpack,咱們能夠看到它看起來好了不少。這裏 main是默認的chunk名。
chunk    {0} bundle.js (main) 287 kB [rendered]
    [0] ./src/index.js 550 bytes {0} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {0} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {0} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {0} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {0} [built]
chunk    {1} 1.bundle.js 3.28 kB {0} [rendered]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
chunk    {2} 2.bundle.js 2.92 kB {0} [rendered]
    [9] ./src/Components/Header.js 1.62 kB {2} [built]
   [10] ./src/Components/Header.html 64 bytes {2} [built]
   [11] ./src/Components/Header.scss 1.05 kB {2} [built]
   [12] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]
咱們也能夠指定實例名  name:'vendor’:
new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    children: true,
    minChunks: 2,
}),
到如今咱們的主chunk不存在,Webpack會建立一個 builds/vendor.js的文件,咱們須要手動導入到HTML中:
<script src="builds/vendor.js"></script>
你也能夠不提供普通的chunk名,而後指定特殊的 async: true屬性,就能夠建立一個被異步加載的普通依賴。Webpack有不少這樣強大的只能優化功能。我不可能將它們全部的都命名可是做爲聯繫咱們來試試建立咱們應用程序的生產版本。
 
生產環境和超越

首先,咱們將會加入一些plugins到咱們的應用程序中,可是咱們只但願在NODE_ENV 等於 production的時候加載它們,因此讓咱們在配置文件中增長一些邏輯。它只是一個JS文件,這將很容易作到:html

var webpack = require('webpack');
var production = process.env.NODE_ENV === 'production';
 
var plugins = [new webpack.optimize.CommonsChunkPlugin({
    name: 'main',
    // Move dependencies to our main file
    children: true,
    // Look for common dependencies in all children,
    minChunks: 2,
    // How many times a dependency must come up before being extracted
}), ];
 
if (production) {
    plugins = plugins.concat([
    // Production plugins go here
    ]);
}
 
module.exports = {
    entry: './src',
    output: {
        path: 'builds',
        filename: 'bundle.js',
        publicPath: 'builds/',
    },
    plugins: plugins,
    // ...
};
第二步,Webpack也有幾個設置用來對生成環境進行開關的:
module.exports = {
    debug: !production,
    devtool: production ? false: 'eval',
第一個設置debug模式的開關,這意味着很容易的在本地調試代碼。 第二個設置是關於sourcemaps生成的。Webpack有幾種方式去渲染sourcemaps, 在本地  eveal僅僅是其中最好的一種。在生產環境中咱們不必關心sourcemaps,因此咱們關閉了它。如今讓咱們加上生產環境的plugins吧:
if (production) {
    plugins = plugins.concat([
 
    // This plugin looks for similar chunks and files
    // and merges them for better caching by the user
    new webpack.optimize.DedupePlugin(),
 
    // This plugins optimizes chunks and modules by
    // how much they are used in your app
    new webpack.optimize.OccurenceOrderPlugin(),
 
    // This plugin prevents Webpack from creating chunks
    // that would be too small to be worth loading separately
    new webpack.optimize.MinChunkSizePlugin({
        minChunkSize: 51200,
        // ~50kb
    }),
 
    // This plugin minifies all the Javascript code of the final bundle
    new webpack.optimize.UglifyJsPlugin({
        mangle: true,
        compress: {
            warnings: false,
            // Suppress uglification warnings
        },
    }),
 
    // This plugins defines various variables that we can set to false
    // in production to avoid code related to them from being compiled
    // in our final bundle
    new webpack.DefinePlugin({
        __SERVER__: !production,
        __DEVELOPMENT__: !production,
        __DEVTOOLS__: !production,
        'process.env': {
            BABEL_ENV: JSON.stringify(process.env.NODE_ENV),
        },
    }),
 
    ]);
}
這些是我使用的最多的插件可是Webpack提供了不少插件供你使用。這裏也有一些第三方插件,它們能夠在NPM上找到來完成各類各樣的需求。plugins的連接能夠在文章最後找到。
 
如今生成環境最理想的是讓資源版本化。如今你還記得咱們設置 output.filename 的值爲  bundle.js?   然而這裏有一些變量你能夠用在你的option中,其中一個是 [hash] ,對應最終包的內容的hash值,因此讓咱們改變咱們的代碼。咱們也但願咱們的chunks被版本化因此咱們將會增長一個 output.chunkFilename 就像下面這樣:
output: {
    path: 'builds',
    filename: production ? '[name]-[hash].js': 'bundle.js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath: 'builds/',
},
在咱們簡單的app中咱們真的沒有辦法動態的檢索編譯後的文件名,例如,在生產環境中咱們僅僅版本化資源文件。在一個生成環境構建以前咱們也許但願咱們的builds文件夾是清空的,因此讓咱們安裝第三方插件來作這個事情:
$ npm install clean-webpack-plugin --save-dev
將它加入到咱們的配置中:
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
 
// ...
if (production) {
    plugins = plugins.concat([
 
    // Cleanup the builds/ folder before
    // compiling our final assets
    new CleanPlugin('builds'), 
好了,咱們已經完成了一些漂亮的優化,來讓咱們比較比較結果吧:
$ webpack
                bundle.js   314 kB       0  [emitted]  main
1-21660ec268fe9de7776c.js  4.46 kB       1  [emitted]
2-fcc95abf34773e79afda.js  4.15 kB       2  [emitted]
$ NODE_ENV=production webpack
main-937cc23ccbf192c9edd6.js  97.2 kB       0  [emitted]  main
Webpack作了些什麼: 首先咱們的例子已經很輕了,兩個異步的chunks不值得發起HTTP請求,因此Webpack將他們合併到了入口文件中。其次一切都被正確的壓縮了。咱們從一個須要發起3個HTTP請求及總共322kb的文件到只須要一個HTTP請求及壓縮到97kb的文件。
 
Webpack是避免了一個大的JS文件的生成? 是的,可是這僅僅發生在咱們的app很小的時候。如今考慮這個:你沒必要要考慮合適何地合併哪些內容。若是你的chunks忽然開始有不少依賴,chunk會被移動到異步chunk中,而不是合併;若是開始尋找到相似的chunks,他們將會被合併。你僅僅須要設置一些規則,Webpack以儘量最好的方式自動優化你的應用程序。不須要手動操做,不須要考慮依賴什麼,哪裏被組要這些問題,一切都是自動的。
 
你也許已經發現了我沒有設置任何東西去壓縮咱們的HTML和CSS, 那是由於 css-loader 和 html-loader已經主要到咱們前面提到的 debug選項被設置爲 false 了。這也是爲何Uglify是一個單獨的插件的緣由: 由於在Webpack中沒有 js-loader , Webpack自身就是 JS loader。
 
抽取 
也許你已經注意到了,在教程開始,SCSS文件編譯後的內容是一個個用style標籤包裹寫入HTML頁面的,有一個SCSS就用一個style標籤來包裹並寫入 ,這樣會有不少的style標籤被插入HTML中,這樣不是很好,那有沒有辦法將這些文件合併而後在鏈接到html中呢? 答案是確定的,咱們須要外部的plugin來作到:
$ npm install extract-text-webpack-plugin --save-dev
這個插件是將特定類型的文件打包到一塊兒,最經常使用的就是CSS. 讓咱們來設置它:
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractPlugin = require('extract-text-webpack-plugin');
var production = process.env.NODE_ENV === 'production';
 
var plugins = [
new ExtractPlugin('bundle.css'), // <=== where should content be piped
new webpack.optimize.CommonsChunkPlugin({
     name: 'main',
     // Move dependencies to our main file
     children: true,
     // Look for common dependencies in all children,
     minChunks: 2,
     // How many times a dependency must come up before being extracted
}),
 ];
 
// ...
module.exports = {
    // ...
    plugins: plugins,
    module: {
        loaders: [{
            test: /\.scss/,
            loader: ExtractPlugin.extract('style', 'css!sass'),
        },
        // ...
        ],
    }
};
extrect方法有兩個參數:第一個是說明來抽取 style類型的內容,第二個參數說明在主文件中如何處理。全部的style是都整合到了builds/bundle.css中。讓咱們來測試它,在應用程序中加一個小的主要的樣式表文件:
src/styles.scss
body {
    font-family: sans-serif;
    background: darken(white, 0.2);
}
src/index.js
import'./styles.scss';
// Rest of our file

如今運行wbpack命令,如今很是肯定咱們有一個bundle.css文件,能夠把它引入到html中:node

$ webpack
                bundle.js    318 kB       0  [emitted]  main
1-a110b2d7814eb963b0b5.js   4.43 kB       1  [emitted]
2-03eb25b4d6b52a50eb89.js    4.1 kB       2  [emitted]
               bundle.css  59 bytes       0  [emitted]  main

你也能夠提取chunks的styles,能夠經過ExtractTextPlugin('bundle.css', {allChunks: true})選項來設置。注意一下,你也能夠額給你的文件名設置成變量,因此若是你須要講你的樣式表版本化的話你僅須要作的是這麼配置一下就行jquery

ExtractTextPlugin('[name]-[hash].css')

在Webpack中使用imageswebpack

如今咱們全部的Javascript文件都很好,可是有一個主題咱們尚未談到多少,那就是固定資源:圖片,文字字體等。

在Webpack環境中是怎麼工做的及如何最好的優化他們?讓咱們在網絡上找一張圖片,咱們將會用這張圖片做爲頁面背景圖,由於我已經看到有人在Geocities有人這麼作了,而且他看起來很是酷:git

讓咱們將這張圖片保存在 img/puppy.jpg , 而後依據下面這樣修改咱們的Sass文件:
src/styles.scss
body {
    font-family: sans-serif;
    background: darken(white, 0.2);
    background-image: url('../img/puppy.jpg');
    background-size: cover;
}
如今若是你這麼作,Webpack將會合理的告訴你"到底讓我來怎麼處理一個JPG文件",由於咱們沒有loader.  這裏有兩個本地loaders,咱們能夠用來處理固定資源:file-loader 和 url-loader :第一個loader僅僅只會返回一個URL資源並無任何其餘改變,容許在處理過程當中對文件版本化;第二個loader將會將資源內聯成一個 data:image/jpeg;base64 URL。
 
實際中它非黑即白:若是你的背景是一個2M的圖片,你不想內聯它就會單獨加載它。從另外一方面看若是它是一個2kb的小圖標,最好用內聯的方式處理來減小HTTP請求,因此咱們設置這兩個loader:
$ npm install url-loader file-loader --save-dev
{
    test: /\.(png|gif|jpe?g|svg)$/i,
    loader: 'url?limit=10000',
},
在這裏,咱們經過查詢參數 limit 告訴 url-loader: 若是資源小於10kb就內聯資源,不然就調用 file-loader 並關聯它。這個語法稱爲查詢字符串,你使用它來配置loaders,做爲一種選擇你也能夠經過一個對象來配置loaders:
{
    test: /\.(png|gif|jpe?g|svg)$/i,
    loader: 'url',
    query: {
        limit: 10000,
    }
}
好了咱們上一個快照:
                bundle.js   15 kB       0  [emitted]  main
1-b8256867498f4be01fd7.js  317 kB       1  [emitted]
2-e1bc215a6b91d55a09aa.js  317 kB       2  [emitted]
               bundle.css  2.9 kB       0  [emitted]  main
咱們能夠看到這裏並無提到一個JPG,由於咱們的圖片文件小於配置的大小,它被內聯了。這覺得着咱們訪問咱們的頁面的時候,咱們能夠看到一個小狗。

這是很強大的由於它意味着Webpack如今經過判斷大小或者請求來優化任何固定資源。這裏有一個很好的loader你能夠經過管道傳輸進一步推進一些可優化的地方,一個很是經常使用的loader是image-loader,它經過imagemin在打包以前壓縮全部的圖片文件。它甚至有個?bypassOnDebug 查詢參數容許你僅在生產環境來壓縮圖片。這裏有不少像這樣的插件,我鼓勵你去看看文章最後列表裏面的內容。github

 
如今咱們的生產環境受到不少關注,如今讓咱們把焦點放在本地開發上。到目前爲止你也許注意到了一個很大的問題,一般提到的構建工具是要在線上加載,線上從新加載,瀏覽器同步。可是整個頁面都須要刷新,讓咱們進一步設置熱模塊替換和熱重載。它的思想是這樣的,自從Webpack準確的知道依賴樹中每一個木塊的位置,一個修改就會被依賴樹中簡單修補的新文件替代。更同屬的說:你的修改將不須要從新刷新頁面在瀏覽器中也會出現。
 

爲了讓HMR被使用,咱們須要一個服務器,熱資源將從它提供。咱們可使用Webpack提供的 dev-server, 讓咱們來加載它:web

$ npm install webpack-dev-server --save-dev
讓咱們來運行 dev-server:
$ webpack-dev-server --inline --hot
第一個標誌告訴咱們內聯HMR的邏輯而後插入頁面(而不是在iframe頁面呈現),第二個標誌啓用HMR. 如今讓咱們訪問 web-server :http://localhost:8080/webpack-dev-server/。你看到的是普通的頁面,可是你嘗試修改你的Sass文件,奇蹟產生了:

 

你能夠將webpack-dev-server當作本地服務器使用。若是你打算爲HMR老是使用它,你能夠在你的配置文件中這麼配置:正則表達式

output: {
    path: 'builds',
    filename: production ? '[name]-[hash].js': 'bundle.js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath: 'builds/',
},
devServer: {
    hot: true,
},

如今不管什麼時候運行 webpack-dev-server 它已是HMR模式了. 注意咱們使用webpack-dev-server來提供熱資源可是你可使用其餘的選擇,好比Express server。Webpack提供了一箇中間件來供你在其餘服務器上使用 HMR.typescript

完全弄明白或者糊里糊塗的?

若是你密切關注本教程你也許發現了一些不奇怪的東西: 爲何loaders被包裹在module.loaders可是plugins不是呢?這是由於你能夠將其餘的東西放在module中!Webpack不只僅只有loaders,它也有pre-loaders 和post-loadrs:這些loaders運行在主loaders以前或者以後。讓咱們看一個例子:我肯定在這篇文章中寫的代碼是驚人的,因此在轉換代碼以前將ESLint應用到咱們的代碼中:
 
$ npm install eslint eslint-loader babel-eslint --save-dev

而後建立以 .eslintrc 命名的文件:

parser: 'babel-eslint'
rules:
   quotes: 2

如今加載咱們的pre-loader,跟以前的語法相似,只不過是在module.preLoaders裏面:

module: {
    preLoaders: [{
        test: /\.js/,
        loader: 'eslint',
    }],

如今運行Webpack,和肯定的它會失敗:

$ webpack
Hash: 33cc307122f0a9608812
Version: webpack 1.12.2
Time: 1307ms
                    Asset      Size  Chunks             Chunk Names
                bundle.js    305 kB       0  [emitted]  main
1-551ae2634fda70fd8502.js    4.5 kB       1  [emitted]
2-999713ac2cd9c7cf079b.js   4.17 kB       2  [emitted]
               bundle.css  59 bytes       0  [emitted]  main
    + 15 hidden modules

ERROR in ./src/index.js

/Users/anahkiasen/Sites/webpack/src/index.js
   1:8   error  Strings must use doublequote  quotes
   4:31  error  Strings must use doublequote  quotes
   6:32  error  Strings must use doublequote  quotes
   7:35  error  Strings must use doublequote  quotes
   9:23  error  Strings must use doublequote  quotes
  14:31  error  Strings must use doublequote  quotes
  16:32  error  Strings must use doublequote  quotes
  18:29  error  Strings must use doublequote  quotes

如今舉另一個pre-loader的例子:每個組件咱們如今引用的樣式列表都是同一個名字,模板名也都是同一個。如今讓咱們使用一個pre-loader來自動加載和模塊名相同的任何文件:

$ npm install baggage-loader --save-dev
{
    test: /\.js/,
    loader: 'baggage?[file].html=template&[file].scss',
}
這高速二Webpack:若是你遇到和模塊相同名字的HTML文件,將它做爲template導入,遇到相同名字的Sass文件液倒入。咱們如今能夠修改下面的組件:
將下面這樣:
import $ from 'jquery';
import template from './Button.html';
import Mustache from 'mustache';
import './Button.scss';
修改爲這樣:
import $ from 'jquery';
import Mustache from 'mustache';
就像你看到的同樣pre-loaders是很是完美的,post-loaders也是同樣的。文章的最後面去看看loaders變量列表,你將會發現不少實用案例。
 
你想知道更多嗎?

如今咱們的應用程序仍是至關小的,可是隨着他開始變得愈來愈大它也許對依賴樹究竟是什麼的深入理解很是有幫助。咱們可能作的對仍是錯,什麼是咱們的應用程序的瓶頸等等。如今在內部,Webpack知道各類事情,可是你的禮貌的問它它知道的將會向你展現。你能夠像下面這樣作來生產一個profile文件:

webpack --profile --json > stats.json
第一個標誌告訴Webpack生成一個profile文件,第二個標誌告訴Webpack生成的文件內容是JSON格式,最後將他輸出到一個JSON文件中。如今有不少各類網站去分析這樣的profile文件,可是Webpack提供了一個官方網站去分析它。 因此去Webpack Analyze 網站而且導入上面生成的JSON文件。如今打開Modules tab 你將會看到下面呈現的依賴樹的樣子:
 
 
紅點,說明你最後的包有問題。在咱們的例子中jQuery是有問題的由於他們是全部模塊中最大的,依次看看全部的tabs,儘管在咱們小的應用程序中你沒有學到太多可是這是一個很是重要的工具來深入理解你的樹依賴及最後的打包。如今就像我說的,其餘服務提供了洞察你的概要文件的能力,另一個我喜歡的是 Webpack Visualizer   ,它向咱們展現了一張圈圈圖來講明咱們打包的內容,下面就是咱們的案例:
 
如今我知道在個人案例中,Webpack已經替代了Grunt或者Gulp: 不少我使用過的,如今它們仍是得手動處理,剩下的我僅適用NPM腳本。過去的每一個例子中一個普通的任務即適用Aglio將API文檔轉換成HTML,像下面這樣作將會很容易:
package.json
{
    "scripts": {
        "build": "webpack",
        "build:api": "aglio -i docs/api/index.apib -o docs/api/index.html"
    }
}
若是不管如何在你的Gulp堆棧中有有一些複雜的任務,他們跟綁定和資源沒有關聯,Wepback和其餘的構建系統相處得很好。下面的例子如何在Gulp中整合Webpack:
var gulp = require('gulp');
var gutil = require('gutil');
var webpack = require('webpack');
var config = require('./webpack.config');
 
gulp.task('default',
function(callback) {
    webpack(config,
    function(error, stats) {
        if (error) throw new gutil.PluginError('webpack', error);
        gutil.log('[webpack]', stats.toString());
 
        callback();
    });
});
差很少就是這樣,自從Wwbpack有Node API 後, 在其餘構建系統中它很容易被使用,在任何狀況下你均可以發如今任何地方它掛載着包裝器。
 
任何狀況下,我想這是一個足夠好的鳥瞰讓你知道Webpack會爲你作什麼。你也許回想咱們在這篇文章中已經覆蓋了不少,可是咱們只是觸及表面:多入口,預提取、上下文替換等都沒有提到。Webpack是一個而使人難忘的強大的工具,我不決絕它。可是你一旦知道怎麼去馴服它,進入你耳朵的將是性能甜美的聲音。我在幾個項目中使用過它,它提供瞭如此強大的智能化和自動化功能。
 
參考資源:
相關文章
相關標籤/搜索