跳到主要内容
版本:Next

前端动态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
}
}
}
},
}
}