Webpack經典入門

原文: Webpack your bags

October 16th, 2015 by Maxime Fabre
(中文譯本更新於2017年10月22日,因爲webpack已更新至3.8.1,所以譯文有改動,譯文最後有各分段代碼包地址,若有須要請自行下載)
javascript


Webpack your bags

以前你可能已經據說過這個叫webpack的很酷的工具,若是你沒仔細瞭解過這個工具,你可能會有些困惑,由於有人說它像 Gulp 之類的構建工具,也有人說它像 Browserify 之類的模塊管理工具,若是你有去仔細地瞭解一下,你可能仍是會不明白究竟是怎麼一回事,由於官網上把webpack說成是這二者。css

說實話,開始的時候我對於「webpack究竟是什麼「 很模糊,而且感受很受挫,最後我直接就把網頁關了,畢竟我在這以前已經有一個構建系統的工具了,並且我用得也很是嗨皮,若是你像我同樣一直密切跟蹤javascript的發展的話,可能你早就由於在各類流行的東西上頻繁地跳來跳去而灰飛煙滅了。還好如今我有些經驗了,以爲能夠寫一篇文章來給那些還處在混沌中的小夥伴們,更清楚地解釋一下webpack究竟是什麼,更重要的是它到底什麼地方那麼出色以致於值得咱們投入那麼多的精力。html

1. Webpack是什麼?

Webpack究竟是一個構建系統仍是一個模塊管理器?好的,如今我立刻來回答一下───答案是兩個都是,固然我不是說它兩個事兒都幹,個人意思是它把二者有機地鏈接起來了,webpack不是先構建你的資源,而後再管理你的模塊,而是把你的資源自己當作模塊。java

更準確的說,它不是先編譯你全部的scss文件,再優化全部的圖片,以後在一個地方引進來,再管理全部的模塊,最後在另外一個地方引到你的頁面上。假設你是下面這樣:node

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) // "Hello"複製代碼

如上面所示,你的全部的資源(不管是scss,html或者其它)均可以被當成模塊,並且這些模塊能夠被import(引入),modify(修改),manipulate(操做),最後被打包到你的最終包(bundle)jquery

爲了達到這個目的,得在你的webpack的配置文件裏註冊加載器(loaders),加載器就是當你遇到某種文件的時候,對它作相應處理的一種插件,下面是一些加載器的例子:webpack

{
  // 當你導入(import)一個a.ts文件時,系統將會用Typescript加載器去解析它
  test: /\.ts/,
  loader: 'typescript',
},
{
  // 當你的項目裏面有圖片時,系統將會用image-webpack加載器把它們壓縮
  // 而且它們會被關聯到data64 URLs
  test: /\.(png|jpg|svg)/,
  loaders: ['url', 'image-webpack'],
},
{
  // 當你的項目裏面有scss文件時,系統將會經過node-sass加載器去解析,而且自動補充前綴
  // 最後返回css(能夠直接被系統解析)
  test: /\.scss/,
  loaders: ['css', 'autoprefixer', 'sass'],
}複製代碼

最後全部的loader返回的都是string,這樣webpack最終能夠把資源都包裝成javascript模塊。就像這個例子裏scss文件被loaders轉換後,看起來差很少是這樣子:
git

export default 'body{font-size:12px}';複製代碼


2. 世界那麼大,你爲何要用它?

一旦你明白webpack是什麼後,極可能就會想到第二個問題:用它能有什麼好處呢?「把Image和CSS放在個人JS裏?什麼鬼?」好吧,以前很長一段時間我一直被告訴要把全部文件都放在一個文件裏,這樣就保證不浪費咱們的HTTP請求。es6

但這樣會致使一個很大的缺點,就是如今大多數人都把全部的資源打包到一個單獨的app.js文件裏,而後把這個文件引入到每個頁面。這就意味着渲染每個頁面的時候大部分時間都浪費在加載一大堆根本就沒用到的資源上。可是若是你不這麼作,那麼你頗有可能得手動把這些資源引入到指定的頁面,這就會致使須要一大團亂七八糟的依賴樹去維護和跟蹤一些問題例如:哪些頁面須要依賴這個文件?修改a.css和b.css會影響到哪些頁面?github

所以這兩個方法都是不對,但也不全是錯的。若是咱們把webpack當作一箇中介───它不只僅是一個構建系統或者一個打包工具,而是一個邪惡的智能打包系統,正確地配置後,它甚至比你還了解你的系統,並且它也比你清楚怎麼樣才能最好優化你的系統。

3. 咱們來作一個小的app

爲了讓你更容易地理解webpack帶來的好處,咱們作一個小的app,而後用webpack來打包咱們app的資源,在作以前我建議用Node 4及以上版本和NPM 3及以上版本,由於良好的依賴關係在你使用webpack的時候會避免不少讓人頭疼的問題,若是你的NPM版本不夠新,你能夠經過 npm install npm -g 來更新。

$ node --version
v6.11.2
$ npm --version
5.4.2複製代碼

同時我也建議你把 node_modules/.bin 加到你的PATH環境變量裏,以免每次都手動打 node_modules/.bin/webpack ,後面的全部例子都不會顯示我要執行的命令行的 node_modules/.bin 部分(全局安裝 webpack 則忽略此步驟)。

備註:全局安裝 webpack 命令行:經過 npm install webpack -g 來更新。

基本引導

如今開始,先建立個名爲webpack-your-bags的文件夾,在文件夾下安裝webpack,而且也加上jquery好在後面證實一些東西

$ npm init -y
$ npm install jquery --save
$ npm install webpack --save-dev

如今讓咱們來建立一個app入口,用如今的純ES5

路徑:webpack-your-bags/src/index.js

var $ = require('jquery');
$('body').html('Hello');
複製代碼

建立webpack的配置文件webpack.config.js,webpack.config.js是javascript,須要導出(export) 一個對象(object)
路徑:webpack-your-bags/webpack.config.js

var path = require("path");//用於處理目錄的對象,提升開發效率(__dirname須要引入path後纔可使用)
var ROOT_PATH = path.resolve(__dirname);//獲取當前整個模塊文件所在目錄的完整絕對路徑
var BUILDS_PATH = path.resolve(ROOT_PATH, "builds");//獲取咱們的builds目錄完整絕對路徑
module.exports = {
    entry: './src',
    output: {
        path: BUILDS_PATH,
        filename: 'bundle.js',
    },
};複製代碼

