diff --git a/excalidraw-app/lib/atproto.ts b/excalidraw-app/lib/atproto.ts index fa6dd2d7..4ff0a2ae 100644 --- a/excalidraw-app/lib/atproto.ts +++ b/excalidraw-app/lib/atproto.ts @@ -17,7 +17,8 @@ import { isValidDid, } from "@atproto/syntax"; import { AtprotoDohHandleResolver } from "@atproto/oauth-client-browser"; -import { DidResolver, DidDocument } from "@atproto/identity"; + +import { DidResolver } from "@atproto/identity"; import { LRUCache } from "lru-cache"; @@ -180,32 +181,6 @@ const isSafeServiceUrl = (value: string) => { ); }; -const readDidDocumentHandle = (doc: DidDocument): string => { - const aka = Array.isArray(doc.alsoKnownAs) ? doc.alsoKnownAs : []; - const first = aka.find((entry): entry is string => typeof entry === "string"); - if (!first?.startsWith("at://")) { - return ""; - } - return first.slice("at://".length).toLowerCase(); -}; - -const readDidDocumentPds = (doc: DidDocument): string => { - const services = Array.isArray(doc.service) ? doc.service : []; - for (const entry of services) { - if (!entry || typeof entry !== "object") { - continue; - } - const { id, type, serviceEndpoint } = entry as Record; - const matches = - (typeof id === "string" && id.split("#").pop() === "atproto_pds") || - type === "AtprotoPersonalDataServer"; - if (matches && typeof serviceEndpoint === "string") { - return serviceEndpoint.replace(/\/+$/, ""); - } - } - return ""; -}; - type Identity = { pdsEndpoint: string; handle: string }; /** Resolves a DID to its PDS endpoint and (unverified) handle. */ @@ -213,15 +188,15 @@ const resolveIdentity = async ( did: string, signal?: AbortSignal, ): Promise => { - const result = await didResolver.resolve(did); + const result = await didResolver.resolveAtprotoData(did); if (!result) { return null; } - const pdsEndpoint = readDidDocumentPds(result); + const pdsEndpoint = result.pds; if (!pdsEndpoint || !isSafeServiceUrl(pdsEndpoint)) { return null; } - return { pdsEndpoint, handle: readDidDocumentHandle(result) }; + return { pdsEndpoint, handle: result.handle }; }; type ThumbRef = { cid: string; mime: string }; @@ -388,7 +363,7 @@ const resolveHandleDid = async ( return typeof cached === "string" ? cached : null; } - let did = await handleResolver.resolve(handle); + const did = await handleResolver.resolve(handle); if (did && isValidDid(did)) { handleDidCache.set(handle, did); diff --git a/excalidraw-app/package.json b/excalidraw-app/package.json index 6beb8d94..aa3a8433 100644 --- a/excalidraw-app/package.json +++ b/excalidraw-app/package.json @@ -58,7 +58,7 @@ "prettier": "@excalidraw/prettier-config", "scripts": { "lex:build": "lex build --lexicons ../lexicons --out ./data/atproto/lexicons --import-ext \"\" --index-file --clear", - "build": "vite build && node scripts/copy-sw.mjs", + "build": "vite build", "dev": "vite dev", "start": "vite dev", "preview": "vite preview" @@ -67,6 +67,11 @@ "@types/pako": "2.0.3", "@vitejs/plugin-rsc": "0.5.30", "sass": "1.85.1", - "vite": "8.1.0" + "vite": "8.1.0", + "workbox-core": "7.4.1", + "workbox-expiration": "7.4.1", + "workbox-precaching": "7.4.1", + "workbox-routing": "7.4.1", + "workbox-strategies": "7.4.1" } } diff --git a/excalidraw-app/pwa/sw.ts b/excalidraw-app/pwa/sw.ts new file mode 100644 index 00000000..8c976643 --- /dev/null +++ b/excalidraw-app/pwa/sw.ts @@ -0,0 +1,112 @@ +/// +import { clientsClaim } from "workbox-core"; +import { ExpirationPlugin } from "workbox-expiration"; +import { cleanupOutdatedCaches, precacheAndRoute } from "workbox-precaching"; +import { registerRoute } from "workbox-routing"; +import { CacheFirst, NetworkFirst } from "workbox-strategies"; + +import type { PrecacheEntry } from "workbox-precaching"; + +declare let self: ServiceWorkerGlobalScope & { + __WB_MANIFEST: Array; +}; + +// autoUpdate semantics (formerly generateSW's skipWaiting/clientsClaim) +self.skipWaiting(); +clientsClaim(); + +// --- Web Share Target ------------------------------------------------------- +// manifest.share_target POSTs multipart form-data (param "file") to +// /web-share-target. The consumer is packages/excalidraw/components/App.tsx +// (restoreFileFromShare): cache named "web-share-target", entry keyed +// "shared-file", client landed on a URL carrying ?web-share-target. The 303 +// turns the POST into a GET navigation. +self.addEventListener("fetch", (event) => { + const url = new URL(event.request.url); + if (event.request.method === "POST" && url.pathname === "/web-share-target") { + event.respondWith( + (async () => { + try { + const file = (await event.request.formData()).get("file"); + if (file instanceof File) { + const cache = await caches.open("web-share-target"); + await cache.put( + "shared-file", + new Response(file, { + headers: { + "Content-Type": file.type || "application/octet-stream", + "Content-Length": String(file.size), + }, + }), + ); + } + } catch { + // fall through — land in the app without a shared file + } + return Response.redirect("/?web-share-target", 303); + })(), + ); + } +}); + +// --- Precache --------------------------------------------------------------- +cleanupOutdatedCaches(); +// `?? []`: in dev the SW source is served directly (see devSwPassthrough in +// vite.config.ts) and the build-time manifest injection never runs +precacheAndRoute(self.__WB_MANIFEST ?? []); + +// one-time cleanup of runtime caches orphaned by the retired generateSW config +self.addEventListener("activate", (event) => { + event.waitUntil( + Promise.all(["locales", "chunk"].map((name) => caches.delete(name))), + ); +}); + +// --- Runtime routes --------------------------------------------------------- +// Navigations: timber renders the document dynamically — there is no +// index.html to precache (a navigateFallback would throw non-precached-url at +// SW startup). Online clients always get a fresh RSC document, offline PWA +// starts get the last cached one. +registerRoute( + ({ request }) => request.mode === "navigate", + new NetworkFirst({ + cacheName: "pages", + networkTimeoutSeconds: 5, + plugins: [new ExpirationPlugin({ maxEntries: 16 })], + }), +); + +// Fonts are same-origin (public/*.woff2 and the bundled hashed ones) +registerRoute( + ({ sameOrigin, url }) => sameOrigin && url.pathname.endsWith(".woff2"), + new CacheFirst({ + cacheName: "fonts", + plugins: [ + new ExpirationPlugin({ + maxEntries: 1000, + maxAgeSeconds: 60 * 60 * 24 * 90, // 90 days + }), + ], + }), +); + +// Everything under /assets/ is content-hashed and immutable. Precached assets +// are served by the precache route above; whatever precache deliberately +// excludes (locales, mermaid chunk-*.js, *.chunk-*.js, CodeMirrorEditor) +// lands here on first use. +registerRoute( + ({ sameOrigin, request, url }) => + sameOrigin && + request.method === "GET" && + url.pathname.startsWith("/assets/"), + new CacheFirst({ + cacheName: "assets", + plugins: [ + new ExpirationPlugin({ + maxEntries: 300, + maxAgeSeconds: 60 * 60 * 24 * 90, // 90 days + purgeOnQuotaError: true, + }), + ], + }), +); diff --git a/excalidraw-app/scripts/copy-sw.mjs b/excalidraw-app/scripts/copy-sw.mjs deleted file mode 100644 index 4a8a0bd3..00000000 --- a/excalidraw-app/scripts/copy-sw.mjs +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env node -/** - * VitePWA emits the service worker (sw.js + its workbox runtime) into the - * top of the vite out dir (.timber/dist/), but nitro only copies client/ - * assets and public/ into its served .output/public — without this step the - * deployed server would 404 /sw.js and the PWA would never register. - */ -import { copyFileSync, readdirSync } from "node:fs"; -import path from "node:path"; - -const distDir = path.resolve(import.meta.dirname, "../.timber/dist"); -const outDir = path.resolve(distDir, "nitro/.output/public"); - -const swFiles = readdirSync(distDir).filter( - (name) => name === "sw.js" || name === "sw.js.map" || /^workbox-.*\.js(\.map)?$/.test(name), -); - -if (!swFiles.includes("sw.js")) { - console.error("copy-sw: no sw.js in .timber/dist — did the PWA build run?"); - process.exit(1); -} - -for (const name of swFiles) { - copyFileSync(path.join(distDir, name), path.join(outDir, name)); -} -console.log(`copy-sw: copied ${swFiles.join(", ")} → .output/public/`); diff --git a/excalidraw-app/timber.config.ts b/excalidraw-app/timber.config.ts index f1e8c7aa..4994e4d2 100644 --- a/excalidraw-app/timber.config.ts +++ b/excalidraw-app/timber.config.ts @@ -22,7 +22,6 @@ export default { // the service-worker chain and boot script must always revalidate, // or clients could pin a dead precache manifest / stale boot logic "/sw.js": { headers: { "Cache-Control": NO_CACHE } }, - "/registerSW.js": { headers: { "Cache-Control": NO_CACHE } }, "/manifest.webmanifest": { headers: { "Cache-Control": NO_CACHE } }, "/boot.js": { headers: { "Cache-Control": NO_CACHE } }, // atproto identity endpoints are fetched cross-origin by other diff --git a/excalidraw-app/vite.aliases.ts b/excalidraw-app/vite.aliases.ts new file mode 100644 index 00000000..311cbc99 --- /dev/null +++ b/excalidraw-app/vite.aliases.ts @@ -0,0 +1,54 @@ +import path from "node:path"; + +import type { Alias } from "vite"; + +const appRoot = import.meta.dirname; +const repoRoot = path.resolve(appRoot, ".."); +const pkg = (...segments: string[]) => + path.resolve(repoRoot, "packages", ...segments); + +/** + * Source aliases shared by excalidraw-app/vite.config.ts and the root + * vitest.config.mts. tsconfig.json `paths` mirrors these for types only. + * + * The fractional-indexing and laser-pointer aliases are load-bearing, not + * conveniences: those packages' `main` points at dist/prod/, which is never + * built here, so workspace resolution fails without them. + * + * draw-collab-wasm is deliberately absent — vitest mocks it, vite appends it. + */ +export const excalidrawAliases: Alias[] = [ + { find: /^~\/(.*?)/, replacement: `${appRoot}/$1` }, + { find: /^@excalidraw\/common$/, replacement: pkg("common/src/index.ts") }, + { find: /^@excalidraw\/common\/(.*?)/, replacement: pkg("common/src/$1") }, + { find: /^@excalidraw\/element$/, replacement: pkg("element/src/index.ts") }, + { find: /^@excalidraw\/element\/(.*?)/, replacement: pkg("element/src/$1") }, + { + find: /^@excalidraw\/excalidraw$/, + replacement: pkg("excalidraw/index.tsx"), + }, + { + find: /^@excalidraw\/excalidraw\/(.*?)/, + replacement: pkg("excalidraw/$1"), + }, + { find: /^@excalidraw\/math$/, replacement: pkg("math/src/index.ts") }, + { find: /^@excalidraw\/math\/(.*?)/, replacement: pkg("math/src/$1") }, + { find: /^@excalidraw\/utils$/, replacement: pkg("utils/src/index.ts") }, + { find: /^@excalidraw\/utils\/(.*?)/, replacement: pkg("utils/src/$1") }, + { + find: /^@excalidraw\/fractional-indexing$/, + replacement: pkg("fractional-indexing/src/index.ts"), + }, + { + find: /^@excalidraw\/fractional-indexing\/(.*?)/, + replacement: pkg("fractional-indexing/src/$1"), + }, + { + find: /^@excalidraw\/laser-pointer$/, + replacement: pkg("laser-pointer/src/index.ts"), + }, + { + find: /^@excalidraw\/laser-pointer\/(.*?)/, + replacement: pkg("laser-pointer/src/$1"), + }, +]; diff --git a/excalidraw-app/vite.config.ts b/excalidraw-app/vite.config.ts index d67bd392..9092d468 100644 --- a/excalidraw-app/vite.config.ts +++ b/excalidraw-app/vite.config.ts @@ -1,15 +1,26 @@ import { execSync } from "node:child_process"; +import { readdirSync, renameSync } from "node:fs"; import path from "node:path"; import { timber } from "@timber-js/app"; import { defineConfig, loadEnv } from "vite"; import { VitePWA } from "vite-plugin-pwa"; +import { excalidrawAliases } from "./vite.aliases"; + const repoRoot = path.resolve(import.meta.dirname, ".."); const appRoot = import.meta.dirname; const pkg = (...segments: string[]) => path.resolve(repoRoot, "packages", ...segments); +// Locale chunks are emitted flat as assets/-.js with no +// distinguishing name pattern, so derive the precache-exclusion globs from +// the source-of-truth directory. A user only ever loads one locale — the +// sw.ts /assets/ runtime route caches it on first use instead. +const localeGlobIgnores = readdirSync(pkg("excalidraw/locales")) + .filter((f) => f.endsWith(".json")) + .map((f) => `assets/${f.replace(/\.json$/, "")}-*.js`); + /** * Dev-only: serve the app's own root-relative .scss module URLs. * @@ -37,6 +48,54 @@ const scssDevPassthrough = () => ({ }, }); +/** + * Dev-only: serve the PWA dev service worker. + * + * vite-plugin-pwa unlocks its catch-all resolveId for `/dev-sw.js?dev-sw` by + * deleting the hook's `filter` at runtime (its options() hook) — but vite 8 + * precompiles hook filters into each environment's plugin pipeline, so the + * delete is a no-op and the virtual dev SW URL falls through to the app + * router as HTML. The SW *source* transforms fine as a plain module, so + * rewrite the registration URL onto it; registration keeps the /dev-sw.js + * path (root scope). sw.ts guards __WB_MANIFEST for this un-injected case. + */ +const devSwPassthrough = () => ({ + name: "lexidraw:dev-sw-passthrough", + apply: "serve" as const, + configureServer(server: import("vite").ViteDevServer) { + server.middlewares.use((req, _res, next) => { + if (req.url === "/dev-sw.js?dev-sw") { + req.url = "/pwa/sw.ts"; + } + next(); + }); + }, +}); + +/** + * Build-only: relocate the generated service worker into the served tree. + * + * VitePWA writes sw.js to vite's root outDir (.timber/dist/), but the nitro + * adapter packs .output/public from .timber/dist/client/ + public/ and bakes + * a public-asset manifest at that moment — files copied into .output/public + * afterwards are never served (the app catch-all answers /sw.js with HTML, + * which browsers reject as a SW script). Hook ordering makes an in-build move + * possible: VitePWA emits the SW during the environment builds (inside the + * RSC driver's default-order buildApp), and the nitro adapter packs in a + * 'post'-order buildApp — so a default-order buildApp hook here runs exactly + * in between. + */ +const swIntoServedClientDir = () => ({ + name: "lexidraw:sw-into-client-dir", + apply: "build" as const, + buildApp: { + async handler() { + const dist = path.resolve(appRoot, ".timber/dist"); + renameSync(path.join(dist, "sw.js"), path.join(dist, "client/sw.js")); + }, + }, +}); + /** * `-`, same shape the retired scripts/build-version.js * stamped into index.html. Inside the Docker build there is no .git (the @@ -89,58 +148,7 @@ export default defineConfig(({ mode }) => { // jotai so editor and app share stores. dedupe: ["react", "react-dom", "jotai"], alias: [ - { - find: /^~\/(.*?)/, - replacement: `${appRoot}/$1`, - }, - { - find: /^@excalidraw\/common$/, - replacement: pkg("common/src/index.ts"), - }, - { - find: /^@excalidraw\/common\/(.*?)/, - replacement: pkg("common/src/$1"), - }, - { - find: /^@excalidraw\/element$/, - replacement: pkg("element/src/index.ts"), - }, - { - find: /^@excalidraw\/element\/(.*?)/, - replacement: pkg("element/src/$1"), - }, - { - find: /^@excalidraw\/excalidraw$/, - replacement: pkg("excalidraw/index.tsx"), - }, - { - find: /^@excalidraw\/excalidraw\/(.*?)/, - replacement: pkg("excalidraw/$1"), - }, - { - find: /^@excalidraw\/math$/, - replacement: pkg("math/src/index.ts"), - }, - { - find: /^@excalidraw\/math\/(.*?)/, - replacement: pkg("math/src/$1"), - }, - { - find: /^@excalidraw\/utils$/, - replacement: pkg("utils/src/index.ts"), - }, - { - find: /^@excalidraw\/utils\/(.*?)/, - replacement: pkg("utils/src/$1"), - }, - { - find: /^@excalidraw\/fractional-indexing$/, - replacement: pkg("fractional-indexing/src/index.ts"), - }, - { - find: /^@excalidraw\/laser-pointer$/, - replacement: pkg("laser-pointer/src/index.ts"), - }, + ...excalidrawAliases, { find: /^draw-collab-wasm$/, replacement: path.resolve(repoRoot, "crates/draw-collab-wasm/pkg"), @@ -155,102 +163,42 @@ export default defineConfig(({ mode }) => { }, plugins: [ scssDevPassthrough(), - // timber() wires @vitejs/plugin-react + plugin-rsc internally - timber(), + devSwPassthrough(), VitePWA({ + // Custom SW (pwa/sw.ts): precache + runtime routes + the + // /web-share-target POST handler, bundled (workbox runtime included) + // into a single revisioned sw.js. + strategies: "injectManifest", + srcDir: "pwa", + filename: "sw.ts", registerType: "autoUpdate", + // registration happens exclusively via virtual:pwa-register in + // app/excalidraw-client.tsx — don't emit an orphan registerSW.js + injectRegister: false, devOptions: { /* set this flag to true to enable in Development mode */ enabled: envVars.VITE_APP_ENABLE_PWA === "true", + // dev SW is served unbundled as ESM (Chromium-only in dev) + type: "module", }, - - workbox: { - // timber renders the document dynamically — there is no index.html - // to precache, and the generated createHandlerBoundToURL fallback - // would throw `non-precached-url` at SW startup. Navigations are - // handled by the NetworkFirst runtime route below instead: online - // clients always get a fresh RSC document, offline PWA starts get - // the last cached one. - navigateFallback: null, - // don't precache fonts, locales and separate chunks + injectManifest: { + // Glob the served root directly: nitro serves client/'s CONTENTS at + // /, so entries must be rooted there — the plugin's default glob of + // .timber/dist/ produced client/-prefixed URLs that all 404'd and + // the precache never installed. This also keeps the ssr/, rsc/ and + // nitro/ sibling dirs (~16MB SSR entry) out of the glob entirely. + globDirectory: path.resolve(appRoot, ".timber/dist/client"), globIgnores: [ - // the RSC build writes client/, ssr/ and rsc/ side by side into - // the out dir; only client/ is ever served to a browser. Without - // this the manifest tries to precache the ~16MB SSR entry (and - // fails the build on the size limit). - "ssr/**", - "rsc/**", - "nitro/**", - "fonts.css", - "**/locales/**", - "service-worker.js", - "**/*.chunk-*.js", + // lazy-loaded and content-hashed — served on demand by the + // sw.ts /assets/ CacheFirst route instead of inflating install + "assets/*.chunk-*.js", // qrcode, subset-shared, subset-worker + "assets/chunk-*.js", // mermaid's prebundled chunks // CodeMirrorEditor can't be assigned a `.chunk` name via // manualChunks because Rollup would hoist shared deps (React) // via a static import from the main bundle, defeating lazy // loading. So we exclude it by name instead. - "**/CodeMirrorEditor-*.js", - ], - runtimeCaching: [ - { - urlPattern: ({ request }: { request: Request }) => - request.mode === "navigate", - handler: "NetworkFirst", - options: { - cacheName: "pages", - networkTimeoutSeconds: 5, - expiration: { - maxEntries: 16, - }, - }, - }, - { - urlPattern: new RegExp(".+.woff2"), - handler: "CacheFirst", - options: { - cacheName: "fonts", - expiration: { - maxEntries: 1000, - maxAgeSeconds: 60 * 60 * 24 * 90, // 90 days - }, - cacheableResponse: { - // 0 to cache "opaque" responses from cross-origin requests (i.e. CDN) - statuses: [0, 200], - }, - }, - }, - { - urlPattern: new RegExp("fonts.css"), - handler: "StaleWhileRevalidate", - options: { - cacheName: "fonts", - expiration: { - maxEntries: 50, - }, - }, - }, - { - urlPattern: new RegExp("locales/[^/]+.js"), - handler: "CacheFirst", - options: { - cacheName: "locales", - expiration: { - maxEntries: 50, - maxAgeSeconds: 60 * 60 * 24 * 30, // <== 30 days - }, - }, - }, - { - urlPattern: new RegExp("(.chunk-.+|CodeMirrorEditor-.+)\\.js"), - handler: "CacheFirst", - options: { - cacheName: "chunk", - expiration: { - maxEntries: 50, - maxAgeSeconds: 60 * 60 * 24 * 90, // <== 90 days - }, - }, - }, + "assets/CodeMirrorEditor-*.js", + ...localeGlobIgnores, ], // Bumped to cover the ~4.5MB draw-collab-wasm binary. maximumFileSizeToCacheInBytes: 8 * 1024 ** 2, // 8MB @@ -266,6 +214,23 @@ export default defineConfig(({ mode }) => { sizes: "192x192", type: "image/png", }, + { + src: "android-chrome-512x512.png", + sizes: "512x512", + type: "image/png", + }, + { + src: "maskable_icon_x192.png", + sizes: "192x192", + type: "image/png", + purpose: "maskable", + }, + { + src: "maskable_icon_x512.png", + sizes: "512x512", + type: "image/png", + purpose: "maskable", + }, { src: "apple-touch-icon.png", type: "image/png", @@ -346,6 +311,12 @@ export default defineConfig(({ mode }) => { ], }, }), + // timber() wires @vitejs/plugin-react + plugin-rsc internally. It sits + // AFTER VitePWA so the PWA dev middleware (manifest.webmanifest — an + // extension timber's dev asset allowlist doesn't know) registers ahead + // of timber's app router. + timber(), + swIntoServedClientDir(), ], }; }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ff967ab4..5b88ab35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,6 +238,21 @@ importers: vite: specifier: 8.1.0 version: 8.1.0(@types/node@26.1.2)(sass@1.85.1)(terser@5.39.0) + workbox-core: + specifier: 7.4.1 + version: 7.4.1 + workbox-expiration: + specifier: 7.4.1 + version: 7.4.1 + workbox-precaching: + specifier: 7.4.1 + version: 7.4.1 + workbox-routing: + specifier: 7.4.1 + version: 7.4.1 + workbox-strategies: + specifier: 7.4.1 + version: 7.4.1 optionalDependencies: '@takumi-rs/core-darwin-arm64': specifier: 2.5.4 diff --git a/public/service-worker.js b/public/service-worker.js deleted file mode 100644 index 55afd7e2..00000000 --- a/public/service-worker.js +++ /dev/null @@ -1,20 +0,0 @@ -// Since we migrated to Vite, the service worker strategy changed, in CRA it was a custom service worker named service-worker.js and in Vite its sw.js handled by vite-plugin-pwa -// Due to this the existing CRA users were not able to migrate to Vite or any new changes post Vite unless browser is hard refreshed -// Hence adding a self destroying worker so all CRA service workers are destroyed and migrated to Vite -// We should remove this code after sometime when we are confident that -// all users have migrated to Vite - -self.addEventListener("install", () => { - self.skipWaiting(); -}); - -self.addEventListener("activate", () => { - self.registration - .unregister() - .then(() => { - return self.clients.matchAll(); - }) - .then((clients) => { - clients.forEach((client) => client.navigate(client.url)); - }); -}); diff --git a/vitest.config.mts b/vitest.config.mts index e5ae74b5..b2c9e1f2 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -1,80 +1,10 @@ -import path from "path"; - import { defineConfig } from "vitest/config"; +import { excalidrawAliases } from "./excalidraw-app/vite.aliases"; + export default defineConfig({ resolve: { - alias: [ - { - find: /^~\/(.*?)/, - replacement: path.resolve(__dirname, "./excalidraw-app/$1"), - }, - { - find: /^@excalidraw\/common$/, - replacement: path.resolve(__dirname, "./packages/common/src/index.ts"), - }, - { - find: /^@excalidraw\/common\/(.*?)/, - replacement: path.resolve(__dirname, "./packages/common/src/$1"), - }, - { - find: /^@excalidraw\/element$/, - replacement: path.resolve(__dirname, "./packages/element/src/index.ts"), - }, - { - find: /^@excalidraw\/element\/(.*?)/, - replacement: path.resolve(__dirname, "./packages/element/src/$1"), - }, - { - find: /^@excalidraw\/excalidraw$/, - replacement: path.resolve(__dirname, "./packages/excalidraw/index.tsx"), - }, - { - find: /^@excalidraw\/excalidraw\/(.*?)/, - replacement: path.resolve(__dirname, "./packages/excalidraw/$1"), - }, - { - find: /^@excalidraw\/math$/, - replacement: path.resolve(__dirname, "./packages/math/src/index.ts"), - }, - { - find: /^@excalidraw\/math\/(.*?)/, - replacement: path.resolve(__dirname, "./packages/math/src/$1"), - }, - { - find: /^@excalidraw\/utils$/, - replacement: path.resolve(__dirname, "./packages/utils/src/index.ts"), - }, - { - find: /^@excalidraw\/utils\/(.*?)/, - replacement: path.resolve(__dirname, "./packages/utils/src/$1"), - }, - { - find: /^@excalidraw\/fractional-indexing$/, - replacement: path.resolve( - __dirname, - "./packages/fractional-indexing/src/index.ts", - ), - }, - { - find: /^@excalidraw\/fractional-indexing\/(.*?)/, - replacement: path.resolve( - __dirname, - "./packages/fractional-indexing/src/$1", - ), - }, - { - find: /^@excalidraw\/laser-pointer$/, - replacement: path.resolve( - __dirname, - "./packages/laser-pointer/src/index.ts", - ), - }, - { - find: /^@excalidraw\/laser-pointer\/(.*?)/, - replacement: path.resolve(__dirname, "./packages/laser-pointer/src/$1"), - }, - ], + alias: excalidrawAliases, }, //@ts-ignore test: {