Создавайте индивидуальные SPA-пакеты с помощью Webpack

Как использовать Webpack для создания независимых пакетов SPA, которые могут загружаться или не загружаться на лету, когда мой пользователь перемещается по моему SPA?

У меня есть модуль контактов и модуль задач. У обоих есть две зависимости. Я хочу, чтобы WebPack создавал пакеты для каждой из них, которые загружаются, когда (и если) необходимо.

Код ниже. Кажется, проблема в том, что каждая из этих записей рассматривается как точка входа приложения, и поэтому в нее вставляется загрузочный код веб-пакета.

Я видел различные примеры сCommonsChunkPlugin но я не могу найти справку / документацию по API для него, и из того, что я могу догадаться, это не то, чего я хочу в любом случае.

Изменить - нашел эти документыВоти добавил попытку с этим плагином ниже в моем редактировании.

Текущая конфигурация

module.exports = {
    entry: {
        contacts: './contacts',
        tasks: './tasks'
    },
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: '[name]-bundle.js'
    }
};

Contacts.js

define(['./ca', './cb'], function(ca, cb){
    var name = 'Contacts';
    alert(ca + ' ' + cb);
});

Tasks.js

define(['./ta', './tb'], function(ta, tb){
    var name = 'TASKS Main';
    alert(ta + ' ' + tb);
});

Задания bundle.js

/******/ (function(modules) { // webpackBootstrap
/******/    // The module cache
/******/    var installedModules = {};

/******/    // 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] = {
/******/            exports: {},
/******/            id: moduleId,
/******/            loaded: false
/******/        };

/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/        // Flag the module as loaded
/******/        module.loaded = true;

/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }


/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;

/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;

/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";

/******/    // Load entry module and return exports
/******/    return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {

    var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [__webpack_require__(3), __webpack_require__(4)], __WEBPACK_AMD_DEFINE_RESULT__ = function(ta, tb){
        var name = 'TASKS Main';
        alert(ta + ' ' + tb);
    }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));

/***/ },
/* 1 */,
/* 2 */,
/* 3 */
/***/ function(module, exports, __webpack_require__) {

    var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){
        var name = 'TASKS - A';
        alert('ta');
    }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));

/***/ },
/* 4 */
/***/ function(module, exports, __webpack_require__) {

    var __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;!(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_RESULT__ = function(){
        var name = 'TASKS - B';
        alert('tb');
    }.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));

/***/ }
/******/ ]);

РЕДАКТИРОВАТЬ

Вот моя попытка № 2 с CommonsChunkPlugin. Я создал пустышку app.js

app.js

var module = window.location.hash.split('/')[0];
alert(module);

Затем я переместил все свои файлы контактов и задач в папку компонентов, но в остальном оставил их в покое. Моя новая конфигурация:

module.exports = {
    entry: {
        app: './app'
    },
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: '[name]-bundle.js'
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: './components/contacts',
            filename: 'contacts-component-bundle.js'
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: './components/tasks',
            filename: 'tasks-component-bundle.js'
        })
    ]
};

Странно, сейчасприложение-bundle.js по-видимому, не имеет никакого кода начальной загрузки Webpack

webpackJsonp([0,1,2],[
/* 0 */
/***/ function(module, exports) {

    var module = window.location.hash.split('/')[0];
    alert(module);

/***/ }
]);

контакты-компонента-bundle.js теперь просто есть это

webpackJsonp([1,2],[]);

а такжезадачи-компонента-bundle.js кажется, есть весь мой код начальной загрузки webpack

/******/ (function(modules) { // webpackBootstrap
/******/    // install a JSONP callback for chunk loading
/******/    var parentJsonpFunction = window["webpackJsonp"];
/******/    window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
/******/        // add "moreModules" to the modules object,
/******/        // then flag all "chunkIds" as loaded and fire callback
/******/        var moduleId, chunkId, i = 0, callbacks = [];
/******/        for(;i < chunkIds.length; i++) {
/******/            chunkId = chunkIds[i];
/******/            if(installedChunks[chunkId])
/******/                callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/            installedChunks[chunkId] = 0;
/******/        }
/******/        for(moduleId in moreModules) {
/******/            modules[moduleId] = moreModules[moduleId];
/******/        }
/******/        if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/        while(callbacks.length)
/******/            callbacks.shift().call(null, __webpack_require__);
/******/        if(moreModules[0]) {
/******/            installedModules[0] = 0;
/******/            return __webpack_require__(0);
/******/        }
/******/    };

/******/    // The module cache
/******/    var installedModules = {};

/******/    // object to store loaded and loading chunks
/******/    // "0" means "already loaded"
/******/    // Array means "loading", array contains callbacks
/******/    var installedChunks = {
/******/        2:0,
/******/        1:0
/******/    };

/******/    // 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] = {
/******/            exports: {},
/******/            id: moduleId,
/******/            loaded: false
/******/        };

