Webpack:模塊方法

2023-05-10 11:39 更新

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

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

  • 在 package.json 中為 .mjs 或 .js 設(shè)置 "type": "module"不允許使用 CommonJS,例如,你不能使用 require,module.exports 或 exports當(dāng)引入文件時(shí),強(qiáng)制編寫擴(kuò)展名,例如,你應(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 文件時(shí),必須編寫文件擴(kuò)展名

ES6

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

import

通過 import 以靜態(tài)的方式導(dǎo)入另一個(gè)通過 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

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

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

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

import()

function(string path):Promise

動(dòng)態(tài)的加載模塊。調(diào)用 import 的之處,被視為分割點(diǎn),意思是,被請(qǐng)求的模塊和它引用的所有子模塊,會(huì)分割到一個(gè)單獨(dú)的 chunk 中。

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

import() 中的表達(dá)式

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

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

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

Magic Comments

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

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

// 多個(gè)可能的目標(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 時(shí),禁用動(dòng)態(tài)導(dǎo)入解析。

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

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

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

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

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

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

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

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

CommonJS

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

require

require(dependency: String);

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

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

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

require.resolve

require.resolve(dependency: String);

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

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

require.cache

多處引用同一模塊,最終只會(huì)產(chǎn)生一次模塊執(zhí)行和一次導(dǎo)出。所以,會(huì)在運(yùn)行時(shí)(runtime)中會(huì)保存一份緩存。刪除此緩存,則會(huì)產(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; // 理論上是不相等的;實(shí)際運(yùn)行中,則會(huì)導(dǎo)致堆棧溢出
require.cache[module.id] !== module;

require.ensure

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

給定 dependencies 參數(shù),將其對(duì)應(yīng)的文件拆分到一個(gè)單獨(dú)的 bundle 中,此 bundle 會(huì)被異步加載。當(dāng)使用 CommonJS 模塊語法時(shí),這是動(dòng)態(tài)加載依賴項(xiàng)的唯一方法。這意味著,可以在模塊執(zhí)行時(shí)才允許代碼,只有在滿足特定條件時(shí)才會(huì)加載 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)依賴項(xiàng)加載完成后,webpack 將會(huì)執(zhí)行此函數(shù),require 函數(shù)的實(shí)現(xiàn),作為參數(shù)傳入此函數(shù)中。當(dāng)程序運(yùn)行需要依賴時(shí),可以使用 require() 來加載依賴。函數(shù)體可以使用此參數(shù),來進(jìn)一步執(zhí)行 require() 模塊。
  • errorCallback:當(dāng) webpack 加載依賴失敗時(shí)會(huì)執(zhí)行此函數(shù)。
  • chunkName:由 require.ensure 創(chuàng)建的 chunk 的名稱。通過將相同 chunkName 傳遞給不同的 require.ensure 調(diào)用,我們可以將其代碼合并到一個(gè)單獨(dú)的 chunk 中,從而只產(chǎn)生一個(gè)瀏覽器必須加載的 bundle。

AMD

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

define (with factory)

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

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

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

  // 導(dǎo)出一個(gè)函數(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ù),將其對(duì)應(yīng)的文件拆分到一個(gè)單獨(dú)的 bundle 中,此 bundle 會(huì)被異步加載。然后會(huì)調(diào)用 callback 回調(diào)函數(shù),并傳入 dependencies 數(shù)組中的每個(gè)項(xiàng)導(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)識(shí)符。

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

require label

在當(dāng)前作用域下,依賴項(xiàng)的所有導(dǎo)出均可用。require 標(biāo)簽可以放置在一個(gè)字符串之前。依賴模塊必須使用 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) /* 可選的,默認(rèn)值是 true */,
  (filter: RegExp) /* 可選的,默認(rèn)值是 /^\.\/.*$/,所有文件 */,
  (mode: String)  /* 可選的, 'sync' | 'eager' | 'weak' | 'lazy' | 'lazy-once',默認(rèn)值是 'sync' */
)

指定一系列依賴項(xiàng),通過使用 directory 的路徑,以及 includeSubdirs ,filter 選項(xiàng),進(jìn)行更細(xì)粒度的模塊引入,使用 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() 文檔 中進(jìn)行了描述。

require.include

require.include((dependency: String));

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

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

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

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

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

require.resolveWeak

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

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

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

示例代碼:

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

Further Reading

  • CommonJS Wikipedia
  • Asynchronous Module Definition


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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)