Linux中國

Webpack 2 入門

Webpack 2 一旦文檔完成,就將結束 Beta 測試期。不過這並不意味著你現在不能開始使用第 2 版,前提是你知道怎麼配置它。(LCTT 譯註:Webpack 2.1 已經發布。)

Webpack 是什麼

簡單來說,Webpack 是一個 JavaScript 模塊打包器。然而,自從它發布以來,它發展成為了你所有的前端代碼的管理工具(或許是有意的,或許是社區的意願)。

老式的任務運行器的方式:你的標記、樣式和 JavaScript 是分離的。你必須分別管理它們每一個,並且你需要確保每一樣都達到產品級

任務運行器 task runner ,例如 Gulp,可以處理許多不同的 預處理器 preprocesser 轉換器 transpiler ,但是在所有的情景下,它都需要一個輸入源並將其壓縮到一個編譯好的輸出文件中。然而,它是在每個部分的基礎上這樣做的,而沒有考慮到整個系統。這就造成了開發者的負擔:找到任務運行器所不能處理的地方,並找到適當的方式將所有這些模塊在生產環境中聯合在一起。

Webpack 試圖通過提出一個大膽的想法來減輕開發者的負擔:如果有一部分開發過程可以自動處理依賴關係會怎樣?如果我們可以簡單地寫代碼,讓構建過程最終只根據需求管理自己會怎樣?

Webpack 的方式:如果 Webpack 了解依賴關係,它會僅捆綁我們在生產環境中實際需要的部分

如果你過去幾年一直參與 web 社區,你已經知道解決問題的首選方法:使用 JavaScript 來構建。而且 Webpack 嘗試通過 JavaScript 傳遞依賴關係使得構建過程更加容易。不過這個設計真正的亮點不是簡化代碼管理部分,而是管理層由 100% 有效的 JavaScript 實現(具有 Nodejs 特性)。Webpack 能夠讓你編寫有效的 JavaScript,更好更全面地了解系統。

換句話來說:你不需要為 Webpack 寫代碼。你只需要寫項目代碼。而且 Webpack 就會持續工作(當然需要一些配置)。

簡而言之,如果你曾經遇到過以下任何一種情況:

  • 載入有問題的依賴項
  • 意外引入一些你不需要在生產中用上的 CSS 樣式表和 JS 庫,使項目膨脹
  • 意外的兩次載入(或三次)庫
  • 遇到作用域的問題 —— CSS 和 JavaScript 都會有
  • 尋找一個讓你在 JavaScript 中使用 Node/Bower 模塊的構建系統,要麼就得依靠一個令人發狂的後端配置才能正確地使用這些模塊
  • 需要優化 資產 asset 交付,但你擔心會弄壞一些東西

等等……

那麼你可以從 Webpack 中收益了。它通過讓 JavaScript 輕鬆處理你的依賴關係和載入順序,而不是通過開發者的大腦。最好的部分是,Webpack 甚至可以純粹在伺服器端運行,這意味著你還可以使用 Webpack 構建漸進增強式網站。

第一步

我們將在本教程中使用 Yarn(運行命令 brew install yarn) 代替 npm,不過這完全取決於你的喜好,它們做同樣的事情。在我們的項目文件夾中,我們將在終端窗口中運行以下代碼,將 Webpack 2 添加到我們的全局軟體包以及本地項目中:

yarn global add webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.9
yarn add --dev webpack@2.1.0-beta.25 webpack-dev-server@2.1.0-beta.9

我們接著會通過項目根目錄的一個 webpack.config.js 文件來聲明 webpack 的配置:

'use strict';

const webpack = require('webpack');

module.exports = {
  context: __dirname + '/src',
  entry: {
    app: './app.js',
  },
  output: {
    path: __dirname + '/dist',
    filename: '[name].bundle.js',
  },
};

注意:此處 __dirname 是指你的項目根目錄

記住,Webpack 「知道」你的項目發生了什麼。它通過閱讀你的代碼來實現(別擔心,它簽署了保密協議 😀 )。Webpack 基本上執行以下操作:

  1. context 文件夾開始……
  2. ……它查找 entry 下的文件名……
  3. ……並讀取其內容。每一個 importES6)或 require()(Nodejs)的依賴會在它解析代碼的時候找到,它會在最終構建的時候打包這些依賴項。然後,它會搜索那些依賴項以及那些依賴項所依賴的依賴項,直到它到達「樹」的最底端 —— 只打包它所需要的,沒有其它東西。
  4. Webpack 從 context 文件夾打包所有東西到 output.path 文件夾,使用 output.filename 命名模板來為其命名(其中 [name] 被替換成來自 entry 的對象的鍵)。

