Webpack:模塊方法

2023-05-10 11:39 更新

本章節(jié)涵蓋了使用 webpack 編譯代碼的所有方法。在 webpack 打包應(yīng)用程序時,你可以選擇各種模塊語法風(fēng)格,包括 ES6,CommonJS 和 AMD

盡管 webpack 支持多種模塊語法,但我們還是建議盡量使用一致的語法,以此避免一些奇怪的行為和 bug。事實上,當(dāng)距離最近的 package.json 文件中包含值為 "module" 或 "commonjs" 的 "type" 字段時,webpack 會啟用語法一致性檢查。請大家在閱讀前,注意此情況:

  • 在 package.json 中為 .mjs 或 .js 設(shè)置 "type": "module"不允許使用 CommonJS,例如,你不能使用 require,module.exports 或 exports當(dāng)引入文件時,強制編寫擴展名,例如,你應(yīng)使用 import './src/App.mjs',而非 import './src/App'(你可以通過設(shè)置 Rule.resolve.fullySpecified 來禁用此規(guī)則)
  • 在 package.json 中為 .cjs 或 .js 設(shè)置 "type": "commonjs"import 和 export 均不可用
  • 在 package.json 中為 .wasm 設(shè)置 "type": "module"引入 wasm 文件時,必須編寫文件擴展名

ES6

webpack 2 支持原生的 ES6 模塊語法,意味著你無須額外引入 babel 這樣的工具,就可以使用 import 和 export。但是注意,如果使用其他的 ES6+ 特性,仍然需要引入 babel。webpack 支持以下的方法:

import

通過 import 以靜態(tài)的方式導(dǎo)入另一個通過 export 導(dǎo)出的模塊。

import MyModule from './my-module.js';
import { NamedExport } from './other-module.js';

你也通過 import 來引入 Data URI:

import 'data:text/javascript;charset=utf-8;base64,Y29uc29sZS5sb2coJ2lubGluZSAxJyk7';
import {
  number,
  fn,
} from 'data:text/javascript;charset=utf-8;base64,ZXhwb3J0IGNvbnN0IG51bWJlciA9IDQyOwpleHBvcnQgY29uc3QgZm4gPSAoKSA9PiAiSGVsbG8gd29ybGQiOw==';

export

默認導(dǎo)出整個模塊或具名導(dǎo)出模塊。

// 具名導(dǎo)出
export var Count = 5;
export function Multiply(a, b) {
  return a * b;
}

// 默認導(dǎo)出
export default {
  // Some data...
};

import()

function(string path):Promise

動態(tài)的加載模塊。調(diào)用 import 的之處,被視為分割點,意思是,被請求的模塊和它引用的所有子模塊,會分割到一個單獨的 chunk 中。

if (module.hot) {
  import('lodash').then((_) => {
    // Do something with lodash (a.k.a '_')...
  });
}

import() 中的表達式

不能使用完全動態(tài)的 import 語句,例如 import(foo)。是因為 foo 可能是系統(tǒng)或項目中任何文件的任何路徑。

import() 必須至少包含一些關(guān)于模塊的路徑信息。打包可以限定于一個特定的目錄或文件集,以便于在使用動態(tài)表達式時 - 包括可能在 import() 調(diào)用中請求的每個模塊。例如, import(`./locale/${language}.json`) 會把 .locale 目錄中的每個 .json 文件打包到新的 chunk 中。在運行時,計算完變量 language 后,就可以使用像 english.json 或 german.json 的任何文件。

// 想象我們有一個從 cookies 或其他存儲中獲取語言的方法
const language = detectVisitorLanguage();
import(`./locale/${language}.json`).then((module) => {
  // do something with the translations
});

Magic Comments

內(nèi)聯(lián)注釋使這一特性得以實現(xiàn)。通過在 import 中添加注釋,我們可以進行諸如給 chunk 命名或選擇不同模式的操作。

// 單個目標(biāo)
import(
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  /* webpackExports: ["default", "named"] */
  'module'
);

