閱讀641 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Webpack 2 入門

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

Webpack 是什麼

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

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

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

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

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

Webpack 的方式:如果 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 添加到我們的全局軟件包以及本地項目中:


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

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


  1. 'use strict';
  2. const webpack = require('webpack');
  3. module.exports = {
  4. context: __dirname + '/src',
  5. entry: {
  6. app: './app.js',
  7. },
  8. output: {
  9. path: __dirname + '/dist',
  10. filename: '[name].bundle.js',
  11. },
  12. };

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

記住,Webpack “知道”你的項目發生了什麼。它通過閱讀你的代碼來實現(別擔心,它簽署了保密協議 :D )。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):


  1. 'use strict';
  2. import moment from 'moment';
  3. var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');
  4. console.log( rightNow );
  5. // "October 23rd 2016, 9:30:24 pm"

我們應該運行:


  1. webpack -p

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

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

使用多個文件

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

打包多個文件


  1. 'use strict';
  2. const webpack = require("webpack");
  3. module.exports = {
  4. context: __dirname + "/src",
  5. entry: {
  6. app: ["./home.js", "./events.js", "./vendor.js"],
  7. },
  8. output: {
  9. path: __dirname + "/dist",
  10. filename: "[name].bundle.js",
  11. },
  12. };

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

輸出多個文件


  1. const webpack = require("webpack");
  2. module.exports = {
  3. context: __dirname + "/src",
  4. entry: {
  5. home: "./home.js",
  6. events: "./events.js",
  7. contact: "./contact.js",
  8. },
  9. output: {
  10. path: __dirname + "/dist",
  11. filename: "[name].bundle.js",
  12. },
  13. };

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

高級打包自動化

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


  1. module.exports = {
  2. // …
  3. plugins: [
  4. new webpack.optimize.CommonsChunkPlugin({
  5. name: "commons",
  6. filename: "commons.bundle.js",
  7. minChunks: 2,
  8. }),
  9. ],
  10. // …
  11. };

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

開發

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


  1. module.exports = {
  2. context: __dirname + "/src",
  3. entry: {
  4. app: "./app.js",
  5. },
  6. output: {
  7. filename: "[name].bundle.js",
  8. path: __dirname + "/dist/assets",
  9. publicPath: "/assets", // New
  10. },
  11. devServer: {
  12. contentBase: __dirname + "/src", // New
  13. },
  14. };

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


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

……在你的終端中運行:


  1. webpack-dev-server

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

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

全局訪問方法

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


  1. module.exports = {
  2. output: {
  3. library: 'myClassName',
  4. }
  5. };

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

加載器

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

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

Babel 和 ES6

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


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

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


  1. module.exports = {
  2. // …
  3. module: {
  4. rules: [
  5. {
  6. test: /\.js$/,
  7. use: [{
  8. loader: "babel-loader",
  9. options: { presets: ["es2015"] }
  10. }],
  11. },
  12. // Loaders for other file types can go here
  13. ],
  14. },
  15. // …
  16. };

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

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

CSS 和 Style 加載器

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


  1. import styles from './assets/stylesheets/application.css';

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


  1. yarn add --dev css-loader style-loader

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


  1. module.exports = {
  2. // …
  3. module: {
  4. rules: [
  5. {
  6. test: /\.css$/,
  7. use: ["style-loader", "css-loader"],
  8. },
  9. // …
  10. ],
  11. },
  12. };

加載器以數組的逆序處理。這意味著 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,我們可以使用:


  1. @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):


  1. module.exports = {
  2. // …
  3. module: {
  4. rules: [
  5. {
  6. test: /\.css$/,
  7. use: [
  8. "style-loader",
  9. { loader: "css-loader", options: { modules: true } }
  10. ],
  11. },
  12. // …
  13. ],
  14. },
  15. };

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


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


  1. const path = require("path");
  2. module.exports = {
  3. //…
  4. resolve: {
  5. modules: [path.resolve(__dirname, "src"), "node_modules"]
  6. },
  7. };

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

Sass

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


  1. yarn add --dev sass-loader node-sass

並添加新的規則:


  1. module.exports = {
  2. // …
  3. module: {
  4. rules: [
  5. {
  6. test: /\.(sass|scss)$/,
  7. use: [
  8. "style-loader",
  9. "css-loader",
  10. "sass-loader",
  11. ]
  12. }
  13. // …
  14. ],
  15. },
  16. };

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

CSS 獨立打包

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


  1. import styles from './assets/stylesheets/application.css';

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


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

並且添加到 webpack.config.js


  1. const ExtractTextPlugin = require("extract-text-webpack-plugin");
  2. module.exports = {
  3. // …
  4. module: {
  5. rules: [
  6. {
  7. test: /\.css$/,
  8. use: [
  9. ExtractTextPlugin.extract("css"),
  10. { loader: "css-loader", options: { modules: true } },
  11. ],
  12. },
  13. // …
  14. ]
  15. },
  16. plugins: [
  17. new ExtractTextPlugin({
  18. filename: "[name].bundle.css",
  19. allChunks: true,
  20. }),
  21. ],
  22. };

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

HTML

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

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

從模塊的角度思考

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


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

將其轉變成這樣:


  1. └── js/
  2. ├── components/
  3. ├── button.js
  4. ├── calendar.js
  5. ├── comment.js
  6. ├── modal.js
  7. ├── tab.js
  8. ├── timer.js
  9. ├── video.js
  10. └── wysiwyg.js
  11. └── application.js // ~ 1KB of code; imports from ./components/

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

原文發布時間為:2017-12-23

本文來自雲棲社區合作夥伴“Linux中國”

最後更新:2017-06-06 07:38:00

  上一篇:go  在 Linux 下生成高強度密碼的四種方法
  下一篇:go  如何在 Debian 和 Ubuntu 係統上自動安裝安全更新