/******/        // Execute the module function
/******/        modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/        // Flag the module as loaded
/******/        module.loaded = true;

/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }

/******/    // This file contains only the entry chunk.
/******/    // The chunk loading function for additional chunks
/******/    __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/        // "0" is the signal for "already loaded"
/******/        if(installedChunks[chunkId] === 0)
/******/            return callback.call(null, __webpack_require__);

/******/        // an array means "currently loading".
/******/        if(installedChunks[chunkId] !== undefined) {
/******/            installedChunks[chunkId].push(callback);
/******/        } else {
/******/            // start chunk loading
/******/            installedChunks[chunkId] = [callback];
/******/            var head = document.getElementsByTagName('head')[0];
/******/            var script = document.createElement('script');
/******/            script.type = 'text/javascript';
/******/            script.charset = 'utf-8';
/******/            script.async = true;

/******/            script.src = __webpack_require__.p + "" + chunkId + "." + ({"0":"app","1":"./components/contacts"}[chunkId]||chunkId) + "-bundle.js";
/******/            head.appendChild(script);
/******/        }
/******/    };

/******/    // expose the modules object (__webpack_modules__)
/******/    __webpack_require__.m = modules;

/******/    // expose the module cache
/******/    __webpack_require__.c = installedModules;

/******/    // __webpack_public_path__
/******/    __webpack_require__.p = "";
/******/ })
/************************************************************************/
/******/ ([]);

Опять же, я просто пытаюсь использовать Webpack, чтобы получить подтверждение и запуск концепции SPA, с некоторой точкой входа root app.js, а затем с произвольным числом модулей / компонентов, которые загружаются по требованию. Это легко сделать с помощью requirejs, поэтому я должен представить, что мне здесь не хватает чего-то ключевого, особенно во всех статьях, в которых я рассказывал о том, насколько хорош Webpack для SPA.

РЕДАКТИРОВАТЬ 2

В ответ на bebraw ниже я попробовал следующее:

app.js

var mod = window.location.hash.split('/')[0];
alert(mod);

require.ensure([], function() {
    require('./components/' + mod).show();
});

webpack.config.js

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

module.exports = {
    entry: {
        app: './app'
    },
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: '[name]-bundle.js'
    }
};

И затем в моей папке сборки у меня остался app-bundle.js, в котором есть весь мой загрузочный код, мой код app.js, а затем 1.1-bundle.js, в котором естьвсе из моих задач и код контактов.

Ятакже попробовал это

module.exports = {
    entry: {
        app: './app'
    },
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: '[name]-bundle.js'
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: './components/contacts',
            filename: 'contacts-component-bundle.js',
            children: true
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: './components/tasks',
            filename: 'tasks-component-bundle.js',
            children: true
        })
    ]
};

Который дает то же, что и выше, но сейчастакже имеет tasks-component-bundle.js и contacts-component-bundle.js, оба из которых имеюттолько некоторый код начальной загрузки веб-пакета; код задач и контактов все еще в пакете 1.1.

Опять же, я просто хочу быть в состоянии сказать Webpack, так или иначе, объединять отдельные модули и их зависимости для последующей отложенной, асинхронной загрузки при необходимости.

Окончательный ответ дал Тобиас - создатель Webpack - ниже, который я положу здесь для потомков.

По-настоящему динамично невозможно. Webpack (в отличие от require.js) компилирует ваше приложение перед его выполнением и не имеет доступа к информации времени выполнения. Динамическое требует погружения в веб-пакете во всех возможных папках, пока ваше динамическое выражение не содержит ... Вы даже должны иметь возможность настроить его для использования mod + '/' + mod с ContextReplacementPlugin и небольшим количеством волшебства RegExp (используйте обратные ссылки в RegExp). По умолчанию это будет включать слишком много модулей.

Ответы на вопрос(3)

я блага других.

