自定义模块解析器
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"
}
}工作原理
- 构建时:配置的模块被标记为 external,不会打包到输出文件中
- 运行时:在等待条件满足后,执行自定义解析器获取模块
- 模块缓存:
- 如果指定了
global,结果会缓存到window.__${global}__ - 如果未指定
global,会自动生成变量名(如__lodash__)
- 如果指定了
- 模块使用:代码中的 import/require 会自动使用解析后的模块
注意事项
- 解析器代码:必须是合法的 JavaScript 代码,最终需要 return 模块对象
- 执行时机:解析器在等待条件满足后、业务代码执行前运行
- 错误处理:解析失败会在控制台输出错误,但不会阻止其他代码执行
- TypeScript 类型:需要在项目中声明这些模块的类型,避免 TypeScript 报错
Webextkits Docs