webpack 2 入門

webpack 2 入門

小編推薦:掘金是一個高質量的技術社區,從 ECMAScript 6 到 Vue.js,性能優化到開源類庫,讓你不錯過前端開發的每一個技術乾貨。各大應用市場搜索「掘金」即可下載APP,技術乾貨盡在掌握..

webpack 2將在文檔完成後將正式發佈(webpack 2.2 中文文檔)。 但這並不意味着你現在不能開始使用版本2,如果你知道如何配置它的話。

webpack 2.2 已經正式發佈! 現在通過 npm 安裝 webpack 會自動安裝 v2 版本。

什麼是 webpack ?

簡單來說,webpack 是一個針對 JavaScript 代碼的模塊打包工具。然而,自從它的發佈,它逐漸發展成爲所有前端代碼的管理工具(不管是其本身有意還是社區的意願)。

老的任務運行方式:HTML、CSS 和 JavaScript 都是分離的。您必須單獨管理,並且還要確保所有東西正確地部署到生產環境。

任務運行器如 Gulp 可以處理許多不同的預處理器和轉換器,但在所有情況下,它將獲取一個源碼輸入並將其壓縮到已編譯好的輸出。然而,它不關心整個系統,逐個去處理的。這對開發者來說是一個負擔:找到任務運行器中斷的位置,併爲所有改動的部分找到正確的方式,將它們在生產環境上協調一致。

webpack通過一個大膽的詢問試圖來減輕開發者負擔:如果開發過程的某個部分可以自己管理依賴會怎麼樣?如果我們可以以這樣一種方式來簡單地寫代碼:構建過程僅基於最後所需要的東西來管理它自己,會怎麼樣?

Webpack 處理方式:如果是 webpack 知道的代碼,那麼它就只會打包實際在生產環境當中使用的部分。

如果你過去幾年一直是Web社區的一員,那麼你肯定已經知道解決問題的首選方法:使用 JavaScript 構建。 因此,webpack 嘗試通過用  JavaScript 傳遞依賴關係使構建過程更容易。但是其的精妙之處並不在於簡單的代碼管理部分;而在於它的管理層面是百分百有效的 JavaScript(還有 Node 特性)webpack使您能夠編寫有效的JavaScript,更好地瞭解系統。

換句話說:你不是爲了 webpack 寫代碼,而是爲了你的項目寫代碼。而且 webpack 在保持進步(當然包括某些配置)。

簡而言之,如果您遇到以下任何問題:

  • 無序地加載依賴關係
  • 在生產中包含了未使用的CSS或JS
  • 意外地重複加載(或多次加載)庫
  • 遇到來自CSS和JavaScript的作用域問題
  • 不停尋找一個好的系統,好讓你可以在 JavaScript 代碼裏使用 Node 或 Bower 的模塊,或者依賴一系列瘋狂的後端配置來正確地使用那些模塊
  • 需要優化資源分發機制卻又擔心會破壞掉某些東西

…那麼你可以從 webpack 中受益。它通過讓 JavaScript 取代開發人員大腦來關心依賴和加載順序,輕鬆地解決了上面這些問題。最好的部分是什麼?webpack 甚至可以在服務端無縫運行,這意味着你仍然可以使用 webpack 來構建漸進式增強的網站。

第一步

我們將在本教程中使用 Yarnbrew install yarn)來替代 npm,但這完全取決於你自己,它們做的是同樣的事情。打開到項目文件夾,在命令行窗口運行下面的命令添加 Webpack 2 到全局包和本地項目裏:

bash 代碼:
  1. npm i -g webpack webpack-dev-server@2
  2. yarn add --dev webpack webpack-dev-server@2

注意:爲了簡單起見,我們在這裏選擇直接安裝webpack,而不是像推薦的那樣使用 NPM 腳本。任何一種方式都可以;文檔解釋了他們的區別。

然後我們在項目目錄的根目錄中新建一個 webpack.config.js 文件,用來聲明 webpack 的配置:

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

注意:__dirname 指的是 webpack.config.js 所在的目錄,在這篇博客中指的是根目錄。