Предполагается, что веб-приложение состоит из одной страницы, на которой изначально загружены определенные служебные программы, а все последующие разделы приложения загружаются по требованию, когда пользователь перемещается и изменяет хэш URL-адреса.

Доказательство концепции app.js framework / точки входа выглядит следующим образом

app.js

var framework = require('./framework/frameworkLoader');

window.onhashchange = hashChanged;
hashChanged(); //handle initial hash

function hashChanged() {
    var mod = window.location.hash.split('/')[0].replace('#', '');

    if (!mod) return;

    framework.loadModule(mod, moduleLoaded, invalidModule);

    function moduleLoaded(moduleClass, moduleHtml){
        //empty to remove handlers previously added
        $('#mainContent').empty();

        $('#mainContent').html(moduleHtml);

        var inst = new moduleClass();
        inst.initialize();
    }

    function invalidModule(){
        alert('Yo - invalid module');
    }
};

Очевидно, интерес представляетframework.loadModule(mod, moduleLoaded, invalidModule);, Как сказал Тобиас, для КАЖДОЙ возможности должна существовать отдельная, отдельная инструкция по требованию в стиле AMD (я полагаю, что есть альтернатива CommonJS, но я ее не исследовала). Очевидно, что никто не захочет выписывать каждую возможность для большого приложения, поэтому я предполагаю, что какая-то простая задача узла будет существовать как часть процесса сборки для навигации по структуре приложения и автоматической генерации всех этих операторов require для каждого модуль для вас. В этом случае предполагается, что каждая папка вmodules содержит модуль, основной код и HTML для которого находятся в одноименных файлах. Например, для контактов определение модуля должно быть в файле modules / contacts / contacts.js, а html - в файле modules / contacts / contacts.htm.

Я просто вручную выписал этот файл, так как с помощью Node можно легко перемещаться по папкам и структурам файлов, а вывод новых файлов.

frameworkLoader.js

//************** in real life this file would be auto-generated*******************

function loadModule(modName, cb, onErr){
    if (modName == 'contacts') require(['../modules/contacts/contacts', 'html!../modules/contacts/contacts.htm'], cb);
    else if (modName == 'tasks') require(['../modules/tasks/tasks', 'html!../modules/tasks/tasks.htm'], cb);
    else onErr();
}

module.exports = {
    loadModule: loadModule
};

С остальными файлами:

webpack.config.js

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

module.exports = {
    entry: {
        app: './app'
    },
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: '[name]-bundle.js',
        publicPath: '/build/',
    }
};

И основной файл HTML

default.htm

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>

        <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
        <script type="text/javascript" src="build/app-bundle.js"></script>
    </head>
    <body>
        <h1>Hello there!</h1>
        <h2>Sub heading</h2>

        <h3>Module content below</h3>
        <div id="mainContent"></div>
    </body>
</html>

