Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,6 @@ vite.config.ts.timestamp-*
# Sentry Config File
.env.sentry-build-plugin

# Generated
*.inline.ts

# Protobuf
src/lib/buf
protos/vendor
Expand Down
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
"dev:bun": "tsx server/check-bun && bun run server/server.ts",
"dev:https": "vite dev -- --https",
"build": "vite build && npm run prepack",
"build:workers": "node scripts/build-workers.js",
"prepack": "svelte-kit sync && pnpm build:workers && svelte-package && publint",
"build:workers": "node scripts/post-process-workers.js",
"prepack": "svelte-kit sync && svelte-package && pnpm build:workers && publint",
"preview": "vite preview",
"prepare": "svelte-kit sync && pnpm build:workers || echo ''",
"check": "svelte-kit sync && pnpm build:workers && svelte-check --tsconfig ./tsconfig.json && pnpm vet",
"check:watch": "svelte-kit sync && pnpm build:workers && svelte-check --tsconfig ./tsconfig.json --watch",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json && pnpm vet",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format": "prettier --write . && eslint . --fix",
"lint:draw": "golangci-lint run ./draw/...",
"lint:client": "golangci-lint run ./client/...",
Expand Down
46 changes: 46 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# scripts/

Build and code generation scripts that run as part of the development workflow or publish pipeline.

---

## `post-process-workers.js`

**Run automatically as part of `pnpm run prepack`** (`svelte-package && node scripts/post-process-workers.js && publint`).

### What it does

After `svelte-package` copies `src/lib` into `dist/`, this script rewrites web worker references so the published package works correctly in consumer projects.

It scans `dist/` for files containing `new Worker(new URL(..., import.meta.url))`, bundles each referenced worker file into a self-contained IIFE using esbuild (inlining all dependencies like Three.js PCDLoader), replaces the `new Worker(new URL(...))` call with a Blob URL equivalent, and deletes the now-unnecessary worker `.js` files from `dist/`.

### Why we need it

This is a workaround for a [known open Vite bug](https://github.com/vitejs/vite/issues/21422). When a library uses `new Worker(new URL('./worker.js', import.meta.url))`, a consumer project's Vite dep optimizer moves the library to `.vite/deps/` but the worker file doesn't follow -- causing a runtime error.

`svelte-package` copies `.ts` files as-is (transpiled to JS), so there is no point in the library packaging pipeline where Vite's bundler runs and can resolve worker imports. The fix is to post-process `dist/` and inline the worker code so that consumers load it from a Blob URL with no file path dependency.

In dev mode within this project, Vite handles `new Worker(new URL(...))` natively with full HMR support -- no post-processing is needed.

---

## `model-pipeline.js`

**Run manually via `pnpm run model-pipeline:run`** when new 3D models need to be added to the project.

### What it does

Converts `.glb` and `.gltf` 3D model files into typed Threlte/Svelte components using [`@threlte/gltf`](https://threlte.xyz/docs/reference/gltf/getting-started).

Place model files in `static/models/`, then run the script. It:

1. Finds all `.glb`/`.gltf` files in `static/models/` (skipping already-transformed files)
2. Runs `@threlte/gltf` on each file to generate a typed Svelte component
3. Moves the generated `.svelte` files to `src/lib/components/models/`
4. Cleans up the intermediate files from `static/models/`

Configuration at the top of the file controls output options (TypeScript types, Draco compression, mesh simplification, etc.). By default `overwrite: false` -- existing components are not replaced.

### Why we need it

Hand-writing Three.js scene graphs for complex GLTF models is tedious and error-prone. `@threlte/gltf` generates Svelte components that exactly mirror the scene hierarchy of a model, including typed props for materials and geometry. This script automates the full pipeline from raw model file to usable Svelte component.
30 changes: 0 additions & 30 deletions scripts/build-workers.js

This file was deleted.

79 changes: 79 additions & 0 deletions scripts/post-process-workers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Post-processes dist/ after svelte-package to inline web workers.
*
* svelte-package copies worker.ts -> worker.js into dist/ alongside the files
* that reference them. But dist/index.js files still contain
* `new Worker(new URL('./worker.js', import.meta.url))` which breaks when
* consumed by another Vite project's dep optimizer.
*
* This script:
* 1. Scans dist/ for JS files containing the `new Worker(new URL(...))` pattern
* 2. Bundles the referenced worker file with esbuild into a self-contained IIFE
* 3. Rewrites the worker instantiation to use a Blob URL instead
* 4. Deletes the now-unnecessary worker.js file from dist/
*/

import { build } from 'esbuild'
import { globSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
import path from 'node:path'

const WORKER_PATTERN =
/new Worker\(new URL\(['"]([^'"]+)['"]\s*,\s*import\.meta\.url\)\s*(?:,\s*\{[^}]*\})?\)/g

const distFiles = globSync('dist/**/*.js')
const deleted = new Set()

for (const file of distFiles) {
if (deleted.has(path.resolve(file))) continue

const code = readFileSync(file, 'utf8')
const matches = [...code.matchAll(WORKER_PATTERN)]

if (matches.length === 0) continue

let result = code

for (const match of matches) {
const [fullMatch, workerRelPath] = match
const workerAbsPath = path.resolve(path.dirname(file), workerRelPath)

console.log(`Inlining worker ${workerRelPath} referenced in ${file}`)

const bundle = await build({
entryPoints: [workerAbsPath],
bundle: true,
format: 'iife',
write: false,
minify: true,
})

const bundledCode = bundle.outputFiles[0].text
const escaped = bundledCode
.replaceAll('\\', '\\\\')
.replaceAll('`', '\\`')
.replaceAll('$', String.raw`\$`)

const replacement = [
`(function() {`,
` const __workerCode = \`${escaped}\``,
` const __blob = new Blob([__workerCode], { type: 'text/javascript' })`,
` return new Worker(URL.createObjectURL(__blob))`,
`})()`,
].join('\n')

result = result.replace(fullMatch, replacement)

try {
unlinkSync(workerAbsPath)
deleted.add(workerAbsPath)
console.log(`Deleted ${workerAbsPath}`)
} catch {
console.warn(`Could not delete ${workerAbsPath} -- may already be removed`)
}
}

writeFileSync(file, result)
console.log(`Rewrote ${file}`)
}

console.log('Post-processed all workers in dist/')
6 changes: 1 addition & 5 deletions src/lib/loaders/pcd/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import type { Message, SuccessMessage } from './messages'

import { workerCode } from './worker.inline'

const blob = new Blob([workerCode], { type: 'text/javascript' })
const url = URL.createObjectURL(blob)
const worker = new Worker(url)
const worker = new Worker(new URL('worker.js', import.meta.url), { type: 'module' })

let requestId = 0
const pending = new Map<
Expand Down
Loading