管理 Webpack 依賴項的指南

已發表: 2022-03-11

模塊化的概念是大多數現代編程語言的固有部分。 然而,在最新版本的 ECMAScript ES6 到來之前,JavaScript 一直缺乏任何正式的模塊化方法。

在當今最流行的 JavaScript 框架之一 Node.js 中,模塊捆綁器允許在 Web 瀏覽器中加載 NPM 模塊,面向組件的庫(如 React)鼓勵和促進 JavaScript 代碼的模塊化。

Webpack 是可用的模塊捆綁器之一,可將 JavaScript 代碼以及所有靜態資產(例如樣式表、圖像和字體)處理到捆綁文件中。 處理可以包括管理和優化代碼依賴關係的所有必要任務,例如編譯、連接、縮小和壓縮。

Webpack:初學者教程

但是,配置 Webpack 及其依賴項可能會帶來壓力,而且並不總是一個簡單的過程,尤其是對於初學者而言。

這篇博文提供瞭如何為不同場景配置 Webpack 的指南和示例,並指出了與使用 Webpack 捆綁項目依賴項相關的最常見缺陷。

這篇博文的第一部分解釋瞭如何簡化項目中依賴項的定義。 接下來,我們討論和演示多頁和單頁應用程序的代碼拆分配置。 最後,我們討論如何配置 Webpack,如果我們想在我們的項目中包含第三方庫。

配置別名和相對路徑

相對路徑與依賴關係不直接相關,但我們在定義依賴關係時會用到它們。 如果項目文件結構複雜,則可能很難解析相關的模塊路徑。 Webpack 配置最基本的好處之一是它有助於簡化項目中相對路徑的定義。

假設我們有以下項目結構:

 - Project - node_modules - bower_modules - src - script - components - Modal.js - Navigation.js - containers - Home.js - Admin.js

我們可以通過我們需要的文件的相對路徑來引用依賴,如果我們想在我們的源代碼中將組件導入到容器中,它看起來像下面這樣:


Home.js

 Import Modal from '../components/Modal'; Import Navigation from '../components/Navigation';


Modal.js

 import {datepicker} from '../../../../bower_modules/datepicker/dist/js/datepicker';

每次我們想要導入腳本或模塊時,我們都需要知道當前目錄的位置,並找到我們想要導入的內容的相對路徑。 如果我們有一個具有嵌套文件結構的大項目,或者我們想要重構複雜項目結構的某些部分,我們可以想像這個問題會如何變得複雜。

我們可以使用 Webpack 的resolve.alias選項輕鬆處理這個問題。 我們可以聲明所謂的別名——目錄或模塊的名稱及其位置,我們不依賴項目源代碼中的相對路徑。


webpack.config.js

 resolve: { alias: { 'node_modules': path.join(__dirname, 'node_modules'), 'bower_modules': path.join(__dirname, 'bower_modules'), } }

Modal.js文件中,我們現在可以更簡單地導入 datepicker:

 import {datepicker} from 'bower_modules/datepicker/dist/js/datepicker';

代碼拆分

我們可能會遇到需要將腳本附加到最終包中,或拆分最終包,或者我們希望按需加載單獨的包的場景。 為這些場景設置我們的項目和 Webpack 配置可能並不簡單。

在 Webpack 配置中, Entry選項告訴 Webpack 最終包的起點在哪裡。 入口點可以具有三種不同的數據類型:字符串、數組或對象。

如果我們有一個單一的起點,我們可以使用這些格式中的任何一種並獲得相同的結果。

如果我們想追加多個文件,並且它們不相互依賴,我們可以使用數組格式。 例如,我們可以將analytics.js附加到bundle.js的末尾:


webpack.config.js

 module.exports = { // creates a bundle out of index.js and then append analytics.js entry: ['./src/script/index.jsx', './src/script/analytics.js'], output: { path: './build', filename: bundle.js ' } };

管理多個入口點

假設我們有一個包含多個 HTML 文件的多頁面應用程序,例如index.htmladmin.html 。 我們可以通過將入口點用作 Object 類型來生成多個捆綁包。 下面的配置會生成兩個 JavaScript 包:


webpack.config.js

 module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) } };


index.html

 <script src=”build/index.js”></script>


admin.html

 <script src=”build/admin.js”></script>

兩個 JavaScript 包都可以共享公共庫和組件。 為此,我們可以使用CommonsChunkPlugin ,它會查找出現在多個條目塊中的模塊,並創建一個可以在多個頁面之間緩存的共享包。