// 多個可能的目標(biāo)
import(
  /* webpackInclude: /\.json$/ */
  /* webpackExclude: /\.noimport\.json$/ */
  /* webpackChunkName: "my-chunk-name" */
  /* webpackMode: "lazy" */
  /* webpackPrefetch: true */
  /* webpackPreload: true */
  `./locale/${language}`
);
import(/* webpackIgnore: true */ 'ignored-module.js');

webpackIgnore:設(shè)置為 true 時,禁用動態(tài)導(dǎo)入解析。

webpackChunkName: 新 chunk 的名稱。 從 webpack 2.6.0 開始,占位符 [index] 和 [request] 分別支持遞增的數(shù)字或?qū)嶋H的解析文件名。 添加此注釋后,將單獨的給我們的 chunk 命名為 [my-chunk-name].js 而不是 [id].js。

webpackMode:從 webpack 2.6.0 開始,可以指定以不同的模式解析動態(tài)導(dǎo)入。支持以下選項:

  • 'lazy' (默認值):為每個 import() 導(dǎo)入的模塊生成一個可延遲加載(lazy-loadable)的 chunk。
  • 'lazy-once':生成一個可以滿足所有 import() 調(diào)用的單個可延遲加載(lazy-loadable)的 chunk。此 chunk 將在第一次 import() 時調(diào)用時獲取,隨后的 import() 則使用相同的網(wǎng)絡(luò)響應(yīng)。注意,這種模式僅在部分動態(tài)語句中有意義,例如 import(`./locales/${language}.json`),其中可能含有多個被請求的模塊路徑。
  • 'eager':不會生成額外的 chunk。所有的模塊都被當(dāng)前的 chunk 引入,并且沒有額外的網(wǎng)絡(luò)請求。但是仍會返回一個 resolved 狀態(tài)的 Promise。與靜態(tài)導(dǎo)入相比,在調(diào)用 import() 完成之前,該模塊不會被執(zhí)行。
  • 'weak':嘗試加載模塊,如果該模塊函數(shù)已經(jīng)以其他方式加載,(即另一個 chunk 導(dǎo)入過此模塊,或包含模塊的腳本被加載)。仍會返回 Promise, 但是只有在客戶端上已經(jīng)有該 chunk 時才會成功解析。如果該模塊不可用,則返回 rejected 狀態(tài)的 Promise,且網(wǎng)絡(luò)請求永遠都不會執(zhí)行。當(dāng)需要的 chunks 始終在(嵌入在頁面中的)初始請求中手動提供,而不是在應(yīng)用程序?qū)Ш皆谧畛鯖]有提供的模塊導(dǎo)入的情況下觸發(fā),這對于通用渲染(SSR)是非常有用的。

webpackPrefetch:告訴瀏覽器將來可能需要該資源來進行某些導(dǎo)航跳轉(zhuǎn)。

webpackPreload:告訴瀏覽器在當(dāng)前導(dǎo)航期間可能需要該資源。

webpackInclude:在導(dǎo)入解析(import resolution)過程中,用于匹配的正則表達式。只有匹配到的模塊才會被打包。

webpackExclude:在導(dǎo)入解析(import resolution)過程中,用于匹配的正則表達式。所有匹配到的模塊都不會被打包。

webpackExports: 告知 webpack 只構(gòu)建指定出口的動態(tài) import() 模塊。它可以減小 chunk 的大小。從 webpack 5.0.0-beta.18 起可用。

CommonJS

CommonJS 的目標(biāo)是為瀏覽器之外的 JavaScript 指定一個生態(tài)系統(tǒng)。webpack 支持以下 CommonJS 的方法:

require

require(dependency: String);

以同步的方式檢索其他模塊的導(dǎo)出。編譯器(compiler)會確保依賴項在輸出 bundle 中可用。

var $ = require('jquery');
var myModule = require('my-module');

也可以為 require 啟用魔法注釋。

require.resolve

require.resolve(dependency: String);

以同步的方式獲取模塊的 ID。編譯器(compiler)會確保依賴項在最終輸出 bundle 中可用。建議將其視為不透明值,只能與 require.cache[id] 或 __webpack_require__(id) 配合使用(最好避免這種用法)。

