插件重构
前面我们把插件整体的读了一遍,接下来就可以用Vue3 + TypeScript
来重构它了。
作者的代码写的很精巧,逻辑方面不用做改动,我只是将它的代码实现从js改成了ts,修改了被Vue3废弃的写法,虽然做的修改比较简单,但是学到了作者的插件设计思想以及踩到的一些ts的坑,收获还算挺大。
接下来,就跟大家分享下我的重构过程以及踩到的一些坑。
安装依赖
在用ts重构前,我们需要先安装相关依赖包,执行下述命令即可安装。
yarn add typescript prettier eslint eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser standard --dev
随后,在项目根目录创建tsconfig.json
文件,为typescript的配置文件,添加下述配置,设置"declaration": true
即可在运行tsc命令时自动在types目录下生成声明文件。
{ "exclude": [ "./node_modules" ], "compilerOptions": { "lib": [ "esnext", "dom" ], "baseUrl": "./", "outDir": "./dist/", // 打包到的目录 "target": "ES2015", // 转换成的目标语言 "module": "esnext", "declaration": true,// 是否生成声明文件 "declarationDir": "./dist/types/",// 声明文件打包的位置 "strict": true, // 开启严格模式 "sourceMap": true, // 便于浏览器调试 "moduleResolution": "node", // 使用node模块 "experimentalDecorators": true, // 使用装饰器 "skipLibCheck": true, // 跳过库检查 "esModuleInterop": true, // es模块互操作 "allowSyntheticDefaultImports": true, // 允许默认导入 "noImplicitAny": true, // 不能使用any "noImplicitThis": true, // 不能使用this "alwaysStrict": true, // 严格模式 "noUnusedLocals": true, // 不能有未使用的变量 "noUnusedParameters": true, // 不能有未使用的参数 "noImplicitReturns": true // 必须声明返回值 }, "include": [ "src/**/*.ts" ]// 要打包的文件 }
修改已经废弃的语法
在插件的入口文件Main.js
中,插件需要向Vue全局挂载属性,即Vue.prototype.xx = xx
,在vue3中这一写法已经废除,需要用app.config.globalProperties.xx = xx
来替换,重构好的main.ts文件部分代码如下:
import { App } from "vue"; export default { install(app: App, connection: string, opts: websocketOpts = { format: "" }): void { // ... 其它代码省略 ....// opts.$setInstance = (wsInstance: EventTarget) => { // 全局属性添加$socket app.config.globalProperties.$socket = wsInstance; }; } }
完整代码请移步:src/Main.ts
beforeDestroy生命周期被移除
在插件的入口文件app.mixin
中,组件销毁前它需要从全局移除已经添加在全局的属性,即beforeDestroy
,在Vue3中这一写法已经被移除,需要用beforeUnmount
来替换,其部分代码如下:
import { App } from "vue"; export default { install(app: App, connection: string, opts: websocketOpts = { format: "" }): void { // .... 其它代码省略 ....// app.mixin({ beforeUnmount() { if (hasProxy) { const sockets = this.$options["sockets"]; if (sockets) { Object.keys(sockets).forEach((key) => { // 销毁前如果代理存在sockets存在则移除$options中给sockets添加过的key delete this.$options.sockets[key]; }); } } } }) } }
扩展全局对象
在Observer.ts
中,需要向Websocket
中添加sendObj
方法,这在js中很简单,直接websocket.sendObj = ()=>{}
即可。但是在ts中它就会报错,Websocket中不存在sendObj方法,一开始我想在lib.dom.d.ts
中定义这个方法,但是想了想这样做不妥,不能修改全局的库声明文件,毕竟这是插件。
image-20201102210949765
经过我的一番折腾后,在ts的文档中找到了答案,ts的官方文档描述如下。
image-20201102210650833
正如官方文档所描述,ts查找声明文件会从当前文件开始找,我们只需要在当前类中用declare global
来扩展即可,代码如下:
// 扩展全局对象 declare global { // 扩展websocket对象,添加sendObj方法 interface WebSocket { sendObj(obj: JSON): void; } }
添加上述代码后,报错就解决了,完整代码请移步:src/Observer.ts
image-20201102211101120
回调函数类型定义
在Emitter.ts
文件里,添加监听的方法调用者可以传一个回调函数进去,这个回调函数的参数是未知的,因此就需要给他指定正确的类型,一开始我用的Function
类型,但是eslint报错了,他不建议这么使用,报错如下:
image-20201102212611648
经过我的一番折腾后,找到了如下解决方案,声明类型时只需要将参数解构即可。
addListener(label: T, callback: (...params: T[]) => void, vm: T): boolean { if (typeof callback === "function") { // label不存在就添加 this.listeners.has(label) || this.listeners.set(label, []); // 向label添加回调函数 this.listeners.get(label).push({ callback: callback, vm: vm }); return true; } return false; }
完整代码请移步:src/Emitter.ts
验证插件能否正常工作
插件重构完成后,我们将整个项目的文件复制到一个vue3项目的node_modules/vue-native-websocket下,替换原先的文件。
image-20201103001444839
在main.ts中导入并使用插件。
import { createApp } from "vue"; const app = createApp(App); // 使用VueNativeSock插件,并进行相关配置 app .use(store) .use(router) .mount("#app"); // 使用VueNativeSock插件,并进行相关配置 app.use( VueNativeSock, `${base.lkWebSocket}/${localStorage.getItem("userID")}`, { // 启用Vuex集成 store: store, // 数据发送/接收使用使用json format: "json", // 开启手动调用 connect() 连接服务器 connectManually: true, // 开启自动重连 reconnection: true, // 尝试重连的次数 reconnectionAttempts: 5, // 重连间隔时间 reconnectionDelay: 3000 } );
在组件中与websocket服务端建立连接
mounted() { // 判断websocket是否连接: 当前为未连接状态并且本地存储中有userID if ( !this.$store.state.socket.isConnected && localStorage.getItem("userID") !== null ) { // 连接websocket服务器 this.$connect(`${base.lkWebSocket}/${localStorage.getItem("userID")}`); } }
调用sendObj方法来发送消息。
this.$socket.sendObj({ msg: msgText, code: 0, username: this.$store.state.username, avatarSrc: this.$store.state.profilePicture, userID: this.$store.state.userID });
调用onmessage方法来接收服务端消息。
// 监听消息接收 this.$options.sockets.onmessage = (res: { data: string }) => { }
完整代码请移步:chat-system,最终结果如下:
image-20201103002555455
给作者提个PR
顺便给作者提个pr,将我修改的代码丢给作者😄vue-native-websocket/pulls
image-20201103005547871
发布至npm仓库
至此,插件的重构就结束了,我们修改package.json中的build命令,替换为tsc
,修改入口文件main
以及类型声明文件入口types
。部分呢代码如下,完整代码请移步:package.json
{ "main": "dist/Main.js", "types": "dist/types/Main.d.ts", "scripts": { "build": "tsc" } }
随后,执行yarn run build
命令,就会在项目的根目录下创建dist文件夹并将打包后的js文件放入其中。
image-20201102214629366
dist目录中的文件就是我们要发布至npm仓库的包,在发布至npm仓库之前,我们要先做一些事情,让插件更加规范化。
定义新版本推送规范
我们在项目根目录创建PUBLISH.md文件,用于告知开发者修改本插件后如何进行推送。
## 新版本推送规范 - 对插件进行修改 - 执行 `yarn build` 来生成打包后的文件 - 修改`package.json`中的版本号 - 提交你的修改 - 运行`package.json`中的`changelog`命令来生成更新记录 - 最后将项目推送到你的仓库,然后为主仓库创建一个Pull request
编写插件使用文档
作为一个插件,README.md文件是必不可少的,这个文件会告诉开发者如何使用这个插件,完整代码请移步:README.md
定义提交规范
无规矩不成方圆,插件亦是如此。我们需要通过一些工具来定义提交代码时规范,这样会使插件更易维护。
安装依赖
执行下述命令安装我们需要的插件包
yarn global add commitizen
上述命令会全局安装commitizen工具,它的作用是提供一个脚本工具给到开发者来按照指引生成符合规范的 commit 信息。
执行下述命令,既可将其保存到package.json
的依赖项,将config.commitizen
配置添加到package.json
的根目录,该配置告诉commitizen
,当我们尝试提交此仓库时,我们实际上希望使用哪个适配器。
commitizen init cz-conventional-changelog --save-dev --save-exact
然后我们就可以通过git cz
命令,来提交 git commit
image-20201102221728435
强制执行commit规范
使用commitizen
工具,我们可以通过执行git cz
命令来提交符合规范的 commit 信息,但是在开发中,插件开发者不是通过命令行的方式来提交 commit 的,如果我们要强制校验其他人通过 vscode/webstorm 等其他工具的方式提交 commit,可以使用commitlint
+husky
的方式来配合使用。
安装commitlint
检查我们的 commit message 是否符合常规的提交格式,通过下述命令安装。
yarn add @commitlint/config-conventional @commitlint/cli --dev
在package.json中添加配置,指定提交规范,这里我们选用Angular 格式的配置
"commitlint": { "extends": [ "@commitlint/config-conventional" ] },
做完上述操作后,我们就可以验证命令提交的commit信息校验了,接下来我们来配合husky实现ide的commit校验,执行下述命令安装依赖包。
yarn add husky --dev
在package.json中添加commit-msg 的钩子,用于检查commitlint
规范。
"husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }
完成上述配置后,不管我们通过什么方式来提交 commit,如果 commit 信息不符合我们的规范,都会进行报错。
自动生成CHANGELOG
如果commit都符合刚才定义的Angular格式,那么发布新版本时, CHANGELOG 就可以用脚本自动生成。
此处我们使用conventional-changelog-cli 工具来生成它,执行下述命令来安装依赖。
yarn global add conventional-changelog-cli
在项目根目录执行下述命令,即可生成CHANGELOG.md 文件:
conventional-changelog -p angular -i CHANGELOG.md -s
我们可以将上述命令配置进package.json中的scripts中,这样我们就可以通过yarn run changelog
来生成了
"scripts": { "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s" },
生成的文件内容如下所示:
image-20201102235321074
插件发布
最后,我们就可以将插件发布至npm仓库了。
此处,重点内容在插件的重构,想从零开始学插件发布步骤的开发者可移步我的另一篇文章:Vue实现一个全屏加载插件并发布至npm仓库
在终端进入项目根目录,执行下述命令,登录npm仓库,输入自己的用户名和密码
npm login
image-20201103003251083
执行下属命令发布至npm仓库。
npm publish --access public
image-20201103003532065
插件发布成功,我们去npm仓库搜一下vue-native-websocket-vue3
,如下所示,已经可以搜到了
image-20201103003826881
npm仓库地址:vue-native-websocket-vue3
最后,我们就可以在项目中使用yarn来安装使用了。
image-20201103004600660
写在最后
- 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
- 本文首发于掘金,未经许可禁止转载💌