diff --git a/.gitignore b/.gitignore index a0e8b44..94d71e4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build-types .env .env.local .env.*.local +/vendor/*/node_modules diff --git a/README.md b/README.md index a58b326..bb8deec 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ See [docs/spec.md](docs/spec.md) for the full architecture documentation (contex - `app/` - application code - `docs/` - architecture documentation (`spec.md`) +- `scripts/` - build helpers (`build:prod`) +- `vendor/secvisogram-editor/` - pinned `@secvisogram/editor` install, kept out of the main `npm ci` - `secvisogram-mock/` - dev-only stub of the `` custom element, loaded only in `npm run dev` so the app is runnable without the real `secvisogram-editor.js` bundle present ## Getting started @@ -34,12 +36,32 @@ Requires Node 24 (see `.nvmrc`). ```sh npm install npm run dev # start the Vite dev server -npm run build # production build (static assets under build/client) +npm run build # production build (static assets under build/client), no editor +npm run build:prod # build + real editor tree in build/client/secvisogram-editor (see below) npm run typecheck # generate route types and run tsc npm run lint # oxlint npm run format:check # oxfmt --check ``` +## Real editor in production builds + +`npm run build:prod` loads the real editor, a `-demo.N` prerelease of `@secvisogram/editor` from the GitHub Packages npm registry. It is installed separately from the main dependencies, so `npm ci` and `npm run build` need no token. + +Log in once to GitHub Packages. Use your GitHub username and a personal access token (classic) with `read:packages` as the password; npm stores the token in `~/.npmrc`, outside the repository: + +```sh +npm login --registry https://npm.pkg.github.com +``` + +Then install the editor and build: + +```sh +cd vendor/secvisogram-editor && npm ci && cd ../.. +npm run build:prod +``` + +`build:prod` fails before building if the editor is not installed. It copies the package's `dist/` to `build/client/secvisogram-editor/`. + ## Testing ```sh diff --git a/app/EditorScript.test.tsx b/app/EditorScript.test.tsx new file mode 100644 index 0000000..44af5c6 --- /dev/null +++ b/app/EditorScript.test.tsx @@ -0,0 +1,30 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { render } from 'vitest-browser-react' + +import { EditorScript } from './EditorScript' + +const EDITOR_SELECTOR = + 'script[src="/secvisogram-editor/secvisogram-editor.js"]' + +afterEach(() => { + vi.unstubAllEnvs() +}) + +describe('EditorScript', () => { + it('emits the deferred classic editor script in production-editor builds', async () => { + vi.stubEnv('VITE_EMBED_EDITOR', 'true') + + await render() + + const script = document.querySelector(EDITOR_SELECTOR) + expect(script).not.toBeNull() + expect(script?.defer).toBe(true) + expect(script?.type).not.toBe('module') + }) + + it('emits nothing when the build constant is not set', async () => { + await render() + + expect(document.querySelector(EDITOR_SELECTOR)).toBeNull() + }) +}) diff --git a/app/EditorScript.tsx b/app/EditorScript.tsx new file mode 100644 index 0000000..33b8abd --- /dev/null +++ b/app/EditorScript.tsx @@ -0,0 +1,6 @@ +export function EditorScript() { + // `VITE_EMBED_EDITOR` is set only by `build:prod`. + return import.meta.env.VITE_EMBED_EDITOR === 'true' ? ( + + ) : null +} diff --git a/app/env.d.ts b/app/env.d.ts new file mode 100644 index 0000000..f9db7a9 --- /dev/null +++ b/app/env.d.ts @@ -0,0 +1,3 @@ +interface ImportMetaEnv { + readonly VITE_EMBED_EDITOR?: string +} diff --git a/app/root.tsx b/app/root.tsx index fa999fd..a09c4b3 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -10,6 +10,7 @@ import { useTranslation } from 'react-i18next' import { AppConfigError, getAppConfig } from '~/auth/appConfig' import { fetchApiOrRedirect } from '~/auth/session' +import { EditorScript } from '~/EditorScript' import '~/i18n/i18n' import { NotificationProvider } from '~/notifications/NotificationContext' import type { Route } from './+types/root' @@ -46,6 +47,7 @@ export function Layout({ children }: { children: React.ReactNode }) { {children} + ) diff --git a/package.json b/package.json index 7151109..833a810 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "scripts": { "dev": "react-router dev", "build": "react-router build && rm -r build/client/secvisogram-mock", + "build:prod": "node scripts/buildProd.ts", "typecheck": "react-router typegen && tsc -b .", "test": "vitest run --project=node", "test:browser": "vitest run --project=browser", diff --git a/scripts/buildProd.ts b/scripts/buildProd.ts new file mode 100644 index 0000000..56b4cc4 --- /dev/null +++ b/scripts/buildProd.ts @@ -0,0 +1,21 @@ +import { execFileSync } from 'node:child_process' +import { cp, stat } from 'node:fs/promises' + +const editorDist = + 'vendor/secvisogram-editor/node_modules/@secvisogram/editor/dist' + +if (!(await stat(editorDist).catch(() => null))?.isDirectory()) { + console.error( + `The Secvisogram editor is not installed (missing ${editorDist}). ` + + 'Run `npm ci` in vendor/secvisogram-editor first (see README).', + ) + process.exitCode = 1 +} else { + execFileSync('npm run build', { + stdio: 'inherit', + env: { ...process.env, VITE_EMBED_EDITOR: 'true' }, + shell: true, + }) + + await cp(editorDist, 'build/client/secvisogram-editor', { recursive: true }) +} diff --git a/vendor/secvisogram-editor/.npmrc b/vendor/secvisogram-editor/.npmrc new file mode 100644 index 0000000..bf03b7b --- /dev/null +++ b/vendor/secvisogram-editor/.npmrc @@ -0,0 +1 @@ +@secvisogram:registry=https://npm.pkg.github.com diff --git a/vendor/secvisogram-editor/package-lock.json b/vendor/secvisogram-editor/package-lock.json new file mode 100644 index 0000000..5f38c48 --- /dev/null +++ b/vendor/secvisogram-editor/package-lock.json @@ -0,0 +1,19 @@ +{ + "name": "secvisogram-editor-vendor", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "secvisogram-editor-vendor", + "dependencies": { + "@secvisogram/editor": "0.1.0-demo.1" + } + }, + "node_modules/@secvisogram/editor": { + "version": "0.1.0-demo.1", + "resolved": "https://npm.pkg.github.com/download/@secvisogram/editor/0.1.0-demo.1/bad3e4778b3fccb42f0f7ea989965e49eb1555dc", + "integrity": "sha512-n/0df+s5RFV4tbIbTqeqj5nYGAFxHatZEfDjF6wEuQZExDo9WkgMt9UqUGaxggh0Zs/YgqC4MxCCsiwImTs5IA==", + "license": "MIT" + } + } +} diff --git a/vendor/secvisogram-editor/package.json b/vendor/secvisogram-editor/package.json new file mode 100644 index 0000000..e3208b0 --- /dev/null +++ b/vendor/secvisogram-editor/package.json @@ -0,0 +1,8 @@ +{ + "name": "secvisogram-editor-vendor", + "private": true, + "description": "Installs the pinned @secvisogram/editor prerelease from GitHub Packages; copied into build/client by build:prod.", + "dependencies": { + "@secvisogram/editor": "0.1.0-demo.1" + } +}