Skip to content

自定义模块解析器

customModuleResolvers 允许你指定某些模块不进行打包,而是通过自定义的方式在运行时获取。

功能介绍

在某些场景下,网站本身已经提供了一些模块(如 React),或者需要通过特殊方法获取模块。自定义模块解析器可以让您灵活地配置这些模块的获取方式,而不是将它们打包到输出文件中。

配置方式

typescript
// vite.config.ts 
import { defineConfig } from "vite";

export default defineConfig({
  //  ...
  plugins: [
    //    ...
    ViteWebExtKits({
        customModuleResolvers: {
            "lodash": "window.require('lodash')",
            react: {
                resolverFnInString: "window.require('react')",
                global: "react",
            },
        }
    }),
  ],
});
// vite.config.ts 
import { defineConfig } from "vite";

export default defineConfig({
  //  ...
  plugins: [
    //    ...
    ViteWebExtKits({
        customModuleResolvers: {
            "lodash": "window.require('lodash')",
            react: {
                resolverFnInString: "window.require('react')",
                global: "react",
            },
        }
    }),
  ],
});

支持两种配置形式:

1. 简单字符串形式

直接提供获取模块的代码:

typescript
customModuleResolvers: {
  "lodash": "window.require('lodash')",
  "moment": "window.globalLibs.moment"
}
customModuleResolvers: {
  "lodash": "window.require('lodash')",
  "moment": "window.globalLibs.moment"
}

2. 对象配置形式

提供更多配置选项:

typescript
type ModuleResolverConfig = {
  // 解析器函数代码字符串
  resolverFnInString: string;

  // 可选:全局变量名(用于缓存解析结果)
  global?: string;
};
type ModuleResolverConfig = {
  // 解析器函数代码字符串
  resolverFnInString: string;

  // 可选:全局变量名(用于缓存解析结果)
  global?: string;
};

完整使用示例

typescript
import { ViteWebExtKits } from "@webextkits/vite-plugins";

export default defineConfig({
  plugins: [
    ViteWebExtKits({
      extensionId: "my-extension",

      // 配置自定义模块解析器
      customModuleResolvers: {
        // 简单形式:直接返回模块
        lodash: "window.require('lodash')",

        // 对象形式:复杂的解析逻辑
        "react/jsx-runtime": {
          resolverFnInString: `
            function createJsxRuntime() {
              const React = window.require("react");
              
              function jsx(type, props) {
                return React.createElement(type, props);
              }
              
              return {
                Fragment: React.Fragment,
                jsx: jsx,
                jsxs: jsx,
                jsxDEV: jsx
              };
            }
            
            return createJsxRuntime();
          `,
          global: "jsxRuntime", // 结果会被缓存到 window.__jsxRuntime__
        },

        react: {
          resolverFnInString: "window.require('react')",
          global: "react",
        },

        "react-dom": {
          resolverFnInString: `
            const moduleName = Object.keys(window.require("__debug").modulesMap)
              .find(name => name.includes("ReactDOM"));
            return window.require(moduleName);
          `,
          global: "reactDom",
        },
      },

      // 通常需要配合等待条件使用
      injectsWaitCondition: {
        condition: "!!window.require",
        timeoutSeconds: 10,
        checkInterval: 100,
      },
    }),
  ],
});
import { ViteWebExtKits } from "@webextkits/vite-plugins";

export default defineConfig({
  plugins: [
    ViteWebExtKits({
      extensionId: "my-extension",

      // 配置自定义模块解析器
      customModuleResolvers: {
        // 简单形式:直接返回模块
        lodash: "window.require('lodash')",

        // 对象形式:复杂的解析逻辑
        "react/jsx-runtime": {
          resolverFnInString: `
            function createJsxRuntime() {
              const React = window.require("react");
              
              function jsx(type, props) {
                return React.createElement(type, props);
              }
              
              return {
                Fragment: React.Fragment,
                jsx: jsx,
                jsxs: jsx,
                jsxDEV: jsx
              };
            }
            
            return createJsxRuntime();
          `,
          global: "jsxRuntime", // 结果会被缓存到 window.__jsxRuntime__
        },

        react: {
          resolverFnInString: "window.require('react')",
          global: "react",
        },

        "react-dom": {
          resolverFnInString: `
            const moduleName = Object.keys(window.require("__debug").modulesMap)
              .find(name => name.includes("ReactDOM"));
            return window.require(moduleName);
          `,
          global: "reactDom",
        },
      },

      // 通常需要配合等待条件使用
      injectsWaitCondition: {
        condition: "!!window.require",
        timeoutSeconds: 10,
        checkInterval: 100,
      },
    }),
  ],
});

