diff --git a/packages/analytics/README.md b/packages/analytics/README.md new file mode 100644 index 000000000..06f041d7a --- /dev/null +++ b/packages/analytics/README.md @@ -0,0 +1,65 @@ + + +# My Module + +My new WXT module for doing amazing things. + +## Features + + + +- ⛰  Foo +- 🚠  Bar +- 🌲  Baz + +## Installation + +Install the module to your WXT extension with one command: + +```bash +pnpm i my-module +``` + +Then add the module to your `wxt.config.ts` file: + +```ts +export default defineConfig({ + modules: ['my-module'], +}); +``` + +That's it! You can now use My Module in your WXT extension ✨ + +## Contribution + +
+ Local development + +```bash +# Install dependencies +pnpm install + +# Generate type stubs +pnpm wxt prepare + +# Develop test extension +pnpm dev + +# Build the test extension +pnpm dev:build + +# Run prettier, publint, and type checks +pnpm check +``` + +
diff --git a/packages/analytics/app.config.ts b/packages/analytics/app.config.ts new file mode 100644 index 000000000..99c54acb4 --- /dev/null +++ b/packages/analytics/app.config.ts @@ -0,0 +1,21 @@ +import { googleAnalytics } from './modules/analytics/providers/google-analytics'; +import { AnalyticsConfig } from './modules/analytics/types'; + +interface AppConfig { + analytics: AnalyticsConfig; +} +function defineAppConfig(config: AppConfig): AppConfig { + return config; +} + +export default defineAppConfig({ + analytics: { + providers: [ + googleAnalytics({ + apiSecret: '...', + measurementId: '...', + }), + ], + debug: true, + }, +}); diff --git a/packages/analytics/build.config.ts b/packages/analytics/build.config.ts new file mode 100644 index 000000000..5f3dbc679 --- /dev/null +++ b/packages/analytics/build.config.ts @@ -0,0 +1,33 @@ +import { defineBuildConfig } from 'unbuild'; +import * as vite from 'vite'; +import { resolve } from 'node:path'; + +// Build module and plugins +export default defineBuildConfig({ + rootDir: 'modules/my-module', + outDir: resolve(__dirname, 'dist'), + entries: ['index.ts', 'plugin.ts'], + replace: { + 'process.env.NPM': 'true', + }, + declaration: true, + hooks: { + 'build:done': prebuildEntrypoints, + }, +}); + +// Prebuild entrypoints +async function prebuildEntrypoints() { + await vite.build({ + root: 'modules/my-module', + build: { + emptyOutDir: false, + rollupOptions: { + input: 'modules/my-module/example.html', + output: { + dir: 'dist/prebuilt', + }, + }, + }, + }); +} diff --git a/packages/analytics/entrypoints/popup.html b/packages/analytics/entrypoints/popup.html new file mode 100644 index 000000000..eb37be630 --- /dev/null +++ b/packages/analytics/entrypoints/popup.html @@ -0,0 +1,12 @@ + + + + + + Popup + + + + + + diff --git a/packages/analytics/modules/analytics/client.ts b/packages/analytics/modules/analytics/client.ts new file mode 100644 index 000000000..6d4726a7f --- /dev/null +++ b/packages/analytics/modules/analytics/client.ts @@ -0,0 +1,27 @@ +import { defineWxtPlugin } from 'wxt/sandbox'; +import { Analytics } from './types'; + +export let analytics: Analytics; + +export default defineWxtPlugin(() => { + const isBackground = globalThis.window == null; // TODO: Support MV2 + analytics = isBackground + ? createBackgroundAnalytics() + : createAnalyticsForwarder(); +}); + +function createBackgroundAnalytics(): Analytics { + return { + identify: () => {}, + page: () => {}, + track: () => {}, + }; +} + +function createAnalyticsForwarder(): Analytics { + return { + identify: () => {}, + page: () => {}, + track: () => {}, + }; +} diff --git a/packages/analytics/modules/analytics/index.ts b/packages/analytics/modules/analytics/index.ts new file mode 100644 index 000000000..9e779c04d --- /dev/null +++ b/packages/analytics/modules/analytics/index.ts @@ -0,0 +1,19 @@ +import 'wxt'; +import { addWxtPlugin, defineWxtModule } from 'wxt/modules'; +import { resolve } from 'node:path'; + +const pluginId = process.env.NPM + ? 'analytics/client' + : resolve(__dirname, 'client.ts'); + +export default defineWxtModule({ + name: 'analytics', + imports: [{ name: 'analytics', from: pluginId }], + setup(wxt, options) { + // Add a plugin + addWxtPlugin( + wxt, + resolve(__dirname, process.env.NPM ? 'plugin.mjs' : 'plugin.ts'), + ); + }, +}); diff --git a/packages/analytics/modules/analytics/providers/google-analytics.ts b/packages/analytics/modules/analytics/providers/google-analytics.ts new file mode 100644 index 000000000..faeb567f7 --- /dev/null +++ b/packages/analytics/modules/analytics/providers/google-analytics.ts @@ -0,0 +1,13 @@ +import type { AnalyticsProvider } from '../types'; + +export interface GoogleAnalyticsProviderOptions {} + +export const googleAnalytics = + (options: GoogleAnalyticsProviderOptions): AnalyticsProvider => + (analytics, config) => { + return { + identify: async () => {}, + page: async () => {}, + track: async () => {}, + }; + }; diff --git a/packages/analytics/modules/analytics/providers/umami.ts b/packages/analytics/modules/analytics/providers/umami.ts new file mode 100644 index 000000000..e04bc41eb --- /dev/null +++ b/packages/analytics/modules/analytics/providers/umami.ts @@ -0,0 +1,5 @@ +export interface UmamiProviderOptions {} + +export const umami = (options: UmamiProviderOptions) => (analytics, config) => { + throw Error('TODO'); +}; diff --git a/packages/analytics/modules/analytics/types.ts b/packages/analytics/modules/analytics/types.ts new file mode 100644 index 000000000..7387912a0 --- /dev/null +++ b/packages/analytics/modules/analytics/types.ts @@ -0,0 +1,50 @@ +export interface Analytics { + /** Report a page change */ + page: (url: string | URL) => void; + /** Report a custom event */ + track: (eventName: string, eventProperties: string) => void; + /** Save information about the user */ + identify: (userId: string, userProperties?: Record) => void; +} + +export interface AnalyticsConfig { + /** Array of providers to send analytics to. */ + providers: AnalyticsProvider[]; + /** Enable debug logs and other provider-specific debugging features. */ + debug?: boolean; + /** Extension version, defaults to `browser.runtime.getManifest().version`. */ + version?: string; +} + +export type AnalyticsProvider = ( + analytics: Analytics, + config: AnalyticsConfig, +) => { + /** Upload a page view event */ + page: (event: AnalyticsPageViewEvent) => Promise; + /** Upload a custom event */ + track: (event: AnalyticsTrackEvent) => Promise; + /** Upload or save information about the user */ + identify: (event: AnalyticsIdentifyEvent) => Promise; +}; + +export interface BaseAnalyticsEvent { + /** Identifier of the session the event was fired from */ + sessionId: string; + /** `Date.now()` of when the event was reported */ + time: number; +} + +export interface AnalyticsPageViewEvent extends BaseAnalyticsEvent { + url: string; + sessionId: string; + title: string; + location: string; +} + +export interface AnalyticsTrackEvent extends BaseAnalyticsEvent { + eventName: string; + eventProperties: Record; +} + +export interface AnalyticsIdentifyEvent extends BaseAnalyticsEvent {} diff --git a/packages/analytics/package.json b/packages/analytics/package.json new file mode 100644 index 000000000..c08651030 --- /dev/null +++ b/packages/analytics/package.json @@ -0,0 +1,44 @@ +{ + "name": "my-module", + "version": "1.0.0", + "description": "My new WXT module", + "repository": "your-org/my-module", + "license": "MIT", + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "./plugin": { + "types": "./dist/plugin.d.mts", + "default": "./dist/plugin.mjs" + } + }, + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "dev": "wxt", + "dev:build": "wxt build", + "check": "check", + "build": "unbuild", + "prepack": "unbuild", + "postinstall": "wxt prepare" + }, + "peerDependencies": { + "wxt": ">=0.18.10" + }, + "devDependencies": { + "@aklinker1/check": "^1.3.1", + "@types/chrome": "^0.0.268", + "prettier": "^3.3.2", + "publint": "^0.2.8", + "typescript": "^5.5.2", + "unbuild": "^2.0.0", + "vite": "^5.3.1", + "wxt": "^0.18.10" + } +} diff --git a/packages/analytics/public/.keep b/packages/analytics/public/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/analytics/tsconfig.json b/packages/analytics/tsconfig.json new file mode 100644 index 000000000..47d6ba416 --- /dev/null +++ b/packages/analytics/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": ["../../tsconfig.base.json", "./.wxt/tsconfig.json"], + "compilerOptions": { + "types": ["chrome"] + } +} diff --git a/packages/analytics/wxt.config.ts b/packages/analytics/wxt.config.ts new file mode 100644 index 000000000..0ebb6402c --- /dev/null +++ b/packages/analytics/wxt.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'wxt'; + +export default defineConfig({ + vite: () => ({ + define: { + 'process.env.NPM': 'false', + }, + }), + myModule: { + example: 'options', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e290003dd..ebf8573e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,33 @@ importers: specifier: ^2.4.5 version: 2.4.5 + packages/analytics: + devDependencies: + '@aklinker1/check': + specifier: ^1.3.1 + version: 1.3.1(typescript@5.5.2) + '@types/chrome': + specifier: ^0.0.268 + version: 0.0.268 + prettier: + specifier: ^3.3.2 + version: 3.3.2 + publint: + specifier: ^0.2.8 + version: 0.2.8 + typescript: + specifier: ^5.5.2 + version: 5.5.2 + unbuild: + specifier: ^2.0.0 + version: 2.0.0(typescript@5.5.2) + vite: + specifier: ^5.3.1 + version: 5.3.2(@types/node@20.14.9) + wxt: + specifier: ^0.18.10 + version: link:../wxt + packages/module-react: dependencies: '@vitejs/plugin-react': @@ -1897,9 +1924,26 @@ packages: '@babel/types': 7.24.7 dev: false + /@types/chrome@0.0.268: + resolution: {integrity: sha512-7N1QH9buudSJ7sI8Pe4mBHJr5oZ48s0hcanI9w3wgijAlv1OZNUZve9JR4x42dn5lJ5Sm87V1JNfnoh10EnQlA==} + dependencies: + '@types/filesystem': 0.0.36 + '@types/har-format': 1.2.15 + dev: true + /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + /@types/filesystem@0.0.36: + resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} + dependencies: + '@types/filewriter': 0.0.33 + dev: true + + /@types/filewriter@0.0.33: + resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==} + dev: true + /@types/fs-extra@11.0.4: resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} dependencies: @@ -1907,6 +1951,10 @@ packages: '@types/node': 20.10.3 dev: true + /@types/har-format@1.2.15: + resolution: {integrity: sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==} + dev: true + /@types/http-cache-semantics@4.0.1: resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} dev: false