Так что, да, после долгих поворотов в конце туннеля, кажется, горит свет. Тем не менее, это не 100% решение, и это, конечно, не для слабонервных, так как это довольно некрасиво и хрупко. Но все же я хочу поделиться с вами своим подходом:
1) ручной разбор моего конфига маршрутов
Мой маршрутизатор использует файл конфигурации, похожий на этот:
import StaticClass from "/src/app/StaticClass.js";
export default {
StaticClass: {
match: /^\//,
module: StaticClass
},
DynClass: {
match: /^\//,
module: "/src/app/DynClass.js"
}
};
Итак, как вы можете видеть, экспорт представляет собой объект с ключами, выступающими в качестве идентификатора маршрута, и объект, который содержит совпадения (на основе регулярных выражений) и модуль, который должен выполняться маршрутизатором, если маршрут совпадает. Я могу снабдить свой маршрутизатор функцией Constructor (или объектом) для модулей, которые доступны сразу (т. Е. Содержатся в основном чанке), или если значение модуля является строкой, это означает, что маршрутизатор должен динамически загружать этот модуль, используя путь, указанный в строке.
Так как я знаю, какие модули могут быть потенциально загружены (но не когда и когда), я теперь могу проанализировать этот файл в процессе сборки и преобразовать конфигурацию маршрута в то, что веб-пакет может понять:
const path = require("path");
const fs = require("fs");
let routesSource = fs.readFileSync(path.resolve(__dirname, "app/routes.js"), "utf8");
routesSource = routesSource.substr(routesSource.indexOf("export default"));
routesSource = routesSource.replace(/module:\s*((?!".*").)*$/gm, "module: undefined,");
routesSource = routesSource.replace(/\r?\n|\r/g, "").replace("export default", "var routes = ");
eval(routesSource);
let dummySource = Object.entries(routes).reduce((acc, [routeName, routeConfig]) => {
if (typeof routeConfig.module === "string") {
return acc + `import(/* webpackChunkName: "${routeName}" */"${routeConfig.module}");`;
}
return acc;
}, "") + "export default ''";
(Да, я знаю, что это довольно уродливо, а также немного хрупко, так что это наверняка можно сделать лучше)
По сути, я создаю новый виртуальный модуль, в который преобразуется каждая запись маршрута, требующая динамического импорта, поэтому:
DynClass: {
match: /^\//,
module: "/src/app/DynClass.js"
}
становится:
import(/* webpackChunkName: "DynClass" */"/src/app/DynClass.js");
Таким образом, идентификатор маршрута просто становится именем чанка!
2) включая виртуальный модуль в сборку
Для этого я использую virtual-module-webpack-plugin
:
plugins: [
new VirtualModulePlugin({
moduleName: "./app/dummy.js",
contents: dummySource
})
],
Где dummySource
- это просто строка, содержащая исходный код моего виртуального модуля, который я только что сгенерировал. Теперь этот модуль подключен, и «виртуальный импорт» может быть обработан через веб-пакет. Но подождите, мне все еще нужно импортировать фиктивный модуль, но у меня его нет в моем режиме разработки (где я все использую изначально, поэтому никаких загрузчиков).
Итак, в моем основном коде я делаю следующее:
let isDev = false;
/** @remove */
isDev = true;
/** @endremove */
if (isDev) { import('./app/dummy.js'); }
Где "dummy.js" - это просто пустой модуль-заглушка, пока я нахожусь в режиме разработки. Части между этими специальными комментариями удаляются при сборке (с использованием загрузчика webpack-loader-clean-pragma
), поэтому, пока webpack «видит» импорт для dummy.js
, этот код не будет выполняться в самой сборке, поскольку isDev
оценивается как false
. И поскольку мы уже определили виртуальный модуль с тем же путем, виртуальный модуль включается при сборке так, как я хочу, и, конечно, все зависимости также разрешаются.
3) Обработка фактической загрузки
Для разработки это довольно просто:
import routes from './app/routes.js';
Object.entries(routes).forEach(async ([routeId, route]) => {
if (typeof route.module === "function") {
new route.module;
} else {
const result = await import(route.module);
new result.default;
}
});
(Обратите внимание, что это не фактический код маршрутизатора, просто достаточно, чтобы помочь мне с моим PoC)
Хорошо, но для сборки мне нужно что-то еще, поэтому я добавил код, специфичный для среды сборки:
/** @remove */
const result = await import(route.module);
new result.default;
/** @endremove */
if (!isDev) {
if (typeof route.module === "string") { await __webpack_require__.e(routeId); }
const result = __webpack_require__(route.module.replace("/src", "."));
new result.default;
}
Теперь код загрузки для среды разработки просто удален, и есть другой код загрузки, который использует веб-пакет для внутреннего использования. Я также проверяю, является ли значение модуля функцией или строкой, и если это последнее, я вызываю внутреннюю функцию require.ensure
для загрузки правильного фрагмента: await __webpack_require__.e(routeId);
. Помните, что я назвал свои чанки при создании виртуального модуля? Вот почему я до сих пор могу найти их сейчас!
4) нужно сделать больше
Еще одна вещь, с которой я столкнулся, - это когда несколько динамически загружаемых модулей имеют одинаковые зависимости, webpack пытается сгенерировать больше кусков с именами вроде module1~module2.bundle.js
, что нарушает мою сборку. Чтобы противостоять этому, мне нужно было убедиться, что все эти общие модули входят в определенный именованный комплект, который я назвал «расшаренным»:
optimization: {
splitChunks: {
chunks: "all",
name: "shared"
}
}
А когда я работаю в производственном режиме, я просто загружаю этот чанк вручную, прежде чем запрашиваются динамические модули в зависимости от него:
if (!isDev) {
await __webpack_require__.e("shared");
}
Опять же, этот код работает только в производственном режиме!
Наконец, я должен запретить веб-пакетам переименовывать мои модули (и чанки) в что-то вроде "1", "2" и т. Д., Но лучше сохранить имена, которые я только что определил:
optimization: {
namedChunks: true,
namedModules: true
}
Да, вот оно!Как я уже сказал, это было не красиво, но, похоже, работает, по крайней мере, с моей упрощенной настройкой теста.Я действительно надеюсь, что впереди меня не останется никаких блокировщиков, когда я сделаю все остальное (например, ESLint, SCSS и т. Д.)!