简体   繁体   English

从运行时块加载生成的 webpack 块

[英]Load generated webpack chunks from runtime chunk

I'm partially updating an existing web application with new (react) code and am using webpack to bundle everything together for production.我正在使用新的(反应)代码部分更新现有的 Web 应用程序,并且正在使用 webpack 将所有内容捆绑在一起以进行生产。 Because the existing HTML page (in fact it is XML converted to HTML) is already there, I can't use the index.html that is generated by the HtmlWebpackPlugin .因为现有的 HTML 页面(实际上是 XML 转换为 HTML)已经存在,所以我无法使用HtmlWebpackPlugin生成的index.html

What I would like to achieve is that webpack generates a small runtime.bundle.js which will dynamically load the other generated chunks ( main.[contenthash] and vendor.[contenthash] ), instead of adding these entries as script tags to the index.html .我想要实现的是 webpack 生成一个小的runtime.bundle.js它将动态加载其他生成的块( main.[contenthash]vendor.[contenthash] ),而不是将这些条目作为script标签添加到index.html This way the runtime.bundle.js can be set to nocache whereas the large other chunks can be cached by the browser and correctly fetched on code changes.通过这种方式, runtime.bundle.js可以设置为nocache而其他大块可以由浏览器缓存并在代码更改时正确获取。

As an example, here is the body block of the generated index.html , note the comment:例如,这里是生成的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>

The runtime file is already loading a different chunk that is imported dynamically from JS with the following code:运行时文件已经加载了一个不同的块,该块是使用以下代码从 JS 动态导入的:

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

This creates the following snippet somewhere within the runtime.bundle.js这会在runtime.bundle.js某处创建以下代码片段

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

So can the same be achieved for the vendors and main chunk?那么对于vendorsmain块可以实现同样的目标吗?

The only other alternative solution that I can think of is to use the WebpackManifestPlugin to generate the manifest.json and use this to inject the chunks into the already existing HTML file.我能想到的唯一其他替代解决方案是使用WebpackManifestPlugin生成manifest.json并使用它来将块注入到现有的 HTML 文件中。

I solved this problem in the end by creating a script that uses the manifest.json (which is generated by the WebpackManifestPlugin ) to generate a runtime.js script that will load the chunks dynamically on page load and insert this runtime.js into the head of an index.html . 我最后通过创建一个脚本来解决这个问题,该脚本使用manifest.json (由WebpackManifestPlugin生成)生成一个runtime.js脚本,该脚本将在页面加载时动态加载块并将此runtime.js插入头部一个index.html This callable from the npm scripts section using tasksfile npm package. 这可以使用tasksfile npm包从npm scripts部分调用。

In you webpack configuration, add the plugin into the plugin array: 在webpack配置中,将插件添加到插件数组中:

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

The I have the following external JS file that I can call from my npm scripts using the tasksfile npm package, which is configured to call this function: 我有以下外部JS文件,我可以使用tasksfile npm包从我的npm scripts调用,该文件被配置为调用此函数:

// 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 built\n`);
  });
}

The function basically loops over all JS entry files in the manifest.json . 该函数基本上遍历manifest.json中的所有JS条目文件。
Script tags are then created with these entries as src property and these script tags are then added to the document.head as children (triggering the loading of the entries). 然后使用这些条目作为src属性创建脚本标记,然后将这些脚本标记作为子项添加到document.head (触发条目的加载)。 Finally this script is save to a runtime.js file and stored in the build directory. 最后,此脚本将保存到runtime.js文件中并存储在构建目录中。

You can now include this runtime.js file into you html file and if all paths are set correctly you chunks should be loaded. 您现在可以将此runtime.js文件包含到您的html文件中,如果正确设置了所有路径,则应加载块。

HtmlWebpackPlugin offers a chunks option that you could use to selectively include certain entries from your webpack config's entry object. HtmlWebpackPlugin 提供了一个chunks选项,您可以使用它来有选择地包含来自 webpack 配置entry对象的某些条目。 Using that, you could actually simplify most of the logic from your custom script by putting it into a separate src/dynamic-load.js file, only adding it to the plugin config:使用它,您实际上可以简化自定义脚本中的大部分逻辑,方法是将其放入单独的src/dynamic-load.js文件中,只需将其添加到插件配置中:

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

(Another example of chunks usage can be seen here ). (可以在此处看到chunks使用的另一个示例)。

It's possible that even their built-in templateParameters would allow you to put the build output file names into a variable and read them in dynamic-load.js .甚至它们的内置templateParameters也有可能允许您将构建输出文件名放入一个变量中并在dynamic-load.js读取它们。 You'll have to make your own template for it, but that could be one route.您必须为它制作自己的模板,但这可能是一种方法。 You can even see how their suggested templateParameters example did it .您甚至可以看到他们建议的templateParameters示例是如何做到的

If that doesn't work, you could always resort to getting the bundled output file names through webpack itself via the afterEmit hook and then output those into a JSON file that dynamic-load.js would call.如果这不起作用,您总是可以通过 webpack 本身通过afterEmit钩子获取捆绑的输出文件名,然后将它们输出到dynamic-load.js将调用的 JSON 文件中。 The gist being something like below, but at that point, you're just doing the same thing WebpackManifestPlugin does.要点如下,但在这一点上,您只是在做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
    });
});

One final note: WebpackManifestPlugin is actually an assets manifest and doesn't produce a correct manifest.json .最后一点:WebpackManifestPlugin 实际上是一个资产清单,不会生成正确的 manifest.json They should update their default file name to assets-manifest.json but I guess no one has pointed this out to them yet.他们应该默认文件名更新为assets-manifest.json但我想还没有人向他们指出这一点。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM