微前端架構設計之構建生成重復 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM