为什么 Webpack 在使用 webpackMode: "weak" 时不发出块?



我正在将一个遗留应用程序转换为Webpack。我使用的是Webpack 5.56(在撰写本文时最新版本)。

我的应用程序是本地化的,我有一个文件夹,里面有一些区域设置文件,

locales
- locale.en.ts
- locale.de.ts
- etc

这些区域设置文件中的每一个都是一个ES模块,它们都导出(不同的实现)相同的函数——getTextprintNumber等。我有一个包装器模块,它动态地import为当前用户提供正确的区域设置:

// localization.ts
interface LocaleModule {
getText(text: string): string;
// etc
}
let module: LocaleModule;
import(`locales/locale.${currentUser.language}`).then(m => {
module = m;
});
export function getText(text: string): string {
return module.getText(text);
}

当呈现页面时,我知道当前用户的语言。我想包含正确的locale.*.js脚本作为初始块,这样:

  • 浏览器不必等待主区块加载后才能开始下载区域设置文件
  • localization.ts中的功能可以是同步的

这似乎非常适合webpackMode: "weak",因为如果locale文件因任何原因丢失(而不是默默地降低性能),我希望在控制台中得到一个错误。文档似乎明确指出了我的用例:

当所需的块总是在初始请求中手动提供(嵌入在页面中)时,这对于通用呈现非常有用。

这是我的代码:

let module: LocaleModule;
import(
/* webpackMode: "weak" */
/* webpackChunkName: "locales/[request]" */
`./locales/locale.${currentUser.language}`
).then(m => {
module = m;
});

然而,webpackMode: "weak"似乎导致Webpack根本不为引用的模块发出。Webpack的输出文件夹中没有任何locale文件。如果一个区块从未被发出,我就不能很好地在HTML中包含它!

这种行为的原因是什么?有没有一种干净的方法可以让Webpack为动态imported模块发出块,但不能异步下载它们?(我知道我可以使用webpackMode: "lazy",只需在脚本标记中预先包含块,但如果区域设置文件丢失,我希望得到一个错误。)或者我有XY问题,有更好的方法可以做到这一点,但我不知道?

我有类似的问题并解决了这个问题。

我的本地文件看起来像:

import dictionary from './locales/en.json'
const en = dictionary
window.__default_dictionary__ = en
module.exports = en

我的区域设置结构如下:在此处输入图像描述

您必须在webpack配置中为splitChunks.cacheGroups添加新的cacheGroup

locales: {
enforce: true,
reuseExistingChunk: true,
priority: 50,
chunks: 'all',
test(module) {
if (/[\/]src/i18n[\/]/.test(module.resource)) return true
return false
},
name(module) {
const moduleFileName = module
.identifier()
.split('/')
.reduceRight((item) => item)
.replace('.json', '')
.replace('.js', '')
return `locales~${moduleFileName}`
},
},

现在,您的所有区域设置文件都将被提取到另一个区块文件中。

您可以使用任何用于加载区域设置的处理程序,例如:

loadLocaleHandler: async (locale: Locale) => {
let localeModule: { default: Dictionary } = await import(`i18n/${locale}`)
return localeModule.default
},

为了使一切正常工作,您必须

  • 为结果html添加区域设置块
<script src="/assets/webpack/js/runtime.js" defer="defer"></script>
<script src="/assets/webpack/js/vendors.js" defer="defer"></script>
<!-- For example it maybe value from cookie or context of app -->
<script src="/assets/webpack/js/locales~(en|it|es).chunk.js" defer="defer"></script>
<script src="/assets/webpack/js/entry.js" defer="defer"></script>
  • 将魔术webpack代码添加到入口点
const defaultLocale: Locale = cookies.getItem('locale') || process.env.DEFAULT_LOCALE
if (__webpack_modules__[`./src/i18n/${defaultLocale}.js`]) {
__webpack_require__(`./src/i18n/${defaultLocale}.js`)
}

总计:

  • 您不需要等待第一个请求通过运行时导入加载区域设置
  • 您可以为多区域和多域应用程序组织区域设置
  • 您的所有区域设置仍然是动态模块,可以在运行时加载

我不能发布这么长的评论,所以它必须是一个答案。。。

因此,模块之间似乎没有真正的链接,bundler无法在编译时解析它们,因此不会发出它们。我在你的代码中唯一改变的是模块是如何导入的,它是开箱即用的:

const langCode = getLangCode();
let mod;
import("./locales/locale.en")
switch (langCode) {
case "en":
import(`./locales/locale.en.js`).then(m => {
mod = m;
console.log("loaded locale");
})
break;
case "de":
import(`./locales/locale.de.js`).then(m => {
mod = m;
console.log("loaded locale");
})
break;
default:
}
export function getText(text) {
return mod.getText(text);
}
function getLangCode() {
return "de";
}

我知道切换的情况并不理想,但bundler无法自动猜测模式:./locales/locale.${langCode}.js并添加目录中与.js匹配的所有文件。

医生说:

"漏洞":如果模块函数已经以其他方式加载(例如,导入的另一个区块或加载了包含模块的脚本),则尝试加载模块。Promise仍然会返回,但只有在块已经在客户端上时才能成功解析。如果模块不可用,Promise将被拒绝。永远不会执行网络请求。当所需的块总是在初始请求中手动提供(嵌入在页面中)时,这对于通用呈现很有用,但在应用程序导航将触发未初始提供的导入的情况下则不然。

据我所知,这意味着块应该已经在页面上,并通过其他方式生成。

我希望这能帮助你解决问题。

为了使用weak,您必须按照文档中的说明手动提供块。这意味着将其作为注释添加到动态导入中不会创建任何块(与lazylazy-once相矛盾)。

有没有一种干净的方法可以让Webpack为动态导入的模块发出块,但不异步下载它们?

对于同步加载:

您可以:

  1. 使用webpackMode: "lazy",并如您所述在脚本标记中预先包含块(如果缺少块,则返回的Promise将被拒绝)
  2. 您可以将locale js文件定义为动态入口点,然后自己手动加载它们

例如,为每个区域设置创建一个入口点可能类似于:

const glob = require('glob')
module.exports = {
devtool: false,
entry: {
...glob.sync('./src/locales/*').reduce((acc, module) => {
const name = module.replace('./src/locales/', '').replace('.js', '')
acc[name] = module
return acc
}, {})
}
};

这将发出locale.de.jslocale.en.js捆绑包,然后您应该以某种方式手动加载<script defer src="locale.<locale>.js"></script>,但这取决于您如何为应用程序提供服务。

对于异步加载:

您可以将webpackMode: "lazy"webpackPreload: true一起使用,以便解耦mainlocale块请求。如文件中所述

预加载的区块开始与父区块并行加载。

最新更新