From bdc3d0d512d95db36e6113f5b019e1569924278e Mon Sep 17 00:00:00 2001
From: johnathan <952508490@qq.com>
Date: Sat, 12 Apr 2025 17:26:36 +0800
Subject: [PATCH] Initial Repo
---
.gitignore | 17 +
.husky/pre-commit | 1 +
.npmrc | 2 +
.prettierignore | 3 +
.prettierrc | 3 +
.vscode/extensions.json | 9 +
.vscode/settings.json | 12 +
LICENSE | 21 +
README_Template.md | 138 +
extension/assets/icon-512.png | Bin 0 -> 2338 bytes
extension/assets/icon.svg | 3 +
modules.d.ts | 10 +
package.json | 68 +
pnpm-lock.yaml | 7363 +++++++++++++++++++++
scripts/manifest.ts | 12 +
scripts/prepare.ts | 41 +
scripts/utils.ts | 12 +
shim.d.ts | 8 +
src/assets/logo.svg | 3 +
src/background/contentScriptHMR.ts | 18 +
src/background/main.ts | 23 +
src/components/Logo.vue | 19 +
src/components/README.md | 11 +
src/components/SharedSubtitle.vue | 10 +
src/composables/useWebExtensionStorage.ts | 155 +
src/contentScripts/index.ts | 36 +
src/contentScripts/views/App.vue | 28 +
src/env.ts | 14 +
src/global.d.ts | 10 +
src/logic/common-setup.ts | 15 +
src/logic/index.ts | 1 +
src/logic/page-worker/index.ts | 64 +
src/logic/page-worker/types.d.ts | 14 +
src/logic/storage.ts | 3 +
src/manifest.ts | 81 +
src/options/Options.vue | 39 +
src/options/index.html | 12 +
src/options/main.ts | 8 +
src/popup/Popup.vue | 43 +
src/popup/index.html | 12 +
src/popup/main.ts | 7 +
src/sidepanel/Sidepanel.vue | 63 +
src/sidepanel/index.html | 12 +
src/sidepanel/main.ts | 7 +
src/styles/index.ts | 1 +
src/styles/main.css | 20 +
src/tests/demo.spec.ts | 7 +
tsconfig.json | 22 +
vite.config.background.mts | 36 +
vite.config.content.mts | 37 +
vite.config.mts | 114 +
51 files changed, 8668 insertions(+)
create mode 100644 .gitignore
create mode 100644 .husky/pre-commit
create mode 100644 .npmrc
create mode 100644 .prettierignore
create mode 100644 .prettierrc
create mode 100644 .vscode/extensions.json
create mode 100644 .vscode/settings.json
create mode 100644 LICENSE
create mode 100644 README_Template.md
create mode 100644 extension/assets/icon-512.png
create mode 100644 extension/assets/icon.svg
create mode 100644 modules.d.ts
create mode 100644 package.json
create mode 100644 pnpm-lock.yaml
create mode 100644 scripts/manifest.ts
create mode 100644 scripts/prepare.ts
create mode 100644 scripts/utils.ts
create mode 100644 shim.d.ts
create mode 100644 src/assets/logo.svg
create mode 100644 src/background/contentScriptHMR.ts
create mode 100644 src/background/main.ts
create mode 100644 src/components/Logo.vue
create mode 100644 src/components/README.md
create mode 100644 src/components/SharedSubtitle.vue
create mode 100644 src/composables/useWebExtensionStorage.ts
create mode 100644 src/contentScripts/index.ts
create mode 100644 src/contentScripts/views/App.vue
create mode 100644 src/env.ts
create mode 100644 src/global.d.ts
create mode 100644 src/logic/common-setup.ts
create mode 100644 src/logic/index.ts
create mode 100644 src/logic/page-worker/index.ts
create mode 100644 src/logic/page-worker/types.d.ts
create mode 100644 src/logic/storage.ts
create mode 100644 src/manifest.ts
create mode 100644 src/options/Options.vue
create mode 100644 src/options/index.html
create mode 100644 src/options/main.ts
create mode 100644 src/popup/Popup.vue
create mode 100644 src/popup/index.html
create mode 100644 src/popup/main.ts
create mode 100644 src/sidepanel/Sidepanel.vue
create mode 100644 src/sidepanel/index.html
create mode 100644 src/sidepanel/main.ts
create mode 100644 src/styles/index.ts
create mode 100644 src/styles/main.css
create mode 100644 src/tests/demo.spec.ts
create mode 100644 tsconfig.json
create mode 100644 vite.config.background.mts
create mode 100644 vite.config.content.mts
create mode 100644 vite.config.mts
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..453b76d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+.DS_Store
+.idea/
+.vite-ssg-dist
+.vite-ssg-temp
+*.crx
+*.local
+*.log
+*.pem
+*.xpi
+*.zip
+dist
+dist-ssr
+extension/manifest.json
+node_modules
+src/auto-imports.d.ts
+src/components.d.ts
+.eslintcache
diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..5ee7abd
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+pnpm exec lint-staged
diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..009aa06
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+shamefully-hoist=true
+auto-install-peers=true
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..1b8ac88
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,3 @@
+# Ignore artifacts:
+build
+coverage
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..544138b
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,3 @@
+{
+ "singleQuote": true
+}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 0000000..96f65d5
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,9 @@
+{
+ "recommendations": [
+ "vue.volar"
+ // "antfu.iconify",
+ // "antfu.unocss",
+ // "dbaeumer.vscode-eslint",
+ // "csstools.postcss"
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..fb068a7
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+ "cSpell.words": ["Vitesse"],
+ "typescript.tsdk": "node_modules/typescript/lib",
+ "vite.autoStart": false,
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ },
+ "files.associations": {
+ "*.css": "postcss"
+ },
+ "prettier.tabWidth": 2
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9b031a2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2021 Anthony Fu
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README_Template.md b/README_Template.md
new file mode 100644
index 0000000..ba226cf
--- /dev/null
+++ b/README_Template.md
@@ -0,0 +1,138 @@
+# WebExtension Vite Starter
+
+A [Vite](https://vitejs.dev/) powered WebExtension ([Chrome](https://developer.chrome.com/docs/extensions/reference/), [FireFox](https://addons.mozilla.org/en-US/developers/), etc.) starter template.
+
+
+Popup
+
+Options Page
+
+Inject Vue App into the Content Script
+
+
+
+## Features
+
+- ⚡️ **Instant HMR** - use **Vite** on dev (no more refresh!)
+- 🥝 Vue 3 - Composition API, [`
+
+
+
+
+
+
+
+
diff --git a/src/env.ts b/src/env.ts
new file mode 100644
index 0000000..bd19ad3
--- /dev/null
+++ b/src/env.ts
@@ -0,0 +1,14 @@
+const forbiddenProtocols = [
+ 'chrome-extension://',
+ 'chrome-search://',
+ 'chrome://',
+ 'devtools://',
+ 'edge://',
+ 'https://chrome.google.com/webstore',
+];
+
+export function isForbiddenUrl(url: string): boolean {
+ return forbiddenProtocols.some((protocol) => url.startsWith(protocol));
+}
+
+export const isFirefox = navigator.userAgent.includes('Firefox');
diff --git a/src/global.d.ts b/src/global.d.ts
new file mode 100644
index 0000000..e3a1e02
--- /dev/null
+++ b/src/global.d.ts
@@ -0,0 +1,10 @@
+///
+
+declare const __DEV__: boolean;
+/** Extension name, defined in packageJson.name */
+declare const __NAME__: string;
+
+declare module '*.vue' {
+ const component: any;
+ export default component;
+}
diff --git a/src/logic/common-setup.ts b/src/logic/common-setup.ts
new file mode 100644
index 0000000..7d98d66
--- /dev/null
+++ b/src/logic/common-setup.ts
@@ -0,0 +1,15 @@
+import type { App } from 'vue';
+
+export function setupApp(app: App) {
+ // Inject a globally available `$app` object in template
+ app.config.globalProperties.$app = {
+ context: '',
+ };
+
+ // Provide access to `app` in script setup with `const app = inject('app')`
+ app.provide('app', app.config.globalProperties.$app);
+
+ // Here you can install additional plugins for all contexts: popup, options page and content-script.
+ // example: app.use(i18n)
+ // example excluding content-script context: if (context !== 'content-script') app.use(i18n)
+}
diff --git a/src/logic/index.ts b/src/logic/index.ts
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/src/logic/index.ts
@@ -0,0 +1 @@
+
diff --git a/src/logic/page-worker/index.ts b/src/logic/page-worker/index.ts
new file mode 100644
index 0000000..b4a4c63
--- /dev/null
+++ b/src/logic/page-worker/index.ts
@@ -0,0 +1,64 @@
+class AmazonPageWorkerImpl implements AmazonPageWorker {
+ public async doSearch(keywords: string): Promise {
+ const url = new URL('https://www.amazon.com/s');
+ url.searchParams.append('k', keywords);
+
+ const tab = await browser.tabs
+ .query({ active: true, currentWindow: true })
+ .then((tabs) => tabs[0]);
+ const currentUrl = new URL(tab.url!);
+ if (
+ currentUrl.hostname !== url.hostname ||
+ currentUrl.searchParams.get('k') !== keywords
+ ) {
+ await browser.tabs.update(tab.id, { url: url.toString() });
+ }
+ return url.toString();
+ }
+
+ public async wanderSearchList(): Promise {
+ const tab = await browser.tabs
+ .query({ active: true, currentWindow: true })
+ .then((tabs) => tabs[0]);
+ const results = await browser.scripting.executeScript({
+ target: { tabId: tab.id! },
+ func: async () => {
+ try {
+ await new Promise((resolve) =>
+ setTimeout(resolve, 500 + ~~(500 * Math.random())),
+ );
+ while (!document.querySelector('.s-pagination-strip')) {
+ window.scrollBy(0, ~~(Math.random() * 500) + 500);
+ await new Promise((resolve) => setTimeout(resolve, 10));
+ }
+ const nextButton =
+ document.querySelector('.s-pagination-next');
+ if (
+ nextButton &&
+ !nextButton.classList.contains('s-pagination-disabled')
+ ) {
+ await new Promise((resolve) =>
+ setTimeout(resolve, 500 + ~~(500 * Math.random())),
+ );
+ nextButton.click();
+ }
+ return true;
+ } catch (e) {
+ return false;
+ }
+ },
+ });
+ console.log('results', results);
+ return new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+}
+
+class PageWorkerFactory {
+ public createAmazonPageWorker(): AmazonPageWorker {
+ return new AmazonPageWorkerImpl();
+ }
+}
+
+const pageWorkerFactory = new PageWorkerFactory();
+
+export default pageWorkerFactory;
diff --git a/src/logic/page-worker/types.d.ts b/src/logic/page-worker/types.d.ts
new file mode 100644
index 0000000..4741766
--- /dev/null
+++ b/src/logic/page-worker/types.d.ts
@@ -0,0 +1,14 @@
+interface AmazonPageWorker {
+ /**
+ * Search for a list of items on Amazon
+ * @param keywords - The keywords to search for on Amazon.
+ * @returns A promise that resolves to a string representing the search URL.
+ */
+ doSearch(keywords: string): Promise;
+
+ /**
+ * Browsing item search page and collect links to those items.
+ * @param entryUrl - The URL of the Amazon search page to start from.
+ */
+ wanderSearchList(): Promise;
+}
diff --git a/src/logic/storage.ts b/src/logic/storage.ts
new file mode 100644
index 0000000..84b4e5b
--- /dev/null
+++ b/src/logic/storage.ts
@@ -0,0 +1,3 @@
+import { useWebExtensionStorage } from '~/composables/useWebExtensionStorage';
+
+export const keywords = useWebExtensionStorage('keywords', '');
\ No newline at end of file
diff --git a/src/manifest.ts b/src/manifest.ts
new file mode 100644
index 0000000..e626f87
--- /dev/null
+++ b/src/manifest.ts
@@ -0,0 +1,81 @@
+import fs from 'fs-extra';
+import type { Manifest } from 'webextension-polyfill';
+import type PkgType from '../package.json';
+import { isDev, isFirefox, port, r } from '../scripts/utils';
+
+export async function getManifest() {
+ const pkg = (await fs.readJSON(r('package.json'))) as typeof PkgType;
+
+ // update this file to update this manifest.json
+ // can also be conditional based on your need
+ const manifest: Manifest.WebExtensionManifest = {
+ manifest_version: 3,
+ name: pkg.displayName || pkg.name,
+ version: pkg.version,
+ description: pkg.description,
+ action: {
+ default_icon: './assets/icon-512.png',
+ default_popup: './dist/popup/index.html',
+ },
+ options_ui: {
+ page: './dist/options/index.html',
+ open_in_tab: true,
+ },
+ background: isFirefox
+ ? {
+ scripts: ['dist/background/index.mjs'],
+ type: 'module',
+ }
+ : {
+ service_worker: './dist/background/index.mjs',
+ },
+ icons: {
+ 16: './assets/icon-512.png',
+ 48: './assets/icon-512.png',
+ 128: './assets/icon-512.png',
+ },
+ permissions: ['tabs', 'storage', 'activeTab', 'sidePanel', 'scripting'],
+ host_permissions: ['*://*/*'],
+ content_scripts: [
+ {
+ matches: [''],
+ js: ['dist/contentScripts/index.global.js'],
+ },
+ ],
+ web_accessible_resources: [
+ {
+ resources: ['dist/contentScripts/index.css'],
+ matches: [''],
+ },
+ ],
+ content_security_policy: {
+ extension_pages: isDev
+ ? // this is required on dev for Vite script to load
+ `script-src \'self\' http://localhost:${port}; object-src \'self\'`
+ : "script-src 'self'; object-src 'self'",
+ },
+ };
+
+ // add sidepanel
+ if (isFirefox) {
+ manifest.sidebar_action = {
+ default_panel: 'dist/sidepanel/index.html',
+ };
+ } else {
+ // the sidebar_action does not work for chromium based
+ (manifest as any).side_panel = {
+ default_path: 'dist/sidepanel/index.html',
+ };
+ }
+
+ // FIXME: not work in MV3
+ if (isDev && false) {
+ // for content script, as browsers will cache them for each reload,
+ // we use a background script to always inject the latest version
+ // see src/background/contentScriptHMR.ts
+ delete manifest.content_scripts;
+ manifest.permissions?.push('webNavigation');
+ }
+
+ return manifest;
+}
diff --git a/src/options/Options.vue b/src/options/Options.vue
new file mode 100644
index 0000000..29b5196
--- /dev/null
+++ b/src/options/Options.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+ Options Page
+
+
+
+
+
+
diff --git a/src/options/index.html b/src/options/index.html
new file mode 100644
index 0000000..72ab198
--- /dev/null
+++ b/src/options/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Options
+
+
+
+
+
+
diff --git a/src/options/main.ts b/src/options/main.ts
new file mode 100644
index 0000000..a1aa667
--- /dev/null
+++ b/src/options/main.ts
@@ -0,0 +1,8 @@
+import { createApp } from 'vue';
+import App from './Options.vue';
+import { setupApp } from '~/logic/common-setup';
+import '../styles';
+
+const app = createApp(App);
+setupApp(app);
+app.mount('#app');
diff --git a/src/popup/Popup.vue b/src/popup/Popup.vue
new file mode 100644
index 0000000..1614abf
--- /dev/null
+++ b/src/popup/Popup.vue
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
diff --git a/src/popup/index.html b/src/popup/index.html
new file mode 100644
index 0000000..28ab624
--- /dev/null
+++ b/src/popup/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Popup
+
+
+
+
+
+
diff --git a/src/popup/main.ts b/src/popup/main.ts
new file mode 100644
index 0000000..a9d6ea0
--- /dev/null
+++ b/src/popup/main.ts
@@ -0,0 +1,7 @@
+import App from './Popup.vue';
+import { setupApp } from '~/logic/common-setup';
+import '../styles';
+
+const app = createApp(App);
+setupApp(app);
+app.mount('#app');
diff --git a/src/sidepanel/Sidepanel.vue b/src/sidepanel/Sidepanel.vue
new file mode 100644
index 0000000..0bcddab
--- /dev/null
+++ b/src/sidepanel/Sidepanel.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+ Azon Seeker
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/sidepanel/index.html b/src/sidepanel/index.html
new file mode 100644
index 0000000..616df1f
--- /dev/null
+++ b/src/sidepanel/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Sidepanel
+
+
+
+
+
+
diff --git a/src/sidepanel/main.ts b/src/sidepanel/main.ts
new file mode 100644
index 0000000..7afa3b2
--- /dev/null
+++ b/src/sidepanel/main.ts
@@ -0,0 +1,7 @@
+import App from './Sidepanel.vue';
+import { setupApp } from '~/logic/common-setup';
+import '../styles';
+
+const app = createApp(App);
+setupApp(app);
+app.mount('#app');
diff --git a/src/styles/index.ts b/src/styles/index.ts
new file mode 100644
index 0000000..74f07de
--- /dev/null
+++ b/src/styles/index.ts
@@ -0,0 +1 @@
+import './main.css';
diff --git a/src/styles/main.css b/src/styles/main.css
new file mode 100644
index 0000000..716ffbb
--- /dev/null
+++ b/src/styles/main.css
@@ -0,0 +1,20 @@
+html,
+body,
+#app {
+ margin: 0;
+ padding: 0;
+}
+
+.btn {
+ @apply px-4 py-1 rounded inline-block
+ bg-teal-600 text-white cursor-pointer
+ hover:bg-teal-700
+ disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50;
+}
+
+.icon-btn {
+ @apply inline-block cursor-pointer select-none
+ opacity-75 transition duration-200 ease-in-out
+ hover:opacity-100 hover:text-teal-600;
+ font-size: 0.9em;
+}
diff --git a/src/tests/demo.spec.ts b/src/tests/demo.spec.ts
new file mode 100644
index 0000000..06c8725
--- /dev/null
+++ b/src/tests/demo.spec.ts
@@ -0,0 +1,7 @@
+import { describe, expect, it } from 'vitest';
+
+describe('demo', () => {
+ it('should work', () => {
+ expect(1 + 1).toBe(2);
+ });
+});
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..4ed786c
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "incremental": false,
+ "target": "es2016",
+ "jsx": "preserve",
+ "lib": ["DOM", "ESNext"],
+ "baseUrl": ".",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "paths": {
+ "~/*": ["src/*"]
+ },
+ "resolveJsonModule": true,
+ "types": ["vite/client"],
+ "strict": true,
+ "noUnusedLocals": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "skipLibCheck": true
+ },
+ "exclude": ["dist", "node_modules"]
+}
diff --git a/vite.config.background.mts b/vite.config.background.mts
new file mode 100644
index 0000000..502f1df
--- /dev/null
+++ b/vite.config.background.mts
@@ -0,0 +1,36 @@
+import { defineConfig } from 'vite';
+import { sharedConfig } from './vite.config.mjs';
+import { isDev, r } from './scripts/utils';
+import packageJson from './package.json';
+
+// bundling the content script using Vite
+export default defineConfig({
+ ...sharedConfig,
+ define: {
+ __DEV__: isDev,
+ __NAME__: JSON.stringify(packageJson.name),
+ // https://github.com/vitejs/vite/issues/9320
+ // https://github.com/vitejs/vite/issues/9186
+ 'process.env.NODE_ENV': JSON.stringify(
+ isDev ? 'development' : 'production',
+ ),
+ },
+ build: {
+ watch: isDev ? {} : undefined,
+ outDir: r('extension/dist/background'),
+ cssCodeSplit: false,
+ emptyOutDir: false,
+ sourcemap: isDev ? 'inline' : false,
+ lib: {
+ entry: r('src/background/main.ts'),
+ name: packageJson.name,
+ formats: ['iife'],
+ },
+ rollupOptions: {
+ output: {
+ entryFileNames: 'index.mjs',
+ extend: true,
+ },
+ },
+ },
+});
diff --git a/vite.config.content.mts b/vite.config.content.mts
new file mode 100644
index 0000000..19f3aac
--- /dev/null
+++ b/vite.config.content.mts
@@ -0,0 +1,37 @@
+import { defineConfig } from 'vite';
+import { sharedConfig } from './vite.config.mjs';
+import { isDev, r } from './scripts/utils';
+import packageJson from './package.json';
+
+// bundling the content script using Vite
+export default defineConfig({
+ ...sharedConfig,
+ define: {
+ __DEV__: isDev,
+ __NAME__: JSON.stringify(packageJson.name),
+ // https://github.com/vitejs/vite/issues/9320
+ // https://github.com/vitejs/vite/issues/9186
+ 'process.env.NODE_ENV': JSON.stringify(
+ isDev ? 'development' : 'production',
+ ),
+ },
+ build: {
+ watch: isDev ? {} : undefined,
+ outDir: r('extension/dist/contentScripts'),
+ cssCodeSplit: false,
+ emptyOutDir: false,
+ sourcemap: isDev ? 'inline' : false,
+ lib: {
+ entry: r('src/contentScripts/index.ts'),
+ name: packageJson.name,
+ formats: ['iife'],
+ cssFileName: 'index',
+ },
+ rollupOptions: {
+ output: {
+ entryFileNames: 'index.global.js',
+ extend: true,
+ },
+ },
+ },
+});
diff --git a/vite.config.mts b/vite.config.mts
new file mode 100644
index 0000000..c561b83
--- /dev/null
+++ b/vite.config.mts
@@ -0,0 +1,114 @@
+///
+
+import { dirname, relative } from 'node:path';
+import type { UserConfig } from 'vite';
+import { defineConfig } from 'vite';
+import Vue from '@vitejs/plugin-vue';
+import Icons from 'unplugin-icons/vite';
+import IconsResolver from 'unplugin-icons/resolver';
+import Components from 'unplugin-vue-components/vite';
+import AutoImport from 'unplugin-auto-import/vite';
+import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
+
+import { isDev, port, r } from './scripts/utils';
+import packageJson from './package.json';
+
+export const sharedConfig: UserConfig = {
+ root: r('src'),
+ resolve: {
+ alias: {
+ '~/': `${r('src')}/`,
+ },
+ },
+ define: {
+ __DEV__: isDev,
+ __NAME__: JSON.stringify(packageJson.name),
+ },
+ plugins: [
+ Vue(),
+ AutoImport({
+ imports: [
+ 'vue',
+ {
+ 'webextension-polyfill': [['=', 'browser']],
+ },
+ {
+ 'naive-ui': [
+ 'useDialog',
+ 'useMessage',
+ 'useNotification',
+ 'useLoadingBar',
+ ],
+ },
+ ],
+ dts: r('src/auto-imports.d.ts'),
+ }),
+
+ // https://github.com/antfu/unplugin-vue-components
+ Components({
+ dirs: [r('src/components')],
+ // generate `components.d.ts` for ts support with Volar
+ dts: r('src/components.d.ts'),
+ resolvers: [
+ // auto import icons
+ IconsResolver({
+ prefix: '',
+ }),
+ NaiveUiResolver(),
+ ],
+ }),
+
+ // https://github.com/antfu/unplugin-icons
+ Icons(),
+
+ // rewrite assets to use relative path
+ {
+ name: 'assets-rewrite',
+ enforce: 'post',
+ apply: 'build',
+ transformIndexHtml(html, { path }) {
+ return html.replace(
+ /"\/assets\//g,
+ `"${relative(dirname(path), '/assets')}/`,
+ );
+ },
+ },
+ ],
+ optimizeDeps: {
+ include: ['vue', '@vueuse/core', 'webextension-polyfill'],
+ exclude: ['vue-demi'],
+ },
+};
+
+export default defineConfig(({ command }) => ({
+ ...sharedConfig,
+ base: command === 'serve' ? `http://localhost:${port}/` : '/dist/',
+ server: {
+ port,
+ hmr: {
+ host: 'localhost',
+ },
+ origin: `http://localhost:${port}`,
+ },
+ build: {
+ watch: isDev ? {} : undefined,
+ outDir: r('extension/dist'),
+ emptyOutDir: false,
+ sourcemap: isDev ? 'inline' : false,
+ // https://developer.chrome.com/docs/webstore/program_policies/#:~:text=Code%20Readability%20Requirements
+ terserOptions: {
+ mangle: false,
+ },
+ rollupOptions: {
+ input: {
+ options: r('src/options/index.html'),
+ popup: r('src/popup/index.html'),
+ sidepanel: r('src/sidepanel/index.html'),
+ },
+ },
+ },
+ test: {
+ globals: true,
+ environment: 'jsdom',
+ },
+}));