При непрерывном развитии и развитии продуктов все приложение часто неизбежно становится все больше и больше, а время рендеринга первого экрана или время первой загрузки будет увеличиваться. Затем планируется оптимизация производительности. Среди многих схем оптимизации производительности загрузка модулей по запросу, несомненно, является одним из наиболее эффективных методов. Загрузка модулей по требованию легко достигается с помощью метода import() веб-пакета.
Давайте сначала воспользуемся простейшим примером, чтобы получить общее представление о том, как Webpack реализует загрузку модулей по запросу.
Простейший пример
Шаг 1. Определите модуль a.js, который необходимо загружать по запросу.
export default function a() {
console.log('我是模块 a');
}
Шаг 2: Добавлен модуль A в основной модуль (index.js)
import('./a').then(({ default: a }) => {
console.log(a);
});
Шаг 3: Запустите службу, используя что-то вроде «npm run start» (разные конфигурации могут немного отличаться) Здесь автор запустил webpack-dev-server, и, используя для просмотра localhost, вы можете увидеть вывод на странице консоли браузера:Затем на странице браузера «Сеть» после фильтрации с помощью JS вы увидите, что загружены два файла js. Один из них — index.js (основной модуль), а другой — 0.async.js (загружать файлы по требованию). Таким образом, легко реализуется загрузка модулей по требованию, и нет необходимости изменять конфигурацию веб-пакета без особых требований, и его можно использовать напрямую. Как использовать загрузку компонента по требованию в React, см.Использование метода React.Lazy.
Примечание. Имя загружаемого js-файла зависит от конфигурации webpack.
Наши потребности постоянно меняются, и базовые методы использования часто не удовлетворяют богатому воображению наших продакт-менеджеров. Давайте взглянем на более интересные варианты использования import(), после чего вы сможете с уверенностью сказать менеджеру по продукту: «Хорошо, нет проблем».
использование импорта()
Динамическое имя файла (путь)
В предыдущих примерах использовались статические имена файлов ('./a', без переменных), но на практике часто требуются динамические имена файлов. Например, модуль, загруженный страницей, является настраиваемым, а результат конфигурации возвращается сервером. К счастью, webpack поддерживает динамические имена файлов. Но при его использовании нам нужно обратить внимание на два момента: 1. Требуется информация о путях хотя бы некоторых файлов webpack не поддерживает полные динамические имена файлов, что означаетИсключение возникает после компиляции со следующим оператором.
const path = './a';
import(path).then(({ default: a }) => {
console.log(a);
});
Запрос на обслуживание не смог найти модуль './a':При компиляции появляется следующее предупреждающее сообщение, а файл запроса по запросу «0.async.js» отсутствует в результатах компиляции:Причина отсутствия поддержки полностью динамических имен файлов заключается в том, что веб-пакет работает путем статического сканирования файлов и последующей их обработки в соответствии с определенными правилами. Когда веб-пакет сканирует синтаксис «import()», он преобразует переменную в «.*» регулярного выражения, а затем сопоставляет имя файла в соответствии с этим правилом и выводит совпадающий файл независимо по частям. Если путь к файлу имеет только одну переменную, он соответствует всем файлам в каталоге, что явно неразумно, поэтому веб-пакет сразу выводит ПРЕДУПРЕЖДЕНИЕ и не обрабатывает его.
2. Файлы, соответствующие правилу динамического имени файла, должны быть доступны для использования.
С предыдущим объяснением в качестве предзнаменования гораздо легче понять этот момент внимания, и мы продолжим с предыдущим содержанием. Если отсканированное заявлениеimport(
./locale/${language}.json)
, то файл .json в каталоге './locale/' после компиляции создаст отдельный файл запроса по запросу. То есть, если файл, сгенерированный матчем, не будет использован, он будет потрачен впустую. Итак, мы пишем имя файла, и файлы, сопоставленные после преобразования переменной в «.*», должны быть доступны для использования.
Настройте имя скомпилированного файла
Обычно нет необходимости настраивать модуль для загрузки по требованию, но иногда у нас могут быть требования к имени файла, сгенерированному при компиляции (например, для легкой идентификации), тогда нам может потребоваться настроить некоторые конфигурации.
В веб-пакете имя выходного файла можно настроить с помощью подраздела chunkFileName вывода. Метод именования chunkFileName такой же, как и имя файла, но для обеспечения уникальности имени выходного файла рекомендуется использовать одну из переменных [name], [id] или [chunkhash]. Здесь [имя] обычно совпадает с [идентификатором], и оно несовместимо только тогда, когда в импорте установлено значение webpackChunkName. Определение выглядит следующим образом:
import(
// 通过定义 webpackChunkName 可以调整 [name] 值
/* webpackChunkName: "a-async" */
// webpackMode 可同选择的值有多个,有兴趣的小伙伴请自行前往帮助文档进一步了解:
// https://www.webpackjs.com/api/module-methods/#import-
/* webpackMode: "lazy" */
'./a').then(({ default: a }) => {
console.log(a);
});
В это время с содержимым конфигурации следующего chunkFileName вы можете вывестиa-async.js
Имя файла выглядит следующим образом:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
chunkFilename: `[name].js`,
}
};
Если chunkFilename не настроен, правила загрузки имен файлов по запросу будут экспортированы на основе имени файла. Конкретные правила заключаются в следующем:
- Если правило имени файла может гарантировать уникальность сгенерированного имени файла, оно будет сгенерировано в соответствии с правилом имени файла;
- Если правило имени файла не может гарантировать, что сгенерированное имя файла является уникальным, «[id]» будет добавлено перед правилом имени файла для обеспечения уникальности.
Например, содержимое конфигурации выглядит следующим образом:
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
}
};
Затем фактически сгенерированный файл по запросу называется[id].bundle.js
.
Как добиться спроса на нагрузку
Webpack сканирует исходный файл для идентификации импорта/экспорта, а затем переупаковывает и ассемблирует для создания нового файла в соответствии с результатом идентификации.Вновь сгенерированный код файла был повторно ассемблирован таким образом, чтобы браузер мог его распознать. Итак, чтобы понять, как import() реализует загрузку по требованию, просто прочитайте код, сгенерированный после обработки weipack. Теперь давайте начнем с загрузки файла './a' по требованию:
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! dynamic exports provided */
/*! all exports used */
/***/ (function(module, exports, __webpack_require__) {
__webpack_require__.e/* import() */(0/*! a-async */).then(__webpack_require__.bind(null, /*! ./a */ 82)).then(function (_ref) {
var a = _ref.default;
console.log(a);
});
/***/ })
Смысл приведенного выше кода:
- webpack_require.e соответствует методу import(), который является основным методом асинхронной загрузки, параметр равен 0, что соответствует асинхронному чанку
- Выполнять первым после асинхронной загрузкиwebpack_requireметод, первый входной параметр равен 82, что соответствует файлу './a'. Затем выполните свой собственный код
Далее, давайте взглянем на содержимое тела метода __webpack_require__.e (см. пояснения в китайских комментариях):
/******/ // This file contains only the entry chunk.
/******/ // The chunk loading function for additional chunks
/******/ __webpack_require__.e = function requireEnsure(chunkId) {
/******/ var installedChunkData = installedChunks[chunkId];
// 0 表示已经加载成功,无需再做任何处理
/******/ if(installedChunkData === 0) {
/******/ return new Promise(function(resolve) { resolve(); });
/******/ }
/******/
/******/ // a Promise means "currently loading".
// 正在加载中
/******/ if(installedChunkData) {
/******/ return installedChunkData[2];
/******/ }
/******/
/******/ // setup Promise in chunk cache
// 将 promise 对象的相关内容存入 installedChunks[chunkId],待后面使用
/******/ var promise = new Promise(function(resolve, reject) {
/******/ installedChunkData = installedChunks[chunkId] = [resolve, reject];
/******/ });
/******/ installedChunkData[2] = promise;
/******/
/******/ // start chunk loading
// 生成一个 script 标签,用于异步加载 js 文件
/******/ var head = document.getElementsByTagName('head')[0];
/******/ var script = document.createElement('script');
/******/ script.type = "text/javascript";
/******/ script.charset = 'utf-8';
/******/ script.async = true;
/******/ script.timeout = 120000;
/******/
/******/ if (__webpack_require__.nc) {
/******/ script.setAttribute("nonce", __webpack_require__.nc);
/******/ }
// __webpack_require__.p 就是 __webpack_public_path__ 对应的地址
/******/ script.src = __webpack_require__.p + "" + ({"0":"a-async"}[chunkId]||chunkId) + ".async.js";
// 超时之后执行 onScriptComplete
/******/ var timeout = setTimeout(onScriptComplete, 120000);
/******/ script.onerror = script.onload = onScriptComplete;
/******/ function onScriptComplete() {
/******/ // avoid mem leaks in IE.
/******/ script.onerror = script.onload = null;
/******/ clearTimeout(timeout);
/******/ var chunk = installedChunks[chunkId];
// 如果文件加载成功,chunk就被设置为 0;后面只处理了加载失败的情况
/******/ if(chunk !== 0) {
/******/ if(chunk) {
/******/ chunk[1](new Error('Loading chunk ' + chunkId + ' failed.'));
/******/ }
/******/ installedChunks[chunkId] = undefined;
/******/ }
/******/ };
/******/ head.appendChild(script);
/******/
/******/ return promise;
/******/ };
Динамически созданный генерация тегов скрипта - это первая нагрузка после успешной реализации содержимого файла, соответствующего тегу, а затем выполнить событие onload. Таким образом, OnscriptComplete в основном адресована ситуация, не удалась загрузить.
Содержимое файла './a', соответствующего тегу скрипта:
webpackJsonp([0],{
/***/ 34:
/*!******************!*\
!*** ./src/a.js ***!
\******************/
/*! exports provided: default */
/*! all exports used */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (immutable) */ __webpack_exports__["default"] = a;
console.log('加载文件 a');
function a() {
console.log('我是模块 a');
}
/***/ })
});
Как только приходит файл './a', выполняется метод webpackJsonp, и в качестве второго значения входного параметра передается содержимое его собственного файла. Метод webpackJsonp определяется следующим образом:
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules, executeModules) {
/******/ // add "moreModules" to the modules object,
/******/ // then flag all "chunkIds" as loaded and fire callback
/******/ var moduleId, chunkId, i = 0, resolves = [], result;
/******/ for(;i < chunkIds.length; i++) {
/******/ chunkId = chunkIds[i];
/******/ if(installedChunks[chunkId]) {
/******/ resolves.push(installedChunks[chunkId][0]);
/******/ }
/******/ installedChunks[chunkId] = 0;
/******/ }
/******/ for(moduleId in moreModules) {
/******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
/******/ modules[moduleId] = moreModules[moduleId];
/******/ }
/******/ }
/******/ if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules, executeModules);
/******/ while(resolves.length) {
/******/ resolves.shift()();
/******/ }
/******/
/******/ };
Метод webpackJsonp делает три вещи по очереди:
- Задайте для installChunks[chunkId] значение 0, чтобы указать на успешную загрузку (L5-11).
- Выполнить содержимое файла './a' (L12-16)
- Выполнить содержимое после асинхронного добавления файла (L18-20)
На этом весь процесс загрузки завершен.
Исключения для поведения Webpack
То, что мы рассмотрели выше, является простейшей ситуацией загрузки по запросу.Если на общий модуль ссылается основной файл и на него ссылается асинхронно загружаемый модуль, как с этим справляется веб-пакет? См. пример ниже: Модуль a, на который ссылаются оба модуля b и c, b импортируется синхронно основным файлом, а c импортируется асинхронно.
// 主文件内容 index.js
import b from './b';
b();
import(
/* webpackChunkName: "c-async" */
/* webpackMode: "lazy" */
'./c').then(({ default: c }) => {
console.log('异步加载 c', c);
});
// b 模块内容 b.js
import a from './a';
export default function b() {
a();
console.log('我是模块 b');
}
// c 模块内容 c.js
import a from './a';
export default function c() {
a();
console.log('我是模块 c');
}
// a 模块内容 a.js
console.log('加载文件 a');
export default function a() {
console.log('我是模块 a');
}
Результаты:Мы видим, что «load file a» выводится только один раз, то есть модуль a загружается только один раз, как и ожидалось. Давайте проверим код модуля b и модуля c, сгенерированный webpack:
/* 83 */
/*!******************!*\
!*** ./src/b.js ***!
\******************/
/*! exports provided: default */
/*! exports used: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
/* harmony export (immutable) */ __webpack_exports__["a"] = b;
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__a__ = __webpack_require__(/*! ./a */ 34);
function b() {
Object(__WEBPACK_IMPORTED_MODULE_0__a__["a" /* default */])();
console.log('我是模块 b');
}
/***/ })
/***/ 84:
/*!******************!*\
!*** ./src/c.js ***!
\******************/
/*! exports provided: default */
/*! all exports used */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony export (immutable) */ __webpack_exports__["default"] = c;
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__a__ = __webpack_require__(/*! ./a */ 34);
function c() {
Object(__WEBPACK_IMPORTED_MODULE_0__a__["a" /* default */])();
console.log('我是模块 c');
}
/***/ })
В приведенных выше двух фрагментах кода мы можем легко обнаружить, что загрузочный модуль a используется в 11-м и 32-мwebpack_require, то находим _webpack requireТело метода:
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {},
/******/ hot: hotCreateModule(moduleId),
/******/ parents: (hotCurrentParentsTemp = hotCurrentParents, hotCurrentParents = [], hotCurrentParentsTemp),
/******/ children: []
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, hotCreateRequire(moduleId));
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
Смысл приведенного выше кода:
- Возврат напрямую, если модуль уже загружен (L5-7)
- Если модуль не загружен, создаются данные объекта модуля, а содержимое модуля выполняется и выводится (L9-25); Примечание: все модули здесь загружаются синхронно (уже загружены), поэтому процесс загрузки отсутствует.
Поэтому, когда модуль a будет выполнен снова, он вернется напрямую, не переходя к _webpack requireВторой шаг (L9) метода. _
Выше Weibpack импортировать () некоторое соответствующее содержание, если я выражаю неуместно, пожалуйста, раскритикуйте исправление.