diff --git a/AGENTS.md b/AGENTS.md index a4873f2..7782b10 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,11 +1,11 @@ # AGENTS ## Project Overview -Write Wall is a Chrome Extension (Manifest V3) that provides a synced text pad backed by `chrome.storage.sync` so text is shared across the signed-in Chrome account. The UI is a single page (`src/html/index.html`) with a textarea and a byte counter; logic lives in `src/main.ts` and background logic in `src/service_worker.ts`. +Write Wall is a Chrome Extension (Manifest V3) that provides a synced text pad backed by `chrome.storage.sync` so text is shared across the signed-in Chrome account. The UI is a single page (`public/html/index.html`) with a textarea and a byte counter; logic lives in `src/main.ts` and background logic in `src/service_worker.ts`. ## Tech Stack - TypeScript (ES2024 target, strict mode) -- Webpack + ts-loader (build) +- Vite (build) - Vitest (tests, `*.spec.ts` naming) - Biome (lint + format) - Husky (pre-commit: lint + test) @@ -16,10 +16,11 @@ Write Wall is a Chrome Extension (Manifest V3) that provides a synced text pad b - `src/main.ts`: UI logic, reads/writes synced text, throttles writes to respect sync quotas. - `src/service_worker.ts`: MV3 service worker, opens `html/index.html` when the action icon is clicked. - `src/utils.ts`: Shared utilities (throttle function). -- `src/html/index.html`: Extension UI page. -- `src/css/main.css`: UI styles (dark theme, CSS custom properties). +- `public/html/index.html`: Extension UI page. +- `public/css/main.css`: UI styles (dark theme, CSS custom properties). +- `public/images/`: Extension icons (copied verbatim to `dist/` by Vite). - `public/manifest.json`: MV3 manifest (source of truth, copied to `dist/` on build). -- `webpack/webpack.config.cjs`: Build config, emits bundles to `dist/` and copies static assets. +- `vite.config.ts`: Build config, emits bundles to `dist/` and copies `public/` assets. - `build.cjs`: Packages `dist/` into `app.zip` for release. - `dist/`: Build output (generated, do not edit). @@ -30,11 +31,11 @@ Write Wall is a Chrome Extension (Manifest V3) that provides a synced text pad b | UI behavior / event handlers | `src/main.ts` | | Background / tab management | `src/service_worker.ts` | | Shared utilities | `src/utils.ts` | -| Styles | `src/css/main.css` | -| HTML structure | `src/html/index.html` | -| Static assets / icons | `src/images/` | +| Styles | `public/css/main.css` | +| HTML structure | `public/html/index.html` | +| Static assets / icons | `public/images/` | | Manifest changes | `public/manifest.json` | -| Build config | `webpack/webpack.config.cjs` | +| Build config | `vite.config.ts` | | New test | `src/.spec.ts` | ## Development Workflow @@ -48,8 +49,8 @@ Write Wall is a Chrome Extension (Manifest V3) that provides a synced text pad b - `pnpm test`: Run Vitest tests. - `pnpm lint`: Run Biome checks. - `pnpm lint:fix`: Run Biome with auto-fix. -- `pnpm develop`: Webpack build in watch mode. -- `pnpm build`: Webpack build + package `dist/` into `app.zip`. +- `pnpm develop`: Vite build in watch mode. +- `pnpm build`: Vite build + package `dist/` into `app.zip`. - `pnpm type:check`: Run TypeScript type checking. - `pnpm prepare`: Install Husky hooks. - `pnpm check-updates`: Run npm-check-updates in interactive mode. @@ -63,7 +64,7 @@ Write Wall is a Chrome Extension (Manifest V3) that provides a synced text pad b ## Common Pitfalls - Sync quota is 8,192 bytes total. Test near-limit behavior. -- Webpack uses `tsconfig.build.json`, not `tsconfig.json`. Build errors may differ from editor errors. +- Vite uses esbuild for transpilation (not `tsconfig.build.json`). Type checking is separate (`pnpm type:check`). - Pre-commit hook runs lint + test. Fix with `pnpm lint:fix` before retrying. - Test environment is Node (not browser). Chrome APIs must be mocked in tests. diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d32aec..2280616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## [Unreleased] + +### Changed + +- Replace Webpack with Vite for build tooling +- Move static assets (`css/`, `html/`, `images/`) from `src/` to `public/` +- Output ESM bundles instead of IIFE (required for Vite multi-entry builds) +- Add `"type": "module"` to manifest background service worker + +### Removed + +- Remove `webpack`, `webpack-cli`, `ts-loader`, `copy-webpack-plugin` dependencies +- Remove `webpack/` directory + ## [2.5.0] - 2026-01-27 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index 20afb14..c88c655 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ ## Architecture Quick Reference -- UI page: `src/html/index.html` with logic in `src/main.ts` +- UI page: `public/html/index.html` with logic in `src/main.ts` - Service worker: `src/service_worker.ts` (tab management only) - Shared utilities: `src/utils.ts` (throttle function) - Sync storage key: `v2` in `chrome.storage.sync` (8,192 byte limit) @@ -33,7 +33,7 @@ if (copyButtonEl) { 1. **Tests fail**: Run `pnpm test` locally. Tests mock Chrome APIs; check mock setup in spec files. 2. **Lint errors**: Run `pnpm lint:fix`. Biome config is in `biome.json`. -3. **Build issues**: Check `webpack/webpack.config.cjs`. Uses `tsconfig.build.json` (not `tsconfig.json`). +3. **Build issues**: Check `vite.config.ts`. Vite uses esbuild for transpilation; type checking is separate (`pnpm type:check`). 4. **Version mismatch**: Both `package.json` and `public/manifest.json` must match. Use `pnpm verify-version`. ## CI/CD Details diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 640b132..6445c46 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,7 @@ pnpm verify-version - Avoid editing `dist/` directly (generated output). - `chrome.storage.sync` is quota-limited; preserve write throttling. -- The UI is `src/html/index.html`; logic is in `src/main.ts`. +- The UI is `public/html/index.html`; logic is in `src/main.ts`. ## Communication diff --git a/README.md b/README.md index f8e6234..e2e5698 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ signing in to Chrome. ## How It Works -- The UI lives in `src/html/index.html` with logic in `src/main.ts` +- The UI lives in `public/html/index.html` with logic in `src/main.ts` - Text is saved to `chrome.storage.sync` under the `v2` key - Writes are throttled to respect Chrome sync quotas - The byte counter uses `chrome.storage.sync.getBytesInUse` @@ -55,7 +55,7 @@ signing in to Chrome. ### Common Scripts -- `pnpm develop`: Webpack build in watch mode +- `pnpm develop`: Vite build in watch mode - `pnpm build`: Production build + `app.zip` packaging - `pnpm lint`: Biome checks - `pnpm lint:fix`: Biome auto-fix diff --git a/app.zip b/app.zip index 9390d46..7a8a33d 100644 Binary files a/app.zip and b/app.zip differ diff --git a/biome.json b/biome.json index 6d7fca4..9359cee 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.12/schema.json", + "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json", "vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true }, "files": { "includes": ["**", "!!**/dist", "!!**/coverage"] }, "formatter": { @@ -122,8 +122,7 @@ "!.idea/*", "!dist/*", "!node_modules/*", - "!*.cjs", - "!webpack/*" + "!*.cjs" ] }, "javascript": { diff --git a/docs/architecture.md b/docs/architecture.md index 3ebd985..c18019a 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -6,7 +6,7 @@ Write Wall is a Chrome Extension built on Manifest V3. It provides a synced text ## Extension Components -### UI Page (`src/html/index.html`) +### UI Page (`public/html/index.html`) Single HTML page rendered when the extension action icon is clicked. Contains: - ` - + diff --git a/src/images/icon-128.png b/public/images/icon-128.png similarity index 100% rename from src/images/icon-128.png rename to public/images/icon-128.png diff --git a/src/images/icon-16.png b/public/images/icon-16.png similarity index 100% rename from src/images/icon-16.png rename to public/images/icon-16.png diff --git a/src/images/icon-48.png b/public/images/icon-48.png similarity index 100% rename from src/images/icon-48.png rename to public/images/icon-48.png diff --git a/src/images/icon-512.png b/public/images/icon-512.png similarity index 100% rename from src/images/icon-512.png rename to public/images/icon-512.png diff --git a/src/images/icon-64.png b/public/images/icon-64.png similarity index 100% rename from src/images/icon-64.png rename to public/images/icon-64.png diff --git a/src/images/icon.png b/public/images/icon.png similarity index 100% rename from src/images/icon.png rename to public/images/icon.png diff --git a/public/manifest.json b/public/manifest.json index ed33712..c1a1470 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -20,7 +20,8 @@ } }, "background": { - "service_worker": "service_worker.bundle.js" + "service_worker": "service_worker.bundle.js", + "type": "module" }, "permissions": ["storage"] } diff --git a/src/main.spec.ts b/src/main.spec.ts index 0f95a35..11d410e 100644 --- a/src/main.spec.ts +++ b/src/main.spec.ts @@ -669,7 +669,7 @@ describe('main UI bootstrap', () => { }); it('ships a placeholder hint in the UI', () => { - const html = readFileSync(new URL('./html/index.html', import.meta.url), 'utf8'); + const html = readFileSync(new URL('../public/html/index.html', import.meta.url), 'utf8'); expect(html).toContain('placeholder="Type here... auto-syncs across Chrome."'); }); diff --git a/tsconfig.build.json b/tsconfig.build.json index 41e8a1d..4b554e4 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -2,12 +2,12 @@ "extends": "./tsconfig.json", "include": ["src/**/*"], "exclude": [ - "webpack/**/*", "dist/**/*", ".github/**/*", ".husky/**/*", "build.cjs", "src/**/*.spec.ts", + "vite.config.ts", "vitest.config.ts" ] } diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..c61d9cc --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,23 @@ +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { defineConfig } from 'vite'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + build: { + outDir: 'dist', + emptyOutDir: true, + sourcemap: false, + target: 'es2024', + rollupOptions: { + input: { + main: resolve(__dirname, 'src/main.ts'), + service_worker: resolve(__dirname, 'src/service_worker.ts'), + }, + output: { + entryFileNames: '[name].bundle.js', + }, + }, + }, +}); diff --git a/webpack/webpack.config.cjs b/webpack/webpack.config.cjs deleted file mode 100644 index edebf8e..0000000 --- a/webpack/webpack.config.cjs +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2023-2026 Jared M. Scott. This work is licensed under the Creative - * Commons Attribution 3.0 Un-ported License. To view a copy of this license, - * visit http://creativecommons.org/licenses/by/3.0/ or send a letter to - * Creative Commons, - * 444 Castro Street, Suite 900, - * Mountain View, California, 94041, USA. - */ - -const path = require('path'); -const CopyPlugin = require('copy-webpack-plugin'); -module.exports = { - mode: 'production', - devtool: 'inline-source-map', - entry: { - main: path.resolve(__dirname, '..', 'src', 'main.ts'), - service_worker: path.resolve(__dirname, '..', 'src', 'service_worker.ts'), - }, - output: { - path: path.join(__dirname, '../dist'), - filename: '[name].bundle.js', - clean: true, - }, - resolve: { - extensionAlias: { - '.js': ['.ts', '.tsx', '.js', '.jsx'], - }, - extensions: ['.tsx', '.ts', '.js'], - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: [ - { - loader: 'ts-loader', - options: { - configFile: path.resolve(__dirname, '..', 'tsconfig.build.json'), - }, - }, - ], - exclude: /node_modules/, - }, - ], - }, - plugins: [ - new CopyPlugin({ - patterns: [ - { from: '.', to: '.', context: 'public' }, - { - from: path.resolve(__dirname, '..', 'src', 'css'), - to: path.resolve(__dirname, '..', 'dist', 'css'), - context: 'public', - }, - { - from: path.resolve(__dirname, '..', 'src', 'html'), - to: path.resolve(__dirname, '..', 'dist', 'html'), - context: 'public', - }, - { - from: path.resolve(__dirname, '..', 'src', 'images'), - to: path.resolve(__dirname, '..', 'dist', 'images'), - context: 'public', - }, - ], - }), - ], -};