我正在用新的(反应性)代码更新现有的Web应用程序,并正在使用Webpack将所有内容捆绑在一起以进行生产。因为现有的HTML页面(实际上是XML转换为HTML)已经存在,所以我不能使用index.html
生成的HtmlWebpackPlugin
。
我想实现的是,webpack生成了一个小的runtime.bundle.js
,它将动态加载其他生成的块(main.[contenthash]
和vendor.[contenthash]
),而不是将这些条目添加为{{ 1}}标记到script
。这样,index.html
可以设置为runtime.bundle.js
,而其他大块可以由浏览器缓存并在代码更改时正确提取。
作为示例,这是生成的nocache
的主体块,请注意以下注释:
index.html
运行时文件已经加载了另一个块,该块是使用以下代码从JS动态导入的:
<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>
这将在const App = React.lazy(() => import(/* webpackChunkName: "modulex" */ './App'));
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'
);
和vendors
块可以实现相同的功能吗?
我能想到的唯一其他替代解决方案是使用main
生成WebpackManifestPlugin
并将其用于将块注入到现有的HTML文件中。
答案 0 :(得分:0)
我最终通过创建一个脚本来解决此问题,该脚本使用manifest.json
(由WebpackManifestPlugin
生成)来生成runtime.js
脚本,该脚本将动态地加载块页面加载,并将此runtime.js
插入index.html
的头部。可使用tasksfile npm软件包在npm scripts
部分中调用此函数。
在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文件中,如果所有路径都设置正确,则应加载块。
答案 1 :(得分:0)
HtmlWebpackPlugin 提供了一个 chunks
option,您可以使用它来选择性地包含来自 webpack 配置的 entry
对象的某些条目。使用它,您实际上可以通过将自定义脚本放入单独的 src/dynamic-load.js
文件中来简化自定义脚本中的大部分逻辑,只需将其添加到插件配置中即可:
entry: {
runtimeLoader: './src/dynamic-load.js'
},
plugins: [
new HtmlWebpackPlugin({
// ...
chunks: [ 'runtimeLoader' ]
}),
]
(可以看到 here 的另一个 chunks
用法示例)。
甚至它们的内置 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
,但我想还没有人向他们指出这一点。