這裏,entry會告訴webpack哪一個文件是你的app的入口點。這些是你的主文件,他們在依賴樹的頂端,而後咱們告訴它編譯咱們的資源到放在 builds 目錄(路徑:webpack-your-bags/builds)的bundle.js文件下,我如今再建立相應的 index.html
路徑:webpack-your-bags/index.html

<!DOCTYPE html>
<html>
<body>
    <h1>My title</h1>
    <a>Click me</a>
    <script src="builds/bundle.js"></script>
</body>
</html>複製代碼

執行webpack,若是一切正常,會看見一個信息告訴咱們正確地編譯了bundle.js

$ webpack
Hash: 65d56cd1e7dddf04958b
Version: webpack 3.8.1
Time: 250ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  271 kB       0  [emitted]  [big]  main
   [0] ./src/index.js 51 bytes {0} [built]
    + 1 hidden module複製代碼

這裏webpack會告訴你bundle.js包含了咱們的入口點(index.js)和一個隱藏的模塊,這個隱藏的模塊就是jquery,默認狀況下webpack會把第三方模塊給隱藏掉,若是想要看見webpack編譯的全部的模塊 ,咱們能夠加 --display-modules 參數

$ webpack --display-modules
Hash: 65d56cd1e7dddf04958b
Version: webpack 3.8.1
Time: 263ms
    Asset    Size  Chunks                    Chunk Names
bundle.js  271 kB       0  [emitted]  [big]  main
   [0] ./src/index.js 51 bytes {0} [built]
   [1] ./node_modules/jquery/dist/jquery.js 268 kB {0} [built]
複製代碼

若是你以爲每次修改都要從新執行webpack太麻煩,你也能夠運行 webpack --watch 來自動監視文件的變化,若是文件有發生變化,便會自動從新編譯。

搭建咱們的第一個loader

還記得咱們討論過webpack能夠導入CSS和HTML還有各類各樣的文件嗎?何時能派上用場呢?好的,若是你過去幾年都朝着web組件的方向大躍進(Angular 4, Vue, React, Polymer, X-Tag, etc.)。那麼估計你聽過這樣一個觀點:你的app若是是用一套可重用,自包含的UI組件搭建起來的話,會比一個單獨的內聚的UI更容易維護───這個可重用的組件就是web 組件(在這裏我說的比較簡單,你能理解就行),如今爲了讓組件變成真正的自包含,須要把組件須要的全部的東西封裝到他們本身的內部,好比咱們來考慮一個button,它的裏面確定有一些HTML,並且還有一些JS來保證它的交互性,可能還得再來一些CSS,這些東西若是須要的時候再一塊兒加載進來就顯得很是完美了,也就是隻有在咱們導入button組件的時候,咱們纔會去加載這些資源文件。

如今咱們來寫一個button,首先假設你已經熟悉了ES2015(即ES6,javascript的新標準),由於有些瀏覽器還不支持ES6,因此咱們須要babel來爲咱們把ES6轉爲瀏覽器支持的ES5 ,咱們先加入babel的加載器(loader)。想要在webpack裏安裝一個loader,有兩步須要作:1. npm install {whatever}-loader ;2.把它加到你的webpack配置文件(webpack.config.js)裏的 module.loaders 部分,好的,那如今咱們要加babel,因此先安裝:

$ npm install babel-loader --save-dev

咱們也得安裝babel自己,由於如今咱們這個例子加載器不會自動安裝它們,所以咱們須要裝babel-core這個包和它的預設 es2015(也就是在代碼被執行前執行的轉換器版本):

$ npm install babel-core babel-preset-es2015 --save-dev

--save-dev : 項目開發過程當中須要依賴的包,發佈以後無需依賴的包,例如咱們的babel,開發過程須要babel爲咱們把書寫的es6轉爲es5,發佈以後因爲咱們全部書寫的es6代碼都被轉爲es5,所以無需繼續依賴;
--save : 項目發佈後依然須要依賴的包,例如咱們jquery

咱們如今建立 .babelrc 文件,告訴babel用哪一個預設,這是一個json文件告訴babel在你的代碼上執行哪一種轉換器,如今咱們告訴它用es2015:
路徑:webpack-your-bags/.babelrc

{
"presets": ["es2015"] 
}
複製代碼

如今babel配置完了,咱們能夠更新webpack的配置(也就是webpack.config.js文件):咱們想要什麼?咱們想babel在咱們全部以.js結尾的文件上運行, 可是因爲webpack會遍歷全部的依賴,可是咱們想避免babel運行在第三方代碼上,如jquery,因此咱們能夠再稍微過濾一下,loaders既能夠有 include 也能夠是 exclude,能夠是字符串(string),正則表達式(regex),也能夠是一個回調(callback),隨便你用哪一個。由於咱們想讓babel只運行在咱們的文件上,因此咱們只須要include到咱們本身的source目錄也就是src文件夾下:

var path = require("path");//用於處理目錄的對象,提升開發效率(__dirname須要引入path後纔可使用)
var ROOT_PATH = path.resolve(__dirname);//獲取當前整個模塊文件所在目錄的完整絕對路徑
var BUILDS_PATH = path.resolve(ROOT_PATH, "builds");//獲取咱們的builds目錄完整絕對路徑
var SRC_PATH = path.resolve(ROOT_PATH, "src");//獲取到咱們的資源目錄src的完整路徑
module.exports = {
    entry: './src',
    output: {
        path: BUILDS_PATH,
        filename: 'bundle.js',
    },
    module: {
        loaders: [{
            test: /\.js/,
            loader: 'babel-loader',
            include: SRC_PATH,
        }],
    }
};
複製代碼

如今咱們能夠用ES6重寫一下咱們的 index.js,因爲咱們引入了babel,從這裏開始後面的全部例子都用ES6。

import $ from 'jquery';
$('body').html('Hello');
複製代碼

寫個小組件

如今咱們來寫一個小的Button組件,它須要有一些SCSS樣式(SCSS是SASS 3 引入新的語法,SASS是CSS的一種預處理器),一個HTML模板,和一些JS交互,那麼咱們先安裝一些咱們須要的東西。首先咱們用一個很是輕量的模板包Mustache(先後端分離的上古框架,瞭解就好),咱們也須要給SCSS和HTML配置loaders,由於結果會經過管道(pipe)從一個loader傳到另外一個,這裏有點繞,怎麼說呢,相似淨水器,咱們寫的SCSS經過第一個loader淨化以後變成CSS,CSS經過管道(pipe)流向第二個loader,CSS再經過第二個loader又變成能夠被 import 的STYLE模塊等等等過程,好了,因此咱們須要一個 sass-loader 加載器來「淨化」SCSS,一旦咱們獲得了CSS,會有多種方式處理它,這裏,咱們用一個 style-loader 加載器,它會接收一段CSS,而後動態地把它插入到頁面。

