需要新建下面两个文件

// config/index.ts 或  config/index.js
baseConfig:{
    // ... 其他配置
    plugins: [
      [path.resolve(__dirname, './publicComponentsPlugin'), {}]
    ],
}

新建文件config\publicComponentsPlugin.ts   

import type { IPluginContext } from '@tarojs/service';
import path from 'node:path'; // 新增path模块引入

export default (ctx: IPluginContext, pluginOpts) => {
    ctx.modifyWebpackChain(({ chain }) => {
        // 加载loader
        const loaderPath = path.resolve('config/publicComponentsLoader.ts');

        chain.module
            .rule('componentInjector')
            .test(/\.(js|ts|jsx|tsx)$/)
            .exclude.add(/node_modules/).end()
            .use('componentInjector')
            .loader(loaderPath)
            .end();
    });
};

新建文件config\publicComponentsLoader.ts,该loader可能还有bug需要仔细查看使用

// 主要功能是注入全局组件,每次更改该文件需要重新编译,
import fs from 'node:fs';
import path, { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
// 公共组件相对于src的路径,默认将公共组件注入到所有src下的pages中,可以通过excludeFolderName排除要注入的文件夹
const publicComponents = [
    '/src/components/MessagePrompt/index.tsx'
]
// 要排除注入的文件,
const excludeFolderName = ["init", "login"];
const resolvePath = (relativePath: string) => {
    // 获取当前文件的 URL(如 file:///path/to/current-file.js)
    const currentFileUrl = import.meta.url;

    // 将文件 URL 转换为文件系统路径(如 /path/to/current-file.js)
    const currentFilePath = fileURLToPath(currentFileUrl);

    // 获取当前文件所在目录
    const currentDir = dirname(currentFilePath);

    // 解析相对路径为绝对路径
    return path.resolve(currentDir, relativePath);
}
const extractComponentName = (path: string): string => {
    // 使用正则表达式匹配路径中的组件名
    const regex = /\/([^/]+)\/index\.(tsx|js|jsx)$/;
    const match = path.match(regex);
    if (match) {
        return match[1];
    } else {
        return "matchError"
    }
}

const removeComments = (code: string) => {
    // 移除单行注释
    code = code.replace(/\/\/.*$/gm, '');
    // 移除多行注释
    code = code.replace(/\/\*[\s\S]*?\*\//g, '');
    return code;
}

const extractPropsNames = (componentCode: string): string[] => {
    // 先移除代码中的注释
    const codeWithoutComments = removeComments(componentCode);
    const propsNames = new Set();
    // 匹配解构赋值部分
    const destructuringRegex = /\(({([^}]+)})\s*:?\s*Props\)/;
    const destructuringMatch = codeWithoutComments.match(destructuringRegex);
    if (destructuringMatch && destructuringMatch[2]) {
        const destructuredProps = destructuringMatch[2].split(',');
        destructuredProps.forEach(prop => {
            prop = prop.trim();
            if (prop) {
                // 处理默认值的情况,提取 prop 名称
                const propName = prop.split('=')[0].trim();
                propsNames.add(propName);
            }
        });
    }

    // 匹配普通 props.xxx 形式
    const normalRegex = /props\.([a-zA-Z_$][0-9a-zA-Z_$]*)/g;
    let normalMatch;
    while ((normalMatch = normalRegex.exec(codeWithoutComments)) !== null) {
        propsNames.add(normalMatch[1]);
    }
    if (propsNames.size === 0) {
        return []
    } else {
        return Array.from(propsNames) as string[];

    }
}
const insertStatementIntoFunction = (str: string, useStateImport: string, insertStatement: string): string => {
    if (!str) return '';
    let functionBodyStartIndex = -1;
    // 处理引用导出情况
    if (str.includes('export default') && !str.includes('function') && !str.includes('=>')) {
        const functionNameMatch = str.match(/export default (\w+);/);
        if (functionNameMatch) {
            const functionName = functionNameMatch[1];
            const functionDefRegex = new RegExp(`function ${functionName}\\s*\\(`);
            const functionDefMatch = str.match(functionDefRegex) as RegExpMatchArray & { index: number };;
            if (functionDefMatch) {
                const start = functionDefMatch.index + functionDefMatch[0].length;
                for (let i = start; i < str.length; i++) {
                    if (str[i] === '{') {
                        functionBodyStartIndex = i + 1;
                        break;
                    }
                }
            }
        }
    }
    // 处理普通函数导出
    if (functionBodyStartIndex === -1) {
        const normalFunctionRegex = /(?<!\/\/.*)function\s+(\w+)\s*\(/;
        const normalFunctionMatch = str.match(normalFunctionRegex) as RegExpMatchArray & { index: number };
        if (normalFunctionMatch) {
            const start = normalFunctionMatch.index + normalFunctionMatch[0].length;
            for (let i = start; i < str.length; i++) {
                if (str[i] === '{') {
                    functionBodyStartIndex = i + 1;
                    break;
                }
            }
        }
    }
    // 处理箭头函数导出
    if (functionBodyStartIndex === -1) {
        const arrowFunctionRegex = /(?<!\/\/.*)\(\s*\)\s*=>\s*{/;
        const arrowFunctionMatch = str.match(arrowFunctionRegex) as RegExpMatchArray & { index: number };
        if (arrowFunctionMatch) {
            functionBodyStartIndex = arrowFunctionMatch.index + arrowFunctionMatch[0].length;
        }
    }
    if (functionBodyStartIndex !== -1) {
        // 判断是否有useState的导入
        if (!str.includes('useState')) {
            return [
                useStateImport,
                str.slice(0, functionBodyStartIndex),
                insertStatement,
                str.slice(functionBodyStartIndex)
            ].join('\n');

        } else {
            return [
                str.slice(0, functionBodyStartIndex),
                insertStatement,
                str.slice(functionBodyStartIndex)
            ].join('\n');
        }

    }
    return str;
}
export default function (source: string) {
    if (publicComponents.length === 0) {
        return source;
    }
    // 仅在页面文件中注入(假设页面都在src/pages目录下)
    const pattern = /\\src\\pages\\.*\.tsx$/;

    // 要注入的语句
    let publicComponentsProps = {};
    let importStatement = '';
    // 没有导入的组件不会有this.resourcePath
    for (let i = 0; i < publicComponents.length; i++) {
        //  注入导入语句
        const publicComponentsName = extractComponentName(publicComponents[i])
        if (publicComponentsName) {
            importStatement += `import ${publicComponentsName} from '@/components/${publicComponentsName}/index.tsx';\n`
        }
        const componentPath = resolvePath(`..${publicComponents[i]}`);
        // 根据componentPath获取文件内容

        const componentCode = fs.readFileSync(componentPath, 'utf8');
        publicComponentsProps[publicComponentsName] = extractPropsNames(componentCode)
    }
    if (pattern.test(this.resourcePath)) {
        // 排除文件夹
        for (let i = 0; i < excludeFolderName.length; i++) {
            if (this.resourcePath.includes(excludeFolderName[i])) {
                return source;
            }
        }
        // 在文件开头添加导入语句,并且使用该组件
        source = importStatement + source;
        // // 找到最后一个</View>标签的位置
        const lastViewIndex = source.lastIndexOf('</View>');
        // // 在</View>标签之前插入<MessagePrompt />
        let publicComponentsUseStr = '';
        let insertStatement = '';
        for (const key in publicComponentsProps) {
            for (let i = 0; i < publicComponentsProps[key].length; i++) {
                if (i === 0) {
                    publicComponentsUseStr += `<${key} ${publicComponentsProps[key][i]}={${publicComponentsProps[key][i]}}`
                } else if (i === publicComponentsProps[key].length - 1) {
                    publicComponentsUseStr += ` ${publicComponentsProps[key][i]}={${publicComponentsProps[key][i]}} />\n`
                } else {
                    publicComponentsUseStr += ` ${publicComponentsProps[key][i]}={${publicComponentsProps[key][i]}} `
                }
                insertStatement += `const [${publicComponentsProps[key][i]}, set${publicComponentsProps[key][i].charAt(0).toUpperCase() + publicComponentsProps[key][i].slice(1)}] = useState();\n`
            }
        }
        if (lastViewIndex !== -1) {
            source = source.slice(0, lastViewIndex) + publicComponentsUseStr + source.slice(lastViewIndex);
        }
        // // 查找导出的函数体
        const useStateImport = `import { useState } from 'react';`;

        const newSource = insertStatementIntoFunction(source, useStateImport, insertStatement);
        // 生成调试文件,查看编译后的代码是否正确方便调试
        // fs.appendFileSync("你的绝对路径/config/path.txt", this.resourcePath + "\n");
        // fs.appendFileSync("你的绝对路径/config/code.txt", newSource + "\n" + this.resourcePath + "--------------------------------------------" + "\n");
        return newSource;
    }
    return source;

}

 现在你只需要有一个全局Taro组件将路径传入到 publicComponents 假如你的全局组件是下面这样的,在下面组件中你使用到了三个参数,isShowPrompt  , promptMessageTitle, promptMessage,然后你就可以在你的src/pages/home/index.tsx  中使用,setIsShowPrompt  , setPromptMessageTitle, setPromptMessage,来设置该组件的值。

你的页面文件结构必须是这样的 src/pages/page1/index.tsx,src/pages/page2/index.tsx

你的组件文件结构必须是这样的 src/components/components1/index.tsx,src/components/components2/index.tsx,


import { View } from "@tarojs/components"
import Taro from "@tarojs/taro"
import "./index.less"
type Props = {
    children: React.ReactNode
    isShowPrompt: boolean
    promptMessage: string
    promptMessageTitle?: string
}
// 这是一个公共组件,只需要定义props的使用方式,在其使用到该组件的页面就可以这样使用该组件的props
// 例如  你使用到了  props.isShow   在父组件你可以使用  setIsShow(false) 来控制该组件的显示隐藏
export default ({ isShowPrompt, promptMessageTitle = "你有一条新消息", promptMessage }: Props) => {
    // .... 其他函数以及功能
    return (
        <View onClick={goChart}>
            <View className={`message-prompt ${isShowPrompt ? "show" : ''}`}>
                <View className="font-bold">{promptMessageTitle}</View>
                <View className="message">{promptMessage}</View>
            </View>
        </View>
    )
} 

,如果ts提示没有这几个函数需要手动在env.d.ts中定义这几个函数,如果你使用的是js就不用管了

declare function setMessagePrompt(params: boolean): void;
declare function setPromptMessageTitle(params: string): void;
declare function setPromptMessage(params: string): void;
declare function setIsShowPrompt(params: boolean): void;

 什么原理?

原理很简单就是将该组件的导入语句和使用语句注入到所有pages/page1/index.tsx中,并将

const [isShowPrompt,setIsShowPrompt] = useState();  语句注入到导出的函数体,所以才能在页面组件中直接使用setIsShowPrompt控制该组件的数据。如果你不想所有的页面都注入公共组件可以使用excludeFolderName 配置不想注入的页面的文件夹名称

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