Как разбить код в Webpack с require.ensure для производства?

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

Здесь моя файловая структура:

app.js       <-- Entry point as introduced to Webpack
Module.js    <-- To be loaded dynamically

Прямой связи между app.js и Module.js, вместо этого второй файл загружается первым следующим образом:

require.ensure([], (require) => {
    let path = "Module";
    let module = require("./" + path).default;
});

Я использовал "./" + path чтобы убедиться, что Webpack не станет умным и попытаться разрешить путь статически. В любом случае, этот код работает в режиме разработки с помощью webpack-dev-сервера. Под этим я подразумеваю, что Module.js не загружается, пока я не запускаю событие для запуска вышеуказанного кода. И после этого он загружается и исполняется отлично.

Но когда я собираю проект для производства, он перестает работать. Он выдает следующую ошибку (в браузере, когда я запускаю событие загрузки), даже не пытаясь отправить запрос:

Ошибка при сборе: Не удается найти модуль './Module'.

Кроме того, когда я компоную путь динамически (например, вышеприведенный код), процесс сборки выдает следующее предупреждение:

ПРЕДУПРЕЖДЕНИЕ в. /src/app/app.js Критические зависимости: 74: 34-47 запрос зависимости - выражение

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


Я тестировал решение, данное @wollnyst, и вот результаты. Когда я реализую его так, он работает:

require.ensure(["./Module"], (require) => {
    let path = "Module";
    let module = require("./" + path).default;
});

Но это не то, как я хочу, я хочу так:

let path = "Module";
require.ensure(["./" + path], (require) => {
    let module = require("./" + path).default;
});

Теперь он выдает ошибку времени выполнения в браузере:

Uncaught TypeError: webpack_require (...). Гарантировать, что это не функция

Еще не повезло!

Ответ 1

Ввод пути, который требуется динамически запрашивать в первом аргументе require.ensure неверен и не должен выполняться. Этот массив предназначен для использования для зависимостей модуля, который вы хотите динамически загружать, и который необходим для безопасного перехода асинхронного кода в ваш обратный вызов.

Критическая часть в понимании того, как webpack может обрабатывать разделение кода, заключается в том, что вы не можете выполнять полностью динамические инструкции, так как webpack нуждается в некоторой информации о местоположении для разрешения требуемого модуля, поэтому dependency is an expression предупреждением dependency is an expression. Даже если вы можете пойти на ум, как вы это делали, предварительно предваряя ./, предпочтительнее фактически повторить полный оператор ensure со статической строкой пути модуля, даже если у вас есть много модулей для динамической загрузки, и это немного неприятно, так что вы не столкнетесь с какой-либо проблемой.

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


Что касается конфигураций, вам нужно будет использовать namedModulesPlugins и CommonsChunkPlugin. То, что я лично делаю, это иметь main.js который содержит все общие источники, vendor.js, содержащие все общие node_modules, и runtime.js (требуется веб-пакетом). Затем фрагменты разделяются, и если каждый зависит от конкретной зависимости поставщика, он будет добавлен в этот конкретный фрагмент, а не в общий vendor.js.

plugins: [
  new webpack.NamedModulesPlugin(),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'vendor',
    minChunks: module => module.context && module.context.indexOf('node_modules'),
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: 'runtime',
  }),
],

Вы также можете убедиться, что у вас есть что-то вроде следующего output.filename:

output: {
  filename: '[name]-[chunkhash].js',
}

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

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

new StatsWriterPlugin({
  fields: null,
  transform: (data, opts) => {
    const stats = opts.compiler.getStats().toJson({ chunkModules: true })
    return JSON.stringify(stats, null, 2)
  },
}),

Следует отметить также, что require.ensure специфичен для webpack и заменяется import() который он теперь также обрабатывает. Поскольку этот вопрос с 2016 года, и у него могут быть не те же элементы, которые у нас есть сейчас с webpack 2, я собираюсь немного расширить его.

Теперь предложение динамического импорта находится на пути к ES, и вы можете использовать его в webpack. Вам понадобится многозадачность Promise и что-то вроде синтаксиса babel -dynamic-import плагина (и теперь он должен быть в предустановленном состоянии) или динамического-import-webpack, если это еще не работает для вас (которые в основном преобразуют import() для ensures).

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

Там есть много других ресурсов, которые вы могли бы изучить на новой странице документации webpack о разбиении кода.

Ответ 2

Вам необходимо передать модуль, который вы хотите запросить, в первом аргументе require.ensure:

require.ensure(['./Module'], function(require) {
    const module = require('./Module');
});