[英]Why doesn't Webpack emit chunks when using webpackMode: "weak"?
I'm transitioning a legacy app to Webpack.我正在将遗留应用程序转换为 Webpack。 I'm using Webpack 5.56 (latest at time of writing).
我正在使用 Webpack 5.56(撰写本文时为最新版本)。
My app is localised and I have a folder with a handful of locale files,我的应用程序已本地化,我有一个包含一些语言环境文件的文件夹,
locales
- locale.en.ts
- locale.de.ts
- etc
Each of these locale files is an ES module and they all export (different implementations of) the same functions — getText
, printNumber
, etc. I have a wrapper module which dynamically import
s the correct locale for the current user:这些语言环境文件中的每一个都是一个 ES 模块,它们都导出(不同的实现)相同的功能 —
getText
、 printNumber
等。我有一个包装器模块,它为当前用户动态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);
}
I know the current user's language when the page is being rendered.呈现页面时,我知道当前用户的语言。 I want to include the correct
locale.*.js
script as an initial chunk, so that:我想包含正确的
locale.*.js
脚本作为初始块,以便:
localization.ts
can be synchronous. localization.ts
中的函数可以是同步的。 This seemed like it'd be a good fit forwebpackMode: "weak"
, since I'd like to get an error in the console if the locale
file is missing for whatever reason (rather than silently degrade performance).这似乎很适合
webpackMode: "weak"
,因为如果由于某种原因(而不是无声地降低性能)丢失locale
文件,我想在控制台中得到一个错误。 The docs seem to explicitly call out my use case:文档似乎明确指出了我的用例:
This is useful for universal rendering when required chunks are always manually served in initial requests (embedded within the page).
当所需的块总是在初始请求中手动提供(嵌入在页面中)时,这对于通用渲染很有用。
Here's my code:这是我的代码:
let module: LocaleModule;
import(
/* webpackMode: "weak" */
/* webpackChunkName: "locales/[request]" */
`./locales/locale.${currentUser.language}`
).then(m => {
module = m;
});
However, it seems webpackMode: "weak"
causes Webpack to emit no chunks for the referenced modules at all .但是,似乎
webpackMode: "weak"
导致 Webpack根本不为引用的模块发出任何块。 There aren't any locale
files in Webpack's output folder. Webpack 的输出文件夹中没有任何
locale
文件。 I can't very well include a chunk in the HTML if it was never emitted!如果它从未发出过,我就不能很好地将它包含在 HTML 中!
What's the reason for this behaviour?这种行为的原因是什么? Is there a clean way to get Webpack to emit chunks for dynamically
import
ed modules but not download them asynchronously?有没有一种干净的方法可以让 Webpack 为动态
import
ed 模块发出块而不是异步下载它们? (I know that I could use webpackMode: "lazy"
and just include the chunk upfront in a script tag, but I'd like to get an error if the locale file is missing.) Or do I have an XY problem , and there's some better way to do this which I'm unaware of? (我知道我可以使用
webpackMode: "lazy"
并且只在脚本标记中预先包含块,但是如果缺少语言环境文件我想得到一个错误。)或者我有一个XY 问题,并且有一些我不知道的更好的方法来做到这一点?
I have similar issues and resolve this.我有类似的问题并解决了这个问题。
My local file looks like:我的本地文件如下所示:
const ru = LOCALE_DICTIONARY_RU
window.__default_dictionary__ = ru
module.exports = ru
My locales structure looks like: enter image description here我的语言环境结构如下: enter image description here
LOCALE_DICTIONARY_RU it's var defined with DefinePlugin LOCALE_DICTIONARY_RU它是用 DefinePlugin 定义的变量
const localePath = path.resolve(__dirname, '../src/i18n/locales')
const localeKeys = fs.readdirSync(localePath)
const locales = localeKeys.reduce((acc, key) => {
const file = require(path.resolve(__dirname, '../src/i18n/locales/', key))
acc[`LOCALE_DICTIONARY_${path.basename(key, '.json').toUpperCase()}`] = JSON.stringify(file)
return acc
}, {})
// in webpack config
new webpack.DefinePlugin(locales),
You must add new cacheGroup
for splitChunks.cacheGroups
in webpack config您必须在 webpack 配置中为
splitChunks.cacheGroups
添加新的cacheGroup
locales: {
enforce: true,
reuseExistingChunk: true,
priority: 50,
chunks: 'all',
test(module) {
if (/src\/i18n\/(.+).js$/.test(module.resource)) return true
return false
},
name(module) {
// Локали лежат в src/i18n
// где *.js файл - файл-обёртка над переменной через DefinePlugin
const moduleFileName = module
.identifier()
.split('/')
.reduceRight((item) => item)
.replace('.js', '')
return `locales~${moduleFileName}`
},
},
Now all of your locales files will be extracted to another chunk files.现在你所有的语言环境文件都将被提取到另一个块文件中。
You can use any handler for load locales, for example:您可以使用任何处理程序来加载语言环境,例如:
loadLocaleHandler: async (locale: Locale) => {
let localeModule: { default: Dictionary } = await import(`i18n/${locale}.js`)
return localeModule.default
},
And for everything to work correctly you must为了让一切正常工作,你必须
<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~(ru|en|it|es).chunk.js" defer="defer"></script>
<script src="/assets/webpack/js/entry.js" defer="defer"></script>
const defaultLocale: Locale = cookies.getItem('locale') || process.env.DEFAULT_LOCALE
if (__webpack_modules__[`./src/i18n/${defaultLocale}.js`]) {
__webpack_require__(`./src/i18n/${defaultLocale}.js`)
}
Totally:完全:
In order to use weak
you have to already manually served the chunks as stated in the docs.为了使用
weak
您必须已经按照文档中的说明手动提供块。 This means that adding it in a dynamic import as comment does not create any chunks (in contradiction with lazy
and lazy-once
).这意味着将它作为注释添加到动态导入中不会创建任何块(与
lazy
和lazy-once
相矛盾)。
Is there a clean way to get Webpack to emit chunks for dynamically imported modules but not download them asynchronously?
有没有一种干净的方法让 Webpack 为动态导入的模块发出块但不异步下载它们?
For synchronous loading:对于同步加载:
You can either:您可以:
webpackMode: "lazy"
and include the chunk upfront in a script tag as you stated (the Promise
returned is rejected in case of missing chunk).webpackMode: "lazy"
并如您所述将块预先包含在脚本标记中(如果缺少块,则返回的Promise
将被拒绝)。 For your example, creating an entrypoint for each locale could be something like:对于您的示例,为每个语言环境创建一个入口点可能类似于:
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
}, {})
}
};
This would emit locale.de.js
and locale.en.js
bundles and then you should somehow manually load a <script defer src="locale.<locale>.js"></script>
, but that depends on how you serve your app.这将发出
locale.de.js
和locale.en.js
包,然后您应该以某种方式手动加载<script defer src="locale.<locale>.js"></script>
,但这取决于您的服务方式你的应用程序。
For asynchronous loading:对于异步加载:
You can use webpackMode: "lazy"
along with webpackPreload: true
in order to decouple main
and locale
chunk requests.您可以使用
webpackMode: "lazy"
和webpackPreload: true
来分离main
块和locale
块请求。 As stated in the docs如文档中所述
A preloaded chunk starts loading in parallel to the parent chunk.
预加载的块开始与父块并行加载。
I can't post such long comment, so it has to be an answer...我不能发表这么长的评论,所以它必须是一个答案......
So it looks like there isn't a real link between the modules and the bundler can't resolve them compile time so they aren't emitted.所以看起来模块之间没有真正的链接,并且打包器无法在编译时解析它们,因此它们不会被发出。 The only think I changed in your code is how modules are imported and it worked out of the box:
我在您的代码中更改的唯一想法是模块的导入方式以及它开箱即用的方式:
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";
}
I know the switch case is not ideal, but the bundler can't automatically guess that pattern: ./locales/locale.${langCode}.js
and add all files in the directory that match .js
.我知道 switch case 并不理想,但打包程序无法自动猜测该模式:
./locales/locale.${langCode}.js
并在目录中添加与.js
匹配的所有文件。
The doc says the following:该文档说以下内容:
'weak': Tries to load the module if the module function has already been loaded in some other way (eg another chunk imported it or a script containing the module was loaded).
'weak':如果模块功能已经以其他方式加载(例如另一个块导入它或包含模块的脚本已加载),则尝试加载模块。 A Promise is still returned, but only successfully resolves if the chunks are already on the client.
仍会返回 Promise,但只有在块已经在客户端上时才会成功解析。 If the module is not available, the Promise is rejected.
如果模块不可用,则 Promise 将被拒绝。 A network request will never be performed.
永远不会执行网络请求。 This is useful for universal rendering when required chunks are always manually served in initial requests (embedded within the page), but not in cases where app navigation will trigger an import not initially served.
当所需的块总是在初始请求中手动提供(嵌入页面中)时,这对于通用渲染很有用,但在应用导航将触发最初未提供的导入的情况下则不然。
From what I understand this means the chunks are expected to be already on the page and generated through some other means.据我所知,这意味着块应该已经在页面上并通过其他方式生成。
I hope that helps you resolve your issue.我希望这可以帮助您解决您的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.