所以如果我們的 src/app.js 文件看起來像這樣(假設我們事先運行了 yarn add --dev moment):

'use strict';

import moment from 'moment';
var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log( rightNow );

// "October 23rd 2016, 9:30:24 pm"

我們應該運行:

webpack -p

注意:p 標誌表示「生產」模式,這會壓縮輸出文件。

它會輸出一個 dist/app.bundle.js,並將當前日期和時間列印到控制台。要注意 Webpack 會自動識別 上面的 'moment' 指代的是什麼(比如說,雖然如果你有一個 moment.js 文件在你的目錄,默認情況下 Webpack 會優先考慮你的 moment Node 模塊)。

使用多個文件

你可以通過僅僅修改 entry 對象來指定任意數量的 入口 entry / 輸出點 output

打包多個文件

'use strict';

const webpack = require("webpack");

module.exports = {
  context: __dirname + "/src",
  entry: {
    app: ["./home.js", "./events.js", "./vendor.js"],
  },
  output: {
    path: __dirname + "/dist",
    filename: "[name].bundle.js",
  },
};

所有文件都會按照數組的順序一起被打包成一個 dist/app.bundle.js 文件。

輸出多個文件

const webpack = require("webpack");

module.exports = {
  context: __dirname + "/src",
  entry: {
    home: "./home.js",
    events: "./events.js",
    contact: "./contact.js",
  },
  output: {
    path: __dirname + "/dist",
    filename: "[name].bundle.js",
  },
};

或者,你可以選擇打包成多個 JS 文件以便於分割應用的某些模塊。這將被打包成 3 個文件:dist/home.bundle.jsdist/events.bundle.jsdist/contact.bundle.js

高級打包自動化

如果你將你的應用分割成多個 output 輸出項(如果你的應用的一部分有大量你不需要預載入的 JS,這會很有用),你可能會重用這些文件的代碼,因為它將分別解析每個依賴關係。幸運的是,Webpack 有一個內置的 CommonsChunk 插件來處理這個:

module.exports = {
  // …

  plugins: [
    new webpack.optimize.CommonsChunkPlugin({
      name: "commons",
      filename: "commons.bundle.js",
      minChunks: 2,
    }),
  ],

  // …
};

現在,在你的 output 文件中,如果你有任何模塊被載入 2 次以上(通過 minChunks 設置),它會把那個模塊打包到 common.js 文件中,然後你可以將其緩存在客戶端。這將生成一個額外的請求頭,但是你防止了客戶端多次下載同一個庫。因此,在很多情景下,這會大大提升速度。

開發

Webpack 實際上有自己的開發伺服器,所以無論你是開發一個靜態網站還是只是你的網站前端原型,它都是無可挑剔的。要運行那個伺服器,只需要添加一個 devServer 對象到 webpack.config.js

module.exports = {
  context: __dirname + "/src",
  entry: {
    app: "./app.js",
  },
  output: {
    filename: "[name].bundle.js",
    path: __dirname + "/dist/assets",
    publicPath: "/assets",            // New
  },
  devServer: {
    contentBase: __dirname + "/src",  // New
  },
};

現在創建一個包含以下代碼的 src/index.html 文件:

<script src="/assets/app.bundle.js"></script>

……在你的終端中運行:

webpack-dev-server

你的伺服器現在運行在 localhost:8080。注意 script 標籤裡面的 /assets 是怎麼匹配到 output.publicPath 的 —— 你可以隨意更改它的名稱(如果你需要一個 CDN 的時候這會很有用)。

Webpack 會熱載入所有 JavaScript 更改,而不需要刷新你的瀏覽器。但是,所有 webpack.config.js 文件裡面的更改都需要重新啟動伺服器才能生效。

全局訪問方法

需要在全局空間使用你的函數?在 webpack.config.js 裡面簡單地設置 output.library

module.exports = {
  output: {
    library: &apos;myClassName&apos;,
  }
};

……這會將你打包好的文件附加到一個 window.myClassName 實例。因此,使用該命名空間,你可以調用入口文件的可用方法(可以在該文檔中閱讀有關此設置的更多信息)。

載入器

到目前為止,我們所做的一切只涉及 JavaScript。從一開始就使用 JavaScript 是重要的,因為它是 Webpack 唯一支持的語言。事實上我們可以處理幾乎所有文件類型,只要我們將其轉換成 JavaScript。我們用 載入器 loader 來實現這個功能。

載入器可以是 Sass 這樣的預處理器,或者是 Babel 這樣的轉譯器。在 NPM 上,它們通常被命名為 *-loader,例如 sass-loaderbabel-loader

