Skip to content

Commit 2d718be

Browse files
committed
feat: replace legacy web explorer preview with Bevy Web
The @dcl/explorer unity web build was a 244MB dependency frozen at an Aug 2024 snapshot, downloaded on every scene install only to serve the deprecated --web-explorer preview. Drop it and the preview-server routes that served it; --web/--bevy-web (now pointing at decentraland.org instead of .zone) covers browser preview via the hosted Bevy Web client, with the local server acting as its realm. --web3 and --no-debug remain declared as no-ops so existing start scripts keep working, and the CLI prints Chrome Local Network Access guidance since decentraland.org needs the 'Apps on device' permission to reach localhost.
1 parent e704246 commit 2d718be

7 files changed

Lines changed: 38 additions & 186 deletions

File tree

docs/ai-agent-context.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,14 +110,14 @@ Uses esbuild with `platform: 'browser'`, `format: 'cjs'`, `target: 'es2020'`, an
110110
| `-p, --port <number>` | HTTP port (auto-detected if omitted) |
111111
| `--dclenv <env>` | Explorer environment: `org` (mainnet production, default), `zone` (staging), `today` |
112112
| `--realm <name>` | Realm name shown in Explorer (default: `Localhost`) |
113-
| `--web3` | Enable Web3 wallet integration in the preview |
113+
| `--web3` | (deprecated) No effect; kept for backwards compatibility |
114114
| `--skip-build` | Serve pre-built files without rebuilding |
115115
| `--no-watch` | Disable file watching / hot reload |
116116
| `--no-browser` | Don't auto-open Explorer |
117117
| `--ci` | CI mode: disable browser and debug panel |
118118
| `--debug` | Enable scene debug panel (on by default with `--explorer-alpha`) |
119119
| `--explorer-alpha` | Use the new Alpha Explorer deeplink (default) |
120-
| `--web-explorer` | Use legacy web-based Explorer |
120+
| `--web, --bevy-web` | Open the preview in Bevy Web (`https://decentraland.org/bevy-web/`) instead of the desktop Explorer. Chrome 142+ requires the Local Network Access permission ("Apps on device" in 145+) for the page to reach the localhost preview server; the CLI prints instructions |
121121
| `--mobile` | Print ASCII QR code for mobile preview |
122122
| `--position <x,y>` | Initial spawn position (default: from `scene.json`) |
123123
| `--skip-auth-screen` | Skip Explorer's authentication screen |

packages/@dcl/sdk-commands/src/commands/start/index.ts

