Загрузка сгенерированных фрагментов веб-пакета из фрагмента среды выполнения

#javascript #webpack

#javascript #webpack

Вопрос:

Я частично обновляю существующее веб-приложение новым (реагирующим) кодом и использую webpack для объединения всего вместе для производства. Поскольку существующая HTML-страница (фактически это XML, преобразованный в HTML) уже существует, я не могу использовать index.html то, что генерируется HtmlWebpackPlugin .

Чего я хотел бы добиться, так это того, что webpack генерирует small runtime.bundle.js , который будет динамически загружать другие сгенерированные фрагменты ( main.[contenthash] и vendor.[contenthash] ), вместо добавления этих записей в качестве script тегов в index.html . Таким образом runtime.bundle.js , можно установить значение, в nocache то время как другие большие фрагменты могут кэшироваться браузером и корректно извлекаться при изменениях кода.

В качестве примера, вот основной блок сгенерированного index.html , обратите внимание на комментарий:

 <html>
  <head>...</head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
    <script type="text/javascript" src="runtime.bundle.js"></script>

    <!-- I want these two files below not injected as script tags, 
         but loaded from the runtime.bundle.js file above  -->

    <script type="text/javascript" src="vendors.31b8acd750477817012d.js"></script>
    <script type="text/javascript" src="main.1e4a456d496cdd2e1771.js"></script>
  </body>
</html>
 

Файл среды выполнения уже загружает другой фрагмент, который динамически импортируется из JS со следующим кодом:

 const App = React.lazy(() => import(/* webpackChunkName: "modulex" */ './App'));
 

Это создает следующий фрагмент где-то в runtime.bundle.js

           a = document.createElement('script');
        (a.charset = 'utf-8'),
          (a.timeout = 120),
          i.nc amp;amp; a.setAttribute('nonce', i.nc),
          (a.src = (function(e) {
            return (
              i.p  
              ''  
              ({ 1: 'modulex' }[e] || e)  
              '.'  
              { 1: '0e0c4000d075e81c1e5b' }[e]  
              '.js'
            );
 

Так можно ли достичь того же для vendors main фрагмента and ?

Единственное альтернативное решение, которое я могу придумать, — использовать WebpackManifestPlugin для генерации manifest.json и использовать это для вставки фрагментов в уже существующий HTML-файл.

Комментарии:

1. Я борюсь с точно таким же сценарием. Вы нашли решение?

2. @nevermind777 Я написал скрипт, который создает JS-файл ‘runtime’, который будет вставлять хэшированные фрагменты в виде сценариев в заголовок HTML-документа (используя manifest.json create by the WebpackManifestPlugin . Этот скрипт можно вызвать как скрипт npm. Я могу поделиться, если хотите…

3. Спасибо @Sjiep звучит интересно, я хотел бы ознакомиться с вашим решением

Ответ №1:

В конце концов я решил эту проблему, создав скрипт, который использует manifest.json (который генерируется WebpackManifestPlugin ) для создания runtime.js скрипта, который будет динамически загружать фрагменты при загрузке страницы и вставлять их runtime.js в заголовок an index.html . Это вызывается из npm scripts раздела с использованием npm-пакета tasksfile.

В вашей конфигурации webpack добавьте плагин в массив плагинов:

 {
  // your other webpack config
  plugins: [
    new ManifestPlugin(),
    // other webpack plugins you need
  ],
}
 

У меня есть следующий внешний JS-файл, который я могу вызвать из своего npm scripts , используя пакет tasksfile npm, который настроен для вызова этой функции:

 // The path where webpack saves the built files to (this includes manifest.json)
const buildPath = './build';
// The URL prefix where the file should be loaded
const urlPrefix = 'https://www.yourdomain.com';

function buildRuntime() {
  const manifest = require(`${buildPath}/manifest`);
  // Loop through each js file in manifest file and append as script element to the head
  // Execute within an IIFE such that we don't pollute global namespace
  let scriptsToLoad = Object.keys(manifest)
    .filter(key => key.endsWith('.js'))
    .reduce((js, key) => {
      return (
        js  
        `
        script = document.createElement('script');
        script.src = urlPrefix   "/js/${manifest[key]}";
        document.head.appendChild(script);`
      );
    }, `(function(){var script;`);
  scriptsToLoad  = '})()';

  // Write the result to a runtime file that can be included in the head of an index file
  const filePath = `${buildPath}/runtime.js`;
  fs.writeFile(filePath, scriptsToLoad, err => {
    if (err) {
      return console.log('Error writing runtime.js: ', err);
    }
    console.log(`n${filePath} succesfully builtn`);
  });
}
 

Функция в основном перебирает все файлы ввода JS в manifest.json .
Затем создаются теги скриптов с этими записями в качестве src свойств, а затем эти теги скриптов добавляются document.head в дочерние элементы as (запуск загрузки записей).
Наконец, этот скрипт сохраняется в runtime.js файле и сохраняется в каталоге сборки.

Теперь вы можете включить этот runtime.js файл в свой HTML-файл, и если все пути заданы правильно, ваши фрагменты должны быть загружены.

Ответ №2:


HtmlWebpackPlugin предлагает chunks опцию, которую вы можете использовать для выборочного включения определенных записей из объекта конфигурации вашего webpack entry . Используя это, вы могли бы фактически упростить большую часть логики вашего пользовательского скрипта, поместив его в отдельный src/dynamic-load.js файл, добавив его только в конфигурацию плагина:

 entry: {
    runtimeLoader: './src/dynamic-load.js'
},
plugins: [
    new HtmlWebpackPlugin({
        // ...
        chunks: [ 'runtimeLoader' ]
    }),
]
 

(Другой пример chunks использования можно увидеть здесь).

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

Если это не сработает, вы всегда можете прибегнуть к получению имен выходных файлов в комплекте через сам webpack через afterEmit хук, а затем вывести их в файл JSON, который dynamic-load.js будет вызывать. Суть примерно в том, что показано ниже, но в этот момент вы просто делаете то же самое WebpackManifestPlugin .

 plugins: [
    {
        apply: compiler => {
            compiler.hooks.afterEmit.tap('DynamicRuntimeLoader', compilation => {
                const outputBundlePaths = Object.keys(compilation.assets)

                // output to dist/files.json
                saveToOutputDir('files.json', outputBundlePaths);
            });
        }
    },
    // ...
]

// dynamic-load.js

fetch('/files.json').then(res => res.json()).then(allFiles => {
    allFiles.forEach(file => {
        // document.createElement logic
    });
});
 

И последнее замечание: WebpackManifestPlugin на самом деле является манифестом активов и не создает правильный manifest.json. Они должны обновить свое имя файла по умолчанию assets-manifest.json , но я думаю, что никто еще не указал им на это.