Babel 和 ES6

如果我們想在項目中通過 Babel 來使用 ES6,我們首先需要在本地安裝合適的載入器:

yarn add --dev babel-loader babel-core babel-preset-es2015

然後將它添加到 webpack.config.js,讓 Webpack 知道在哪裡使用它。

module.exports = {
  // …

  module: {
    rules: [
      {
        test: /.js$/,
        use: [{
          loader: "babel-loader",
          options: { presets: ["es2015"] }
        }],
      },

      // Loaders for other file types can go here
    ],
  },

  // …
};

Webpack 1 的用戶注意:載入器的核心概念沒有任何改變,但是語法改進了。直到官方文檔完成之前,這可能不是確切的首選語法。

/.js$/ 這個正則表達式查找所有以 .js 結尾的待通過 Babel 載入的文件。Webpack 依靠正則檢查給予你完全的控制權 —— 它不限制你的文件擴展名或者假定你的代碼必須以某種方式組織。例如:也許你的 /my_legacy_code/ 文件夾下的內容不是用 ES6 寫的,所以你可以修改上述的 test/^((?!my_legacy_folder).).js$/,這將會排除那個特定的文件夾,不過會用 Babel 處理其餘的文件。

CSS 和 Style 載入器

如果我們只想為我們的應用所需載入 CSS,我們也可以這樣做。假設我們有一個 index.js 文件,我們將從那裡引入:

import styles from &apos;./assets/stylesheets/application.css&apos;;

我們會得到以下錯誤:你可能需要一個合適的載入器來處理這種類型的文件。記住,Webpack 只能識別 JavaScript,所以我們必須安裝合適的載入器:

yarn add --dev css-loader style-loader

然後添加一條規則到 webpack.config.js

module.exports = {
  // …

  module: {
    rules: [
      {
        test: /.css$/,
        use: ["style-loader", "css-loader"],
      },

      // …
    ],
  },
};

載入器以數組的逆序處理。這意味著 css-loader 會比 style-loader 先執行。

你可能會注意到,即使在生產版本中,這實際上是將你的 CSS 和 JavaScript 打包在一起,style-loader 手動將你的樣式寫到 <head>。乍一看,它可能看起來有點怪異,但你仔細想想就會發現這就慢慢開始變得更加有意義了。你已經節省了一個頭部請求 —— 節省了一些連接上的時間。如果你用 JavaScript 來載入你的 DOM,無論如何,這從本質上消除了 FOUC

你還會注意到一個開箱即用的特性 —— Webpack 已經通過將這些文件打包在一起以自動解決你所有的 @import 查詢(而不是依靠 CSS 默認的 import 方式,這會導致無謂的頭部請求以及資源載入緩慢)。

從你的 JS 載入 CSS 是非常驚人的,因為你現在可以用一種新的強大的方式將你的 CSS 模塊化。比如說你要只通過 button.js 來載入 button.css,這將意味著如果 button.js 從來沒有真正使用過的話,它的 CSS 就不會膨脹我們的生產版本。如果你堅持面向組件的 CSS 實踐,如 SMACSS 或 BEM,你會看到更緊密地結合你的 CSS 和你的標記和 JavaScript 的價值。

CSS 和 Node 模塊

我們可以使用 Webpack 來利用 Node.js 使用 ~ 前綴導入 Node 模塊的優勢。如果我們運行 yarn add normalize.css,我們可以使用:

@import "~normalize.css";

……並且充分利用 NPM 來管理我們的第三方樣式 —— 版本控制、沒有任何副本和粘貼的部分。此外,讓 Webpack 為我們打包 CSS 比起使用 CSS 的默認導入方式有明顯的優勢 —— 節省無謂的頭部請求和載入時間。

更新:這一節和下面一節已經更新為準確的用法,不再使用 CSS 模塊簡單地導入 Node 的模塊。感謝 Albert Fernández 的幫助!

CSS 模塊

你可能聽說過 CSS 模塊,它把 CSS 變成了 SS,消除了 CSS 的 層疊性 Cascading 。通常它的最適用場景是只有當你使用 JavaScript 構建 DOM 的時候,但實質上,它神奇地將你的 CSS 類放置到載入它的 JavaScript 文件里(在這裡了解更多)。如果你打算使用它,CSS 模塊已經與 css-loader 封裝在一起(yarn add --dev css-loader):

module.exports = {
  // …

  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          "style-loader",
          { loader: "css-loader", options: { modules: true } }
        ],
      },

      // …
    ],
  },
};

