用Vue3+TypeScript手撸了一个简约的浏览器新标签页扩展

前言

最近手痒,想做一个自己的新标签页扩展。

经过一段时间的折腾,终于搞出了这个叫"瓦砾Tab"的扩展。主打的就是简约简单

先看看效果

​​​​​​​

​​​​​​​​

项目架构

项目结构大概是这样的:

csharp 复制代码
vue-quick-extension/
├── public/                    # 静态资源
│   ├── manifest.json         # Chrome扩展配置
│   ├── manifest-firefox.json # Firefox扩展配置
│   └── imgs/                 # 背景图片
├── src/
│   ├── newtab/              # 新标签页模块
│   │   ├── App.vue          # 主组件
│   │   ├── components/      # 各种组件
│   │   └── composables/     # 组合式API
│   └── styles/              # 全局样式
├── scripts/                 # 构建脚本
├── vite.config.ts          # Chrome构建配置
├── vite.config.firefox.ts  # Firefox构建配置
└── package.json

核心功能实现

1. 搜索引擎切换

这个功能其实挺简单的,就是维护一个搜索引擎列表,然后根据用户选择拼接URL:

php 复制代码
const searchEngines = ref([
  { name: 'Google', url: 'https://www.google.com/search?q=' },
  { name: '百度', url: 'https://www.baidu.com/s?wd=' },
  { name: '必应', url: 'https://www.bing.com/search?q=' },
  { name: 'GitHub', url: 'https://github.com/search?q=' }
])

const handleSearch = () => {
  const query = searchQuery.value.trim()
  
  // 判断是否为网址
  if (query.includes('.') && !query.includes(' ')) {
    const url = query.startsWith('http') ? query : `https://${query}`
    window.location.href = url
  } else {
    // 使用搜索引擎
    const searchUrl = currentEngine.value.url + encodeURIComponent(query)
    window.location.href = searchUrl
  }
}

2. 快捷链接管理

这个是比较复杂的功能,涉及到数据的增删改查,还有分类管理。我用了组合式API来封装:

typescript 复制代码
export const useQuickLinks = () => {
  const quickLinks = ref<QuickLink[]>([])
  const categories = ref<Category[]>([])

  // 按分类分组
  const linksByCategory = computed(() => {
    const grouped: Record<string, QuickLink[]> = {}
    categories.value.forEach(category => {
      grouped[category.id] = quickLinks.value
        .filter(link => link.categoryId === category.id)
        .sort((a, b) => a.order - b.order)
    })
    return grouped
  })

  // 添加链接
  const addQuickLink = (linkData) => {
    const newLink = {
      ...linkData,
      id: generateId(),
      createdAt: Date.now(),
      updatedAt: Date.now()
    }
    quickLinks.value.push(newLink)
    return newLink
  }

  // 其他CRUD操作...
}

3. 数据持久化

由于是浏览器扩展,数据都存在localStorage里。为了防止数据丢失,我做了容错处理:

javascript 复制代码
const loadData = () => {
  try {
    const savedQuickLinks = localStorage.getItem(QUICKLINKS_KEY)
    if (savedQuickLinks) {
      quickLinks.value = JSON.parse(savedQuickLinks)
    } else {
      // 没有数据就用默认的
      quickLinks.value = [...defaultQuickLinks]
    }
  } catch (error) {
    console.error('加载数据失败:', error)
    // 出错了也用默认数据
    quickLinks.value = [...defaultQuickLinks]
  }
}

4. 响应式设计

CSS这块主要用了Grid和Flexbox,再配合媒体查询做响应式:

css 复制代码
.links-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: 8px;
}

@media (max-width: 768px) {
  .links-grid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  }
}

多浏览器支持的坑

这个项目最大的挑战就是要同时支持Chrome和Firefox。两个浏览器的扩展API虽然大部分兼容,但manifest文件格式完全不同:

  • Chrome用的是Manifest V3
  • Firefox还是Manifest V2

所以我搞了两套构建配置:

javascript 复制代码
// vite.config.ts - Chrome版本
export default defineConfig({
  plugins: [
    vue(),
    {
      name: 'copy-manifest',
      generateBundle() {
        this.emitFile({
          type: 'asset',
          fileName: 'manifest.json',
          source: JSON.stringify(require('./public/manifest.json'), null, 2)
        })
      }
    }
  ],
  // ...
})

// vite.config.firefox.ts - Firefox版本  
export default defineConfig({
  build: {
    outDir: 'dist-firefox',
    // ...
  }
  // ...
})

构建和打包

为了方便开发和发布,我写了几个脚本:

json 复制代码
{
  "scripts": {
    "dev": "vite",
    "build:chrome": "vite build",
    "build:firefox": "vite build --config vite.config.firefox.ts",
    "build:all": "npm run build:chrome && npm run build:firefox",
    "build:package": "node scripts/build-extension.js"
  }
}

build:package这个命令会自动构建两个版本,然后打包成zip文件,直接就能上传到应用商店了。

项目地址

代码已经开源,欢迎star和fork:

有什么问题欢迎在评论区讨论!


如果这篇文章对你有帮助,记得点个赞👍

相关推荐
薛定谔的算法13 分钟前
# 前端路由进化史:从白屏到丝滑体验的技术突围
前端·react.js·前端框架
拾光拾趣录1 小时前
Element Plus表格表头动态刷新难题:零闪动更新方案
前端·vue.js·element
Adolf_19932 小时前
React 中 props 的最常用用法精选+useContext
前端·javascript·react.js
前端小趴菜052 小时前
react - 根据路由生成菜单
前端·javascript·react.js
喝拿铁写前端2 小时前
`reduce` 究竟要不要用?到底什么时候才“值得”用?
前端·javascript·面试
空の鱼2 小时前
js与vue基础学习
javascript·vue.js·学习
鱼樱前端2 小时前
2025前端SSR框架之十分钟快速上手Nuxt3搭建项目
前端·vue.js
極光未晚2 小时前
React Hooks 中的时空穿梭:模拟 ComponentDidMount 的奇妙冒险
前端·react.js·源码
Codebee2 小时前
OneCode 3.0 自治UI 弹出菜单组件功能介绍
前端·人工智能·开源
ui设计兰亭妙微2 小时前
# 信息架构如何决定搜索效率?
前端