「本文正在参与技术专题征文Node.js进阶之路,点击查看详情」
如果你用过 vscode
或者Eclipse
亦或者是jetbrains
的开发工具 (甚至 steam平台 游戏的DLC也可以理解为插件), 都会发现这些工具可以在使用过程中下载丰富的插件. 插件带给IDE
更多的可能性, 既拥有了更人性化的功能又增添了可玩性.
那么插件式开发是怎么做到的呢?
本文提供了使用node模拟一个简易的插件式软件.
(node v14.17.1)
. 本文灵感来自于css-tricks.com/designing-a…
定义
首先向还没听说过
插件式架构
或者OSGI规范
的同学解释一下插件是什么?
所谓插件化, 就是在开发时不
将全部功能同时写进主程序中, 可以把方法逻辑取出放入插件模块中. 当主程序需要功能方法时从插件模块中按需调用
.
这样可以使的主程序小而精悍
, 大大减少了安装、加载体积. 插件可以进行热插拔, 安装即用的同时即使没有插件也不影响主程序运行.
优点
- 减少了主包体积.
- 更多的开发者可以加入补充生态.
- 便于修改Bug.
代码实现
说了这么多插件式开发的好处, 那么就让我们从下面简易的程序来进一步.
我们需要实现一个
计算器程序
, 已插件的方式给计算器提供按钮
, 让计算器实现能够实现加减乘除等功能.
1. 初始化node环境
npm init
生成node目录结构
calculator
|
| -- node_modules
| -- plugins // 插件目录
| | -- mult.js // 乘法插件
| -- calculator.js // 计算器主程序
| -- index.js // 程序入口
| -- package.json
2. calculator.js
1. 首先我们需要定义一个值用来存放我们显示在屏幕上的数字.
module.exports = {
// 显示数值
currentValue: 0,
....
}
2. 这里我们定义一个方法专门用来修改显示的数值.
module.exports = {
...
/*
* 因为每次进行计算后, 结果数值基本都会改变,
* 所以我们这里遵循面向对象封装的基本思想将方法封装.
*/
// 设置值
setValue: function(value) {
this.currentValue = value
console.log(this.currentValue)
},
....
}
3. 我们给计算器制作两个基础系统功能 加法
和 减法
.
module.exports = {
// 显示数值
currentValue: 0,
...
// 加法函数
plus(addend) {
this.setValue(this.currentValue + addend);
},
// 减法函数
minus(subtrahend) {
this.setValue(this.currentValue - subtrahend);
}
....
}
5. 插件注册机制
module.exports = {
// 插件注册入口
register(plugin) {
const { name, exec } = plugin;
this[name] = exec;
}
}
4. 使用计算器 (index.js)
// 引入计算器模块
const myCal = require('./calculator.js');
// 使用计算器
myCal.setValue(5); // => 5
myCal.plus(5); // => 10
myCal.minus(2); // => 8
5. 自定义插件并使用
// 引入计算器模块
const myCal = require('./calculator.js');
...
// 定义除法
const divisionPlugin = {
name: 'division',
exec: function(dividend) {
myCal.setValue(myCal.currentValue / dividend)
}
}
// 注册插件
myCal.register(divisionPlugin)
// 使用插件
myCal.divisionPlugin(2); // => 4
引入问题
这样的插件安全吗?
通过代码可以发现, 这样的设计我们可以轻松获取到系统核心代码
(及加法&减法), 这样的做法显然是不会被接受的, 所以下面我们需要将修改一下代码, 将系统核心代码与插件相分离, 插件开发这只能获取到 plugins
(插件), 从而使得系统更加安全.
所以我们将修改思路, 将核心代码与插件代码相分离, 加入新的函数 button()
帮助我们调用计算器的各种方法.
// calculator.js
module.exports = {
// 当前值
currentValue: 0,
// 显示数值
setValue: function(value) {
this.currentValue = value
console.log(this.currentValue)
},
// ------------------------代码更新 开始-----------------------------
// 系统核心代码
core: {
// 加法
'plus': (currentValue, addend) => {
return currentValue + addend
},
// 减法
'minus': (currentValue, subtrahend) => {
return currentValue - subtrahend
}
},
// 已安装插件
plugins: {},
// 新增 button() 函数用于调用方法
button: function(apiName, parameter) {
let func = this.core[apiName] || this.plugins[apiName];
this.setValue(func(this.currentValue, parameter))
},
// ------------------------代码更新 结束-----------------------------
// 插件注册入口
register: function(plugin) {
const { name, exec } = plugin;
// ------------注意更新-----------
// 插件注册
this.plugins[name] = exec;
}
}
定义乘法插件
目录请看
1.初始化node环境
// mult.js
module.exports = {
name: 'mult',
exec: function(currentValue, multiplicand) {
return currentValue * multiplicand
}
}
注册乘法插件
// index.js
// 引入计算器模块
const myCal = require('./calculator.js');
// 使用计算器
myCal.setValue(3); // => 3
myCal.button('add', 17); // => 20
// 引入乘法插件
const multPlugin = require('./plugins/mult');
// 注册插件
myCal.register(multPlugin);
// 使用插件
myCal.button('mult', 5); // => 100
远程下载安装插件
这里使用node.js的
http
模块, 模拟了一个插件市场服务器, 并从远程下载并安装插件.
模拟插件市场
目录格式
pluginServer
|
| -- plugins // 插件库
| | -- squared.js // 计算平方插件
|
| -- index.js // 程序主入口
| -- package.json
index.js
这里不是本文重点不做详细注释
const http = require('http');
const server = http.createServer()
const fs = require("fs");
const path = require('path');
server.on('request', (request, response) => {
let url = request.url;
if (url == '/getSquaredPlugin') {
var filePath = path.join(__dirname, './plugins/squared.js');
var stats = fs.statSync(filePath);
if (stats.isFile()) {
response.writeHead(200, [
['Content-Type', 'application/octet-stream'],
['Content-Disposition', 'attachment; filename=squared.js'],
['Content-Length', stats.size]
]);
fs.createReadStream(filePath).pipe(response);
} else {
response.end(404);
}
}
})
server.listen(3000, () =>
console.log('server is listening at port 3000');
})
squared.js
// squared.js
module.exports = {
name: 'squared',
exec: function(currentValue) {
return currentValue * currentValue
}
}
回到刚刚的计算器程序
// index.js
// 引入计算器模块
const myCal = require('./calculator.js');
// 从服务器下载插件
let fs = require("fs");
let path = require("path");
let request = require("request");
let fileName = 'squared.js';
let dirPath = path.join(__dirname, "plugins");
let url = "http://localhost:3000/getSquaredPlugin"
let stream = fs.createWriteStream(path.join(dirPath, fileName));
// 将插件文件保存在项目根目录下 plugins 文件夹中
request(url).pipe(stream).on("close", function(err) {
console.log("插件[" + fileName + "]下载完毕");
});
// 使用计算器
myCal.setValue(3); // => 3
/*
* 这里我们延迟5秒等待文件下载以及保存在本地,
* 在实战开发中应该设计更合理的方式.
*/
setTimeout(() => {
// 加载插件
const squaredPlugin = require('./plugins/squared')
// 注册插件
myCal.register(squaredPlugin)
// 使用插件
myCal.press('squared'); // => 9
}, 5000)
🎉🎉🎉 大工告成 🎉🎉🎉
此案例是我在做毕业设计的时候, 想把毕业设计制作成像vscode那样的插件式架构应用软件. 特点提前先从小案例试水, 研究插件式开发的奥秘.
这里做一个小预告
我的毕业设计是一个管理 docker
与 k8s
(Kubernetes) 的一个多平台界面化管理软件, 专为学校教学打造, 屏蔽了大量且容易出错的配置. 而且还提供了自制的捆绑镜像(比如在一个Linux系统中事先准备好某些程序或者文件, 拉去捆绑镜像即可完成所有的工作), 系统快照如下:
等答辩结束后, 项目中基于vue3.x的UI框架, chaosUI将在GitHub开源,本人事先也做过一期部分组件的讲解文章 vue3 实现仿苹果系统侧边消息提示 欢迎大家捧场.
如果你想继续了解
插件式架构开发
或者了解我的毕业设计先阶段情况, 点赞 + 关注仪表鼓励哦 🖖🖖🖖
如果你有疑问或者为本文提供更好的建议欢迎在评论区评论
本人也是刚刚接触插件式开发, 如果有大佬发现了本文的不合理或改进之处, 恳请大佬一定留言.