图片来源: github.com/Microsoft/v…
震惊, 10多万start的仓库6年前没解决的问题, 居然被一个刚毕业者解决了?
好吧我承认我标题党了(但只是子标题), 我并不是从根源解决了这个问题, 而是用一种临时的方案去达到相近的效果,等vscode支持了私有插件市场后, 我的这个方案将不值一提。
上图描述的问题是vscode不支持私有插件市场, 但实际上用户需要的可能只是插件私有化和自动更新。下图的discussion维护者也说明了这个目的。
图片来源: github.com/microsoft/v…
我的方法是基于npm私有仓库的,公司没有npm私有仓库的话就借鉴一下我的思路自己搞一下吧, 用远程文件上传下载+版本号管理的方式也是可以搞的
嫌啰嗦想直接使用的, 可以直接看 总结用法
追溯痛点
vscode插件的更新机制
vscode有个默认的插件更新机制:打开vscode窗口时,vscode会去检测所有插件的版本,如果有最新版本就会自动更新。
但是只有发布到vscode插件市场上的插件才能享受自动更新机制,如果是你们公司的插件,且老板不允许你们将插件市场发布到插件市场上呢,那该怎么自动更新?
谈谈现状
google上大肆搜索vscode插件更新 私有插件
等关键词,也只能看到:每次更新都需要将插件重新打包成vsix文件,然后下发给团队的各个成员,让他们手动安装。
这很煞笔,不是吗,如果插件更新频繁的话,手动行为就会非常频繁,这明显很不符合程序员的作风。
这时候应该会有人想到:npm仓库都能搭建私服,那vscode插件市场能搭建私服吗?
可惜不支持,甚至连检测插件和更新插件的接口都无从获取,这就没办法去在公司内网去代理接口修改数据的行为。
我的方案
核心思路
将vsix文件以npm包的方式存储到npm私有仓库上,因为npm仓库提供了两个功能:
- 版本管理:可以轻松的发布新的版本,同时可以通过网络api去检测版本
- 文件io服务:npm仓库提供了发布和下载vsix文件的途径
但有个问题是, 检测更新逻辑是写在代码里的, 插件启动后, 才会去执行代码。所以虽然能起到自动更新的效果, 但也比不上vscode检测更新的行为那么丝滑。这也是没有的办法, 因为vscode貌似没暴露出vscode启动前的钩子。
细节设计
我已经封装好一个基于npm仓库进行插件自动更新的包了,你们可以自行下载来使用: www.npmjs.com/package/upd…
update-vscode-extension
需要在你的vscode插件中使用, 支持功能有: 定时检测版本、更新插件等
1. 打包附带visx文件的npm包
vsix文件本身作为npm包进行更新和发布, npm包结构如下
/ # 包的根目录
./extension.vsix # 通过vsce命令打包的vsix文件
./package.json
这里vsix文件是vscode插件打包后的产物, 打包方式是通过vsce
cli执行: vsce package
然后把该npm包上传到你们团队的npm私有仓库上
2. 检测版本
这里是利用npm仓库服务的api去检测更新的, 不做过多讲解,这里我也封装了一个npm包,直接拿来即用 —— npm-pkg-version
检测到最新版本后,则需要去更新插件了
3. 更新插件
vscode安装vsix文件的核心api是
vscode-root-path/bin/code --install-extension vsix-file-path
先将携带vsix文件的npm包下载到本地的某个暂存目录中, 然后调用核心api去安装它。
插件自动更新机制到这里就结束了。
一开始我以为已经结束了,然而...
4. 最后一个痛点
一般来说, npm私有仓库的包名是必须按 @scope/pkg-name
的格式的, 而vscode的插件名是不能带 @scope
的, 这就导致了插件源码的package.json不能和npm包的package.json共用一个, 所以生成npm包就会费劲许多。
这里解释一下为什么vscode的插件名是不能带
@scope
的:其实vscode插件名是能带
@scope
的, 只不过不推荐而已, 但是用vsce
打包时如果package.json的name包含@scope
就会报错。
这里提供两个个有解决思路:
思路1: 写一套发布新版本的脚本(不推荐)。
- 包名保持为
scope-pkg-name
的格式, 用vsce
打包vsix文件 - 将package.json的name转成
@scope/pkg-name
- 更新package.json的version
- 发布npm包
- 发布后将package.json的name再转成
scope-pkg-name
这样就能保证vsix携带的包名是 符合vscode插件名规范的(scope-pkg-name
), 且发布到npm私有仓库上的包名也是符合 @scope/pkg-name
的格式的。
这个打包-发布的方案的最大缺点就是不可中断不可控, 整个流程如果中间执行一半就意外结束了,就会很麻烦。
思路2: 比如npm包名直接保持为 scope-pkg-name
格式, 然后基于 vsce package
再去封装一个cli —— update-vscode-extension-cli
简称 uvec
(推荐)。
uvec
的目的是打包目标npm包并发布, 这样就能与源码隔离开来, 不会有一丝的副作用了。
uvec
支持两种模式,默认模式: 打包并发布npm包;onlyBuild模式: 只构建npm包, 剩余流程靠你自己实现, 更详细的可以进链接自己看看。
总结用法
- 准备工作
假设你已经有个现成的vscode插件了,如果没有,可以用 yo generator-code
创建一个。
然后安装依赖包:
npm i update-vscode-extension -S
npm i uvec -D
- 编写自动更新逻辑
update-vscode-extension
支持对检测更新的自动、手动、暂停、继续等操作。
这里是自动更新的写法, 直接在插件入口 src/extension.ts
下注册即可:
import vscode, { ExtensionContext } from 'vscode';
import registerUpdateVscodeExtension from 'update-vscode-extension';
import packageJSON from '../package.json';
const registerUpdate = async () => {
const { runSlice } = registerUpdateVscodeExtension(packageJSON.name, {
currentVersion: packageJSON.version,
vscodeAppRoot: vscode.env.appRoot,
interval: 10 * 60 * 1000, // 十分钟检测更新一次
});
await runSlice();
};
export async function activate(context: ExtensionContext) {
registerUpdate();
// you code
}
或者你想要支持手动更新、并可以支持配置间隔, 也可以实现, 具体的不多说, 大致看看就行:
import { isHupuOnline } from '@/utils/utils';
import vscode, { window } from 'vscode';
import registerUpdateVscodeExtension from 'update-vscode-extension';
import packageJSON from '../package.json';
let checkAndUpdate: (() => Promise<void>) | null = null;
let singletonStop: (() => void) | null = null;
const defaultInterval = 10 * 60 * 1000;
const minInterval = 60 * 1000;
const releaseNPMName = '@hupu/vscode-extension';
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 0);
const restartAutoUpdate = (interval: number | null) => {
singletonStop?.();
if (interval !== null && interval < minInterval) {
window.showInformationMessage(`自动更新间隔设置得太小: ${interval}; 已帮你自动设置成: ${defaultInterval}`);
interval = defaultInterval;
}
const { runSlice, stop } = registerUpdateVscodeExtension(releaseNPMName, {
npmTag: 'latest',
registryUrl: 'http://hnpm.hupu.io/',
currentVersion: packageJSON.version,
vscodeAppRoot: vscode.env.appRoot,
interval,
async beforeCheck() {
if (!await isHupuOnline({ timeout: 5000, checkUrl: 'http://hnpm.hupu.io' })) {
throw new Error('连不上<http://hnpm.hupu.io>,自动更新插件停止');
}
},
async beforeUpdate(err) {
if (err) {
const errMsg = err.toString().includes('404') ? `npm包[${releaseNPMName}]未找到` : err.toString();
vscode.window.showErrorMessage(`检测插件[键盘侠]最新版本失败: ${errMsg}`);
} else {
statusBarItem.text = '插件[键盘侠]自动更新中...';
statusBarItem.show();
}
},
async afterUpdate(err) {
statusBarItem.hide();
if (err) {
vscode.window.showErrorMessage(`插件自动更新失败: ${err}`);
} else {
const buttonLabel = await vscode.window.showInformationMessage(
`插件[键盘侠]自动更新完毕, 是否重启该窗口`,
'是',
'否',
);
if (buttonLabel === '是') {
vscode.commands.executeCommand('workbench.action.reloadWindow');
}
}
},
});
checkAndUpdate = runSlice;
singletonStop = stop;
runSlice();
};
const registerUpdate = async (ctx: vscode.ExtensionContext) => {
restartAutoUpdate(null);
ctx.subscriptions.push(
statusBarItem,
vscode.commands.registerCommand('hupu.check-version-and-update'.checkVersionAndUpdate, async () => {
await checkAndUpdate?.();
}),
vscode.commands.registerCommand('hupu.restart-auto-update', async () => {
const interval = await window.showInputBox({
title: '设置检查更新的间隔',
placeHolder: '请设置检查更新的间隔 (单位ms)',
value: (config.autoUpdateInterval && config.autoUpdateInterval > minInterval) ? `${config.autoUpdateInterval}` : `${defaultInterval}`,
validateInput(value) {
if (!value.match(/^\d+$/)) {
return '必须为数值';
}
if (+value < minInterval) {
return '间隔太小容易卡死...';
}
return null;
},
});
if (interval) {
config.autoUpdateInterval = +interval;
restartAutoUpdate(+interval);
window.showInformationMessage('自动更新已开启/重启');
}
}),
vscode.commands.registerCommand('hupu.close-auto-update', async () => {
singletonStop?.();
window.showInformationMessage('自动更新已关闭');
}),
);
};
export default registerUpdate;
- 发布新版本
在package.json中的scripts加入update
{
"scripts": {
"update": "uvec package . --registry-url='http://hnpm.hupu.io/' --pkg-name='@hupu/vscode-extension' --vsce.no-yarn --vsce.allow-star-activation"
}
}
这里的 @hupu/vscode-extension
记得替换成你最终要发布到npm私有仓库上的包名, 另外如果需要加一些vsce运行时的参数,可以通过 --vece.param=xxx
的方式进行配置。更详细的可以看 www.npmjs.com/package/uve…
如果想更新最新版本, 运行 npm run update
就行了。
然后结合之前在vscode中写的自动更新逻辑, 你的vscode插件就可以自动更新了。
- 编写
README
先用 vsce package
命令打包个vsix文件, 然后上传到cdn上, 让团队的所有人都安装一次, 后面就全靠自动更新了, 不需要再手动安装。
## 下载与安装
[点击下载](https://static.hoopchina.com.cn/upload-xxx.vsix)
安装方式: <https://xxxxxxxx>
安装后在vscode中插件会自动更新到最新版本
为自己打个广告
这是我的github首页: github.com/z-juln
造了很多我觉得很有意义的轮子, 比如:
- npm publish命令拦截器 ——
np-guard
: www.npmjs.com/package/np-… - 快速构建支持生成模板的脚手架 ——
pull-ejs-tpl
: www.npmjs.com/package/pul…
这些npm包我应该算是首创轮子吧?反正到现在没找到相似的npm包
在虎扑实习加工作时长有1年多了, 个人觉得技术还是很ok的, 明年7月左右打算去厦门找份少加班的前端工作(主要是离家近)。
简历: github首页应该能算半个简历吧?
微信: A1850021148