前端动态Provider插件原理
provider
对于每个编译的组件,都可以通过引入provider来进行包裹,达到统一使用一些环境变量和样式,可以自己引入,也可以不写,定义插件自动实现引入
import BusinessFieldSetting from './Views/BusinessFieldSetting'
import Setting from '@/components/Setting/Setting'
import { provider } from '@/provider/index'
import field from '@/assets/svg/field.svg'
export default {
is: 'BusinessFieldSetting',
name: '业务字段设置',
category: 'run',
icon: field,
authorizationRequired: true,
canvasView: provider(BusinessFieldSetting), <---这里的provider
settingsView: Setting,
}
插件实现原理:
该 Vite 插件名为 vite-plugin-widget-provider
,专用于构建阶段自动处理 src/widgets/**/index.ts
路径下的组件入口文件,提升组件封装一致性与开发效率。插件通过构造正则表达式匹配所有 .ts
文件,并进一步筛选出 widgets 目录下的 index.ts
文件作为目标。
核心逻辑分为两步:
1、首先通过 parseCode
函数提取文件中的 import
区块、export default
内容以及 canvasView
字段;
2、其次在 mergeCodeString
中判断 canvasView
字段是否已使用 provider()
包裹,若未包裹且未引入 provider/index
,则自动插入 import { provider } from "@/provider/index"
,并将原始 canvasView: SomeComponent
转换为 canvasView: provider(SomeComponent)
。
这一变换通过 Vite 提供的 transform
钩子完成,可直接替换目标文件内容,从而实现组件的自动包装与上下文能力注入。插件只在 apply: 'build'
模式下生效,确保不影响开发调试。整体设计逻辑清晰,避免了重复注入与手动操作,在多组件开发场景下具有良好的扩展性和维护性。
import path from 'path'
const isWin = process.platform === 'win32'
const fileRegex = /\.(ts)$/
const basePath = path.resolve(process.cwd(), './src/widgets')
const filePath = isWin ? basePath.replaceAll('\\', '/') : basePath
const regex = new RegExp(`${filePath}/([^/]*)/index.ts`)
const NOT_PAGE = 'notPage:true'
/**
* 提取关键字符
* @param {*} code
* @returns
*/
const parseCode = (code) => {
const importRegion = code.match(/import[^]*?("|')(.*?)\1;/g)
const exportDefaultRegion = code.match(/export default [\s\S]*?;/)
const canvasView = code.match(/canvasView: ([^,]+),/)
const canvasViewValue = canvasView ? canvasView[1] : ''
const imports = importRegion ? importRegion.join('\n') : ''
const exportDefault = exportDefaultRegion ? exportDefaultRegion[0] : ''
return { imports, exportDefault, canvasView: canvasView[0], canvasViewValue }
}
/**
* 合并处理代码
* @param {*} param0
* @param {*} originCode
*/
const mergeCodeString = (
{ imports, exportDefault, canvasView, canvasViewValue },
originCode
) => {
const code = `${imports}\n${exportDefault}`
if (
canvasView.includes('provider(') &&
originCode.includes('provider/index')
) {
return code
}
const providerCode = 'import { provider } from "@/provider/index";'
const exportDefaultCode = exportDefault.replace(
canvasView,
`canvasView: provider(${canvasViewValue}),`
)
return `${imports}\n${providerCode}\n${exportDefaultCode}`
}
export default function VitePluginWidgetProvider(): any {
return {
name: 'vite-plugin-widget-provider',
apply: 'build',
transform(code, id) {
if (fileRegex.test(id)) {
if (regex.test(id)) {
const codeData = parseCode(code)
const transformCode = mergeCodeString(codeData, code)
// const emptyCode = code.replaceAll(' ', '')
const newCode = transformCode
// const newCode = emptyCode.includes(NOT_PAGE) ? code : transformCode
return {
code: newCode,
map: null, // 如果可行将提供 source map
}
}
}
},
}
}