webpack.config.js

 var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js'); module.exports = { entry: { index: './src/script/index.jsx', admin: './src/script/admin.jsx' }, output: { path: './build', filename: '[name].js' // template based on keys in entry above (index.js & admin.js) }, plugins: [commonsPlugin] };

現在,我們一定不要忘記在捆綁腳本之前添加<script src="build/common.js"></script>

啟用延遲加載

Webpack 可以將靜態資源拆分成更小的塊,這種方法比標準串聯更靈活。 如果我們有一個大的單頁應用程序 (SPA),簡單地連接到一個包中並不是一個好方法,因為加載一個巨大的包可能會很慢,而且用戶通常不需要每個視圖上的所有依賴項。

我們之前解釋瞭如何將應用程序拆分為多個包、連接公共依賴項以及從瀏覽器緩存行為中受益。 這種方法非常適用於多頁應用程序,但不適用於單頁應用程序。

對於 SPA,我們應該只提供渲染當前視圖所需的那些靜態資產。 SPA 架構中的客戶端路由器是處理代碼拆分的理想場所。 當用戶輸入路由時,我們可以只加載結果視圖所需的依賴項。 或者,我們可以在用戶向下滾動頁面時加載依賴項。

為此,我們可以使用 webpack 可以靜態檢測的require.ensureSystem.import函數。 Webpack 可以根據這個拆分點生成一個單獨的 bundle,按需調用。

在這個例子中,我們有兩個 React 容器; 管理視圖和儀表板視圖。


admin.jsx

 import React, {Component} from 'react'; export default class Admin extends Component { render() { return <div > Admin < /div>; } }


dashboard.jsx

 import React, {Component} from 'react'; export default class Dashboard extends Component { render() { return <div > Dashboard < /div>; } }

如果用戶輸入/dashboard/admin URL,則僅加載相應的所需 JavaScript 包。 下面我們可以看到有和沒有客戶端路由器的例子。


index.jsx

 if (window.location.pathname === '/dashboard') { require.ensure([], function() { require('./containers/dashboard').default; }); } else if (window.location.pathname === '/admin') { require.ensure([], function() { require('./containers/admin').default; }); }