注意:對於 css-loader,我們現在使用 擴展對象語法 expanded object syntax 來給它傳遞一個選項。你可以使用一個更為精簡的字元串來取代默認選項,正如我們仍然使用了 style-loader

值得注意的是,當允許導入 CSS 模塊的時候(例如:@import &apos;normalize.css&apos;;),你完全可以刪除掉 ~。但是,當你 @import 你自己的 CSS 的時候,你可能會遇到構建錯誤。如果你遇到「無法找到 ____」的錯誤,嘗試添加一個 resolve 對象到 webpack.config.js,讓 Webpack 更好地理解你的模塊載入順序。

const path = require("path");

module.exports = {
  //…

  resolve: {
    modules: [path.resolve(__dirname, "src"), "node_modules"]
  },
};

我們首先指定源目錄,然後指定 node_modules。這樣,Webpack 會更好地處理解析,按照既定的順序(分別用你的源目錄和 Node 模塊的目錄替換 "src""node_modules"),首先查找我們的源目錄,然後再查找已安裝的 Node 模塊。

Sass

需要使用 Sass?沒問題。安裝:

yarn add --dev sass-loader node-sass

並添加新的規則:

module.exports = {
  // …

  module: {
    rules: [
      {
        test: /.(sass|scss)$/,
        use: [
          "style-loader",
          "css-loader",
          "sass-loader",
        ]
      }

      // …
    ],
  },
};

然後當你的 Javascript 對一個 .scss.sass 文件調用 import 方法的時候,Webpack 會處理的。

CSS 獨立打包

或許你在處理漸進增強的問題;或許你因為其它原因需要一個單獨的 CSS 文件。我們可以通過在我們的配置中用 extract-text-webpack-plugin 替換 style-loader 而輕易地做到這一點,這不需要更改任何代碼。以我們的 app.js 文件為例:

import styles from &apos;./assets/stylesheets/application.css&apos;;

讓我們安裝這個插件到本地(我們需要 2016 年 10 月的測試版本):

yarn add --dev extract-text-webpack-plugin@2.0.0-beta.4

並且添加到 webpack.config.js

const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
  // …

  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          ExtractTextPlugin.extract("css"),
          { loader: "css-loader", options: { modules: true } },
        ],
      },

      // …
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      filename: "[name].bundle.css",
      allChunks: true,
    }),
  ],
};

現在當運行 webpack -p 的時候,你的 output 目錄還會有一個 app.bundle.css 文件。只需要像往常一樣簡單地在你的 HTML 中向該文件添加一個 <link> 標籤即可。

HTML

正如你可能已經猜到,Webpack 還有一個 [html-loader][6] 插件。但是,當我們用 JavaScript 載入 HTML 時,我們針對不同的場景分成了不同的方法,我無法想出一個單一的例子來為你計划下一步做什麼。通常,你需要載入 HTML 以便於在更大的系統(如 ReactAngularVueEmber)中使用 JavaScript 風格的標記,如 JSXMustacheHandlebars。或者你可以使用類似 Pug (以前叫 Jade)或 Haml 這樣的 HTML 預處理器,抑或你可以直接把同樣的 HTML 從你的源代碼目錄推送到你的構建目錄。你怎麼做都行。

教程到此為止了:你可以用 Webpack 載入標記,但是進展到這一步的時候,關於你的架構,你將做出自己的決定,我和 Webpack 都無法左右你。不過參考以上的例子以及搜索 NPM 上適用的載入器應該足夠你發展下去了。

從模塊的角度思考

為了充分使用 Webpack,你必須從模塊的角度來思考:細粒度的、可復用的、用於高效處理每一件事的獨立的處理程序。這意味著採取這樣的方式:

└── js/
    └── application.js   // 300KB of spaghetti code

將其轉變成這樣:

└── js/
    ├── components/
    │   ├── button.js
    │   ├── calendar.js
    │   ├── comment.js
    │   ├── modal.js
    │   ├── tab.js
    │   ├── timer.js
    │   ├── video.js
    │   └── wysiwyg.js
    │
    └── application.js  // ~ 1KB of code; imports from ./components/

結果呈現了整潔的、可復用的代碼。每一個獨立的組件通過 import 來引入自身的依賴,並 export 它想要暴露給其它模塊的部分。結合 Babel 和 ES6,你可以利用 JavaScript 類 來實現更強大的模塊化,而不用考慮它的工作原理。

有關模塊的更多信息,請參閱 Preethi Kasreddy 這篇優秀的文章

延伸閱讀

via: https://blog.madewithenvy.com/getting-started-with-webpack-2-ed2b86c68783#.oozfpppao

作者:Drew Powers 譯者:OneNewLife 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國