Lines changed: 21 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ export const args = declareArgs({
5353
'--skip-build': Boolean,
5454
'--data-layer': Boolean,
5555
'--explorer-alpha': Boolean,
56-
'--web-explorer': Boolean,
5756
'--hub': Boolean,
5857
'--mobile': Boolean,
5958
'-m': '--mobile',
@@ -67,6 +66,7 @@ export const args = declareArgs({
6766
'--landscape-terrain-enabled': Boolean,
6867
'-n': Boolean,
6968
'--bevy-web': Boolean,
69+
'--web': '--bevy-web',
7070
'--multi-instance': Boolean,
7171
'--no-client': Boolean,
7272
'--mcp': Boolean,
@@ -81,13 +81,12 @@ export async function help(options: Options) {
8181
8282
-h, --help Displays complete help
8383
-p, --port Select a custom port for the development server
84-
-d, --no-debug Disable debugging panel
84+
-d, --no-debug (deprecated) No effect. Kept for backwards compatibility
8585
-b, --no-browser Do not open a new browser window
8686
-w, --no-watch Do not open watch for filesystem changes
8787
-c, --ci Run the parcel previewer on a remote unix server
88-
--web3 Connects preview to browser wallet to use the associated avatar and account
88+
--web3 (deprecated) No effect. Kept for backwards compatibility
8989
--skip-build Skip build and only serve the files in preview mode
90-
--web-explorer Launch the scene in the Web Explorer
9190
--debug Enables Debug panel mode inside DCL Explorer (default=true)
9291
--dclenv Decentraland Environment. Which environment to use for the content. This determines the catalyst server used, asset-bundles, etc. Possible values: org, zone, today. (default=org)
9392
--realm Realm used to serve the content. (default=Localhost)
@@ -96,7 +95,7 @@ export async function help(options: Options) {
9695
--skip-auth-screen Skip the auth screen (accepts 'true' or 'false').
9796
--landscape-terrain-enabled Enable landscape terrain.
9897
-n Open a new instance of the Client even if one is already running.
99-
--bevy-web Opens preview using the Bevy Web browser window.
98+
--web, --bevy-web Opens preview using the Bevy Web browser window.
10099
--mobile Show QR code for mobile preview on the same network.
101100
--multi-instance Allow running multiple Explorer instances simultaneously.
102101
--no-client Suppress every auto-launch (desktop Explorer deeplink, browser open, mobile QR). The file watcher still notifies a desktop Explorer if it connects on its own — useful when an external tool owns the Explorer process.
@@ -127,27 +126,23 @@ export async function main(options: Options) {
127126
let baseCoords = { x: 0, y: 0 }
128127
const workingDirectory = path.resolve(process.cwd(), options.args['--dir'] || '.')
129128
const isCi = options.args['--ci'] || process.env.CI || false
130-
const debug = !options.args['--no-debug'] && !isCi
131129
const openBrowser = !options.args['--no-browser'] && !isCi
132130
const build = !options.args['--skip-build']
133131
const watch = !options.args['--no-watch']
134132
const withDataLayer = options.args['--data-layer']
135-
const enableWeb3 = options.args['--web3']
136133
const isHub = !!options.args['--hub']
137134
const skipClient = !!options.args['--no-client']
138135
const bevyWeb = !!options.args['--bevy-web']
139136
const isMobile = !!options.args['--mobile']
140-
const explorerAlpha = !options.args['--web-explorer'] && !bevyWeb
137+
const explorerAlpha = !bevyWeb
141138

142-
let hasSmartWearable = false
143139
const workspace = await getValidWorkspace(options.components, workingDirectory)
144140

145141
/* istanbul ignore if */
146142
if (workspace.projects.length > 1)
147143
printWarning(options.components.logger, 'Support for multiple projects is still experimental.')
148144

149145
for (const project of workspace.projects) {
150-
if (project.kind === 'smart-wearable') hasSmartWearable = true
151146
if (project.kind === 'scene' || project.kind === 'smart-wearable') {
152147
printCurrentProjectStarting(options.components.logger, project, workspace)
153148

@@ -218,12 +213,7 @@ export async function main(options: Options) {
218213
await wireRouter(components, workspace, dataLayer)
219214
if (watch) {
220215
for (const project of workspace.projects) {
221-
await wireFileWatcherToWebSockets(
222-
components,
223-
project.workingDirectory,
224-
project.kind,
225-
!!explorerAlpha || !!bevyWeb
226-
)
216+
await wireFileWatcherToWebSockets(components, project.workingDirectory)
227217
}
228218
}
229219
await startComponents()
@@ -232,23 +222,11 @@ export async function main(options: Options) {
232222
const availableURLs: string[] = []
233223

234224
printProgressInfo(options.components.logger, 'Preview server is now running!')
235-
if (!explorerAlpha) {
236-
components.logger.log('Available on:\n')
237-
}
238225

239226
Object.keys(networkInterfaces).forEach((dev) => {
240227
;(networkInterfaces[dev] || []).forEach((details) => {
241228
if (details.family === 'IPv4') {
242-
const oldBackpack = 'DISABLE_backpack_editor_v2=&ENABLE_backpack_editor_v1'
243-
let addr = `http://${details.address}:${port}?position=${baseCoords.x}%2C${baseCoords.y}&${oldBackpack}`
244-
if (debug) {
245-
addr = `${addr}&SCENE_DEBUG_PANEL`
246-
}
247-
if (enableWeb3 || hasSmartWearable) {
248-
addr = `${addr}&ENABLE_WEB3`
249-
}
250-
251-
availableURLs.push(addr)
229+
availableURLs.push(`http://${details.address}:${port}`)
252230
}
253231
})
254232
})
@@ -260,17 +238,20 @@ export async function main(options: Options) {
260238
const sortedURLs = availableURLs.sort((a, _b) => {
261239
return a.toLowerCase().includes('localhost') || a.includes('127.0.0.1') || a.includes('0.0.0.0') ? -1 : 1
262240
})
263-
const bevyUrl = `https://decentraland.zone/bevy-web/?preview=true&realm=${
241+
const bevyUrl = `https://decentraland.org/bevy-web/?preview=true&realm=${
264242
new URL(sortedURLs[0]).origin
265243
}&position=${baseCoords.x},${baseCoords.y}`
266-
if (!explorerAlpha) {
267-
if (bevyWeb) {
268-
components.logger.log(` ${bevyUrl}`)
269-
} else {
270-
for (const addr of sortedURLs) {
271-
components.logger.log(` ${addr}`)
272-
}
273-
}
244+
if (bevyWeb) {
245+
components.logger.log('Available on:\n')
246+
components.logger.log(` ${bevyUrl}`)
247+
printWarning(
248+
components.logger,
249+
'Chromium-based browsers require permission for websites to reach localhost (Local Network Access).\n' +
250+
'When the browser asks to access apps on your device, click "Allow".\n' +
251+
'If the scene never loads and no prompt appears, enable it manually and reload:\n' +
252+
' chrome://settings/content/siteDetails?site=https%3A%2F%2Fdecentraland.org\n' +
253+
' → "Apps on device" (Chrome 145+) or "Local network access" (Chrome 142-144) → Allow'
254+
)
274255
}
275256
components.logger.log('\nPress CTRL+C to exit\n')
276257

@@ -290,11 +271,9 @@ export async function main(options: Options) {
290271
})
291272
}
292273

293-
// Open preferably localhost/127.0.0.1
294-
if ((!explorerAlpha || bevyWeb) && openBrowser && !skipClient && sortedURLs.length) {
274+
if (bevyWeb && openBrowser && !skipClient && sortedURLs.length) {
295275
try {
296-
const url = bevyWeb ? bevyUrl : sortedURLs[0]
297-
await open(url)
276+
await open(bevyUrl)
298277
} catch (_) {
299278
components.logger.warn('Unable to open browser automatically.')
300279
}

packages/@dcl/sdk-commands/src/commands/start/server/endpoints.ts

Lines changed: 10 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,16 @@ export async function setupEcs6Endpoints(
172172
}
173173
})
174174

175-
serveStatic(components, workspace, router)
175+
router.get('/feature-flags/:file', async (ctx) => {
176+
const res = await components.fetch.fetch(`https://feature-flags.decentraland.zone/${ctx.params.file}`, {
177+
headers: {
178+
connection: 'close'
179+
}
180+
})
181+
return {
182+
body: await res.arrayBuffer()
183+
}
184+
})
176185

177186
// TODO: get workspace scenes & wearables...
178187

@@ -419,104 +428,6 @@ async function getSceneJson(
419428
return resultEntities
420429
}
421430

422-
function serveStatic(
423-
components: Pick<CliComponents, 'fs' | 'fetch'>,
424-
workspace: Workspace,
425-
router: Router<PreviewComponents>
426-
) {
427-
const sdkPath = path.dirname(
428-
require.resolve('@dcl/sdk/package.json', {
429-
paths: [workspace.rootWorkingDirectory, ...workspace.projects.map(($) => $.workingDirectory)]
430-
})
431-
)
432-
const dclExplorerJsonPath = path.dirname(
433-
require.resolve('@dcl/explorer/package.json', {
434-
paths: [workspace.rootWorkingDirectory, ...workspace.projects.map(($) => $.workingDirectory), sdkPath]
435-
})
436-
)
437-
438-
const dclKernelDefaultProfilePath = path.resolve(dclExplorerJsonPath, 'default-profile')
439-
const dclKernelImagesDecentralandConnect = path.resolve(dclExplorerJsonPath, 'images', 'decentraland-connect')
440-
441-
const routes = [
442-
{
443-
route: '/',
444-
path: path.resolve(dclExplorerJsonPath, 'preview.html'),
445-
type: 'text/html'
446-
},
447-
{
448-
route: '/favicon.ico',
449-
path: path.resolve(dclExplorerJsonPath, 'favicon.ico'),
450-
type: 'text/html'
451-
},
452-
{
453-
route: '/@/explorer/index.js',
454-
path: path.resolve(dclExplorerJsonPath, 'index.js'),
455-
type: 'text/javascript'
456-
}
457-
]
458-
459-
for (const route of routes) {
460-
router.get(route.route, async (_ctx) => {
461-
return {
462-
headers: { 'Content-Type': route.type },
463-
body: components.fs.createReadStream(route.path)
464-
}
465-
})
466-
}
467-
468-
function createStaticRoutes(
469-
components: Pick<CliComponents, 'fs'>,
470-
route: string,
471-
folder: string,
472-
transform = (str: string) => str
473-
) {
474-
router.get(route, async (ctx, next) => {
475-
const file = ctx.params.path
476-
const fullPath = path.resolve(folder, transform(file))
477-
478-
// only return files IF the file is within a baseFolder
479-
if (!(await components.fs.fileExists(fullPath))) {
480-
return next()
481-
}
482-
483-
if (await components.fs.directoryExists(fullPath)) {
484-
return { status: 404 }
485-
}
486-
487-
const headers: Record<string, any> = {
488-
'x-timestamp': Date.now(),
489-
'x-sent': true,
490-
'cache-control': 'no-cache,private,max-age=1'
491-
}
492-
493-
if (fullPath.endsWith('.wasm')) {
494-
headers['content-type'] = 'application/wasm'
495-
}
496-
497-
return {
498-
headers,
499-
body: components.fs.createReadStream(fullPath)
500-
}
501-
})
502-
}
503-
504-
createStaticRoutes(components, '/images/decentraland-connect/:path+', dclKernelImagesDecentralandConnect)
505-
createStaticRoutes(components, '/default-profile/:path+', dclKernelDefaultProfilePath)
506-
createStaticRoutes(components, '/@/explorer/:path+', dclExplorerJsonPath, (filePath) => filePath.replace(/.br+$/, ''))
507-
508-
router.get('/feature-flags/:file', async (ctx) => {
509-
const res = await components.fetch.fetch(`https://feature-flags.decentraland.zone/${ctx.params.file}`, {
510-
headers: {
511-
connection: 'close'
512-
}
513-
})
514-
return {
515-
body: await res.arrayBuffer()
516-
}
517-
})
518-
}
519-
520431
async function fakeEntityV3FromProject(
521432
components: Pick<CliComponents, 'fs' | 'logger'>,
522433
project: ProjectUnion,

packages/@dcl/sdk-commands/src/commands/start/server/file-watch-notifier.ts

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { sdk } from '@dcl/schemas'
21
import path from 'path'
32
import { WebSocket } from 'ws'
43
import chokidar from 'chokidar'
54
import { getDCLIgnorePatterns } from '../../../logic/dcl-ignore'
65
import { PreviewComponents } from '../types'
76
import { sceneUpdateClients } from './routes'
8-
import { ProjectUnion } from '../../../logic/project-validations'
97
import { b64HashingFunction } from '../../../logic/project-files'
108
import {
119
WsSceneMessage,
@@ -16,14 +14,10 @@ import { debounce } from '../../../logic/debounce'
1614
/**
1715
* This function gets file modification events and sends them to all the connected
1816
* websockets, it is used to hot-reload assets of the scene.
19-
*
20-
* IMPORTANT: this is a legacy protocol and needs to be revisited for SDK7
2117
*/
2218
export async function wireFileWatcherToWebSockets(
2319
components: Pick<PreviewComponents, 'fs' | 'ws' | 'logger'>,
24-
projectRoot: string,
25-
projectKind: ProjectUnion['kind'],
26-
desktopClient: boolean
20+
projectRoot: string
2721
) {
2822
const ignored = await getDCLIgnorePatterns(components, projectRoot)
2923
const sceneId = b64HashingFunction(projectRoot)
@@ -36,17 +30,12 @@ export async function wireFileWatcherToWebSockets(
3630
cwd: projectRoot
3731
})
3832
.on('unlink', (_: unknown, file: string) => {
39-
if (desktopClient) {
40-
return removeModel(sceneId, file)
41-
}
33+
removeModel(sceneId, file)
4234
})
4335
.on(
4436
'all',
45-
debounce(async (a, file) => {
46-
if (desktopClient) {
47-
updateScene(sceneId, file)
48-
}
49-
return __LEGACY__updateScene(projectRoot, sceneUpdateClients, projectKind)
37+
debounce(async (_, file) => {
38+
updateScene(sceneId, file)
5039
}, 800)
5140
)
5241
}
@@ -93,21 +82,3 @@ function sendSceneMessage(sceneMessage: WsSceneMessage) {
9382
}
9483
}
9584
}
96-
97-
/**
98-
* @deprecated old explorer (kernel)
99-
*/
100-
export function __LEGACY__updateScene(dir: string, clients: Set<WebSocket>, projectKind: ProjectUnion['kind']): void {
101-
for (const client of clients) {
102-
if (client.readyState === WebSocket.OPEN) {
103-
const message: sdk.SceneUpdate = {
104-
type: sdk.SCENE_UPDATE,
105-
payload: { sceneId: b64HashingFunction(dir), sceneType: projectKind }
106-
}
107-
108-
// Old explorer
109-
client.send(sdk.UPDATE, { binary: false })
110-
client.send(JSON.stringify(message), { binary: false })
111-
}
112-
}
113-
}

packages/@dcl/sdk/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
"dependencies": {
77
"@dcl/ecs": "file:../ecs",
88
"@dcl/ecs-math": "2.1.0",
9-
"@dcl/explorer": "1.0.164509-20240802172549.commit-fb95b9b",
109
"@dcl/js-runtime": "file:../js-runtime",
1110
"@dcl/react-ecs": "file:../react-ecs",
1211
"@dcl/sdk-commands": "file:../sdk-commands"

test/kernel-and-renderer-version.spec.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,7 @@ describe('Check there is fixed version', () => {
2727

2828
it('should @dcl/sdk has fixed version dependencies', async () => {
2929
const sdkDeps = checkDeps(SDK_PATH)
30-
const requiredDependencies = [
31-
'@dcl/sdk-commands',
32-
'@dcl/ecs-math',
33-
'@dcl/ecs',
34-
'@dcl/js-runtime',
35-
'@dcl/explorer',
36-
'@dcl/react-ecs'
37-
]
30+
const requiredDependencies = ['@dcl/sdk-commands', '@dcl/ecs-math', '@dcl/ecs', '@dcl/js-runtime', '@dcl/react-ecs']
3831

3932
const dependencies = Object.keys(sdkDeps)
4033

test/snapshots/package-lock.json

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)