Сбой динамической загрузки внешних модулей в веб-пак - PullRequest
0 голосов
/ 20 июня 2019

Я пытаюсь настроить следующую архитектуру: основное приложение React, которое создается с некоторыми базовыми функциями, и возможность загружать дополнительные компоненты React во время выполнения.Эти дополнительные компоненты React могут быть загружены по требованию, и они не доступны во время сборки для основного приложения (поэтому они не могут быть включены в пакеты для основного приложения и должны быть собраны отдельно).После исследований в течение некоторого времени я наткнулся на Webpack Externals , который мне показался подходящим.Сейчас я строю свои модули отдельно, используя следующий файл webpack.config.js:

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

process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';

const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);

module.exports = {
  entry: './src/MyModule.jsx',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'MyModule.js',
    library: 'MyModule',
    libraryTarget: 'umd'
  },
   externals: {
    "react": "react",
    "semantic-ui-react": "semantic-ui-react"
   },
   module: {
    rules: [
        {
            test: /\.(js|jsx|mjs)$/,
            include: resolveApp('src'),
            loader: require.resolve('babel-loader'),
            options: {              
              compact: true,
            },
        }
    ]
  },
  resolve: {
    extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx']
  }
};

Взглянул на сгенерированный файл MyModule.js, и он выглядит мне корректно.

СейчасВ моем основном приложении я импортирую модуль следующим образом:

let myComponent = React.lazy(() => import(componentName + '.js'));

, где componentName - это переменная, которая соответствует имени моего модуля, в данном случае «MyModule». Имя неизвестно.во время сборки, а файл отсутствует в папке src во время сборки.Чтобы избежать ошибок из webpack при создании этого кода с неизвестным импортом, я добавил следующее в мой webpack.config.js для основного проекта:

module.exports = {
    externals: function (context, request, callback/*(err, result)*/) {
        if (request === './MyModule.js') {
            callback(null, "umd " + request);
        } else {
            callback();
        }
    }
}

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

Затем, чтобы протестировать динамическую загрузку, я помещаю MyModule.js в папку static / js, где находятся пакеты для моего основного приложения, затем перехожу на страницу в своем основном приложении, которая запрашивает MyModule через let myComponent = React.lazy(() => import(componentName + '.js'));

Я вижу ошибку времени выполнения в консоли в строке импорта,

TypeError: undefined is not a function
    at Array.map (<anonymous>)
    at webpackAsyncContext 

Мне кажется, что он не может найти модуль.Я не понимаю, где он ищет модуль или как получить больше информации для отладки.

1 Ответ

0 голосов
/ 12 июля 2019

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

У меня были проблемы с двумя вещами - тип загружаемого модуля и способ его загрузки.

  1. Динамический импорт еще не является стандартной функцией ES - он из-за стандартизации в ES 2020 . Этот динамический импорт only вернет модуль, если объект модуля, который вы пытаетесь загрузить, является модулем ES6 (то есть чем-то, что содержит «имя_экспорта модуля»). Если вы попытаетесь загрузить что-то, упакованное как модуль CommonJS, AMD, UMD, импорт завершится успешно, но вы получите пустой объект. Похоже, что Webpack не поддерживает создание пакетов в формате ES6 - он может создавать различные типы модулей, и в моем конфигурационном файле выше я фактически создавал модули UMD (настроенные с помощью параметра libraryTarget).

  2. У меня были проблемы с самим оператором импорта, потому что я использовал его в приложении, упакованном Webpack. Webpack дает новую интерпретацию стандартного оператора импорта ES. В стандартной конфигурации веб-пакета (включая тот, который вы получаете от CRA) веб-пакет использует этот оператор в качестве точки разделения для пакетов, поэтому ожидается, что даже модули, которые динамически импортируются, будут присутствовать во время сборки веб-пакета (и процесс сборки завершится неудачей, если они недоступны). Я пытался использовать внешние веб-пакеты, чтобы сказать веб-пакету динамически загружать модули, что позволило выполнить сборку без наличия модулей. Однако приложение все еще использовало функцию импорта Webpack вместо стандартной функции импорта JS во время выполнения. Я подтвердил это, пытаясь запустить импорт ('modulename') из консоли браузера и получить результат, отличный от моего приложения, которое было в комплекте с веб-пакетом.

Чтобы решить проблему # 2, вы можете указать Webpack не интерпретировать динамический импорт ES, добавив некоторую аннотацию в оператор импорта.

import(/*webpackIgnore: true*/ 'path/to/module.js');

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

Задачу № 1 было немного сложнее решить. Как я уже упоминал выше, импорт модуля не из ES6 вернет пустой объект (если вы ожидаете обещание или используете .then ()). Однако, как оказалось, сам файл загружается, и код выполняется. Вы можете экспортировать модуль в формате «окна» с помощью Webpack, а затем загрузить его следующим образом.

await import(/*webpackIgnore: true*/`path/to/module.js`);
let myModule = window['module'].default;

Другое потенциальное решение, которое позволяет избежать использования оконного объекта, - это сборка модуля с использованием системы, способной создавать модули ES6 (т.е. не Webpack). В итоге я использовал Rollup для создания модуля ES6, который вытащил все зависимости в один файл и провел вывод через Babel. Это произвело модуль, который успешно загрузился через динамический импорт ES. Ниже был мой rollup.config.js (обратите внимание, что я включил все модули внешних узлов, необходимые для моего модуля - это раздуло размер модуля, но является требованием для моего конкретного приложения - ваши, скорее всего, будут отличаться, и вам нужно будет настроить накопительный пакет, чтобы исключить модули)

// node-resolve will resolve all the node dependencies
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import replace from 'rollup-plugin-replace';

export default {
  input: 'src/myModule.jsx',
  output: {
    file: 'dist/bundle.js',
    format: 'esm'
  },
  plugins: [
    resolve(),
    babel({
      exclude: 'node_modules/**'
    }),
    commonjs({
      include: 'node_modules/**',      
      namedExports: {
        'node_modules/react/index.js': ['Children', 'Component', 'PropTypes',   'PureComponent', 'React', 'createElement', 'createRef', 'isValidElement', 'cloneElement', 'Fragment'],
        'node_modules/react-dom/index.js': ['render', 'createElement', 'findDOMNode', 'createPortal'],
        'node_modules/react-is/index.js': ['isForwardRef']
      }
    }),
    replace({
      'process.env.NODE_ENV': JSON.stringify( 'production' )
    })
  ]
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...