当前位置: 首页 > news >正文

electron-vite 动态加载脚本 实现动态插件

上一章讲了动态脚本注入 但是有很大的问题 就是动态注入部分的loadSript后 要通过入参的方式才能获取electron和win 本次讲解如何解放这种耦合 让脚本更加自由

代码回顾

这里有一个很不方便的的问题 就是我引入以后 我想单独传递win或electron 要去修改这个很恶心的字符串数据 不能直接在本地调用

        // 动态读取脚本const LoadScript = async (setting, win) => {// 动态读取插件下的脚本let script = fs.readFileSync(path.join(setting.path, setting.script)).toString()const head = (path) => {return `import fs from "node:fs"import path from "node:path"import url from "node:url";var __dirname = "${encodeURIComponent(path)}"; __dirname=decodeURIComponent(__dirname);`}const loadMethod = () => {return `const LoadScript=async (target_path)=>{let scriptPath=path.join(__dirname, target_path);let name=scriptPath.substring(scriptPath.lastIndexOf("\\\\")+1,scriptPath.lastIndexOf("."));let script = fs.readFileSync(scriptPath).toString();script = script.replace(/import\s+fs\s+from\s+['"]node:fs['"]/g, "")script = script.replace(/import\s+path\s+from\s+['"]node:path['"]/g, "")script = script.replace(/import\s+url\s+from\s+['"]node:url['"]/g, "")// 去除路径文件名scriptPath=scriptPath.substring(0,scriptPath.lastIndexOf("\\\\"));const head = (path) => {return "var __dirname ='"+encodeURIComponent(path)+"';"+"__dirname=decodeURIComponent(__dirname);"}const injectedCode=head(scriptPath)+script;const tmpFile=path.join(scriptPath,name+Date.now()+".mjs");try {fs.writeFileSync(tmpFile, injectedCode, "utf-8");const freshModule = await import(url.pathToFileURL(tmpFile).href);let target=typeof(freshModule.default)=="function"?freshModule.default(LoadScript):freshModule.defaultreturn target} catch (err) {console.log(err);return null}finally{fs.rmSync(tmpFile);}}`}script = script.replace(/import\s+fs\s+from\s+['"]node:fs['"]/g, "")script = script.replace(/import\s+path\s+from\s+['"]node:path['"]/g, "")script = script.replace(/import\s+path\s+from\s+['"]node:url['"]/g, "")const injectedCode = `${head(setting.path)}${loadMethod()}${script}`;const tmpFile = path.join(setting.path, `.temp-${Date.now()}.mjs`)fs.writeFileSync(tmpFile, injectedCode, "utf-8");try {// 2. 用真实文件路径 import,Node 会自动解析 pluginDir/node_modulesconst mod = await import(pathToFileURL(tmpFile).href);return mod.default(this.EletronPack(setting.name), win)} finally {fs.rmSync(tmpFile);}}

解决方案

在import引入的时候 它的特性如下

  1. 通过静态 import 导入的模块是唯一的,共享相同的实例。
  2. 模块内部的变量和状态在所有导入该模块的地方是共享的。
  3. 模块的代码在第一次导入时执行,并且导出被缓存。

知道这个某个模块导入的时候是单例 那我们就好办了

空导出文件创建 share.mjs

export default {}

主进程中import该文件 并对其初始化

        let share = nullconst getShare = async () => {share = await import(SHARE_PATH)share = share.defaultshare.win = {}share.loadScript = {}}getShare()

修改我们的LoadPlugin脚本 放入需要共享的模块