生成的代码示例

配置自定义模块解析器后,会在等待条件满足后生成解析代码:

javascript
// 生成的 injects 代码示例
(function () {
  // ... 等待条件代码 ...

  waitingForCondition(function () {
    // 自定义模块解析器
    (function () {
      try {
        // 解析模块1: react/jsx-runtime
        window["__jsxRuntime__"] = (function () {
          function createJsxRuntime() {
            const React = window.require("react");

            function jsx(type, props) {
              return React.createElement(type, props);
            }

            return {
              Fragment: React.Fragment,
              jsx: jsx,
              jsxs: jsx,
              jsxDEV: jsx,
            };
          }

          return createJsxRuntime();
        })();

        // 解析模块2: lodash (没有指定 global)
        window["__lodash__"] = (function () {
          return window.require("lodash");
        })();
      } catch (e) {
        console.error("[injects] 自定义模块解析器初始化失败:", e);
      }
    })();

    // 原始的 injects 代码
    (function () {
      // 您的代码可以使用这些模块
      // 例如:import { jsx } from 'react/jsx-runtime'
      // 实际上会使用 window.__jsxRuntime__
    })();
  });
})();
// 生成的 injects 代码示例
(function () {
  // ... 等待条件代码 ...

  waitingForCondition(function () {
    // 自定义模块解析器
    (function () {
      try {
        // 解析模块1: react/jsx-runtime
        window["__jsxRuntime__"] = (function () {
          function createJsxRuntime() {
            const React = window.require("react");

            function jsx(type, props) {
              return React.createElement(type, props);
            }

            return {
              Fragment: React.Fragment,
              jsx: jsx,
              jsxs: jsx,
              jsxDEV: jsx,
            };
          }

          return createJsxRuntime();
        })();

        // 解析模块2: lodash (没有指定 global)
        window["__lodash__"] = (function () {
          return window.require("lodash");
        })();
      } catch (e) {
        console.error("[injects] 自定义模块解析器初始化失败:", e);
      }
    })();

    // 原始的 injects 代码
    (function () {
      // 您的代码可以使用这些模块
      // 例如:import { jsx } from 'react/jsx-runtime'
      // 实际上会使用 window.__jsxRuntime__
    })();
  });
})();

使用场景

1. 网站已提供的全局模块

typescript
customModuleResolvers: {
  "jquery": "window.$",
  "react": "window.React",
  "vue": "window.Vue"
}
customModuleResolvers: {
  "jquery": "window.$",
  "react": "window.React",
  "vue": "window.Vue"
}

2. 通过特殊方法获取的模块

typescript
customModuleResolvers: {
  "internal-api": {
    resolverFnInString: `
      return window.APP.getModule('internal-api');
    `
  }
}
customModuleResolvers: {
  "internal-api": {
    resolverFnInString: `
      return window.APP.getModule('internal-api');
    `
  }
}

3. 动态生成的模块

typescript
customModuleResolvers: {
  "config": {
    resolverFnInString: `
      return {
        apiUrl: window.location.origin + '/api',
        version: document.querySelector('meta[name="version"]').content,
        features: window.APP_FEATURES || {}
      };
    `,
    global: "appConfig"
  }
}
customModuleResolvers: {
  "config": {
    resolverFnInString: `
      return {
        apiUrl: window.location.origin + '/api',
        version: document.querySelector('meta[name="version"]').content,
        features: window.APP_FEATURES || {}
      };
    `,
    global: "appConfig"
  }
}

工作原理

  1. 构建时:配置的模块被标记为 external,不会打包到输出文件中
  2. 运行时:在等待条件满足后,执行自定义解析器获取模块
  3. 模块缓存
    • 如果指定了 global,结果会缓存到 window.__${global}__
    • 如果未指定 global,会自动生成变量名(如 __lodash__
  4. 模块使用:代码中的 import/require 会自动使用解析后的模块

注意事项

  1. 解析器代码:必须是合法的 JavaScript 代码,最终需要 return 模块对象
  2. 执行时机:解析器在等待条件满足后、业务代码执行前运行
  3. 错误处理:解析失败会在控制台输出错误,但不会阻止其他代码执行
  4. TypeScript 类型:需要在项目中声明这些模块的类型,避免 TypeScript 报错

Powered by Vitepress