$ npm install mustache --save
$ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev
複製代碼

爲了讓webpack用管道(pipe)把東西從一個loader傳到另外一個loader,咱們簡傳入幾個loader方向由右到左,用一個 ! 分開,或者你能夠用一個數組經過 loaders 屬性,不是 loader:

{
test: /\.js/,
loader: 'babel-loader',
include: SRC_PATH,
}, {
test: /\.scss/,
loader: 'style-loader!css-loader!sass-loader',
// Or
//loaders: ['style-loader', 'css-loader', 'sass-loader'],
}, {
test: /\.html/,
loader: 'html-loader',
}
複製代碼

如今咱們把loaders準備好了,咱們來寫一個button:
路徑:webpack-your-bags/src/Components/Button.scss

.button {background: tomato; color: white; }
複製代碼

路徑:webpack-your-bags/src/Components/Button.html

<a class="button" href="{{link}}">{{text}}</a>
複製代碼

路徑:webpack-your-bags/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();

        // 渲染咱們的按鈕
        $(node).html(
            Mustache.render(template, {text})
        );

        // 增長監聽事件
        $('.button').click(this.onClick.bind(this));
    }
}
複製代碼

你的Button如今是百分之百的完總體了,不管何時導入,不管運行在什麼地方,它都有全部須要的東西,而後正確地渲染到那個地方,如今咱們只須要把咱們的Button經過index.js渲染到咱們的網頁上:

// import $ from 'jquery';
// $('body').html('Hello');

import Button from './Components/Button';
const button = new Button('google.com');
button.render('a');
複製代碼

咱們來運行一下webpack,而後刷新一下index.html頁面,你應該就能看見你的挺醜的button出來了。


如今你已經學會怎麼配置loader和怎麼給你的app的每個部分定義依賴,可是如今這些可能看起來已經不過重要,由於咱們要把這個例子再作一下改進。

代碼拆分

上面這個例子還不錯由於什麼都有,但咱們有時可能不須要咱們的Button,由於有些界面若是沒有 a 標籤的話,那就不須要咱們作無謂的把 a 標籤渲染成 Button 的操做了。也就是說咱們根本就不須要導入咱們上面定製的Button的樣式,模板,Mustache 和一切相關的東西了對吧?這個時候咱們就須要代碼拆分了。
代碼拆分是webpack對咱們去手動把Button導入須要的界面相關繁瑣操做的解決方案,也就是有了webpack你根本不用去找哪一個頁面須要導入,哪一個頁面不用導入。代碼拆分實質是在你的代碼裏定義分割點:你的代碼能夠輕鬆拆分開到部分單獨文件中,而後按需加載,語法很是簡單:

import $ from 'jquery';

// 這是一個拆分點
require.ensure([], () => {
  // 這裏全部的代碼都是須要被導入的
  // 在一個單獨的頁面裏
  const library = require('some-big-library');
  $('foo').click(() => library.doSomething());
});
複製代碼

在 require.ensure 的回調(即() => {})裏的任何東西都會被拆分紅代碼塊(chunk) ─── 頁面須要加載的時候會經過ajax單獨加載的包,這意味着咱們的包基本上有這些:

bundle.js
|- jquery.js
|- index.js // 咱們全部文件都要導入的代碼
chunk1.js
|- some-big-libray.js
|- index-chunk.js // 在回調裏面的代碼
複製代碼

你無需導入 chunk1.js ,webpack只有在須要它的時候纔會去加載它,這意味着你能夠把你的代碼按照各類邏輯分紅多塊,咱們將在下面修改咱們的 index.js ,讓只在頁面裏有 a 標籤的時候才加載 Button:

if (document.querySelectorAll('a').length) {
    require.ensure([], () => {
        const Button = require('./Components/Button').default;
        const button = new Button('google.com');
        button.render('a');
    });
}
複製代碼

注意,用 require 的時候,若是你想獲取Button對象 export default 的東西你須要經過 .default 去手動獲取它,由於 require 不會同時處理 export default 和 exports.obj,因此你必須指定返回哪一個,不過 import 能夠處理這個,因此它已經知道了(例如: import foo from 'bar'獲取到'bar'對象export default的東西, import {baz} from 'bar'獲取到'bar'對象的exports.baz的東西)。這裏的東西有點複雜,若是不是很理解又想理解能夠去深刻了解一下ES6和nodeJS。

如今 webpack 的 output 也應該會相應地不一樣了,咱們來用webpack --display-modules --display-chunks運行一下,看一下哪一個模塊在哪一個chunk裏

$ webpack --display-modules --display-chunks
Hash: c419d385603afdd301ab
Version: webpack 3.8.1
Time: 1489ms
      Asset     Size  Chunks                    Chunk Names
0.bundle.js   307 kB       0  [emitted]  [big]
  bundle.js  6.48 kB       1  [emitted]         main
chunk    {0} 0.bundle.js 305 kB {1} [rendered]
    [1] ./src/Components/Button.js 1.92 kB {0} [built]
    [2] ./node_modules/jquery/dist/jquery.js 268 kB {0} [built]
    [3] ./src/Components/Button.html 70 bytes {0} [built]
    [4] ./node_modules/mustache/mustache.js 19.4 kB {0} [built]
    [5] ./src/Components/Button.scss 1.16 kB {0} [built]
    [6] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {0} [built]
    [7] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} [built]
    [8] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} [built]
    [9] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} [built]
chunk    {1} bundle.js (main) 615 bytes [entry] [rendered]
    [0] ./src/index.js 615 bytes {1} [built]
   [0] ./src/index.js 615 bytes {1} [built]
   [1] ./src/Components/Button.js 1.92 kB {0} [built]
   [2] ./node_modules/jquery/dist/jquery.js 268 kB {0} [built]
   [3] ./src/Components/Button.html 70 bytes {0} [built]
   [4] ./node_modules/mustache/mustache.js 19.4 kB {0} [built]
   [5] ./src/Components/Button.scss 1.16 kB {0} [built]
   [6] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {0} [built]
   [7] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} [built]
   [8] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} [built]
   [9] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} [built]
複製代碼

能夠看到咱們的入口( bundle.js )如今只有一些webpack的邏輯,其它的東西(jQuery, Mustache, Button) 都在 0.bundle.js ,只有頁面上有 a 標籤的時候纔會加載 0.bundle.js,爲了讓webpack知道用ajax加載的時候在哪能找到chunks,咱們必須在咱們的配置中加一行:

