zoukankan      html  css  js  c++  java
  • 微前端构建生成重复 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

  • 相关阅读:
    C/C++中的abort、atexit、exit和_Exit
    从QQ聊天看交流的有效性
    HDU 1711 Number Sequence (KMP)
    pintos操作系统thread部分的实现
    搜狗面试的经典题(C++map按值排序,class struct的差别)
    SQL学习之使用order by 依照指定顺序排序或自己定义顺序排序
    选中多个或全中订单逻辑
    HDU 4415 Assassin's Creed(贪心)
    ashx文件中使用session提示“未将对象引用设置到对象的实例”
    .NET运行机制
  • 原文地址:https://www.cnblogs.com/givingwu/p/13067822.html
Copyright © 2011-2022 走看看