前言
本文主要分享笔者怎么从开发一个给自己用的小作品,慢慢拓展成一款能给用户使用的产品的历程以及一些浅显的技术分享,文末也会贴上作品的链接,算是给自己辛苦做的作品打一波小广告~
背景
起因只是一次小小的尝试,因为工作上的需要,笔者希望在打开浏览器新标签页时,能够开启一个风格炫酷、支持网站管理、书签管理以及各种办公工具的启动页,就给自己开发了一个浏览器新标签页,起初的功能也很简单,仅支持:
- 网站管理: 不同站点的拖拉拽
- 壁纸切换: 静态壁纸、动态壁纸等
- 书签管理: 书签的导入以及快捷管理
伴随着笔者的辛苦耕耘,插件也渐渐地拥有了更多的功能,支持上了:
- 数据同步: 支持多端登录,各端数据实时同步
- 平台适配: 基于最初的chrome版本拓展了web版以及edge版
- 实用工具: 在原先仅能添加网站入口的基础上,拓展了: 实时热搜、天气预报、计算器、ChatGPT、流程图等工具
具体更多的功能,笔者会在文末贴上体验的地址,这里不一一介绍,总而言之,就是一款原本只是做来给自己用的插件,慢慢地迭代成了现在上架了Chrome、Edge插件市场的产品;
技术栈
整个开发流程当中,主要包括
前端
- React
- Vite(适配不同平台)
- dnd-kit(拖拉拽)
- zustand(状态管理、持久化)
后端
- Nest
- nodemailer(邮箱登录)
- mongoose(存储JSON数据)
- cheerio(爬虫)
代码分享
打包框架
起初,笔者是基于市面上非常成熟的plasmo
这款浏览器开发框架开发的,它也为笔者带来了许多的便捷,但它也导致了笔者在做一些定制化开发时处处受限,并且它并未发布1.*.*版本,目前的版本迭代总是碰见一些颠覆之前的更新,迫使用户被动变更,这里不展开,可以参考笔者在其github下提的一个issue,最终笔者自己用Vite封装了一个脚手架。
创建若干个env文件,编写chrome、develop、web的不同平台的配置,再根据不同的配置,加载不同的plugin,最终生成不同的编译产物,具体代码如下:
javascript
export default defineConfig(({ command, mode }) => {
const { VITE_EXTENSION_TYPE } = loadEnv(mode, "./")
const isWeb = VITE_EXTENSION_TYPE === "web"
const isChrome = VITE_EXTENSION_TYPE === "chrome"
const isDev = mode === "develop"
// 通用插件
const commonPlugins = [
react(),
svgr(),
visualizer(),
]
// 扩展插件
const extensionPlugins = [
generateManifest(VITE_EXTENSION_TYPE), // 自己编写的插件, 根据package.json中的配置生成manifest文件
generateExtensionHTML(), // 删除一些拓展不需要的HTML标签
zipPack({ // 上传插件市场需要的压缩包
inDir: resolve(__dirname, "dist/chrome"),
outDir: resolve(__dirname, "dist"),
outFileName: "ovotab.zip"
})
]
// web插件
const webPlugins = [
viteCompression({ // 网页版本可以提前进行gzip压缩
filter: /(?<!sw)\.(js|json|css|html|svg)$/i
})
]
return {
// 省略一些无关紧要的配置
plugins: [
...commonPlugins,
...(isWeb ? webPlugins : []),
...(isChrome ? extensionPlugins : [])
],
build: {
input: {
newtab: resolve(__dirname, "newtab.html") // 生成newtab.html 拓展新标签页默认会取newtab.html
},
}
}
})
数据持久化
因为需要记录用户拖来拽的排序、主题、背景等,所以数据的持久化处理至关重要,这里之所以选用zustand,原因有二:其一,支持中间件,使得开发者可以轻松添加日志记录、持久化存储等增强功能;其二,比较实在,笔者实际工作中是一个"vuer",react生态中的其他状态管理工具对笔者来说心智负担太大,zustand笔者只是稍微阅读了一遍官方文档便能投入开发;
这里以主题的代码举例:
javascript
import { create } from "zustand" // 创建状态管理
import { persist } from "zustand/middleware" // 处理持久化的中间件
export type StyleState = {
isBlack: boolean // 黑暗主题
}
type StyleAction = {
setIsBlack: (isBlack: boolean) => void
reset: () => void
}
export const useStyleStore = create(
persist<StyleState & StyleAction>(
(set) => ({
isBlack: true,
setIsBlack: (isBlack: boolean) => set({ isBlack }),
}),
{
name: LOCAL_KEY.STYLE_KEY,
getStorage: () => localStorage,
version: 1
}
)
)
仅使用上述的代码,便完成了将状态存储localStorage
的功能。
数据库选型
作为一个前端开发,笔者对于数据库接触的不多,只是在写博客时用到了Mysql,但是考虑到用户在进行数据同步的本质实际上就是一个JSON数据到用户看到的内容的过程,用户进行拖拉拽排序、插入、删除的本质就是对JSON的增删改查,MongoDB完美的适配了这些操作的需要。
MongoDB的优势有很多,这里要说的有2点:
- 高性能: MongoDB 提供了高效的索引支持,这使得它在处理大规模数据时能够获得良好的性能表现。同时,它还支持在内存中进行数据处理,从而提高了读取和写入的速度。
- 文档存储: MongoDB 使用 JSON 格式的文档存储数据,这使得它与许多现代应用程序的开发语言和框架更加契合。
实际开发当中,笔者是使用@nestjs/mongoose
这个包来进行数据库的链接以及增删改查,其具体的操作这里也不多做介绍,可以参考官方文档;
对于整个产品的开发,碰到的困难、遇到的问题也非常多,这里仅仅只是做了一些简单的分享,也不知道大家是否有兴趣了解,如有兴趣,可以提出,笔者可以再开一帖分享;
结语
以上就是这次分享的全部,才疏学浅,不敢在大佬们面前班门弄斧,也知道市面上已经有诸多相当成熟的同类型产品。这个小小的作品用爱发电,欢迎大家来体验,如果在使用过程中发现了BUG,也麻烦提出: