Использование React Router и Webpack 2 как требовать внешних библиотек только на определенных маршрутах?

В настоящее время работает

Используя Webpack 2 и React Router v4, я смог настроить разбиение рабочего кода. Существует промежуточный <AsyncComponent>, который разрешает обещание и возвращает компонент (шаблон, найденный в проблемах github).

Пример набора маршрутов ниже:

<Switch>
    <Route 
        path="me" 
        render={(props) => 
            <AsyncComponent promise={ require.ensure([], (require) => require('./modules/Profile'), 'profile') } props={props} />
        } 
    />
    <Route 
        path="credit-card" 
        render={(props) => 
            <AsyncComponent promise={ require.ensure([], (require) => require('./modules/CreditCard'), 'credit-card') } props={props} />
        } 
    />
</Switch>

Цель

Я хотел бы расширить это, и для определенных маршрутов только загрузите дополнительные библиотеки. В приведенном выше примере я хотел бы получить библиотеку StripeJS (https://js.stripe.com/v2/) только на маршруте кредитной карты.

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

Покушение

Следующие попытки были предприняты для небольшого успеха:

  • Маркировка lib external в конфигурации webpack. Это (правильно) помещает библиотеку как внешнюю и не будет пытаться связывать ее во время последовательности запроса require. Однако ожидание заключается в том, что библиотека вводится вручную.
  • Я играл с идеей использования загрузчика псевдо script (при ударе по этому маршруту вручную создайте <script> с атрибутом src, дождитесь его загрузки, а затем пусть компонент выполнит свою задачу. Это работает нормально, но на самом деле ужасно из-за ремонтопригодности (если нужны две или более библиотеки, мне тогда нужно реплицировать неуклюжую ручную загрузку script) и, похоже, работает против "способа" webpack.

Соответствующие части конфигурации

const core = [
    'lodash',
    'react',
    'react-dom',
    'axios',
    'react-router-dom',
];

const config = {
    context: path.resolve(__dirname, './ts_build'),
    node: {
        fs: "empty"
    },
    entry: {
        app: './app.js',
        core: core,
    },
    output: {
        filename: '[name].js',
        chunkFilename: '[name].[id].chunk.js',
        path: path.resolve(__dirname, './../../public'),
        publicPath: 'http://example.org/',
    },
    resolve: {
        modules: [
            path.resolve('./src'),
        ],
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            names: ['core'], 
            minChunks: Infinity,
        }),
        new webpack.NamedModulesPlugin(),
    ],
};

Ответы

Ответ 1

https://github.com/hinok/webpack2-react-router-code-splitting

Репозиторий содержит webpack2 + react-router v4 + реализованный code splitting с помощью динамического import() и загрузки внешних библиотек по требованию только один раз используя loadjs. В качестве примера он загружает Stripe.js для определенного маршрута.

В репозитории вы можете найти два способа сделать разбиение кода

Это основано на официальной документации и техническом документе Webpack от Andrew Clark

При подготовке этого репозитория я обнаружил, что @Chris хотел бы загрузить Stripe.js только для определенных маршрутов, размещенных на внешнем CDN. С тех пор я думал о том, как лучше использовать модули AMD и избегать утечки глобальных переменных, но это немного сложно, потому что каждый модуль AMD может иметь другую оболочку, говоря об обертке, что-то вроде:

(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['b'], function (b) {
            return (root.amdWebGlobal = factory(b));
        });
    } else {
        root.amdWebGlobal = factory(root.b);
    }
}(this, function (b) {
    return {};
}));

Я мог бы сказать, что UMD обертка является стандартным, но иногда люди предпочитают менее самоуверенные обертки или им просто все равно в других средах. Я упоминаю об этом, потому что в истории git вы можете найти commit под названием Amdify, что было более доказательством концепции того, как AMD env может быть смоделированы. Наконец, я решил удалить его, потому что он далек от идеала и не охватывает все случаи использования и края.

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

@mc-zone вы можете загрузить модули AMD с помощью script.js. Но это не имеет ничего общего с webpack, это просто работает, потому что AMD была разработана для этого. Таким образом, вы не сможете использовать функции webpack внутри этих модулей AMD. webpack требует статического анализа времени сборки.

by @jhns см. в github

webpack не обрабатывает загрузку script. Для этого используйте отдельную библиотеку. я. е. https://github.com/ded/script.js/

by @sokra посмотреть на github

Если это невозможно, вы не должны требовать(), но загружаете его через script -loader, например script.js.

by @jhns см. в github

Связанные с этим проблемы в Github:


Сегодня я нашел статью Джеймса Кале о среде о своем проекте react-loadable, которые выполняют почти то же самое, что и компонент LazilyLoad, но с обещанием, что

  • он поддерживает рендеринг на стороне сервера с помощью динамического require()
  • он с готовностью предварительно загружает компоненты при необходимости

Я настоятельно рекомендую проверить его.

Ответ 2

Если вы используете Webpack 2, вы используете import() в конфигурационном файле React Router

export default {
 component: App,
 childRoutes: [
   {
     path: '/',
     getComponent(location, cb) {
       import('external-library-here')
       .then(function(){
         return System.import('pages/Home');
       })
      .then(loadRoute(cb)).catch(errorLoading);
     }
   },
   {
     path: 'blog',
     getComponent(location, cb) {
       import('pages/Blog').then(loadRoute(cb)).catch(errorLoading);
     }
   },
   {
     path: 'about',
     getComponent(location, cb) {
       import('pages/About').then(loadRoute(cb)).catch(errorLoading);
     }
   },
 ]
};

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

Ответ 3

В этом вопросе есть несколько случаев, которые следует принять подробно.

Пусть начнется.

Наблюдения

  • Вы должны переключить использование react-router из компонентов в объект PlainRoute, это даст вам больше гибкости, когда дело доходит до выполняет разделение кода, а также пропускает создание компонента <AsyncComponent>
  • Я уверен, что у вас будет больше вложенных компонентов в вашем маршруте, так что, если вложенный компонент в маршруте credit-card нуждается в библиотеке?

Предложения

  • Используйте свой компонент маршрута в качестве точки входа, поэтому он не станет огромной реализацией в одном компоненте
  • Учитывая приведенное выше предложение, вы можете добавить туда индекс с вашими зависимостями в библиотеке и использовать их в этом маршруте (у вас вложенные компоненты)
  • Вам не следует импортировать эти библиотеки в любую часть кода, иначе эта функция будет ломаться
  • После того, как вы загрузили эти библиотеки, добавьте их в объект окна. и ваши вложенные компоненты смогут глобально обращаться к вашим библиотекам.

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

В конце концов, это тот подход, который я предложил:

import App from 'components/App';

function errorLoading(err) {
  console.error('Dynamic page loading failed', err);
}

function loadRoute(cb) {
  return (module) => cb(null, module.default);
}

function injectLibraries(libraries) {
  Object.keys(libraries).forEach(key => {
    window[key] = libraries[key];
  });
}

export default {
  component: App,
  childRoutes: [
    {
      path: '/(index.html)',
      name: 'home',
      getComponent(location, cb) {
        const importModules = Promise.all([
          import('components/HomePage/'),
          import('components/HomePage/libraries'),
        ]);

        const renderRoute = loadRoute(cb);

        importModules.then(([component, libraries]) => {
          injectLibraries(libraries);
          renderRoute(component);
        });

        importModules.catch(errorLoading);
      },
    },
    {
      path: '/credit-card',
      name: 'credit-card',
      getComponent(nextState, cb) {
        const importModules = Promise.all([
          import('components/CreditCardPage/'),
          import('components/CreditCardPage/libraries'),
        ]);

        const renderRoute = loadRoute(cb);

        importModules.then(([component, libraries]) => {
          injectLibraries(libraries);
          renderRoute(component);
        });

        importModules.catch(errorLoading);
      },
    },
  ],
};

Файл ваших библиотек должен выглядеть так:

import stripe from 'stripe';

export default {
  stripe 
};

Ответ 4

Возможно, вы захотите изучить:

https://github.com/faceyspacey/webpack-flush-chunks

https://github.com/faceyspacey/require-universal-module

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

Этот пакет - это то, что вы используете для очистки узлов Webpack, которые были определены как необходимые по запросу:

https://github.com/faceyspacey/webpack-flush-chunks

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