// 对应插件的窗体
share.win[setting.name] = win
// 共享的electron库数据
share.electron = this.EletronPack
share.console = this.CustomConsole()
// 给后续需要导入脚本的对应插件添加导入函数 每个函数都是基于插件根路径去加载 所以loadScript每个内容都不一样 隔离函数去调用除目录下的脚本
share.loadScript[setting.name] = (target_path) => {let t_p = target_path.replace(/\//g, "/")return LoadScript({ path: setting.path, name: setting.name, script: t_p })
}
PLUGINS[setting.name] = {}
PLUGINS[setting.name].win = win
PLUGINS[setting.name].js = await LoadScript(setting)

修改LoadScript脚本 重点

import share from “${SHARE_PATH}” 这个是核心代码 等于我们加载了这个share 这样我们可以提取出来我们之前定义的东西


// 动态读取脚本
const LoadScript = async (setting) => {// 判断是不是开发的插件let dev = DEV_PLUGINS && DEV_PLUGINS.name == setting.name// 动态读取插件下的脚本let script = fs.readFileSync(path.join(setting.path, setting.script)).toString()// 自定义的console 因为process.stdout 这些捕捉不到自己线程的输出 除非是spawn的const ConsoleString = `let Console=new Proxy(share.console,{get(target,prop){return function (...args) {return target[prop].apply(this,[__filename,...args]);};}});`// 定义注入的头部 重点是第一句 导入我们的共享包// 并且将里面所需要的东西提出出来 并定义 让我们在脚本中能获取到// 补充types/index.d.ts 让编辑器识别即可const head = (path) => {return `import share from "${SHARE_PATH}"var __filename="${encodeURIComponent(setting.script)}";__filename=decodeURIComponent(__filename);var __dirname = "${encodeURIComponent(path)}"; __dirname=decodeURIComponent(__dirname);let electron=share.electron("${setting.name}");let win=share.win["${setting.name}"];let loadScript=share.loadScript["${setting.name}"];${dev ? ConsoleString : "let Console=console;"}`}const injectedCode = `${head(setting.path)}${script}`;// console.log(script)const tmpFile = path.join(setting.path, `.temp-${Date.now()}.mjs`)fs.writeFileSync(tmpFile, injectedCode, "utf-8");try {// 2. 用真实文件路径 import,Node 会自动解析 pluginDir/node_modulesconst mod = await import(pathToFileURL(tmpFile).href)return mod// 捕获错误 并输出正确行数&& mod.default && mod.default().catch((err) => {const lines = err.stack.split("\n")let match = lines[1].match(/at (.*) \((.*):(\d+):(\d+)\)/);let message = `at ${match[1]}${match[2]}:${Number(match[3]) - EXTRA_LINE}:${match[4]}`dev && Application.window.webContents.send(api.TOOLS.TOOLS_DEV_CONSOLE, "error", [setting.script, lines[0], message])if (!dev) {throw Error(`${setting.name}插件加载失败`)}})} finally {fs.rmSync(tmpFile);}
}

编写index.d.ts 让编辑器知道我们引入了什么

编写后在需要编辑器提示的js代码中加入

 /// <reference path="./types/index.d.ts"/>
import { BrowserWindow, dialog,ipcMain,screen, shell } from "electron"
import { Sequelize } from "./sequelize/index"
import { ModelCtor,Model,Attributes,ModelOptions,ModelAttributes } from "./sequelize/model"
export declare global {/*** @description: 加载需要热更新的脚本* @param {string} path 路径 已插件根路径为准* @return {T} 根据脚本export 为准*/declare function loadScript<T>(path: string): T/*** @description: 调试控制台输出* @return {*}*/declare const Console = {log(...args: any[]): void {},error(...args: any[]): void {},warn(...args: any[]): void {},trace(...args: any[]): void {}}/*** @description: 开放的electron权限*/  declare const electron = {database: {createDatabase<M extends Model, TAttributes = Attributes<M>>(modelName: string,attributes: ModelAttributes<M, TAttributes>,options?: ModelOptions<M>): ModelCtor<M>{},checkTableExist(name: string): Promise<boolean>},dialog,screen,ipcMain,BrowserWindow,shell}/*** @description: 当前窗体*/  declare const win: BrowserWindow;/*** @description: 完整路径*/  declare const __dirname: string;/*** @description: 当前文件名*/  declare const __filename: string;}

在这里插入图片描述

打包运行 测试代码

打包后在运行的应用中 开发插件 并添加测试代码

在加载的脚本中index.js通过loadScript(“./static/test.js”) 并在test.写下如下代码

/// <reference path="../types/index.d.ts"/>Console.log(win.getSize())

在这里插入图片描述
查看引用脚本输出

在这里插入图片描述
添加故意报错的代码 查看控制台 我们能拿到报错的地方以及错误的行数
在这里插入图片描述
注释错误代码 查看控制台

在这里插入图片描述
ok 我们的重载代码功能也是正常的 也动态更新了

http://www.dtcms.com/a/304923.html

相关文章:

  • 如何为你的WordPress网站选择合适的安全插件
  • 【效率工具】255款工作计划表格Excel电子版模板:总结日月周报日历安排提醒时间管理
  • 遍历-找到匹配的节点
  • 零基础-动手学深度学习-7.6. 残差网络(ResNet)
  • [leetcode] 子集
  • OpenCL - study - code04 canny
  • 泰勒图中RMSD和RMSE是一个指标吗?
  • 掌控AI工具链:用 Python + API 构建 AI MCP 服务器
  • VUE进阶案例
  • Apple: A Legendary Journey of Innovation, Business, and Global Influence
  • [SWPU2019]Web1
  • VxWorks入门 【VxWorks程序运行】六
  • 数据库表的运算及表示方法
  • jQuery DOM 遍历详解
  • docker技术框架
  • 2024年蓝桥杯Scratch10月图形化stema选拔赛真题——旋转的图形
  • Luogu P2577 午餐(ZJOI2004)
  • 市政道路积水监测系统:守护城市雨天出行安全的 “智慧防线”
  • iOS仿写 —— 计算器
  • 前端代码格式化工具HTML离线版
  • redhat7.9更换源为centos7(阿里云源-目前centos7可用的源)
  • 函数对象 vs 函数指针 vs lambda:该用哪个才高效?
  • 利用对称算法及非对称算法实现安全启动
  • 【车联网kafka】Kafka核心架构与实战经验(第一篇)
  • 【机器学习深度学习】分布式训练的核心技术全解:数据并行、模型并行、流水线并行与3D混合并行
  • 基于最小二乘支持向量机(LSSVM)的气象预测
  • 原生html+js+jq+less 实现时间区间下拉弹窗选择器
  • css 二维变换之详说
  • 引领汽车加速向具身智能进化,吉利携阶跃星辰参展WAIC 2025
  • GitHub下载项目完整配置SSH步骤详解