有關(guān)更多模塊的信息,詳見 module.id。

require.cache

多處引用同一模塊,最終只會產(chǎn)生一次模塊執(zhí)行和一次導(dǎo)出。所以,會在運行時(runtime)中會保存一份緩存。刪除此緩存,則會產(chǎn)生新的模塊執(zhí)行和新的導(dǎo)出。

var d1 = require('dependency');
require('dependency') === d1;
delete require.cache[require.resolve('dependency')];
require('dependency') !== d1;
// in file.js
require.cache[module.id] === module;
require('./file.js') === module.exports;
delete require.cache[module.id];
require.cache[module.id] === undefined;
require('./file.js') !== module.exports; // 理論上是不相等的;實際運行中,則會導(dǎo)致堆棧溢出
require.cache[module.id] !== module;

require.ensure

require.ensure(
  dependencies: String[],
  callback: function(require),
  errorCallback: function(error),
  chunkName: String
)

給定 dependencies 參數(shù),將其對應(yīng)的文件拆分到一個單獨的 bundle 中,此 bundle 會被異步加載。當(dāng)使用 CommonJS 模塊語法時,這是動態(tài)加載依賴項的唯一方法。這意味著,可以在模塊執(zhí)行時才允許代碼,只有在滿足特定條件時才會加載 dependencies。

var a = require('normal-dep');

if (module.hot) {
  require.ensure(['b'], function (require) {
    var c = require('c');

    // Do something special...
  });
}

按照上面指定的順序,require.ensure 支持以下參數(shù):

  • dependencies:字符串?dāng)?shù)組,聲明 callback 回調(diào)函數(shù)中所需要的所有模塊。
  • callback:當(dāng)依賴項加載完成后,webpack 將會執(zhí)行此函數(shù),require 函數(shù)的實現(xiàn),作為參數(shù)傳入此函數(shù)中。當(dāng)程序運行需要依賴時,可以使用 require() 來加載依賴。函數(shù)體可以使用此參數(shù),來進一步執(zhí)行 require() 模塊。
  • errorCallback:當(dāng) webpack 加載依賴失敗時會執(zhí)行此函數(shù)。
  • chunkName:由 require.ensure 創(chuàng)建的 chunk 的名稱。通過將相同 chunkName 傳遞給不同的 require.ensure 調(diào)用,我們可以將其代碼合并到一個單獨的 chunk 中,從而只產(chǎn)生一個瀏覽器必須加載的 bundle。

AMD

AMD(Asynchronous Module Definition)是一種定義了用于編寫和加載模塊接口的 JavaScript 規(guī)范。

define (with factory)

define([name: String], [dependencies: String[]], factoryMethod: function(...))

如果提供了 dependencies 參數(shù),就會調(diào)用 factoryMethod 方法,并(以相同的順序)導(dǎo)出每個依賴項。如果未提供 dependencies 參數(shù),調(diào)用 factoryMethod 方法時會傳入 require , exports 和 module(用于兼容?。H绻朔椒ǚ祷匾粋€值,則返回值會作為此模塊的導(dǎo)出。由編譯器(compiler)來確保依賴項在最終輸出的 bundle 中可用。

define(['jquery', 'my-module'], function ($, myModule) {
  // 使用 $ 和 myModule 做一些操作...

  // 導(dǎo)出一個函數(shù)
  return function doSomething() {
    // ...
  };
});

define (with value)

define(value: !Function)

這種方式只將提供的 value 導(dǎo)出。這里的 value 可以是除函數(shù)以外的任何值。

define({
  answer: 42,
});

require (amd-version)

require(dependencies: String[], [callback: function(...)])

與 require.ensure 類似,給定 dependencies 參數(shù),將其對應(yīng)的文件拆分到一個單獨的 bundle 中,此 bundle 會被異步加載。然后會調(diào)用 callback 回調(diào)函數(shù),并傳入 dependencies 數(shù)組中的每個項導(dǎo)出。

require(['b'], function (b) {
  var c = require('c');
});

標(biāo)簽?zāi)K(Labeled Modules)