記住,webpack “知道”你的項目中發生了什麼?它是通過讀取你的代碼知道的(不用擔心,它簽署了一份保密協議)。
webpack 基本上做以下這些事情:

  1. 從 context 對應的文件夾開始…
  2. …尋找 entry 裏所有的文件名…
  3. … 並讀取內容。每個通過 import(ES6) 或 require()(Node) 引入的依賴關係,會被解析代碼,並且被打包到最終的構建結果當中。然後它搜索這些依賴,以及這些依賴的依賴,直到“依賴樹”的最末端節點 — 只打包它所需要的依賴,沒有其他的東西。
  4. 接着,Webpack 將所有東西打包到 output.path 對應的文件夾裏,使用 output.filename 對應的命名模板來命名([name] 被 entry 裏的對象鍵值所替代)

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

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

接着運行:

bash 代碼:
  1. webpack -p

注意:p 標誌是 “production”生產模式並且 uglifies(混淆) / minify(壓縮) 輸出。

它會輸出一個 dist/app.bundle.js 文件,控制檯會打印出當前日期和時間。注意,webpack自動知道 'moment' 指的是什麼(但是,如果你有一個 moment.js 文件在你的目錄中,默認情況下,webpack會優先使用這個而不是 moment 的 Node 模塊)。

處理多個文件

您可以通過僅修改 entry 對象來指定任意數量的 entry 或 output 點。

多個文件,打包在一起

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

所有文件會按數組順序一起打包到 dist/app.bundle.js 文件中。

多個文件,多個輸出

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

或者,您可以選擇打包成多個JS文件來將應用拆解成幾個部分。上面的配置就可以打包成三個文件:dist/home.bundle.jsdist/events.bundle.js 和 dist/contact.bundle.js

自動打包第三方庫

如果你把你的應用程序拆解,打包成多個 output 的話(如果應用的某部分有大量不需要提前加載的 JS 的話,這樣做會很有用),在這些文件(通常是第三方庫)裏就有可能出現重複的代碼,因爲它將分別解析每個依賴關係。幸運的是,webpack有一個內置的 CommonsChunk 插件來處理這個問題:

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

現在,在您的 output 文件裏,如果你有任何模塊被加載2次或更多次(通過 minChunks 設置該值),它就會被打包進一個叫 commons.js 的文件中,然後可以在客戶端中緩存這個文件。當然,這將導致一次額外的請求,但是避免了客戶端多次下載相同的庫。因此,在許多情況下,這是提升速度的舉措。

手工打包第三方庫

如果你喜歡自己做更多的事情,您可以選擇採用更人工的方法:

JavaScript 代碼:
  1. module.exports = {
  2. entry: {
  3. index: './index.js',
  4. vendor: ['react', 'react-dom', 'rxjs'],
  5. },
  6. // …
  7. }

在這裏,你明確告訴 webpack 導出包含 reactreact-dom, 和 rxjs Node 模塊的vendor 包,而不是依靠 CommonsChunkPlugin自動這些事情。

開發

webpack 實際上有自己的開發服務器,所以無論你是開發一個靜態網站還是隻是正在原型化前端階段,這個服務器都是完美可用的。要運行它,只需要在 webpack.config.js 裏添加一個 devServer 對象:

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

現在新建一個 src/index.html 文件包含下面這行代碼:

JavaScript 代碼:
  1. <script src="/assets/app.bundle.js"></script>

然後在命令行中,運行:

bash 代碼:
  1. webpack-dev-server

您的服務器現在正在 localhost:8080 上運行。注意 script 標記中的 /assets 對應的是 output.publicPath 的值,因此您可以從任何需要的地方加載資源(如果您使用CDN,則很有用)。

webpack 將熱替換任何的 JavaScript 更改,因爲您無需刷新瀏覽器。但是,對 webpack.config.js文件的任何更改都需要重新啓動服務器才能生效。

全局可訪問的方法

需要從全局命名空間中使用某些函數?只需在 webpack.config.js 裏設置  output.library 即可:

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