output: {
    path: BUILDS_PATH,
    filename: 'bundle.js',
    publicPath: 'builds/',
},
複製代碼

output.publicPath 選項告訴 webpack 相對於當前的文件在哪能找到構建後的資源,如今訪問咱們的頁面咱們會看到一切都正常工做,但更重要的是,咱們能看到,因爲頁面上有 a 標籤,所以webpack準確地加載了咱們拆分出來的代碼塊 0.bundle.js:


若是咱們的頁面上沒有 a 標籤,就只有 bundle.js 會被加載,這點可讓你智能地把你的app裏的大片邏輯拆分開,讓每一個頁面只加載它真正須要的,咱們也能夠給咱們拆分出來的代碼包的Chunk Names命名 ,咱們能夠用語義的名字,你能夠經過傳給 require.ensure 第三個參數來指定:

require.ensure([], () => {
const Button = require('./Components/Button').default;
const button = new Button('google.com');
button.render('a');
}, 'button');
複製代碼

這樣就會生成代碼包的 Chunk Names 就是 button 而不是空白了:

$ webpack
Hash: 50ed6a7993b581f0bf0a
Version: webpack 3.8.1
Time: 1524ms
      Asset     Size  Chunks                    Chunk Names
0.bundle.js   307 kB       0  [emitted]  [big]  button
  bundle.js  6.49 kB       1  [emitted]         main
   [0] ./src/index.js 625 bytes {1} [built]
   [1] ./src/Components/Button.js 1.92 kB {0} [built]
   [3] ./src/Components/Button.html 70 bytes {0} [built]
   [5] ./src/Components/Button.scss 1.16 kB {0} [built]
   [6] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {0} [built]
    + 5 hidden modules
複製代碼

加入第二個組件

如今什麼都有了已經很是酷了,不過咱們再來加一個組件看看好很差使:
路徑:webpack-your-bags/src/Components/Header.scss

.header {
  font-size: 3rem;
}
複製代碼

路徑:webpack-your-bags/src/Components/Header.html

<header class="header">{{text}}</header>
複製代碼

路徑:webpack-your-bags/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 })

        );

    }

}
複製代碼

咱們在咱們的 index.js 裏把它渲染一下:

// 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');
    });
}
複製代碼

如今用 --display-chunks --display-modules 來看一下 webpack 的 output:

$ webpack --display-modules --display-chunks
Hash: 66f9e900ac553f5d66eb
Version: webpack 3.8.1
Time: 1646ms
      Asset     Size  Chunks                    Chunk Names
0.bundle.js   306 kB       0  [emitted]  [big]
1.bundle.js   307 kB       1  [emitted]  [big]
  bundle.js  6.56 kB       2  [emitted]         main
chunk    {0} 0.bundle.js 305 kB {2} [rendered]
    [2] ./src/Components/Header.js 1.7 kB {0} [built]
    [3] ./node_modules/jquery/dist/jquery.js 268 kB {0} {1} [built]
    [4] ./node_modules/mustache/mustache.js 19.4 kB {0} {1} [built]
    [5] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} {1} [built]
    [6] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} {1} [built]
    [7] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} {1} [built]
   [11] ./src/Components/Header.html 62 bytes {0} [built]
   [12] ./src/Components/Header.scss 1.16 kB {0} [built]
   [13] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
chunk    {1} 1.bundle.js 305 kB {2} [rendered]
    [1] ./src/Components/Button.js 1.92 kB {1} [built]
    [3] ./node_modules/jquery/dist/jquery.js 268 kB {0} {1} [built]
    [4] ./node_modules/mustache/mustache.js 19.4 kB {0} {1} [built]
    [5] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} {1} [built]
    [6] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} {1} [built]
    [7] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} {1} [built]
    [8] ./src/Components/Button.html 70 bytes {1} [built]
    [9] ./src/Components/Button.scss 1.16 kB {1} [built]
   [10] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
chunk    {2} bundle.js (main) 601 bytes [entry] [rendered]
    [0] ./src/index.js 601 bytes {2} [built]
   [0] ./src/index.js 601 bytes {2} [built]
   [1] ./src/Components/Button.js 1.92 kB {1} [built]
   [2] ./src/Components/Header.js 1.7 kB {0} [built]
   [3] ./node_modules/jquery/dist/jquery.js 268 kB {0} {1} [built]
   [4] ./node_modules/mustache/mustache.js 19.4 kB {0} {1} [built]
   [5] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} {1} [built]
   [6] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} {1} [built]
   [7] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} {1} [built]
   [8] ./src/Components/Button.html 70 bytes {1} [built]
   [9] ./src/Components/Button.scss 1.16 kB {1} [built]
  [10] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
  [11] ./src/Components/Header.html 62 bytes {0} [built]
  [12] ./src/Components/Header.scss 1.16 kB {0} [built]
  [13] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
複製代碼

能看見這裏有一個問題:咱們的兩個組件都會用到 jQuery 和 Mustache,也就是說這兩個依賴在chunk裏有重複,雖然webpack 默認會作一點點的優化,可是它也能夠以 plugin(插件) 的形式來給 webpack 提供更強大的功能。

plugin 跟 loader 不一樣,它不是對指定的文件執行一些操做,而是對全部文件進行處理,作一些更高級的操做,但不必定非得像 loader 同樣是轉換,webpack 中有自帶 plugin 能夠作各類優化,此時咱們比較感興趣的一個是 CommonChunksPlugin:它分析你的chunk的遞歸依賴,而後把它們抽出來放在別的地方,能夠是一個徹底獨立的文件或者是你的主文件。

在咱們如今的例子中,咱們須要把公用的依賴移到咱們的entry(入口) 文件,若是全部的文件須要jQuery 和 Mustache,咱們也能夠把它往上層移動,咱們來更新一下咱們的配置文件webpack.config.js :

......
var webpack = require('webpack');
module.exports = {
    entry:   './src',
    output:  {
      // ...
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name:      'main', // 將依賴移到咱們的主文件
            children:  true, // 在全部被拆分的代碼塊中尋找共同的依賴關係
            minChunks: 2, // 在被提取以前,一個依賴要出現多少次(也就是一個依賴會在遍歷全部拆分的代碼塊時被重複發現多少次)
        }),
    ],
    module:  {
      // ...
    }
};
複製代碼

若是咱們從新運行一下 webpack --display-modules --display-chunks,咱們能夠看到如今比以前好多了,由於 0.bundle.js 和 1.bundle.js 的共同部分都被移到了 bundle.js 。

