create-playcanvas 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,7 +50,7 @@ The `web-lens` starter is a portrait game ready to publish to Snapchat as a [Web
50
50
  npm create playcanvas@latest my-lens -- --starter web-lens
51
51
  ```
52
52
 
53
- Its `npm run package` script builds the game, checks it against the Web Lens upload rules (size limits, relative paths and no network access) and writes the zip to upload through [My Lenses](https://my-lenses.snapchat.com/).
53
+ Its `npm run package` script builds the game, checks it against the Web Lens upload rules (size limits, relative paths, no network access and no camera or microphone) and writes the zip to upload through [My Lenses](https://my-lenses.snapchat.com/).
54
54
 
55
55
  ## CLI Options
56
56
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-playcanvas",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "Scaffold a PlayCanvas project",
5
5
  "keywords": [
6
6
  "playcanvas",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "devDependencies": {
46
46
  "@clack/prompts": "^1.8.1",
47
- "@playcanvas/eslint-config": "3.0.0-beta.8",
47
+ "@playcanvas/eslint-config": "3.0.0",
48
48
  "@types/node": "22.20.5",
49
49
  "eslint": "^9.39.5",
50
50
  "mri": "^1.2.0",
@@ -52,7 +52,7 @@
52
52
  "prettier": "^3.9.10",
53
53
  "unbuild": "^3.6.1",
54
54
  "vite": "^8.3.5",
55
- "vitest": "^5.0.5"
55
+ "vitest": "^5.1.0"
56
56
  },
57
57
  "engines": {
58
58
  "node": ">=22.23.2"
@@ -8,7 +8,8 @@ description: Use when adding visual effects to a PlayCanvas app, to choose betwe
8
8
  Pick the Engine building block first, then adapt its closest official example with
9
9
  `find-examples`:
10
10
 
11
- - Particle systems: the `particlesystem` component (`graphics/particles-*` examples).
11
+ - Particle systems: the `particlesystem` component (`particles/*` examples; `graphics/particles-*`
12
+ before 2.24).
12
13
  - Custom shading on existing surfaces: `StandardMaterial` chunk overrides
13
14
  (`override-shader-chunks`), or a `ShaderMaterial` when the effect needs its own shader pair.
14
15
  - Procedural or per-frame geometry: a runtime-updated `Mesh` on a `render` component
@@ -15,17 +15,18 @@ it locally. The npm package omits `examples/`, so otherwise fetch from the insta
15
15
 
16
16
  ```sh
17
17
  TAG="v$(node -p "JSON.parse(require('fs').readFileSync('node_modules/playcanvas/package.json')).version")" || exit 1
18
- gh api "repos/playcanvas/engine/git/trees/$TAG?recursive=1" | grep -o '"examples/src/examples/[^"]*\.example\.mjs"'
19
- gh api "repos/playcanvas/engine/contents/examples/src/examples/<category>/<name>.example.mjs?ref=$TAG" \
20
- --jq .content | base64 --decode
18
+ gh api "repos/playcanvas/engine/git/trees/$TAG?recursive=1" | grep -o '"examples/src/examples/[^"]*example\.mjs"'
19
+ gh api "repos/playcanvas/engine/contents/<listed path>?ref=$TAG" --jq .content | base64 --decode
21
20
  ```
22
21
 
23
22
  Read the resolved package, not the dependency list; `playcanvas` is often transitive. An empty list
24
- means an invalid tag: stop rather than guess. Use `main` only when the version has no tag.
23
+ means an invalid tag: stop rather than guess. Use `main` only when the version has no tag. From
24
+ 2.24, each Engine example is a folder with the shaders and other files it imports; earlier tags use
25
+ `<name>.example.mjs` with sibling `<name>.*` files.
25
26
 
26
27
  | Surface | Repository | Ref | Example paths |
27
28
  | --- | --- | --- | --- |
28
- | direct Engine | `playcanvas/engine` | `v<version>` | `examples/src/examples/<category>/<name>.example.mjs` |
29
+ | direct Engine | `playcanvas/engine` | `v<version>` | `examples/src/examples/<category>/<name>/example.mjs` |
29
30
  | Web Components | `playcanvas/web-components` | `v<version>` | `examples/<name>.html` |
30
31
  | React | `playcanvas/developer-site` | `main` | `docs/user-manual/react/examples/<name>.mdx` |
31
32
 
@@ -37,4 +37,5 @@ transforms:
37
37
  npx --yes @gltf-transform/cli@4.4.2 copy in.glb scratch.glb
38
38
  ```
39
39
 
40
- The Engine cannot load `EXT_meshopt_compression`; see `load-assets` before shipping such files.
40
+ The Engine loads Meshopt files only from 2.24, with a decoder; see `load-assets` before shipping
41
+ them.
@@ -16,10 +16,8 @@ From Engine 2.23, every app announces itself to a hook at
16
16
  - Page code then reads `globalThis.__playcanvasApps`, a `Map` from each live app to
17
17
  `{ version, revision, protocol }`. `app.init()` adds the entry and `app.destroy()` removes it.
18
18
  - The script chains a hook defined before it, such as a devtools extension's.
19
- - No entry means the Engine predates 2.23, the hook loaded late, or the app set
20
- `devtools: false`. The Engine swallows errors thrown by the hook.
21
- - In 2.23.0 only the debug build (`playcanvas/debug`, `build/playcanvas.dbg.*`) announces apps; the
22
- default import never calls the hook. Later 2.23 patches fix every build.
19
+ - No entry means the Engine predates 2.23, is a non-debug 2.23.0 build, the hook loaded late, or
20
+ the app set `devtools: false`. The Engine swallows hook errors.
23
21
 
24
22
  ## Read state
25
23
 
@@ -34,8 +32,11 @@ From Engine 2.23, every app announces itself to a hook at
34
32
 
35
33
  ## Inspector panel
36
34
 
37
- `@playcanvas/inspector` adds a panel for people: `new Inspector(app)` shows the hierarchy, assets,
38
- materials, shaders, textures, render targets, frame graph, and physics. It must import the app's
39
- own `playcanvas` package, so add it only to development builds. It draws over the canvas and
40
- applies view overrides, so destroy it before `verify-pixels` captures. Stepping through draws
41
- needs the debug build.
35
+ `@playcanvas/inspector` is a debug panel: `new Inspector(app)` shows the hierarchy, assets, GPU
36
+ resources, frame graph, physics, and frame timings; `record: true` records from creation. It must
37
+ import the app's own `playcanvas` package, so add it only to development builds. It draws over the
38
+ canvas and applies view overrides, so destroy it before `verify-pixels` captures. Stepping through
39
+ draws needs the debug build.
40
+
41
+ With `mcp: true` (0.5.0+), `await inspector.executeTool(name, args)` runs the tools `getTools()`
42
+ lists: find entities, get objects by `$id`, record frames, take screenshots, and read logs.
@@ -15,8 +15,9 @@ and `EXT_mesh_gpu_instancing`; do not rebuild these by hand. Preload asset sets
15
15
 
16
16
  ## Compress for size
17
17
 
18
- - Geometry: Draco (`KHR_draco_mesh_compression`). Meshopt is not supported, so decode such files
19
- before shipping.
18
+ - Geometry: Draco (`KHR_draco_mesh_compression`), or from Engine 2.24 Meshopt
19
+ (`EXT_meshopt_compression`, `KHR_meshopt_compression`). Older Engines cannot load Meshopt, so
20
+ decode such files before shipping.
20
21
  - Textures: KTX2 with Basis (`KHR_texture_basisu`) transcodes to a GPU format, cutting both
21
22
  download size and VRAM. ETC1S is smallest and lossy. UASTC is larger and keeps
22
23
  detail, which matters most for normal maps. Weigh size against visible detail.
@@ -42,6 +43,10 @@ basisInitialize({
42
43
  });
43
44
  ```
44
45
 
46
+ From 2.24, also copy `meshopt/` and call
47
+ `meshoptInitialize({ url: '/wasm/meshopt/meshopt_decoder.mjs' })` before loading Meshopt files;
48
+ without it they fail unless they carry uncompressed fallback buffers.
49
+
45
50
  In Web Components, a `<pc-wasm name="DracoDecoderModule">` element under `<pc-app>` sets the Draco
46
51
  config with `glue`, `wasm`, and `fallback` attributes.
47
52
 
@@ -8,7 +8,7 @@ description: Use when a PlayCanvas StandardMaterial needs custom shading its pro
8
8
  Overriding a `StandardMaterial` chunk keeps fog, tone mapping, shadows, skinning, and instancing
9
9
  intact. Use a `ShaderMaterial` only when the look needs its own vertex and fragment pair.
10
10
 
11
- - Set `material.shaderChunksVersion` to the installed major.minor, such as `'2.23'`. Chunk names
11
+ - Set `material.shaderChunksVersion` to the installed major.minor, such as `'2.24'`. Chunk names
12
12
  and contracts change between versions, so read the installed chunk source, not memory.
13
13
  - Set chunks with `material.getShaderChunks(SHADERLANGUAGE_GLSL).set(name, source)` and the WGSL
14
14
  equivalent, then call `material.update()`. For every material on a device, use
@@ -5,6 +5,9 @@ This project is a Snapchat Web Lens: Snapchat runs it in a sandbox, from an uplo
5
5
  - Run `npm run package` after a change. It builds into `dist/lens/`, checks the build against the Web Lens upload rules and writes the zip to `dist/`, or reports why it wrote none.
6
6
  - A Lens can't reach the network. Bundle every asset into the build, and load nothing from another host, such as a script from a CDN, a web font or a remote model.
7
7
  - The zip must stay under 8 MB and `index.html` under 1 MB.
8
+ - Snapchat disables the camera and microphone for Web Lenses. Keep `getUserMedia`, `getDisplayMedia` and `ImageCapture` out of the build entirely.
9
+ - Motion sensors, geolocation, payments, MIDI, USB and WebRTC are off too, so design for pointer input.
10
+ - `localStorage` and IndexedDB start empty every time the Lens opens. To keep data between launches, use the Web Lens API's [storage](https://developers.snap.com/web-lenses/storage), `snapchat.storage`: check `snapchat.isSupported('storage')` after `init()` resolves, write nothing until the first read answers, and chain writes through one promise. Each value can be up to 32 KiB, and the whole store 100 KiB. `src/lens.ts` keeps the best score this way.
8
11
  - `npm run start` serves the build under the Lens sandbox's rules, so a request that would fail in Snapchat also fails in the browser console.
9
12
  - `src/lens.ts` is the only code that talks to Snapchat. Outside Snapchat, the Web Lens API answers from a stub with canned data, so the game runs in any browser. Call `recordScore(score)` when a run ends.
10
13
  - Keep the layout portrait and the HUD inside the safe area, using the `--inset-*` properties from `src/lens.css`.
@@ -18,8 +18,9 @@ This builds the game into `dist/lens/`, checks it against the [Web Lens upload r
18
18
  - the build refers to one of its own files by an absolute path, such as `/assets/game.js`
19
19
  - the build loads anything from another host, such as a script from a CDN or a web font
20
20
  - the build uses something the Lens sandbox blocks, such as an `<iframe>` or a web app manifest
21
+ - the build's code asks for the camera, microphone or screen (`getUserMedia`, `getDisplayMedia` or `ImageCapture`), which Snapchat disables for Web Lenses
21
22
 
22
- It warns about any other host your scripts mention, since a Lens can't reach the network. To check the build itself, `npm run start` serves it under the same rules, so a request for anything outside the zip fails in the browser console as it would in Snapchat.
23
+ It warns about any other host your scripts mention, since a Lens can't reach the network. It also warns about code that uses something Snapchat [turns off for Web Lenses](https://developers.snap.com/web-lenses/capabilities): motion sensors, geolocation, payments, MIDI, USB and WebRTC, and `localStorage` or IndexedDB, which start empty every time the Lens opens, so keep data between launches with the Web Lens API's [storage](https://developers.snap.com/web-lenses/storage) instead. To check the build itself, `npm run start` serves it under the same rules, so a request for anything outside the zip, or for a device such as the camera, fails in the browser console as it would in Snapchat.
23
24
 
24
25
  ### How it fits together
25
26
 
@@ -19,6 +19,32 @@ const BLOCKED_TAGS = ['base', 'embed', 'iframe', 'object'];
19
19
  // error messages link to
20
20
  const TEXT_HOSTS = ['www.w3.org', 'github.com', 'react.dev', 'api.playcanvas.com'];
21
21
 
22
+ // APIs that open the camera, microphone or screen, which Snapchat disables for Web Lenses
23
+ const CAPTURE_APIS =
24
+ /\b(?:mediaDevices|getUserMedia|webkitGetUserMedia|mozGetUserMedia|getDisplayMedia|ImageCapture)\b/g;
25
+
26
+ // other features Snapchat turns off for Web Lenses: code that relies on them gets nothing, so it's
27
+ // worth a look rather than a failure
28
+ const UNAVAILABLE_APIS = [
29
+ {
30
+ pattern:
31
+ /\b(?:deviceorientation|devicemotion|DeviceOrientationEvent|DeviceMotionEvent|Accelerometer|Gyroscope|Magnetometer|LinearAccelerationSensor|GravitySensor|AbsoluteOrientationSensor|RelativeOrientationSensor)\b/g,
32
+ reason: 'Motion sensors are off in a Web Lens, so design for pointer input rather than tilt'
33
+ },
34
+ {
35
+ pattern: /\b(?:geolocation|PaymentRequest|requestMIDIAccess|navigator\.usb)\b/g,
36
+ reason: 'Geolocation, payments, MIDI and USB are off in a Web Lens'
37
+ },
38
+ {
39
+ pattern: /\bRTC(?:PeerConnection|DataChannel)\b/g,
40
+ reason: "A Web Lens can't make WebRTC connections"
41
+ },
42
+ {
43
+ pattern: /\b(?:localStorage|indexedDB)\b/g,
44
+ reason: "localStorage and IndexedDB start empty every time the Lens opens, so keep data between launches with the Web Lens API's storage (snapchat.storage)"
45
+ }
46
+ ];
47
+
22
48
  const buildDir = path.resolve(process.argv[2] ?? 'dist/lens');
23
49
  const { name = 'web-lens' } = JSON.parse(fs.readFileSync('package.json', 'utf8'));
24
50
  const zipFile = path.join(path.dirname(buildDir), `${name.replace(/^@/, '').replace('/', '-')}.zip`);
@@ -134,6 +160,32 @@ for (const file of files.filter((f) => /\.m?js$/.test(f))) {
134
160
  }
135
161
  }
136
162
 
163
+ // where `pattern` first matches in `source`, as line:column, and every API name it matched
164
+ const findUses = (source, pattern) => {
165
+ const uses = Array.from(source.matchAll(pattern));
166
+ if (!uses.length) return null;
167
+ const before = source.slice(0, uses[0].index);
168
+ return {
169
+ at: `${before.split('\n').length}:${uses[0].index - before.lastIndexOf('\n')}`,
170
+ apis: [...new Set(uses.map(([api]) => api))].join(', ')
171
+ };
172
+ };
173
+
174
+ // features a Lens can't use, in scripts or inline in markup
175
+ for (const file of files.filter((f) => /\.(?:html|m?js)$/.test(f))) {
176
+ const source = read(file);
177
+ const capture = findUses(source, CAPTURE_APIS);
178
+ if (capture) {
179
+ errors.push(
180
+ `${file}:${capture.at}: uses ${capture.apis}. Snapchat disables the camera and microphone for Web Lenses, so remove the capture code`
181
+ );
182
+ }
183
+ for (const { pattern, reason } of UNAVAILABLE_APIS) {
184
+ const use = findUses(source, pattern);
185
+ if (use) warnings.push(`${file}:${use.at}: uses ${use.apis}. ${reason}`);
186
+ }
187
+ }
188
+
137
189
  // a plain zip: deflated entries with forward-slash names and index.html at the root
138
190
  const zipEntries = (entries) => {
139
191
  const local = [];
@@ -10,6 +10,7 @@ A PlayCanvas Engine app in TypeScript, built with Vite. The scene is built in co
10
10
  - `npm run lint` runs ESLint, and `npm run lint:fix` fixes what it can.
11
11
  - `npm run fmt` checks formatting, and `npm run fmt:fix` applies Prettier.
12
12
  - `npm run build` builds the production site into `dist/`, and `npm run start` serves it.
13
+ - `npm run dev` and `npm run start` open the page in the default browser.
13
14
 
14
15
  ## Layout
15
16
 
@@ -13,7 +13,7 @@ npm install
13
13
  npm run dev
14
14
  ```
15
15
 
16
- Open <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
16
+ The app opens in your browser at <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
17
17
 
18
18
  ## Inspector
19
19
 
@@ -23,14 +23,14 @@ Open <http://localhost:5173>. Edit the files under `src/` and save to see the sc
23
23
 
24
24
  Run these with `npm run <script>`:
25
25
 
26
- | Script | Description |
27
- | ----------- | --------------------------------- |
28
- | `dev` | Start the Vite development server |
29
- | `build` | Build for production |
30
- | `start` | Preview the production build |
31
- | `lint` | Run ESLint |
32
- | `fmt` | Check formatting |
33
- | `typecheck` | Run TypeScript checks |
26
+ | Script | Description |
27
+ | ----------- | -------------------------------------------------- |
28
+ | `dev` | Start the Vite development server and open the app |
29
+ | `build` | Build for production |
30
+ | `start` | Preview the production build and open the app |
31
+ | `lint` | Run ESLint |
32
+ | `fmt` | Check formatting |
33
+ | `typecheck` | Run TypeScript checks |
34
34
 
35
35
  Run `npm run build` to generate a deployable static site in `dist/`. Its paths are relative, so it runs from any folder, such as a GitHub Pages project site or an itch.io upload.
36
36
 
@@ -6,20 +6,20 @@
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "vite build",
9
- "dev": "vite",
9
+ "dev": "vite --open",
10
10
  "fmt": "prettier --check .",
11
11
  "fmt:fix": "prettier --write .",
12
12
  "lint": "eslint .",
13
13
  "lint:fix": "eslint . --fix",
14
- "start": "vite preview",
14
+ "start": "vite preview --open",
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "playcanvas": "^2.23.3"
18
+ "playcanvas": "^2.24.0"
19
19
  },
20
20
  "devDependencies": {
21
- "@playcanvas/eslint-config": "3.0.0-beta.8",
22
- "@playcanvas/inspector": "^0.4.0",
21
+ "@playcanvas/eslint-config": "3.0.0",
22
+ "@playcanvas/inspector": "^0.5.0",
23
23
  "eslint": "^9.39.5",
24
24
  "prettier": "^3.9.10",
25
25
  "typescript": "~6.0.3",
@@ -11,6 +11,21 @@ const LENS_SANDBOX = [
11
11
  "form-action 'none'"
12
12
  ].join('; ');
13
13
 
14
+ // Approximates its permissions policy, which turns off device features such as the camera and
15
+ // motion sensors
16
+ const LENS_PERMISSIONS = [
17
+ 'camera=()',
18
+ 'microphone=()',
19
+ 'display-capture=()',
20
+ 'accelerometer=()',
21
+ 'gyroscope=()',
22
+ 'magnetometer=()',
23
+ 'geolocation=()',
24
+ 'payment=()',
25
+ 'usb=()',
26
+ 'midi=()'
27
+ ].join(', ');
28
+
14
29
  // https://vite.dev/config/
15
30
  export default defineConfig({
16
31
  // Snapchat serves a Lens from its own origin, so every URL in the build must be relative
@@ -25,10 +40,11 @@ export default defineConfig({
25
40
  chunkSizeWarningLimit: 3000
26
41
  },
27
42
  preview: {
28
- // npm run start serves the build under the Lens rules, so a request for anything outside
29
- // the zip fails in the browser console as it would in Snapchat
43
+ // npm run start serves the build under the Lens rules, so loading anything from outside the
44
+ // zip, or using a device such as the camera, fails in the browser as it would in Snapchat
30
45
  headers: {
31
46
  'Content-Security-Policy': LENS_SANDBOX,
47
+ 'Permissions-Policy': LENS_PERMISSIONS,
32
48
  'Cross-Origin-Opener-Policy': 'same-origin',
33
49
  'Cross-Origin-Embedder-Policy': 'require-corp'
34
50
  }
@@ -10,6 +10,7 @@ A PlayCanvas app in TypeScript and React, built with Vite. The scene is declared
10
10
  - `npm run lint` runs ESLint, and `npm run lint:fix` fixes what it can.
11
11
  - `npm run fmt` checks formatting, and `npm run fmt:fix` applies Prettier.
12
12
  - `npm run build` builds the production site into `dist/`, and `npm run start` serves it.
13
+ - `npm run dev` and `npm run start` open the page in the default browser.
13
14
 
14
15
  ## Layout
15
16
 
@@ -13,7 +13,7 @@ npm install
13
13
  npm run dev
14
14
  ```
15
15
 
16
- Open <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
16
+ The app opens in your browser at <http://localhost:5173>. Edit the files under `src/` and save to see the scene update.
17
17
 
18
18
  ## Inspector
19
19
 
@@ -23,14 +23,14 @@ Open <http://localhost:5173>. Edit the files under `src/` and save to see the sc
23
23
 
24
24
  Run these with `npm run <script>`:
25
25
 
26
- | Script | Description |
27
- | ----------- | --------------------------------- |
28
- | `dev` | Start the Vite development server |
29
- | `build` | Build for production |
30
- | `start` | Preview the production build |
31
- | `lint` | Run ESLint |
32
- | `fmt` | Check formatting |
33
- | `typecheck` | Run TypeScript checks |
26
+ | Script | Description |
27
+ | ----------- | -------------------------------------------------- |
28
+ | `dev` | Start the Vite development server and open the app |
29
+ | `build` | Build for production |
30
+ | `start` | Preview the production build and open the app |
31
+ | `lint` | Run ESLint |
32
+ | `fmt` | Check formatting |
33
+ | `typecheck` | Run TypeScript checks |
34
34
 
35
35
  Run `npm run build` to generate a deployable static site in `dist/`. Its paths are relative, so it runs from any folder, such as a GitHub Pages project site or an itch.io upload.
36
36
 
@@ -1,15 +1,4 @@
1
1
  import reactConfig from '@playcanvas/eslint-config/react';
2
2
  import typescriptConfig from '@playcanvas/eslint-config/typescript';
3
3
 
4
- export default [
5
- { ignores: ['.claude', '.agents', 'public/ammo'] },
6
- ...typescriptConfig,
7
- ...reactConfig,
8
- {
9
- rules: {
10
- 'react/jsx-uses-react': 'off',
11
- 'react/prop-types': 'off',
12
- 'react/react-in-jsx-scope': 'off'
13
- }
14
- }
15
- ];
4
+ export default [{ ignores: ['.claude', '.agents', 'public/ammo'] }, ...typescriptConfig, ...reactConfig];
@@ -6,26 +6,26 @@
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "vite build",
9
- "dev": "vite",
9
+ "dev": "vite --open",
10
10
  "fmt": "prettier --check .",
11
11
  "fmt:fix": "prettier --write .",
12
12
  "lint": "eslint .",
13
13
  "lint:fix": "eslint . --fix",
14
- "start": "vite preview",
14
+ "start": "vite preview --open",
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
18
  "@playcanvas/react": "^0.11.7",
19
- "playcanvas": "^2.23.3",
19
+ "playcanvas": "^2.24.0",
20
20
  "react": "^19.3.1",
21
21
  "react-dom": "^19.3.1"
22
22
  },
23
23
  "devDependencies": {
24
- "@playcanvas/eslint-config": "3.0.0-beta.8",
25
- "@playcanvas/inspector": "^0.4.0",
24
+ "@playcanvas/eslint-config": "3.0.0",
25
+ "@playcanvas/inspector": "^0.5.0",
26
26
  "@types/react": "^19.3.0",
27
27
  "@types/react-dom": "^19.3.0",
28
- "@vitejs/plugin-react": "^6.1.2",
28
+ "@vitejs/plugin-react": "^6.2.1",
29
29
  "eslint": "^9.39.5",
30
30
  "prettier": "^3.9.10",
31
31
  "typescript": "~6.0.3",
@@ -12,6 +12,21 @@ const LENS_SANDBOX = [
12
12
  "form-action 'none'"
13
13
  ].join('; ');
14
14
 
15
+ // Approximates its permissions policy, which turns off device features such as the camera and
16
+ // motion sensors
17
+ const LENS_PERMISSIONS = [
18
+ 'camera=()',
19
+ 'microphone=()',
20
+ 'display-capture=()',
21
+ 'accelerometer=()',
22
+ 'gyroscope=()',
23
+ 'magnetometer=()',
24
+ 'geolocation=()',
25
+ 'payment=()',
26
+ 'usb=()',
27
+ 'midi=()'
28
+ ].join(', ');
29
+
15
30
  // https://vite.dev/config/
16
31
  export default defineConfig({
17
32
  // Snapchat serves a Lens from its own origin, so every URL in the build must be relative
@@ -26,10 +41,11 @@ export default defineConfig({
26
41
  chunkSizeWarningLimit: 3000
27
42
  },
28
43
  preview: {
29
- // npm run start serves the build under the Lens rules, so a request for anything outside
30
- // the zip fails in the browser console as it would in Snapchat
44
+ // npm run start serves the build under the Lens rules, so loading anything from outside the
45
+ // zip, or using a device such as the camera, fails in the browser as it would in Snapchat
31
46
  headers: {
32
47
  'Content-Security-Policy': LENS_SANDBOX,
48
+ 'Permissions-Policy': LENS_PERMISSIONS,
33
49
  'Cross-Origin-Opener-Policy': 'same-origin',
34
50
  'Cross-Origin-Embedder-Policy': 'require-corp'
35
51
  }
@@ -10,6 +10,7 @@ A PlayCanvas app in TypeScript, built with Vite. The scene is declared in HTML w
10
10
  - `npm run lint` runs ESLint, and `npm run lint:fix` fixes what it can.
11
11
  - `npm run fmt` checks formatting, and `npm run fmt:fix` applies Prettier.
12
12
  - `npm run build` builds the production site into `dist/`, and `npm run start` serves it.
13
+ - `npm run dev` and `npm run start` open the page in the default browser.
13
14
 
14
15
  ## Layout
15
16
 
@@ -14,7 +14,7 @@ npm install
14
14
  npm run dev
15
15
  ```
16
16
 
17
- Open <http://localhost:5173>. Edit `index.html` to change the scene.
17
+ The app opens in your browser at <http://localhost:5173>. Edit `index.html` to change the scene.
18
18
 
19
19
  ## Inspector
20
20
 
@@ -24,14 +24,14 @@ Open <http://localhost:5173>. Edit `index.html` to change the scene.
24
24
 
25
25
  Run these with `npm run <script>`:
26
26
 
27
- | Script | Description |
28
- | ----------- | --------------------------------- |
29
- | `dev` | Start the Vite development server |
30
- | `build` | Build for production |
31
- | `start` | Preview the production build |
32
- | `lint` | Run ESLint |
33
- | `fmt` | Check formatting |
34
- | `typecheck` | Run TypeScript checks |
27
+ | Script | Description |
28
+ | ----------- | -------------------------------------------------- |
29
+ | `dev` | Start the Vite development server and open the app |
30
+ | `build` | Build for production |
31
+ | `start` | Preview the production build and open the app |
32
+ | `lint` | Run ESLint |
33
+ | `fmt` | Check formatting |
34
+ | `typecheck` | Run TypeScript checks |
35
35
 
36
36
  Run `npm run build` to generate a deployable static site in `dist/`. Its paths are relative, so it runs from any folder, such as a GitHub Pages project site or an itch.io upload.
37
37
 
@@ -6,21 +6,21 @@
6
6
  "type": "module",
7
7
  "scripts": {
8
8
  "build": "vite build",
9
- "dev": "vite",
9
+ "dev": "vite --open",
10
10
  "fmt": "prettier --check .",
11
11
  "fmt:fix": "prettier --write .",
12
12
  "lint": "eslint .",
13
13
  "lint:fix": "eslint . --fix",
14
- "start": "vite preview",
14
+ "start": "vite preview --open",
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@playcanvas/web-components": "^0.28.1",
19
- "playcanvas": "^2.23.3"
18
+ "@playcanvas/web-components": "^0.29.0",
19
+ "playcanvas": "^2.24.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@playcanvas/eslint-config": "3.0.0-beta.8",
23
- "@playcanvas/inspector": "^0.4.0",
22
+ "@playcanvas/eslint-config": "3.0.0",
23
+ "@playcanvas/inspector": "^0.5.0",
24
24
  "eslint": "^9.39.5",
25
25
  "prettier": "^3.9.10",
26
26
  "typescript": "~6.0.3",
@@ -11,6 +11,21 @@ const LENS_SANDBOX = [
11
11
  "form-action 'none'"
12
12
  ].join('; ');
13
13
 
14
+ // Approximates its permissions policy, which turns off device features such as the camera and
15
+ // motion sensors
16
+ const LENS_PERMISSIONS = [
17
+ 'camera=()',
18
+ 'microphone=()',
19
+ 'display-capture=()',
20
+ 'accelerometer=()',
21
+ 'gyroscope=()',
22
+ 'magnetometer=()',
23
+ 'geolocation=()',
24
+ 'payment=()',
25
+ 'usb=()',
26
+ 'midi=()'
27
+ ].join(', ');
28
+
14
29
  // https://vite.dev/config/
15
30
  export default defineConfig({
16
31
  // Snapchat serves a Lens from its own origin, so every URL in the build must be relative
@@ -25,10 +40,11 @@ export default defineConfig({
25
40
  chunkSizeWarningLimit: 3000
26
41
  },
27
42
  preview: {
28
- // npm run start serves the build under the Lens rules, so a request for anything outside
29
- // the zip fails in the browser console as it would in Snapchat
43
+ // npm run start serves the build under the Lens rules, so loading anything from outside the
44
+ // zip, or using a device such as the camera, fails in the browser as it would in Snapchat
30
45
  headers: {
31
46
  'Content-Security-Policy': LENS_SANDBOX,
47
+ 'Permissions-Policy': LENS_PERMISSIONS,
32
48
  'Cross-Origin-Opener-Policy': 'same-origin',
33
49
  'Cross-Origin-Embedder-Policy': 'require-corp'
34
50
  }