• 微前端构建生成重复 moduleId 的原因和解决办法


    在我们的 vue-mfe 微前端项目中,出现了重复的 moduleId。第一次我们的解决办法是使用增大 hash-module-ids 的 hashDigestLength 到 8 位,vue-cli3 默认是 4 位,Webpack 默认也是 4 位。但是随着 SubApp 资源的增多,还是出现了重复。于是不得不排查了下生成重复的 moduleId 的原因:

    案例

    construction SubApp 的 portal.entry.js 中使用了 import commonService from './service/commonService',而在material SubApp portal.entry.js 中也存在相同路径的引用import commonService from './service/commonService'

    然后在 constructionmaterial 两个 SubApp 之间就出现了冲突,比如说先加载construction,那么material加载的commonService就是先前被加载的moduleCache[modueId]

    Q1: 为什么会出现相同的 modueId 在不同的 webpack 构建上下文中?

    因为 webpack hashModuleId 的构建方式是基于当前相对路径生成 moduleId,分别看下文档和代码:

    文档:

    This plugin will cause hashes to be based on the relative path of the module, generating a four character string as the module id. Suggested for use in production.

    代码:

    apply(compiler) {
    		const options = this.options;
    		compiler.hooks.compilation.tap("HashedModuleIdsPlugin", compilation => {
    			const usedIds = new Set();
    			compilation.hooks.beforeModuleIds.tap(
    				"HashedModuleIdsPlugin",
    				modules => {
    					for (const module of modules) {
    						if (module.id === null && module.libIdent) {
                  // 这就是相对路径位置,调用 module.libIdent 方法
    							const id = module.libIdent({
    								context: this.options.context || compiler.options.context
    							});
    							const hash = createHash(options.hashFunction);
    							hash.update(id);
    							const hashId = /** @type {string} */ (hash.digest(
    								options.hashDigest
    							));
    							let len = options.hashDigestLength;
    							while (usedIds.has(hashId.substr(0, len))) len++;
    							module.id = hashId.substr(0, len);
    							usedIds.add(module.id);
    						}
    					}
    				}
    			);
    		});
    	}
    

    而调用 module.libIdent 方法返回是这样的字符串:

    有 loader 的会加上 loader 路径: css:./node_modules/css-loader/index.js?!./node_modules/postcss-loader/src/index.js?!./src/components/virtual-table/table.css",

    js: ./node_modules/css-loader/lib/css-base.js

    而在我们的 SubApp 项目中因为两个路径一致,则生成的 hashId 就成了一样一样的了。

    Q2: 如何修复?

    我的解决办法重写了 HashedModuleIdsPlugin,主要就是添加了一个 id 选项,用来标识当前不同的 SubApp 上下文:

    "use strict"
    const createHash = require("webpack/lib/util/createHash")
    
    const validateOptions = require("schema-utils")
    const schema = require("webpack/schemas/plugins/HashedModuleIdsPlugin.json")
    const extendSchema = {
      ...schema,
      properties: {
        ...schema.properties,
        id: {
          description:
            "The identifier to generates the unique hash module id between different Sub-App.",
          type: "string",
        },
      },
    }
    
    /** @typedef {import("webpack/declarations/plugins/HashedModuleIdsPlugin").HashedModuleIdsPluginOptions} HashedModuleIdsPluginOptions */
    
    class EnhancedHashedModuleIdsPlugin {
      /**
       * @param {HashedModuleIdsPluginOptions=} options options object
       */
      constructor(options) {
        if (!options) options = {}
    
        validateOptions(extendSchema, options, "Hashed Module Ids Plugin")
    
        /** @type {HashedModuleIdsPluginOptions} */
        this.options = Object.assign(
          {
            id: "id",
            context: null,
            hashFunction: "md4",
            hashDigest: "base64",
            hashDigestLength: 4,
          },
          options
        )
      }
    
      apply(compiler) {
        const options = this.options
        compiler.hooks.compilation.tap(
          "EnhancedHashedModuleIdsPlugin",
          (compilation) => {
            const usedIds = new Set()
            compilation.hooks.beforeModuleIds.tap(
              "EnhancedHashedModuleIdsPlugin",
              (modules) => {
                for (const module of modules) {
                  if (module.id === null && module.libIdent) {
                    // 用 id 再加上 libIdent 返回的结果
                    const id =
                      this.options.id +
                      " " +
                      module.libIdent({
                        context: this.options.context || compiler.options.context,
                      })
                    const hash = createHash(options.hashFunction)
                    hash.update(id)
                    const hashId = /** @type {string} */ (hash.digest(
                      options.hashDigest
                    ))
                    let len = options.hashDigestLength
                    while (usedIds.has(hashId.substr(0, len))) len++
                    module.id = hashId.substr(0, len)
                    usedIds.add(module.id)
                  }
                }
              }
            )
          }
        )
      }
    }
    
    module.exports = EnhancedHashedModuleIdsPlugin
    

    然后在 vue.config.js 中:

    const WebpackEnhancedId = require("./plugins/webpack-enhanced-id-plugin")
    
    new WebpackEnhancedId({
    	// 将包名和包版本号对应的 ID 传进去
    	id: PACKAGE_NAME + " " + PACKAGE_VERSION,
    	context: api.getCwd(),
    	hashDigestLength: 8,
    })
    

    Webpack hash

    因为完全不是 hash 的问题,导致我们走了点弯路。怪自己开始没有认真看代码,摆手。

    hash 的主要目的是为了用来命中缓存,无论是浏览器缓存还是服务器静态文件缓存。使用不同的 hash type 是为了应对不同的缓存策略。跟打包构建moduleId没有任何关系。

    hash:

    每次构建都会生成当前构建的 hash id,所有的 bundled files 都是相同的 hash id。

    Unique hash generated for every build, The hash of the module identifier

    Hash number will be generated for each build. Generated Hash Number will be same for all the bundled files.

    contenthash:

    根据抽取的内容生成的 hash id,因此每个资源都有其对应的 hash id。

    Hashes generated for extracted content, the hash of the content of a file, which is different for each asset

    Hash number will be generated based on the entrypoints and it will be different for all the files.

    chunkhash:

    根据每个 chunk 的生成 hash id,即每个分块的 hash。

    Hashes based on each chunks' content, The hash of the chunk content

    Hash will be generated only if you made any changes in the particular file and each file will be having the unique hash number.

    Bundle, Chunk and Module

    Bundle: Produced from a number of distinct modules, bundles contain the final versions of source files that have already undergone the loading and compilation process.

    Chunk: This webpack-specific term is used internally to manage the bundling process. Bundles are composed out of chunks, of which there are several types (e.g. entry and child). Typically, chunks directly correspond with the output bundles however, there are some configurations that don't yield a one-to-one relationship.

    Module: Discrete chunks of functionality that provide a smaller surface area than a full program. Well-written modules provide solid abstractions and encapsulation boundaries which make up a coherent design and clear purpose.

    What are module, chunk and bundle in webpack? 举个例子:

    {
      entry: {
        foo: ["webpack/hot/only-dev-server.js","./src/foo.js"],
        bar: ["./src/bar.js"]
      },
      output: {
        path: "./dist",
        filename: "[name].js"
      }
    }
    
    • Modules: "webpack/hot/only-dev-server.js", "./src/foo.js", "./src/bar.js" ( + any other modules that are dependencies of these entry points!)
    • Chunks: foo, bar
    • Bundles: foo, bar

    References

  • 相关阅读:
    HDU2647(拓扑排序+反向建图)
    Android Activity之间通信
    Swift 编程语言学习0.1——Swift简单介绍
    HDU 5012 Dice (BFS)
    当向后台插入或读取JSON数据遇见回车时
    Android CTS測试Fail项改动总结(四)
    【Unity 3D】学习笔记三十五:游戏实例——摄像机切换镜头
    android蓝牙4.0(BLE)开发之ibeacon初步
    Error opening zip file or JAR manifest missing : D:play-1.2.5/framework/play-1.2.5.jar
    Codeforces Round #256 (Div. 2)——Multiplication Table
  • 原文地址:https://www.cnblogs.com/givingwu/p/13067822.html
Copyright © 2020-2023  润新知