$ webpack --display-modules --display-chunks
Hash: 22fbf6bdd63c4bbbb096
Version: webpack 3.8.1
Time: 1554ms
      Asset     Size  Chunks                    Chunk Names
0.bundle.js  3.36 kB       0  [emitted]
1.bundle.js  3.58 kB       1  [emitted]
  bundle.js   310 kB       2  [emitted]  [big]  main
chunk    {0} 0.bundle.js 3.12 kB {2} [rendered]
    [7] ./src/Components/Header.js 1.7 kB {0} [built]
   [11] ./src/Components/Header.html 62 bytes {0} [built]
   [12] ./src/Components/Header.scss 1.16 kB {0} [built]
   [13] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
chunk    {1} 1.bundle.js 3.37 kB {2} [rendered]
    [6] ./src/Components/Button.js 1.92 kB {1} [built]
    [8] ./src/Components/Button.html 70 bytes {1} [built]
    [9] ./src/Components/Button.scss 1.16 kB {1} [built]
   [10] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
chunk    {2} bundle.js (main) 303 kB [entry] [rendered]
    [0] ./src/index.js 601 bytes {2} [built]
    [1] ./node_modules/style-loader/lib/urls.js 3.01 kB {2} [built]
    [2] ./node_modules/jquery/dist/jquery.js 268 kB {2} [built]
    [3] ./node_modules/mustache/mustache.js 19.4 kB {2} [built]
    [4] ./node_modules/css-loader/lib/css-base.js 2.26 kB {2} [built]
    [5] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {2} [built]
   [0] ./src/index.js 601 bytes {2} [built]
   [1] ./node_modules/style-loader/lib/urls.js 3.01 kB {2} [built]
   [2] ./node_modules/jquery/dist/jquery.js 268 kB {2} [built]
   [3] ./node_modules/mustache/mustache.js 19.4 kB {2} [built]
   [4] ./node_modules/css-loader/lib/css-base.js 2.26 kB {2} [built]
   [5] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {2} [built]
   [6] ./src/Components/Button.js 1.92 kB {1} [built]
   [7] ./src/Components/Header.js 1.7 kB {0} [built]
   [8] ./src/Components/Button.html 70 bytes {1} [built]
   [9] ./src/Components/Button.scss 1.16 kB {1} [built]
  [10] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
  [11] ./src/Components/Header.html 62 bytes {0} [built]
  [12] ./src/Components/Header.scss 1.16 kB {0} [built]
  [13] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
複製代碼

你也能夠經過不提供一個公用的塊名(chunk name)或者是指定 async: true 來讓公用的dependency被異步加載,webpack有很是多這樣強大的智能優化。我不可能全列出來,但做爲練習,讓咱們嘗試來給咱們的app建一個生產版本。

生產和優化

好吧,首先,咱們加幾個 plugin 到配置文件中,但咱們只想在 NODE_ENV 等於 production 時才加載這些 plugin ,因此讓咱們來給配置文件加一些邏輯,由於只是一個 js 文件,因此比較簡單:

......
var webpack    = require('webpack');
var production = process.env.NODE_ENV === 'production';
var plugins = [
    new webpack.optimize.CommonsChunkPlugin({
            name:      'main', // 將依賴移到咱們的主文件
            children:  true, // 在全部被拆分的代碼塊中尋找共同的依賴關係
            minChunks: 2, // 在被提取以前,一個依賴要出現多少次(也就是一個依賴會在遍歷全部拆分的代碼塊時被重複發現多少次)
    }),
];

if (production) {
    plugins = plugins.concat([
       // 生產環境的插件放在這裏
    ]);
}

module.exports = {
    entry:   './src',
    output:  {
        path:       BUILDS_PATH,
        filename:   'bundle.js',
        publicPath: 'builds/',
    },
    plugins: plugins,
    ......
};
複製代碼

第二步,Webpack有一些設置咱們也能夠在生產中關閉:

......
if (production) {
    plugins = plugins.concat([

        new webpack.LoaderOptionsPlugin({
            debug: true
        }),
        
        ......
    ]);
}

module.exports = {
	//debug:   !production,
    //loaders的調試模式已經在webpack3及以上版本徹底移除,爲了保持與舊的loaders的兼容性,loaders能夠經過LoaderOptionsPlugin插件切換到調試模式
    devtool:production ? false : 'eval',
    ......
    }
複製代碼

第1個設置爲非debug模式,這意味着系統不會處理太多的代碼,讓你能夠更輕鬆地調試本地的資源,第2個是控制 sourcemap 生成的,webpack有幾個方法能夠呈現 sourcemap,自帶的 eval 是最好的,生產環境咱們不太關心 sourcemap 的事,因此咱們把它禁用掉,下面加一下咱們的生產插件(plugins):

if (production) {
    plugins = plugins.concat([

        // 這個插件會尋找相似的塊和文件而後進行合併
        //new webpack.optimize.DedupePlugin(),
        //webpack3版本以後這個插件已經被移除

        // 這個插件會根據模塊在你的程序中使用的次數來優化模塊
        //new webpack.optimize.OccurenceOrderPlugin(),
        //webpack2版本以後這個插件的功能已是默認開啓,無需調用

        // 這個插件能夠防止webpack建立過小以致於不值得單獨加載的塊
        new webpack.optimize.MinChunkSizePlugin({
            minChunkSize: 51200, // ~50kb
        }),

        //這個插件會最小化全部最終資源的javascript代碼
        new webpack.optimize.UglifyJsPlugin({
            mangle:   true,
            compress: {
                warnings: false, //  阻止難看的警告
            },
        }),
        //loaders的最小化模式也會在webpack3或者之後的版本中移除

        // 這個插件容許咱們能夠在生產中設置各類爲錯誤的變量,以免它們被編譯在咱們的最後打包的代碼中
        new webpack.DefinePlugin({
            __SERVER__:      !production,
            __DEVELOPMENT__: !production,
            __DEVTOOLS__:    !production,
            'process.env':   {
                BABEL_ENV: JSON.stringify(process.env.NODE_ENV),
            },
        }),

    ]);
}
複製代碼

上面這幾個是我最經常使用的,不過webpack提供不少插件(plugin)用來優化處理你的modules和chunks,npm 上也有其餘人寫的有各類功能的插件(plugin),自行按需選擇。

關於生產環境下的資源的另外一個方面是,咱們有時想給咱們的打包後的資源加版本,還記得在上面咱們把 output.filename 設置爲 bundle.js 吧,這有幾個變量能夠在命名的時候用,其中一個是 [hash] ,表明最終資源文件的版本,同時用 output.chunkFilename 給chunk也加上版本,如今讓咱們來修改咱們的配置文件:

