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

  • 相关阅读:
    Bug
    [转]C# 向web网站GET、POST 数据
    使用JavaScript触发ASP.NET Validator验证
    Asp.net 布尔运算符
    HTML 后退功能JS
    [转]C# 获取硬盘序列号 Volume Serial Number
    ASP.NET中,Gridview如何将源数据中的
    显示成回车

    ASP.NET 验证控件
    与或非的运算法则
    [转]WinForm开发,窗体显示和窗体传值相关知识总结
  • 原文地址:https://www.cnblogs.com/givingwu/p/13067822.html
Copyright © 2011-2022 走看看