webpack 內(nèi)置的 LabeledModulesPlugin 插件,允許你使用下面的方法導(dǎo)出和導(dǎo)入模塊:

export label

導(dǎo)出給定的 value。標(biāo)簽可以出現(xiàn)在函數(shù)聲明或變量聲明之前。函數(shù)名或變量名是導(dǎo)出值的標(biāo)識符。

export: var answer = 42;
export: function method(value) {
  // Do something...
};

require label

在當(dāng)前作用域下,依賴項的所有導(dǎo)出均可用。require 標(biāo)簽可以放置在一個字符串之前。依賴模塊必須使用 export 標(biāo)簽導(dǎo)出值。CommonJS 或 AMD 模塊無法通過這種方式使用。

some-dependency.js

export: var answer = 42;
export: function method(value) {
  // Do something...
};
require: 'some-dependency';
console.log(answer);
method(...);

Webpack

除了上述模塊語法之外,還允許使用一些 webpack 特定的方法:

require.context

require.context(
  (directory: String),
  (includeSubdirs: Boolean) /* 可選的,默認值是 true */,
  (filter: RegExp) /* 可選的,默認值是 /^\.\/.*$/,所有文件 */,
  (mode: String)  /* 可選的, 'sync' | 'eager' | 'weak' | 'lazy' | 'lazy-once',默認值是 'sync' */
)

指定一系列依賴項,通過使用 directory 的路徑,以及 includeSubdirs ,filter 選項,進行更細粒度的模塊引入,使用 mode 定義加載方式。以此可以很容易的解析模塊:

var context = require.context('components', true, /\.html$/);
var componentA = context.resolve('componentA');

如果 mode 設(shè)置為 lazy,基礎(chǔ)模塊將以異步方式加載:

var context = require.context('locales', true, /\.json$/, 'lazy');
context('localeA').then((locale) => {
  // do something with locale
});

mode 的可用模式及說明的完整列表在 import() 文檔 中進行了描述。

require.include

require.include((dependency: String));

引入一個不需要執(zhí)行的 dependency,這樣可以用于優(yōu)化輸出 chunk 中依賴模塊的位置。

require.include('a');
require.ensure(['a', 'b'], function (require) {
  /* ... */
});
require.ensure(['a', 'c'], function (require) {
  /* ... */
});

這會產(chǎn)生以下輸出:

  • entry chunk: file.js and a
  • anonymous chunk: b
  • anonymous chunk: c

不使用 require.include('a'),輸出的兩個匿名 chunk 都會有模塊 a。

require.resolveWeak

與 require.resolve 類似,但是不會把 module 引入到 bundle 中。這就是所謂的“弱(weak)”依賴。

if (__webpack_modules__[require.resolveWeak('module')]) {
  // 當(dāng)模塊可用時,執(zhí)行一些操作……
}
if (require.cache[require.resolveWeak('module')]) {
  // 在模塊加載完成之前,執(zhí)行一些操作……
}

// 你可以像執(zhí)行其他 require/import 方法一樣,
// 執(zhí)行動態(tài)解析上下文 resolves ("context")。
const page = 'Foo';
__webpack_modules__[require.resolveWeak(`./page/${page}`)];
require.resolveWeak 是_通用渲染_(服務(wù)器端渲染 SSR + 代碼分割 Code Splitting)的基礎(chǔ)。例如在 react-universal-component 等包中的用法。它允許代碼在服務(wù)器端和客戶端初始頁面的加載上同步渲染。它要求手動或以某種方式提供 chunk。它可以在不需要指示應(yīng)該被打包的情況下引入模塊。它與 import() 一起使用,當(dāng)用戶導(dǎo)航觸發(fā)額外的導(dǎo)入時,它會被接管。
warning:
如果模塊源碼包含無法靜態(tài)分析的 require,則會發(fā)出關(guān)鍵依賴項警告。

示例代碼:

someFn(require);
require.bind(null);
require(variable);

Further Reading

  • CommonJS Wikipedia
  • Asynchronous Module Definition


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號