为什么Webpack在使用webpackMode时不发出块:“软弱”?

vjrehmav  于 2022-12-23  发布在  Webpack
关注(0)|答案(3)|浏览(143)

我正在将一个旧的应用程序转换到Webpack,我使用的是Webpack5.56(撰写本文时最新的版本)。
我的应用程序是本地化的,我有一个文件夹,里面有一些区域设置文件,

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

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

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

  • 浏览器在开始下载locale文件之前不必等待主块加载。
  • 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为动态import ed模块发出块,但异步下载它们?(我知道我可以使用webpackMode: "lazy",并且只在脚本标记中包含块,但是如果缺少locale文件,我希望得到一个错误。)或者我有一个XY problem,还有我不知道的更好的方法吗

3npbholx

3npbholx1#

我有类似的问题,并解决了这个问题。
我的本地文件如下所示:

import dictionary from './locales/en.json'

const en = dictionary

window.__default_dictionary__ = en

module.exports = en

我的语言环境结构如下所示:enter image description here
必须在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>
  • 将magic webpack代码添加到入口点
const defaultLocale: Locale = cookies.getItem('locale') || process.env.DEFAULT_LOCALE
if (__webpack_modules__[`./src/i18n/${defaultLocale}.js`]) {
  __webpack_require__(`./src/i18n/${defaultLocale}.js`)
}

总计:

  • 你不需要等待第一次请求时通过运行时导入加载语言环境
  • 您可以为多区域设置和多域应用程序组织区域设置
  • 所有语言环境都保持动态模块,可以在运行时加载
ryevplcw

ryevplcw2#

我不能发表这么长的评论,所以它必须是一个答案...
所以看起来模块之间没有真实的的链接,捆绑器不能在编译时解析它们,所以它们没有被发出。我在代码中唯一改变的是模块是如何导入的,它是开箱即用的:

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";
}

我知道开关的情况并不理想,但捆绑器不能自动猜测这种模式:./locales/locale.${langCode}.js并添加目录中与.js匹配的所有文件。
doc表示以下内容:
“弱”:如果模块函数已经以其他方式加载,则尝试加载模块(例如,另一个块导入了该模块,或者加载了包含该模块的脚本)。仍会返回Promise,但只有在块已在客户端上时才能成功解析。如果模块不可用,承诺被拒绝。网络请求将永远不会被执行。当所需的块总是在初始请求中手动提供时,这对于通用呈现很有用(嵌入在页面中),但不适用于应用导航将触发最初未提供的导入的情况。
据我所知,这意味着块应该已经在页面上,并通过其他方式生成。
我希望这能帮助你解决你的问题。

64jmpszr

64jmpszr3#

为了使用weak,你必须已经手动提供了文档中所述的块。这意味着将其作为注解添加到动态导入中不会创建任何块(与lazylazy-once相反)。
有没有一种干净的方法可以让Webpack为动态导入的模块发出块,但不异步下载它们?

    • 对于同步加载:**

您可以:
1.使用webpackMode: "lazy"并按照您的说明在脚本标记中包含块(如果缺少块,则拒绝返回的Promise)。
1.您可以将locale js文件定义为dynamic entry points,然后自己手动加载它们。
对于您的示例,为每个语言环境创建一个入口点可能类似于:

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块请求。
预加载的块开始与父块并行加载。

相关问题