output: {
    path:          'builds',
    filename:      production ? '[name]-[hash].js' : 'bundle.js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath:    'builds/',
},
複製代碼

因爲沒有什麼特別好的辦法能動態地獲取這個簡化版app編譯後的bundle的名字(因爲加了版本號),因此只是在生產環境上給資源加版本,屢次編譯後目錄裏帶版本的最終包會愈來愈多,因此爲了節省空間,還得加一個第三方插件來每次清理咱們以前的生產版本(output 裏面的 path):

$ npm install clean-webpack-plugin --save-dev

並把它添加到咱們的配置裏:

var webpack     = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');

// ...

if (production) {
    plugins = plugins.concat([

        // 清理之前的版本/文件夾
        // 編譯咱們的最終資源
        new CleanPlugin('builds'),
        ......
        }
        ......
複製代碼

OK,作完了幾個小優化,下面比較一下結果:

$ webpack
Hash: 8b942bcf553d3d782e6c
Version: webpack 3.8.1
Time: 1542ms
                    Asset     Size  Chunks                    Chunk Names
0-294790a46bbc09afc580.js  4.34 kB       0  [emitted]
1-24fa8b23bd35ecac8a8f.js  4.57 kB       1  [emitted]
                bundle.js   348 kB       2  [emitted]  [big]  main
......
複製代碼
$ NODE_ENV=production webpack
clean-webpack-plugin: D:\other\webpack-your-bags\builds has been removed.
Hash: 22674bf33e7cdefb2776
Version: webpack 3.8.1
Time: 2973ms
                       Asset    Size  Chunks             Chunk Names
main-22674bf33e7cdefb2776.js  101 kB       0  [emitted]  main
......
複製代碼

那麼webpack到底作了什麼呢?首先,因爲咱們的例子是很是輕量級的,因此咱們的兩個異步塊不是HTTP請求,所以Webpack將它們合併到入口。並且全部的東西都被最小化了,咱們從總共356KB的3個HTTP請求到101KB的1個HTTP請求。

但webpack居然能毫無徵兆地就把一些大的js文件給消除掉?

是的,的確是這樣,但這只是由於咱們的應用程序很是的小才發生的。如今考慮到一點:你根本想不到什麼被合併,而且在什麼地方何時被合併。若是你拆分的代碼塊忽然有了更多的依賴,那麼這些拆分塊將被移動到一個異步塊,而不是被合併。 若是這些拆分塊過於類似以致於不值得單獨加載,它們將被合併,你僅僅須要配置一下,webpack就會用最好的方式自動優化你的應用程序。不用你去手動操做,也不用去想依賴在哪些時候或者哪些地方須要被依賴,一切都是全自動。


你可能已經注意到咱們沒有使用任何設置來最小化咱們的HTML和CSS,那是由於在默認狀況下,若是調試(debug)選項是咱們以前所說的false的話, css-loader 和 html-loader 就會去幫咱們作這件事。這也是爲何UglifyJsPlugin是一個單獨的插件(而不是一個loader)的緣由:由於在webpack中沒有js-loader,由於webpack中自己就是JS的加載器。

提取

好吧,如今你可能已經注意到,因爲本教程的一開始,咱們的樣式就已經在網頁裏面,而且作出了一個很是醜的按鈕。如今若是咱們能夠把全部的樣式經過webpack構建出一個最終的CSS文件,豈不是很好?咱們固然能夠啦,讓咱們在一些外部的插件(plugin)的幫助下實現這種操做:

$ npm install extract-text-webpack-plugin --save-dev

這個插件具體是作什麼的我來講一下:就是從你的最終的資源中提取一些指定類型的內容,這個常常用在CSS上,所以讓咱們來配置一下webpack.config.js(在這裏切合版本的更替,咱們將用module.rules替換module.loaders,由於在將來module.loaders將徹底被module.rules代替):

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'), // <=== 內容將被打包到哪裏
    new webpack.optimize.CommonsChunkPlugin({
            name:      'main', // 將依賴移到咱們的主文件
            children:  true, // 在全部被拆分的代碼塊中尋找共同的依賴關係
            minChunks: 2, // 在被提取以前,一個依賴要出現多少次(也就是一個依賴會在遍歷全部拆分的代碼塊時被重複發現多少次)
    }),
];

......

module.exports = {
   	......
    plugins: plugins,
    module:  {
		rules: [{
			test: /\.js/,
			loader: 'babel-loader',
			include: SRC_PATH,
		},{
			test: /\.html/,
			loader: 'html-loader',
		},{
			test: /\.scss$/,
			use: ExtractPlugin.extract({
				fallback: 'style-loader',
				use: ['css-loader', 'sass-loader']
			})
		}],
        // loaders: [{
		// 	test: /\.js/,
		// 	loader: 'babel-loader',
		// 	include: SRC_PATH,
		// },{
		// 	test: /\.html/,
		// 	loader: 'html-loader',
		// },
		// {
		// 	test: /\.scss/,
		// 	loader: 'style-loader!css-loader!sass-loader',
		// 	// Or
		// 	// loaders: ['style-loader', 'css-loader', 'sass-loader'],
		// }
		// ]
    }
};
複製代碼

如今 rules 的 extract 方法有兩個參數:第一個是當咱們在一個代碼塊('style-loader')中,未提取的CSS該怎麼辦,二是當資源在一個主文件('css-loader!sass-loader')中時該怎麼轉換。如今若是咱們在一個代碼塊中,那麼在使用 loader 前咱們不能只是奇蹟般地讓這個代碼塊中的CSS追加到生成的CSS中,但對於那些在主文件中找到的樣式,它們就應該被打包到一個builds/bundle.css文件中。如今讓咱們來測試一下,添加一個小主樣式表到咱們的應用程序中:
路徑:webpack-your-bags/src/styles.scss

body {
  font-family: sans-serif;
  background: darken(white, 0.2);
}
複製代碼

修改咱們的 index.js :

import './styles.scss';
......
複製代碼

讓咱們運行webpack,果真咱們如今有一個bundle.css,如今咱們能夠在咱們的HTML文件導入:

$ webpack
Hash: 6890269852e3f4363514
Version: webpack 3.8.1
Time: 1607ms
                    Asset      Size  Chunks                    Chunk Names
0-4a6171ca8018a700cec4.js   4.26 kB       0  [emitted]
1-ffe316264a9c9fafbb4c.js   4.58 kB       1  [emitted]
                bundle.js    348 kB       2  [emitted]  [big]  main
               bundle.css  70 bytes       2  [emitted]         main
......複製代碼


這個時候刷新頁面可能會出錯,由於你必須將全部CSS文件合併到一個文件裏面去,你能夠傳遞選項ExtractTextPlugin('bundle.css', {allChunks: true}) ,你也能夠在這裏的文件名使用變量,若是你想要一個版本的樣式你只須要作ExtractTextPlugin('[name]-[hash].css')

載入圖片

如今對咱們全部的JavaScript文件來講是很是完美的了,但咱們尚未談論的一個話題是具體的資源:圖像、字體等在webpack裏是如何工做的而且是如何通過優化的呢?下面讓咱們從網站上拷一張圖片,而且咱們將使用它做爲咱們的網頁背景,由於我見過人們把它放在Geocities上並且看起來挺酷的:


讓咱們保存這一張圖片在 webpack-your-bags/img/puppy.jpg ,並相應地更新咱們的styles.scss:

body{
	font-family:sans-serif;
	background:url('../img/puppy.jpg') no-repeat 0 0;
	background-size:50%;
}
複製代碼

若是你這樣作, webpack 會義正詞嚴地告訴你「它嗎的究竟是作個什麼png出來」,由於咱們沒有加載它。這裏有兩個原生的加載器,咱們能夠用它們來處理具體的資源: file-loader 和 url-loader : 第一個將只返回一個沒有什麼特別變化的資源的URL,讓你獲得一個在這個過程當中產生的版本文件(這是默認的行爲) ;第二個將資源內聯到一個 data:image/jpeg;base64 的地址。

事實上這兩個插件不是對立的關係:好比你的背景是一個2MB的圖像,那就不要內聯,最好單獨加載它。另外一方面若是它是一個2KB的小圖標,最好內聯,節省HTTP請求。因此咱們就設置了兩個:

$ npm install url-loader file-loader --save-dev

{
    test:   /\.(png|gif|jpe?g|svg)$/i,
    loader: 'url-loader?limit=10000',
},
複製代碼

在這裏,咱們向 url-loader 傳遞一個 limit 查詢參數告訴它:若是資產大於10KB或者更小則內聯,不然,就退回到了file-loader並引用它。該語法被稱爲查詢字符串,你用它來配置加載器(loader),或者你也能夠經過一個對象配置加載器:

{
    test:   /\.(png|gif|jpe?g|svg)$/i,
    loader: 'url-loader',
    query: {
      limit: 10000,
    }
}
複製代碼

好吧,讓咱們來看看結果:

$ webpack
Hash: e672f7becf7b049e759d
Version: webpack 3.8.1
Time: 1653ms
                    Asset     Size  Chunks                    Chunk Names
0-4a6171ca8018a700cec4.js  4.26 kB       0  [emitted]
1-ffe316264a9c9fafbb4c.js  4.58 kB       1  [emitted]
                bundle.js   348 kB       2  [emitted]  [big]  main
               bundle.css  2.88 kB       2  [emitted]         main
複製代碼

正如咱們看到的那樣:沒有一個JPG被打包,由於咱們的小狗圖片(2KB)比配置的尺寸(10KB)小,因此它就被內聯了。這意味着若是咱們訪問咱們的網頁,無需加載圖片咱們就能夠沉浸在咱們的小狗霸主的榮耀下了。


這是很是強大的,由於這意味着webpack如今能夠根據大小/HTTP請求的比值來智能優化具體的資源。有一個良好的加載器,你甚至能夠進一步的把全部東西聯繫起來,最多見的一個是image-loader,將在打包它們以前壓縮全部圖片。它甚至有一個?bypassOnDebug查詢參數讓你只在生產的時候作這種事。有不少這樣的插件,我鼓勵你在本文的結尾看看那些插件。

咱們如今將把它作活起來

咱們上面所作的建設都只是爲了體現webpack的強大功能,如今讓咱們更多的關注本地代碼調試。
可能當你提到構建工具時,常常注意到的一個比較大的缺陷:實時加載:LiveReload,BrowserSync,不須要等過久。可是當咱們修改一點什麼東西的時候咱們但願整個頁面會自動進行刷新,讓它一步就作到咱們想要的效果的就是所謂的模塊更換或熱重載。它的想法是,既然webpack知道咱們依賴樹的每一個模塊的位置,那麼一個小小的變化就能用新文件簡單地修補來體現。更清楚的表達就是:你所作的修改實時地表如今頁面上可是你卻沒發現頁面已經從新刷新過了。

爲了要使用HMR(熱模塊加載),咱們須要一個能爲咱們的熱資源服務的服務器。webpack 中有自帶的 dev-server,咱們能夠利用這個,因此讓咱們來安裝它:

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

如今運行咱們開發的服務器,沒有比這個更簡單的了,只須要運行下面的命令:

$ webpack-dev-server

如今請讓咱們訪問web服務器 http://localhost:8080/webpack-dev-server/ 。你會看到你平時的頁面,可是如今咱們修改一個SASS文件,像變魔術同樣:


如今,每當咱們運行 webpack-dev-server 它就已是HMR模式。請注意,咱們使用 webpack-dev-server 這裏是爲了服務咱們的熱資源,但你也可使用其餘幾個選項,像 Express server 。webpack 提供一箇中間件,而且你可使用這個中間件把即插即用的HMR移到其餘服務器上。

乾淨或者凋零

若是你一直在跟進這門教程,你可能已經注意到一些奇怪的東西:爲何加載器嵌套在 module.rules(module.loaders) 但插件卻不是呢?固然這是由於你還能夠向 module 投入其它的東西了!webpack 不僅是有加載器,它還具備前加載器,後加載器:在代碼執行以前或以後,咱們主要的加載器。讓咱們舉個例子:我敢確定,我寫這篇文章的代碼是可怕的,因此咱們申請在咱們改造它以前 ESLint 咱們的代碼:

$ npm install eslint eslint-loader babel-eslint --save-dev

如今讓咱們建立一個簡單的.eslintrc文件,我知道等一下會運行失敗:
路徑:webpack-your-bags/.eslintrc

{
    "parser": "babel-eslint",
    "rules": {
        "quotes": 2
    }
}
複製代碼

如今加入咱們的前加載器,咱們只是像之前同樣使用相同的語法,只是在 module.preLoaders 裏(module.preLoaders已經在webpack2版本以後移除,須要用到preLoader的地方能夠改到rules的enfore中進行配置):

module:  {
        // preLoaders: [{
        //     test: /\.js/,
        //     loader: 'eslint-loader',
        // }],
        rules: [{
            test: /\.js$/,
            exclude: /node_modules/,
            enforce: "pre",
            loader: "eslint-loader"
        }],
}
複製代碼

如今,若是咱們運行webpack,果真失敗:

$ webpack
Hash: 891f6c68504d0f787afa
Version: webpack 3.8.1
Time: 2335ms
                    Asset     Size  Chunks                    Chunk Names
0-4a6171ca8018a700cec4.js  4.26 kB       0  [emitted]
1-ffe316264a9c9fafbb4c.js  4.58 kB       1  [emitted]
                bundle.js   348 kB       2  [emitted]  [big]  main
               bundle.css  2.88 kB       2  [emitted]         main
   [0] ./src/index.js 627 bytes {2} [built] [1 error]
   [1] ./src/styles.scss 41 bytes {2} [built]
   [7] ./src/Components/Button.js 1.92 kB {1} [built] [1 error]
   [8] ./src/Components/Header.js 1.7 kB {0} [built] [1 error]
   [9] ./src/Components/Button.html 70 bytes {1} [built]
  [10] ./src/Components/Button.scss 1.16 kB {1} [built]
  [11] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
  [12] ./src/Components/Header.html 62 bytes {0} [built]
  [13] ./src/Components/Header.scss 1.16 kB {0} [built]
  [14] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
  [15] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/styles.scss 297 bytes [built]
  [16] ./img/puppy.jpg 2.8 kB [built]
    + 5 hidden modules

ERROR in ./src/Components/Button.js

D:\other\webpack-your-bags\src\Components\Button.js
   1:15  error  Strings must use doublequote  quotes
   2:22  error  Strings must use doublequote  quotes
   3:22  error  Strings must use doublequote  quotes
   4:8   error  Strings must use doublequote  quotes
  25:11  error  Strings must use doublequote  quotes
......
複製代碼

上面報錯留給你們本身解決,舒適提示:將報錯內容「Strings must use doublequote quotes」翻譯一下。若是暫時解決不了則註釋掉 eslint 相關內容接着進行下面內容。
讓咱們運行前加載器的另外一個例子:對於每一個組件咱們導入其同名樣式表,而且具備相同名稱的模板。讓咱們用一個前加載器來自動加載任何帶有相同的名稱的文件做爲一個模塊:

$ npm install baggage-loader --save-dev

{
	test: /\.js/,
	enforce: "pre",
    loader: 'baggage-loader?[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';
複製代碼

正如你所看到的如此強大的前加載器同樣,後加載器也是如此。看看可用的裝載機在本文末尾的列表,你必定會發現不少用例在裏面。看看這篇文章的結尾那個可用的加載器列表,你確定會發現更多用例。

附加

關於 webpack-dev-server 咱們不只能夠經過直接運行命令行 webpack-dev-server 開啓咱們的web服務器,並且能夠在 package.json 文件 scripts 中配置快捷命令行,如:

"scripts": {
  "start:dev": "webpack-dev-server"
}
複製代碼

而後咱們就能夠直接運行命令行 npm run start:dev (等同於 webpack-dev-server )來開啓咱們的web服務器了。

你想了解更多嗎?

目前,咱們的應用程序是至關小的因此仍是看不出 webpack 的優點,但隨着這個應用程序愈來愈大,webpack 就會變得有用,你也可以獲得更多的領悟,咱們實際的依賴關係樹是什麼。咱們可能會作對或作錯,咱們的應用程序何時會變得步履維艱等等等。如今應用程序的內部,webpack 知道一切,但你要有禮貌地讓它展現它所知道的。您能夠經過產生一個配置文件來運行如下命令:

webpack --profile --json > stats.json

第一個標誌(--profile)告訴 webpack 生成一個配置文件,第二個(--json)產生它的JSON,最後一個(> stats.json) 咱們讓這一切都輸出到JSON文件。如今有不少個網站都在分析這些配置文件,但 webpack 提供了一個官方的來一一解說這個信息。因此去 webpack 分析和導入你的JSON文件。如今進入「Modules」選項卡,你應該會看到你的依賴關係樹的可視化表示:


一個點越紅,越對你最後的包有影響。在咱們這個應用程序下,這個點是 Jquery ,由於它在咱們全部的模塊是最重要的。看到全部選項卡,看一下週圍,你不會在咱們這個小應用程序裏學到太多,但這個工具倒是很是重要的由於它能洞察到你的依賴樹和最終資源。如今,正如我所說的,其它的服務可以提供洞察你的配置文件的功能,另一個我喜歡的是 Webpack Visualizer: 它用一個餅圖表示你的應用程序的依賴佔用了一個什麼空間,固然它也能表示咱們的案例:


這是咱們的所有

如今我所知道的狀況是:webpack 已經徹底取代 grunt 和 gulp:我以前大部分都是用它們可是如今我是用 webpack 來處理,而對於其他的一小部分我只是用NPM腳本。每一個例子,咱們過去有一個共同的任務是咱們的API文檔經過 Aglio 轉換爲HTML,這很容易作到,像這樣咱們修改咱們的 package.json文件:

{
  "scripts": {
    "build": "webpack",
    "build:api": "aglio -i docs/api/index.apib -o docs/api/index.html"
  }
}
複製代碼

可是若是你有更復雜的跟打包和資源的任務在你的 gulp ,webpack 在其餘系統的構建會發揮更好的做用。看看這個例子是如何把webpack整合在 gulp裏的:

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();
  });
});
複製代碼

而就是它,由於 webpack 中還具備節點的API,所以它能夠很容易地在其它構建系統中使用,在任何狀況下,你會發現它的思想無處不在。

不管如何,我認爲這是能夠爲你作的一個 webpack 的足夠好的鳥瞰圖。你可能會認爲,咱們已經在這篇文章中介紹了不少,但咱們認爲只是皮毛而已:多個入口點,預取,背景更換等等。webpack 是一個使人印象深入而且功能強大的工具,這固然是由於它比傳統的構建工具更加不透明,我不會否定這一點。可是,一旦你知道如何馴服它,它就會發出甜美的聲音進入你的耳朵讓你耐人尋味。我把它用在幾個項目裏,它提供了優化和自動化,我不能想象本身要來回敲打腦殼多少次才能知道所須要的資源何時在什麼地方。

資源

備註

各分段代碼包已經上傳到GitHub,訪問地址:

github.com/hrh94/Webpa…

相關文章
相關標籤/搜索