简体   繁体   English

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

[英]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 模块,它们都导出(不同的实现)相同的功能 — 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);
}

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脚本作为初始块,以便:

  • The browser doesn't have to wait for the main chunk to load before it can start downloading the locale file.浏览器不必等待主块加载就可以开始下载语言环境文件。
  • The functions in 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为了让一切正常工作,你必须

  • Add locale chunk for result html为结果 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~(ru|en|it|es).chunk.js" defer="defer"></script>
<script src="/assets/webpack/js/entry.js" defer="defer"></script>
  • Add magic webpack code to entry point将神奇的 webpack 代码添加到入口点
const defaultLocale: Locale = cookies.getItem('locale') || process.env.DEFAULT_LOCALE
if (__webpack_modules__[`./src/i18n/${defaultLocale}.js`]) {
  __webpack_require__(`./src/i18n/${defaultLocale}.js`)
}

Totally:完全:

  • you don't need wait loading locales by runtime import for first request您不需要为第一个请求等待通过运行时导入加载语言环境
  • you can organize locales for multilocale and multidomain app您可以为多语言环境和多域应用程序组织语言环境
  • all of your locales remain dynamic modules and can be loaded at runtime您所有的语言环境都是动态模块,可以在运行时加载

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 ).这意味着将它作为注释添加到动态导入中不会创建任何块(与lazylazy-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:您可以:

  1. Use 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将被拒绝)。
  2. You can define the locale js files as dynamic entry points and load them manually by yourself.您可以将 locale js 文件定义为动态入口点,并自行手动加载它们。

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.jslocale.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.

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