index.jsx

 ReactDOM.render( <Router> <Route path="/" component={props => <div>{props.children}</div>}> <IndexRoute component={Home} /> <Route path="dashboard" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/dashboard').default) }, "dashboard")}} /> <Route path="admin" getComponent={(nextState, cb) => { require.ensure([], function (require) { cb(null, require('./containers/admin').default) }, "admin")}} /> </Route> </Router> , document.getElementById('content') );

將樣式提取到單獨的包中

在 Webpack 中,加載器(如style-loadercss-loader )會預處理樣式表並將它們嵌入到輸出的 JavaScript 包中,但在某些情況下,它們會導致無樣式內容 (FOUC) 的 Flash

我們可以避免使用 ExtractTextWebpackPlugin 的ExtractTextWebpackPlugin ,它允許將所有樣式生成到單獨的 CSS 包中,而不是將它們嵌入到最終的 JavaScript 包中。


webpack.config.js

 var ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { loaders: [{ test: /\.css/, loader: ExtractTextPlugin.extract('style', 'css')' }], }, plugins: [ // output extracted CSS to a file new ExtractTextPlugin('[name].[chunkhash].css') ] }

處理第三方庫和插件

很多時候,我們需要使用第三方庫、各種插件或附加腳本,因為我們不想花時間從頭開始開發相同的組件。 有許多可用的遺留庫和插件沒有得到積極維護,不理解 JavaScript 模塊,並假設在預定義名稱下全局存在依賴項。

下面是一些 jQuery 插件的示例,解釋瞭如何正確配置 Webpack 以生成最終的包。

提供插件

大多數第三方插件依賴於特定全局依賴項的存在。 對於 jQuery,插件依賴於定義的$jQuery變量,我們可以通過在代碼中調用$('div.content').pluginFunc()來使用 jQuery 插件。

我們可以使用 Webpack 插件ProvidePlugin在每次遇到全局$標識符時添加var $ = require("jquery")


webpack.config.js

 webpack.ProvidePlugin({ '$': 'jquery', })

當 Webpack 處理代碼時,它會查找存在$ ,並提供對全局依賴項的引用,而無需導入require函數指定的模塊。

進口裝載機

一些 jQuery 插件在全局命名空間中假設$或依賴this作為window對象。 為此,我們可以使用imports-loader將全局變量注入模塊。


example.js

 $('div.content').pluginFunc();

然後,我們可以通過配置imports-loader$變量注入到模塊中:

 require("imports?$=jquery!./example.js");

這只是在前面加上var $ = require("jquery");example.js

在第二個用例中:


webpack.config.js

 module: { loaders: [{ test: /jquery-plugin/, loader: 'imports?jQuery=jquery,$=jquery,this=>window' }] }

通過使用=>符號(不要與 ES6 箭頭函數混淆),我們可以設置任意變量。 最後一個值將全局變量this重新定義為指向window對象。 它與用(function () { ... }).call(window);包裝文件的全部內容相同。 並以window作為參數調用this函數。

我們還可以要求使用 CommonJS 或 AMD 模塊格式的庫:

 // CommonJS var $ = require("jquery"); // jquery is available // AMD define(['jquery'], function($) { // jquery is available });

一些庫和模塊可以支持不同的模塊格式。

在下一個示例中,我們有一個使用 AMD 和 CommonJS 模塊格式並具有 jQuery 依賴項的 jQuery 插件:


jquery-plugin.js

 (function(factory) { if (typeof define === 'function' && define.amd) { // AMD format is used define(['jquery'], factory); } else if (typeof exports === 'object') { // CommonJS format is used module.exports = factory(require('jquery')); } else { // Neither AMD nor CommonJS used. Use global variables. } });


webpack.config.js

 module: { loaders: [{ test: /jquery-plugin/, loader: "imports?define=>false,exports=>false" }] }

我們可以選擇要用於特定庫的模塊格式。 如果我們聲明define等於false ,Webpack 不會解析 AMD 模塊格式的模塊,如果我們聲明變量 export 等於falseexports不會解析 CommonJS 模塊格式的模塊。

暴露裝載機

如果我們需要將一個模塊暴露給全局上下文,我們可以使用expose-loader 。 這可能很有幫助,例如,如果我們有不屬於 Webpack 配置的外部腳本並且依賴於全局命名空間中的符號,或者我們使用需要訪問瀏覽器控制台中的符號的瀏覽器插件。


webpack.config.js

 module: { loaders: [ test: require.resolve('jquery'), loader: 'expose-loader?jQuery!expose-loader?$' ] }

jQuery 庫現在可以在全局命名空間中用於網頁上的其他腳本。

 window.$ window.jQuery

配置外部依賴

如果我們想包含來自外部託管腳本的模塊,我們需要在配置中定義它們。 否則,Webpack 無法生成最終的包。

我們可以使用 Webpack 配置中的externals選項來配置外部腳本。 例如,我們可以通過單獨的<script>標籤使用 CDN 中的庫,同時在我們的項目中仍顯式聲明它為模塊依賴項。


webpack.config.js

 externals: { react: 'React', 'react-dom': 'ReactDOM' }

支持庫的多個實例

在前端開發中使用 NPM 包管理器來管理第三方庫和依賴項非常棒。 但是,有時我們可以擁有具有不同版本的同一個庫的多個實例,並且它們不能在一個環境中很好地協同工作。

例如,這可能發生在 React 庫中,我們可以在其中從 NPM 安裝 React,稍後可以通過一些額外的包或插件獲得不同版本的 React。 我們的項目結構可能如下所示:

 project | |-- node_modules | |-- react |-- react-plugin | |--node_modules | |--react

來自react-plugin的組件與項目中的其他組件具有不同的 React 實例。 現在我們有兩個獨立的 React 副本,它們可以是不同的版本。 在我們的應用程序中,這種情況可能會弄亂我們的全局可變 DOM,並且我們可以在 Web 控制台日誌中看到錯誤消息。 這個問題的解決方案是在整個項目中使用相同版本的 React。 我們可以通過 Webpack 別名來解決它。


webpack.config.js

 module.exports = { resolve: { alias: { 'react': path.join(__dirname, './node_modules/react'), 'react/addons': path.join(__dirname, '/node_modules/react/addons'), } } }

react-plugin嘗試 require React 時,它使用項目的node_modules中的版本。 如果我們想知道我們使用的是哪個版本的 React,我們可以在源代碼中添加console.log(React.version)

專注於開發,而不是 Webpack 配置

這篇文章只是觸及了 Webpack 的強大功能和實用性的表面。

還有許多其他 Webpack 加載器和插件可以幫助您優化和簡化 JavaScript 捆綁。

即使您是初學者,本指南也為您提供了開始使用 Webpack 的堅實基礎,這將使您能夠更多地專注於開發而不是捆綁配置。

相關:維護控制:Webpack 和 React 指南,Pt。 1