…這樣將他附加到 window.myClassName 實例上。所以使用這種命名作用域,就可以調用 entry 點裏面的方法了(
(您可以在文檔中瞭解有關此設置的更多信息)。

加載器(Loaders)

到現在爲止,我們只涉及到的都是使用JavaScript。從 JavaScript 代碼開始是非常重要的,因爲這是 Webpack 唯一使用的語言。我們幾乎可以處理任何文件類型,只要我們把它傳遞給 JavaScript 。 我們使用 加載器(Loaders) 來實現。

加載器可以指向一個像 Sass 的預處理器,或者像 Babel 的編譯器。在 NPM 中,它們通常被命名爲*-loader,如sass-loader 或 babel-loader 。

Babel + ES6

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

bash 代碼:
  1. yarn add --dev babel-loader babel-core babel-preset-es2015

…然後把它們添加進 webpack.config.js 好讓 Webpack 知道哪裏使用它們。

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

一個給 webpack 1.x用戶的提示:加載器(Loaders)的核心概念保持不變,但語法有所改進。

這樣做就可以爲 /\.js$/ 正則表達式尋找以 .js 結尾的文件,最後通過 Babel 編譯加載。webpack 依賴正則表達式給予你完整的控制 – 但它不會限制你的文件後綴,或者假設你的代碼必須以某種特定形式組織起來。

如果你發現一個加載器(Loaders)的 mangling 文件,或其他不應該被處理的文件,您可以指定 exclude 選項以跳過這些文件。在這裏,我們將我們的 node_modules 文件夾從 Babel 處理中排除 – 我們不需要它。但我們也可以在我們任何項目文件中應用這個,例如,如果我們有一個 my_legacy_code 文件夾。這不會阻止您加載這些文件;相反,你只是讓 webpack知道可以直接導入,不處理它們。你可以使用 include 來包含文件(但通常不需要此選項)。

 

CSS + Style Loader

如果只想加載我們應用需要的 CSS,也可以那麼做。假設有一個 index.js 文件,在裏面引入:

JavaScript 代碼:
  1. import styles from './assets/stylesheets/application.css';

就會得到一個錯誤:You may need an appropriate loader to handle this file type。記住 webpack 只能讀取 JavaScript,所以我們必須安裝相應的加載器(Loaders):

bash 代碼:
  1. yarn add --dev css-loader style-loader

然後在 webpack.config.js 裏添加一個規則:

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

這些 loader 會以數組逆序運行。這意味着 css-loader 會在 style-loader 之前運行。

你可能注意到甚至在生產構建的結果中,也把 CSS 打包進了 JavaScript 裏面,並且 style-loader手動地將樣式寫進了 <head> 中。乍一看這可能有點奇怪,但當你考慮足夠多的時候就會慢慢發現這其實是有道理的。你保存了一個頭部請求(在某些連接上節省寶貴的時間),並且如果你用 JavaScript 來加載 DOM,這麼做基本上就消除了它自身的無樣式閃屏問題。

還注意到 Webpack 已經通過把所有文件打包成一個從而自動解決了所有的 @import 查詢問題(比起依賴 CSS 默認的引入導致不必要的頭部請求和緩慢的資源加載,這麼做顯然更好)。

從 JS 里加載 CSS 相當爽,因爲你可以用一種強有力的新方式去模塊化 CSS 代碼了。假設你只通過 button.js 加載了 button.css,這就意味着如果 button.js 沒有實際用到的話,它的 CSS 也不會打包進我們的生產構建結果。如果你堅持使用像 SMACSS 或者 BEM 那樣的面向組件的 CSS,就會知道把 CSS 和 HTML + JavaScript 代碼放更近的價值了。

CSS + Node modules

我們可以在 Webpack 裏用 Node 的 ~ 前綴去引入 Node Modules。假設我們提前運行了 yarn add normalize.css,就可以這麼用:

JavaScript 代碼:
  1. @import "~normalize.css";

…這樣就可以全面使用 NPM 來管理第三方樣式庫(版本及其他)而對我們而言就無需複製粘貼了。更進一步的是,webpack 打包 CSS 比使用默認的 CSS 引入有着顯而易見的優勢,讓客戶端遠離不必要的頭部請求和緩慢的資源加載。

更新:這個部分和下面的部分爲了更準確都進行了更新,不用再困擾於使用 CSS Modules 去簡單地引入 Node Modules 了。感謝 Albert Fernández 的幫助!

CSS Modules

你可能已經聽說過 CSS Modules,它將 CSS(Cascading Style Sheets)裏的 C(Cascading)給提出來了。它只在用 JavaScript 構建 DOM 的時候使用有最佳效果,但本質上來說,它巧妙地將 CSS 在加載它的 JavaScript 裏作用域化了(點擊這個鏈接學習更多相關知識)。如果你計劃使用它,CSS Modules 對應的 loader 是 css-loaderyarn add --dev css-loader):

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