Следующим шагом является добавление специальных зависимостей к этим модулям. К сожалению добавлениеrequire(['dep1', 'dep2'], function(){ не совсем работает, как я бы надеялся; который охотно отыскивает все зависимости в списке и связывает их все с соответствующим модулем, а не загружает их по требованию. Это означает, что если и для модулей контактов, и для задач требуется одинаковая зависимость (как они собираются), оба модуля с этой полной зависимостью связаны, что приводит к загрузке и перезагрузке, когда пользователь просматривает контакты, а затем задачи.

Решением является пакетный загрузчикnpm install bundle-loader --save, Это позволяет нам делатьrequire('bundle!../../libs/alt') который возвращаетфункция что при вызове извлекает нашу зависимость. Функция принимает в качестве аргумента функцию обратного вызова, которая принимает нашу вновь загруженную зависимость. Очевидно, что загрузка N зависимостей, подобных этой, потребует неприятного кода для обхода N обратных вызовов, так что я добавлю поддержку Promise через мгновение. Но сначала нужно обновить структуру модуля для поддержки спецификации зависимостей.

contacts.js

function ContactsModule(){
    this.initialize = function(alt, makeFinalStore){
        //use module
    };
}

module.exports = {
    module: ContactsModule,
    deps: [require('bundle!../../libs/alt'), require('bundle!alt/utils/makeFinalStore')]
};

tasks.js

function TasksModule(){
    this.initialize = function(alt){
        //use module
    };
}

module.exports = {
    module: TasksModule,
    deps: [require('bundle!../../libs/alt')]
};

Теперь каждый модуль возвращает литерал объекта с самим модулем и нужными ему зависимостями. Очевидно, что было бы неплохо просто написать список строк, но нам нужноrequire('bundle! звонит прямо туда, чтобы Webpack мог видеть, что нам нужно.

Теперь встроить поддержку Promise в наш основной app.js

app.js

var framework = require('./framework/frameworkLoader');

window.onhashchange = hashChanged;
hashChanged(); //handle initial hash

function hashChanged() {
    var mod = window.location.hash.split('/')[0].replace('#', '');

    if (!mod) return;

    framework.loadModule(mod, moduleLoaded, invalidModule);

    function moduleLoaded(modulePacket, moduleHtml){
        var ModuleClass = modulePacket.module,
            moduleDeps = modulePacket.deps;

        //empty to remove handlers previous module may have added
        $('#mainContent').empty();

        $('#mainContent').html(moduleHtml);

        Promise.all(moduleDeps.map(projectBundleToPromise)).then(function(deps){
            var inst = new ModuleClass();
            inst.initialize.apply(inst, deps);
        });

        function projectBundleToPromise(bundle){
            return new Promise(function(resolve){ bundle(resolve); });
        }
    }

    function invalidModule(){
        alert('Yo - invalid module');
    }
};

Это приводит к тому, что для контактов, задач, alt и makeFinalStore создаются отдельные отдельные файлы комплектов. Загрузка задач сначала показывает пакет с модулем задач, а пакет с альтернативной загрузкой - на вкладке сети; загрузка контактов после этого показывает загрузку пакета контактов вместе с пакетом makeFinalStore. Загрузка контактов сначала показывает контакты, alt и makeFinalStore комплекты загрузки; загрузка задач после этого показывает только загрузку пакета задач.

Наконец, я хотел расширить модуль контактов, чтобы он поддерживал собственную динамическую загрузку. В реальной жизни модуль контактов может на лету загружать платежную информацию о контакте, контактную информацию, информацию о подписке и так далее. Очевидно, что это доказательство концепции будет более простым, граничащим с глупым.

В папке контактов я создал папку contactDynamic со следующими файлами

contentA.js
contentA.htm
contentB.js
contentB.htm
contentC.js
contentC.htm

contentA.js

module.exports = {
    selector: '.aSel',
    onClick: function(){ alert('Hello from A') }
};

contentA.htm

<h1>Content A</h1>

<a class="aSel">Click me for a message</a>

contentB.js

module.exports = {
    selector: '.bSel',
    onClick: function(){ alert('Hello from B') }
};

contentB.htm

<h1>Content B</h1>

<a class="bSel">Click me for a message</a>

contentC.js

module.exports = {
    selector: '.cSel',
    onClick: function(){ alert('Hello from C') }
};

contentC.htm

<h1>Content C</h1>

<a class="cSel">Click me for a message</a>

Обновленный код для contacts.js ниже. Несколько вещей, чтобы отметить. Мы строим динамические контексты заранее, чтобы мы могли соответствующим образом исключать файлы. Если мы этого не сделаем, то наша динамика требует сbundle! потерпит неудачу, когда попадет в HTML-файлы; наш контекст ограничивает файлы*.js, Мы также создаем контекст для файлов .htm - обратите внимание, что мы используем обаbundle! а такжеhtml! грузчики вместе. Также обратите внимание, чтопорядок имеет значение - bundle!html! работает ноhtml!bundle! приводит к тому, что эти связки не создаются, и я надеюсь, что кто-то может прокомментировать, почему. Но как есть, отдельные пакеты создаются для каждого отдельного файла .js и .htm и загружаются по требованию только при необходимости. И конечно я завернуbundle! звонки в обещаниях, как и раньше.

Кроме того, я понимаю, чтоContextReplacementPlugin может быть использован вместо этих контекстов, и я надеюсь, что кто-то может показать мне, как: это экземплярContextReplacementPlugin перешел в динамическийrequire?

contacts.js

function ContactsModule(){
    this.initialize = function(alt, makeFinalStore){
        $('#contacts-content-loader').on('click', '.loader', function(){
            loadDynamicContactContent($(this).data('name'));
        });
    };
}

function loadDynamicContactContent(name){
    var reqJs = require.context('bundle!./contactDynamic', false, /.js$/);
    var reqHtml = require.context('bundle!html!./contactDynamic', false, /.htm$/);

    var deps = [reqJs('./' + name + '.js'), reqHtml('./' + name + '.htm')];

    Promise.all(deps.map(projectBundleToPromise)).then(function(deps){
        var code = deps[0],
            html = deps[1];

        $('#dynamicPane').empty().html(html);
        $('#dynamicPane').off().on('click', code.selector, function(){
            code.onClick();
        });
    });
}

function projectBundleToPromise(bundle){
    return new Promise(function(resolve){ bundle(resolve); });
}

module.exports = {
    module: ContactsModule,
    deps: [require('bundle!../../libs/alt'), require('bundle!alt/utils/makeFinalStore')]
};

contacts.htm

<h1>CONTACTS MODULE</h1>

<div id="contacts-content-loader">
    <a class="loader" data-name="contentA">Load A</a>
    <a class="loader" data-name="contentB">Load B</a>
    <a class="loader" data-name="contentC">Load C</a>
</div>

<div id="dynamicPane">
    Nothing loaded yet
</div>

Наконец, последний default.htm

default.htm

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>

        <script type="text/javascript" src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
        <script type="text/javascript" src="build/app-bundle.js"></script>
    </head>
    <body>
        <h1>Hello there!</h1>
        <h2>Sub heading</h2>

        <a href="#contacts">Load contacts</a>
        <br><br>
        <a href="#tasks">Load tasks</a>

        <h3>Module content below</h3>
        <div id="mainContent"></div>
    </body>
</html>

require.ensure может быть ключом здесь. Это позволит вам установить точки разделения для динамической загрузки. Вы бы каким-то образом связали его с роутером вашего SPA. Вот основная идея от Пита Ханта:https://github.com/petehunt/webpack-howto#9-async-loading .

Решение Вопроса

quire (require.ensure или драмrequire([])). Так что вам нужно написатьrequire([]) за лениво загруженную часть вашего приложения.

Ваш SPA имеет только одну точку входа: маршрутизатор (на стороне клиента). Давайте назовем этоapp.js, Страницы загружаются по требованию и не являются точками входа.

webpack.config.js:

module.exports = {
    entry: {
        app: './app'
    },
    output: {
        path: path.resolve(__dirname, 'build'),
        filename: '[name]-bundle.js'
    }
}

app.js:

var mod = window.location.hash.split('/')[0].toLowerCase();
alert(mod);

switch(mod) {
    case "contacts":
        require(["./pages/contacts"], function(page) {
            // do something with "page"
        });
        break;
    case "tasks":
        require(["./pages/tasks"], function(page) {
            // do something with "page"
        });
        break;
}

Альтернатива: использование «контекста».

При использовании динамической зависимости я. еrequire("./pages/" + mod) Вы не можете написать точку разделения на файл. Для этого случая есть загрузчик, который оборачивает файл вrequire.ensure блок:

app.js

var mod = window.location.hash.split('/')[0].toLowerCase();
alert(mod);

require("bundle!./pages/" + mod)(function(page) {
    // do something with "page"
});

Это зависит от веб-пакета. Не забудьnpm install bundle-loader --save, Проверьте правильный корпус, он чувствителен к регистру.

 Adam Rackis31 июл. 2015 г., 19:30
Еще раз спасибо @Tobias!
 Adam Rackis28 июл. 2015 г., 18:48
Спасибо большое @tobias - я попробую это, как только найду немного свободного времени позже.
 Tobias K.29 июл. 2015 г., 17:33
По-настоящему динамично невозможно. Webpack (в отличие от require.js) компилирует ваше приложение перед его выполнением и не имеет доступа к информации времени выполнения. Динамическое требует погружения в веб-пакете во все возможные папки, пока ваше динамическое выражение не содержит.., Вы даже должны быть в состоянии настроить его для использованияmod + '/' + mod с ContextReplacementPlugin и небольшим волшебством RegExp (используйте обратные ссылки в RegExp). По умолчанию это будет включать слишком много модулей.
 Adam Rackis29 июл. 2015 г., 02:36
Еще раз спасибо Тобиас. Это, кажется, работает, но сделать пример простонемного более реалистично это ломает.require('bundle!./components/' + mod + '/' + mod)(function(module){ }); вызывает (понятно) разрыв, предположительно, так как плагин не может статически решить, где искать, и не закодирован, чтобы погрузиться во все возможные папки. Не пытаясь изменить проблему - но в реальной жизни у меня есть (и есть) отдельные папки для каждого модуля. Поддерживается ли что-то действительно динамичное в Webpack, как это требуется в requireJS?

Ваш ответ на вопрос