предисловие
Говоря о роутинге ленивой загрузки, все быстро знают, как его реализовать, но когда меня спрашивают о принципе роутинга ленивой загрузки, я боюсь, что некоторые из моих друзей в недоумении. Давайте познакомим всех с принципом ленивой маршрутизации.
Отложенную загрузку маршрутизации также можно назвать отложенной загрузкой компонентов маршрутизации, чаще всего используется черезimport()чтобы достичь этого.
function load(component) {
return () => import(`views/${component}`)
}
Затем, после компиляции и упаковки через Webpack, код каждого компонента роутинга будет разбит на js-файлы один за другим, эти js-файлы не будут загружаться при инициализации, а соответствующие js-файлы будут загружаться только при активации компонента роутинга. .
Здесь неважно, как Webpack разделяет код по компонентам роутинга, только как загружать соответствующие js-файлы компонента роутинга по запросу после компиляции Webpack.
1. Подготовка
1. Создайте проект
Чтобы понять принцип ленивой маршрутизации, рекомендуется начать с самого простого проекта и построить проект с помощью Vue Cli3, который содержит только один компонент маршрутизации. В main.js введен только vue-router, больше ничего не нужно.
main.js
import Vue from 'vue';
import App from './App.vue';
import Router from 'vue-router';
Vue.use(Router);
//路由懒加载
function load(component) {
return () => import(`views/${component}`)
}
// 路由配置
const router = new Router({
mode: 'history',
base: process.env.BASE_URL,
routes: [
{
path: '/',
name: 'home',
component: load('Home'),
meta: {
title: '首页'
}
},
]
});
new Vue({
router,
render: h => h(App)
}).$mount('#app')
views/Home.vue
<template>
<div>
{{tip}}
</div>
</template>
<script>
export default {
data(){
return {
tip:'欢迎使用Vue项目'
}
}
}
</script>
2. имя веб-пакета
использоватьwebpackChunkName, чтобы имя скомпилированного и упакованного файла js могло соответствовать компоненту маршрутизации один за другим, и модифицировать функцию загрузки.
function load(component) {
return () => import(/* webpackChunkName: "[request]" */ `views/${component}`)
}
3. Удалите сжатие кода и путаницу
Устраните путаницу со сжатием кода, чтобы мы могли читать скомпилированный и упакованный код. Настроить в vue.config.js
module.exports={
chainWebpack:config => {
config.optimization.minimize(false);
},
}
4. npm запустить сборку
Выполнение заказаnpm run build, скомпилированная и упакованная файловая структура dist выглядит следующим образом
Среди них Home.67f3cd34.js — это соответствующий файл js после компиляции и упаковки компонента маршрутизации Home.vue.
2. Анализ index.html
Как видно из вышеизложенного, сначала используйте ссылку, чтобы определить отношения между Home.js, app.js, chunk-vendors.js и веб-клиентом.
-
ref=preload: Скажите браузеру, что этот ресурс должен быть загружен рано для меня. -
rel=prefetch: указать браузеру загружать этот ресурс для меня, когда он бездействует. -
as=script: Сообщите браузеру, что этот ресурс является скриптом, и увеличьте приоритет загрузки.
Затем в тело загружаются два ресурса js, chunk-vendors.js и app.js. Видно, что два ресурса js загружаются при инициализации веб-клиента.
3. Проанализируйте chunk-vendors.js
chunk-vendors.js можно назвать набором публичных модулей проекта, код упрощен следующим образом,
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["chunk-vendors"],{
"01f9":(function(module,exports,__webpack_require__){
...//省略
})
...//省略
}])
Как видно из кода, выполняем chunk-vendors.js, просто ставим следующий массивpushприбытьwindow["webpackJsonp"], а второй элемент массива — это объект, и каждое значение объекта — это функциональное выражение, которое выполняться не будет. Это конец, конечно, нет, мы приносимwindow["webpackJsonp"]Перейдите в app.js и найдите его.
В-четвертых, проанализируйте app.js
app.js можно назвать входным файлом проекта.
В app.js есть самовыполняющаяся функция, путем поискаwindow["webpackJsonp"]Соответствующий код можно найти ниже.
(function(modules){
//省略...
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback;
jsonpArray = jsonpArray.slice();
for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
//省略...
}({
0:(function(module, exports, __webpack_require__) {
module.exports = __webpack_require__("56d7");
})
//省略...
}))
- первый
window["webpackJsonp"]назначить вjsonpArray. - Пучок
jsonpArrayизpushметод, назначенныйoldJsonpFunction. - использовать
webpackJsonpCallbackперехват функцииjsopArrayизpushметод, то есть вызовwindow["webpackJsonp"]изpushметод будет выполнятьсяwebpackJsonpCallbackфункция. - будет
jsonpArrayНеглубокое копирование и назначениеjsonpArray. - Поскольку выполнение chunk-vendors.js в
window["webpackJsonp"].pushВремяpushметод не былwebpackJsonpCallbackПерехват функции, поэтому циклjsonpArray, передавая каждый элемент в качестве параметраwebpackJsonpCallbackфункция и вызов. - будет
jsonpArrayизpushметод переназначается наparentJsonpFunction.
1. Функция webpackJsonpCallback
Далее мы смотрим наwebpackJsonpCallbackэта функция.
(function(modules){
function webpackJsonpCallback(data) {
var chunkIds = data[0];
var moreModules = data[1];
var executeModules = data[2];
var moduleId, chunkId, i = 0, resolves = [];
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (Object.prototype.hasOwnProperty.call(installedChunks, chunkId)
&& 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(data);
while (resolves.length) {
resolves.shift()();
}
deferredModules.push.apply(deferredModules, executeModules || []);
return checkDeferredModules();
};
var installedChunks = {
"app": 0
};
//省略...
}({
0:(function(module, exports, __webpack_require__) {
module.exports = __webpack_require__("56d7");
})
//省略...
}))
хочу знатьwebpackJsonpCallbackКакова функция функции, мы должны сначала понятьmodules,installedChunks,deferredModulesроль этих трех переменных.
- Модуль — это произвольный блок кода, а чанк — это набор модулей, сгруппированных во время обработки веб-пакета.
-
modulesКэшировать все модули (кодовые блоки), вызыватьmodulesМодуль может выполнять код внутри. -
installedChunksКэшировать статус загрузки всех чанков, еслиinstalledChunks[chunk]Если он равен 0, это означает, что чанк был загружен. -
deferredModulesКаждый элемент также является массивом, например.[module,chunk1,chunk2,chunk3], Его функция заключается в том, что если вы хотите выполнить модуль, он должен быть выполнен после загрузки чанк1, чанк2 и чанк3.
if (parentJsonpFunction) parentJsonpFunction(data)Этот код работает только в проектах с несколькими входами, как упоминалось ранее.jsonpArrayизpushметод назначен наparentJsonpFunction,перечислитьparentJsonpFunctionОн действительно подталкивает параметры метода push в чанке кwindow["webpackJsonp"]в этом массиве.
Например, в проекте теперь две записи, app.js и app1.js, часть модулей кешируется в app.js, а в app1.js можно передатьwindow["webpackJsonp"]Для вызова этих модулей код вызова выглядит следующим образом.
for (var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
понять сноваwebpackJsonpCallbackСтала ли функция намного понятнее? Давайте посмотримcheckDeferredModulesэта функция.
2. функция checkDeferredModules
var deferredModules = [];
var installedChunks = {
"app": 0
}
function checkDeferredModules() {
var result;
for (var i = 0; i < deferredModules.length; i++) {
var deferredModule = deferredModules[i];
var fulfilled = true;
for (var j = 1; j < deferredModule.length; j++) {
var depId = deferredModule[j];
if (installedChunks[depId] !== 0) fulfilled = false;
}
if (fulfilled) {
deferredModules.splice(i--, 1);
result = __webpack_require__(__webpack_require__.s = deferredModule[0]);
}
}
return result;
}
- цикл
deferredModules, создайте переменнуюfulfilledвыражатьdeferredModuleЗагрузка чанка в ,trueУказывает, что вся загрузка завершена,falseУказывает, что не все загружены. - от
j=1начать циклdeferredModuleкуски в , потому чтоdeferredModule[0]является модулем, еслиinstalledChunks[chunk]!==0, то чанк не грузится, ставим переменнуюfulfilledУстановить какfalse. Возврат к результату после завершения цикла. - зацикленный
deferredModuleОценив статус загрузки чанка,fulfilledпо-прежнему верно, тогда позвоните__webpack_require__функция, будетdeferredModule[0](модуль) передается в качестве параметра выполнению. -
deferredModules.splice(i--, 1), удалите deferredModule, удовлетворяющий условию, и уменьшите i на единицу, гдеi--состоит в том, чтобы сначала использовать i, а затем вычесть один.
Так какwebpackJsonpCallbackв функцииdeferredModulesза[], поэтому вернитесь к основной функции и продолжайте смотреть вниз.
deferredModules.push([0, "chunk-vendors"]);
return checkDeferredModules();
После анализа приведенной выше логики он выполнит__webpack_require__(0), тогда взгляните__webpack_require__эта функция.
3. Функция __webpack_require__
var installedModules = {};
function __webpack_require__(moduleId) {
if (installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
Зная из кода__webpack_require__Это метод, который выполняет модуль.
-
installedModulesИспользуется для кэширования состояния выполнения модуля. - По moduleId в модулях (в
webpackJsonpCallbackКоллекция всех модулей кэшируется в функции), чтобы получить соответствующий модуль и выполнить его с помощью метода вызова. - Назначьте результат выполнения module.exports и верните.
так выполнить__webpack_require__(0), по сути, заключается в выполнении следующего кода.
(function (module, exports, __webpack_require__) {
module.exports = __webpack_require__("56d7");
}),
используется снова в__webpack_require__Выполняем модуль с id 56d7, находим соответствующий модуль и продолжаем читать, смотрим фрагменты кода ключа внутри.
function load(component) {
return function () {
return __webpack_require__("9dac")("./".concat(component));
};
}
var routes = [{
path: '/',
name: 'home',
component: load('Home'),
meta: {
title: '首页'
}
}, {
path: '*',
redirect: {
path: '/'
}
}];
Посмотрите, если это очень знакомо, это место для настройки маршрутизации.loadИли как функция загрузки компонентов маршрутизации, которая использует__webpack_require__("9dac")Метод вернулся для выполнения компонента маршрута загрузки, давайте посмотрим__webpack_require__("9dac").
(function (module, exports, __webpack_require__) {
var map = {
"./Home": [
"bb51",
"Home"
],
"./Home.vue": [
"bb51",
"Home"
]
};
function webpackAsyncContext(req) {
if (!__webpack_require__.o(map, req)) {
return Promise.resolve().then(function () {
var e = new Error("Cannot find module '" + req + "'");
e.code = 'MODULE_NOT_FOUND';
throw e;
});
}
var ids = map[req], id = ids[0];
return __webpack_require__.e(ids[1]).then(function () {
return __webpack_require__(id);
});
}
webpackAsyncContext.keys = function webpackAsyncContextKeys() {
return Object.keys(map);
};
webpackAsyncContext.id = "9dac";
module.exports = webpackAsyncContext;
})
4. функция webpackAsyncContext
Ключевая функцияwebpackAsyncContext,перечислитьload('Home')час,reqза'./Home',__webpack_require__.oметод
__webpack_require__.o = function (object, property) {
return Object.prototype.hasOwnProperty.call(object, property);
};
Этот метод заключается в определении переменнойmapЕсть ли ключ для./Homeпредмет, если не брошенныйCannot find module './Home'ошибка. иметь казнь__webpack_require__.eметод, параметрыHome.
5.webpack_require.e метод
var installedChunks = {
"app": 0
}
__webpack_require__.p = "/";
function jsonpScriptSrc(chunkId) {
return __webpack_require__.p + "js/" + ({ "Home": "Home" }[chunkId] || chunkId) +
"." + { "Home": "37ee624e" }[chunkId] + ".js"
}
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
var installedChunkData = installedChunks[chunkId];
if (installedChunkData !== 0) {
if (installedChunkData) {
promises.push(installedChunkData[2]);
} else {
var promise = new Promise(function (resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
var script = document.createElement('script');
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.src = jsonpScriptSrc(chunkId);
var error = new Error();
onScriptComplete = function (event) {
// 避免IE内存泄漏。
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if (chunk !== 0) {
if (chunk) {
var errorType = event &&
(event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId
+ ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
installedChunks[chunkId] = undefined;
}
};
var timeout = setTimeout(function () {
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script);
}
}
return Promise.all(promises);
};
__webpack_require__.eМетод является ядром реализации ленивой загрузки., который обрабатывает три вещи в этом методе.
- Используйте режим JSONP для загрузки js-файла, соответствующего маршруту, который также можно назвать чанком.
- Установите три состояния загрузки чанка и кеша в
installedChunks, чтобы предотвратить повторную загрузку фрагментов. - Обработка тайм-аута загрузки чанков и сценариев ошибок загрузки.
Три состояния загрузки чанка
-
installedChunks[chunkId]за0, указывая на то, что фрагмент был загружен. -
installedChunks[chunkId]заundefined, что означает, что фрагмент не удалось загрузить, истекло время ожидания или он никогда не загружался. -
installedChunks[chunkId]заPromiseОбъект, представляющий загружаемый фрагмент.
обработка тайм-аута загрузки чанков
script.timeout = 120;
var timeout = setTimeout(function () {
onScriptComplete({ type: 'timeout', target: script });
}, 120000);
script.timeout = 120Истечет время ожидания, если чанк не был загружен после 120 секунд загрузки.
использоватьsetTimeoutУстановите таймер на 120 секунд, чтобы он выполнялся через 120 секунд.onScriptComplete({ type: 'timeout', target: script }).
смотря наonScriptCompleteфункция
var onScriptComplete = function (event) {
// 避免IE内存泄漏。
script.onerror = script.onload = null;
clearTimeout(timeout);
var chunk = installedChunks[chunkId];
if (chunk !== 0) {
if (chunk) {
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId
+ ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
installedChunks[chunkId] = undefined;
}
};
На данный момент chunkIdHome, загрузка Home.js, код
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["Home"],{
"bb51":(function(module, __webpack_exports__, __webpack_require__){
//省略...
})
}]))
упомянутый ранееwindow["webpackJsonp"]Метод проталкиванияwebpackJsonpCallbackФункция перехвачена, если Home.js успешно загружен, она будет автоматически выполнена, а затем выполненаwebpackJsonpCallbackфункция, которая имеетinstalledChunks[chunkId] = 0;положитinstalledChunks['Home']значение устанавливается равным 0.
То есть, если время загрузки Home.js истекло, он не может быть выполнен и не может бытьinstalledChunks['Home']установлен на 0, так что на этот разinstalledChunks['Home']Значение по-прежнемуPromiseобъект. Затем он перейдет к следующему выполнению кода и, наконец,chunk[1](error)Скиньте ошибку.
var chunk = installedChunks[chunkId];
if(chunk!==0){
if(chunk){
var errorType = event && (event.type === 'load' ? 'missing' : event.type);
var realSrc = event && event.target && event.target.src;
error.message = 'Loading chunk ' + chunkId
+ ' failed.\n(' + errorType + ': ' + realSrc + ')';
error.name = 'ChunkLoadError';
error.type = errorType;
error.request = realSrc;
chunk[1](error);
}
}
chunk[1]Фактически это функция отклонения, которой в следующем коде присваивается значение.
var promise = new Promise(function (resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
обработка сбоев при загрузке чанков
Ошибка загрузки делится на два случая, один из которых заключается в том, что ресурс Home.js не загружается, другой заключается в том, что ресурс загружается успешно, но выполнение кода в Home.js вызывает ошибку, поэтому код для загрузки фрагмента неудача должна быть написана так
script.onerror = script.onload = onScriptComplete;
Последнее обрабатывается так же, как и тайм-ауты загрузки.
__webpack_require__.eОкончательный возврат представляет собойPromiseобъект. назадwebpackAsyncContextв функции
return __webpack_require__.e(ids[1]).then(function () {
return __webpack_require__(id);
});
__webpack_require__.e(ids[1])После успешного выполнения выполнить __webpack_require__(id);, в настоящее время идентификатор bb51. затем обратно в__webpack_require__функция. упомянутый ранее__webpack_require__Функция функции заключается в выполнении модуля. Узел с id bb51 находится в Home.js, вwebpackJsonpCallbackФункция имеет следующий код
function webpackJsonpCallback(data) {
var moreModules = data[1];
for (moduleId in moreModules) {
if (Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {
modules[moduleId] = moreModules[moduleId];
}
}
}
5. Проанализируйте Home.js
Home.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["Home"],{
"bb51":(function(module, __webpack_exports__, __webpack_require__){
//省略...
})
}]))
Видно, что moreModules есть{"bb51":(function(module, __webpack_exports__, __webpack_require__){})},
Зациклить moreModules и кэшировать модули в Home.js в модулях в app.js.
посмотри снова__webpack_require__В функции есть этот код
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
Таким образом, модуль в Home.js выполняется, и в модуле есть ряд методов для рендеринга страниц, а также рендерится страница компонента маршрутизации Home.vue.
На этом весь процесс ленивой загрузки компонентов маршрутизации завершен, а также подробно рассказывается, как загружать чанки и как выполнять модули.