【问题标题】:Load generated webpack chunks from runtime chunk从运行时块加载生成的 webpack 块
【发布时间】:2019-09-07 10:39:39
【问题描述】:

我正在使用新的(反应)代码部分更新现有的 Web 应用程序,并正在使用 webpack 将所有内容捆绑在一起以进行生产。因为现有的 HTML 页面(实际上是 XML 转换为 HTML)已经存在,所以我无法使用由 HtmlWebpackPlugin 生成的 index.html

我想要实现的是 webpack 生成一个小的 runtime.bundle.js,它将动态加载其他生成的块(main.[contenthash]vendor.[contenthash]),而不是将这些条目作为 script 标签添加到 @987654327 @。这样runtime.bundle.js 可以设置为nocache,而其他大块可以被浏览器缓存并在代码更改时正确获取。

例如,这里是生成的index.html的body块,注意注释:

<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 中的某处创建以下 sn-p

          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'
            );

那么vendorsmain 块可以实现同样的效果吗?

我能想到的唯一其他替代解决方案是使用WebpackManifestPlugin 生成manifest.json 并使用它将块注入到已经存在的HTML 文件中。

【问题讨论】:

  • 我正在为完全相同的场景而苦苦挣扎。你找到解决办法了吗?
  • @nevermind777 我编写了一个脚本来创建一个“运行时”JS 文件,该文件会将散列块作为脚本注入 HTML 文档的头部(使用由 WebpackManifestPlugin 创建的 manifest.json。这个脚本可以称为 npm 脚本,如果你喜欢我可以分享...
  • 谢谢@Sjiep 听起来很有趣,我想看看你的解决方案

标签: javascript webpack


【解决方案1】:

我最终通过创建一个脚本解决了这个问题,该脚本使用manifest.json(由WebpackManifestPlugin 生成)生成一个runtime.js 脚本,该脚本将在页面加载时动态加载块并插入此@ 987654325@ 进入index.html 的头部。这可从 npm scripts 部分使用 tasksfile npm 包调用。

在你的 webpack 配置中,将插件添加到插件数组中:

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

我有以下外部 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`);
  });
}

该函数基本上循环遍历manifest.json中的所有JS入口文件。
然后使用这些条目创建脚本标记作为src 属性,然后将这些脚本标记作为子项添加到document.head(触发条目的加载)。 最后将此脚本保存到runtime.js 文件并存储在构建目录中。

您现在可以将此 runtime.js 文件包含到您的 html 文件中,如果所有路径设置正确,您应该加载块。

【讨论】:

    【解决方案2】:

    HtmlWebpackPlugin 提供了一个chunks option,您可以使用它来选择性地包含来自您的 webpack 配置的 entry 对象的某些条目。使用它,您实际上可以通过将自定义脚本放入单独的src/dynamic-load.js 文件来简化大部分逻辑,只需将其添加到插件配置中:

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

    chunks 用法的另一个例子可见here)。

    甚至它们的内置templateParameters 也可能允许您将构建输出文件名放入变量中并在dynamic-load.js 中读取它们。您必须为它制作自己的模板,但这可能是一种方法。你甚至可以看到他们建议的templateParameters example did it

    如果这不起作用,您总是可以通过 webpack 本身通过 afterEmit 钩子获取捆绑的输出文件名,然后将它们输出到 dynamic-load.js 将调用的 JSON 文件中。要点如下所示,但此时,您只是在做与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 实际上是一个assets manifest,不会产生correct manifest.json。他们应该将他们的default file name 更新为assets-manifest.json,但我想还没有人向他们指出这一点。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-04-04
      • 2016-01-24
      • 2017-12-04
      • 2016-02-26
      • 1970-01-01
      • 2021-01-13
      • 2018-02-13
      相关资源
      最近更新 更多