Как включить импорт вручную () в пакет веб-пакетов - PullRequest
2 голосов
/ 07 марта 2019

Я довольно новичок в Webpack, поэтому потерпите меня, если это глупый вопрос.

Моя цель - преобразовать мою старую кодовую базу на базе AMD в решение на базе ES6 Module.То, с чем я борюсь, это обработка динамических import() с.Таким образом, мой маршрутизатор приложения работает на модульной основе, то есть каждый маршрут сопоставляется с путем к модулю, а затем require d.Поскольку я знаю , какие модули будут включены, я просто добавляю эти динамически импортированные модули в свою конфигурацию r.js и могу собрать все в одном файле, при этом все требуемые вызовы все еще работают.

Теперь я пытаюсь сделать то же самое с модулями ES6 и Webpack.С моим devmode это не проблема, так как я могу просто заменить require() на import().Однако я не могу заставить это работать с комплектацией.Либо Webpack разделяет мой код (и все равно не может загрузить динамический модуль), либо - если я использую формат Array для конфигурации entry, динамический модуль будет включенным в комплект, но загрузка все равно не удастся: Error: Cannot find module '/src/app/DynClass.js'

Вот так выглядит моя конфигурация Webpack:

const webpack = require('webpack');
const path = require('path');

module.exports = {
    mode: "development",
    entry: ['./main.js', './app/DynClass.js'],
    output: {
        filename: 'main.js',
        path: path.resolve(__dirname, "../client/")
    },
    resolve: {
        alias: {
            "/src": path.resolve(__dirname, '')
        }
    },
    module: {
        rules: [
            {
                test: /\.tpl$/i,
                use: 'raw-loader',
            },
        ]
    }
};

Итак, я хочу сказать Webpack: «эй, есть другой модуль (или более), который долженбыть загружен динамически, и я хочу, чтобы он был включен в комплект "

Как я могу это сделать?

1 Ответ

0 голосов
/ 16 марта 2019

Так что, да, после долгих поворотов в конце туннеля, кажется, горит свет. Тем не менее, это не 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 и т. Д.)!

...