当前位置: 首页 > 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.lryc.cn/news/604005.html

相关文章:

  • 使用jQuery时的注意事项
  • 爬虫逆向之瑞数五案例:某某医学院(补环境,联调)
  • 直播间里的酒旅新故事:内容正在重构消费链路
  • logtrick 按位或最大的最小子数组长度
  • 计算器4.0:新增页签功能梳理页面,通过IO流实现在用户本地存储数据
  • Java注解全面解析与应用实战
  • 三维扫描相机:工业自动化的智慧之眼——迁移科技赋能智能制造新纪元
  • 前端优化之虚拟列表实现指南:从库集成到手动开发
  • MongoDB系列教程-第一章:MongoDB简介、安装 、概念解析、用户管理、连接、实际应用示例
  • Java抽Oracle数据时编码问题
  • Spring Boot with RabbitMQ:四大核心模式指南
  • TDengine 中 TDgpt 异常检测的数据密度算法
  • TDengine 中 TDgpt 异常检测的机器学习算法
  • 中科米堆CASAIM金属件自动3d测量外观尺寸三维检测解决方案
  • 【数据结构初阶】--二叉树(四)
  • C# _列表(List<T>)_ 字典(Dictionary<TKey, TValue>)
  • uniapp 实现全局变量
  • C++与C#实战:FFmpeg屏幕录制开发指南
  • 高级机器学习
  • RTSP协议详解与C++实现实例
  • Witsbb健敏思携手奥运冠军吴敏霞 共启科学分龄育儿新时代
  • ubuntu22.04 安装 petalinux 2021.1
  • Makefile 快速入门指南
  • 用FunASR轻松实现音频转SRT字幕:完整脚本与解析
  • Jenkins 节点连接故障定位及解决方案总结 - PKIX path validation failed
  • VSCode使用Code Runner运行C/C++输出[Done] exited with code=0 in xxx seconds
  • 第二十五节 MATLAB矩阵的加法和减法、除法(左,右)矩阵
  • Curtain MonGuard 屏幕水印-稳住电子支付企业资料安全线
  • 格雷码的应用场景
  • 【Delphi】快速理解泛型(Generics)