注意:對於 css-loader 我們使用了展開的對象語法來爲它添加配置。你可以寫簡單的字符串代表使用默認配置,style-loader 就還是這麼做的。


值得注意的是實際上在使用 CSS Modules 引入 Node Modules 的時候可以去掉 ~ 符號(如 @import "normalize.css";)。但是,當 @import 你自己的 CSS 時可能會遇到錯誤。如果你得到了 “can’t find ___” 這樣的錯誤,嘗試添加一個 resolve 對象到 webpack.config.js 裏,好讓webpack 更好地理解你預期的模塊順序。

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

首先指定了我們自己的源文件目錄,然後是 node_modules。這樣子 webpack 解決起來就會處理得更好一些,按照那個順序先找我們的源文件目錄,然後是已安裝的 Node Modules(分別用你自己的源碼和 Node Modules 目錄替換其中的 src 和 node_modules)。

Sass

想用 Sass?沒問題,安裝:

bash 代碼:
  1. yarn add --dev sass-loader node-sass

然後添加另一條規則:

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

接下來當 JavaScript 調用 import 引入一個 .scss 或 .sass 文件時,webpack 就會做它該做的事情了。記住:use 順序是相反的,所以我們首先加載 Sass ,然後是 CSS 解析器,最後是 Style 加載器(loader) ,將我們解析的CSS加載到頁面的 <head> 。

分開打包 CSS

或許你正在處理漸進式增強的網站,又或許因爲其他的原因你需要一個分離的 CSS 文件。我們可以簡單地實現,只需要在配置裏用 extract-text-webpack-plugin 替換掉 style-loader,而無需改變其他任何代碼。以 app.js文件爲例:

JavaScript 代碼:
  1. import styles from './assets/stylesheets/application.css';

本地安裝插件(我們需要這個的測試版本):

bash 代碼:
  1. yarn add --dev extract-text-webpack-plugin@2.0.0-beta.5

添加到 webpack.config.js

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

現在運行 webpack -p 的時候就可以看到一個 app.bundle.css 文件出現在 output 目錄裏了。像往常一樣簡單地添加一個 <link> 標籤到 HTML 文件裏就可以了。

HTML

你可能已經猜到,Webpack 還有一個 html-loader 插件。但是,當我們開始用 JavaScript 加載 HTML 的時候,這其實是一個可以分支成不同方法的地方,而且我想不到一個單獨的簡單示例可以覆蓋所有下一步操作的可能性。通常,你可能會在用 ReactAngularVue 或者 Ember 構建的大型系統中加載諸如 JSXMustache 或者 Handlebars 這樣偏向 JavaScript 的模板 HTML;或者你可能使用一個像 Pug(以前的 Jade)這樣的 HTML 預處理器;或者你可能只是想簡單地將源文件目錄裏的 HTML 複製到構建結果目錄裏。不管你想做什麼,我沒辦法假設。

所以我準備在此結束本教程:你可以用 Webpack 加載 HTML,但這一點你必須自己根據你的架構做出決策,不管是我還是 Webpack 都沒辦法幫到你。不過使用上述例子作爲參考並在 NPM 上找到正確的 loader 應該足夠讓你繼續下去了。

從模塊角度思考

爲了最大程度發揮 Webpack 的作用,你不得不從模塊的角度去思考(小、可複用、自包含進程),一件件事情慢慢去做好。這意味着下面這樣的東西:

JavaScript 代碼:
  1. └── js/
  2. └── application.js // 300KB of spaghetti code

把它變成:

JavaScript 代碼:
  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. └── index.js // ~ 1KB of code; imports from ./components/

結果是乾淨且可複用的代碼。每個獨立的組件取決於import(導入)自身的依賴,並按照它想要的方式export(導出)到其他模塊。配合 Babel + ES6 使用,還可以利用 JavaScript Classes 做出更好的模塊化,並且不要去想它,作用域只是在起作用。

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

延伸閱讀

原文地址:Getting Started with Webpack 2

部分翻譯來自:https://llp0574.github.io/2016/11/29/getting-started-with-webpack2/

  copy自:http://www.css88.com/archives/6992

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章