Skip to content

Commit 328bc56

Browse files
feat(edge-apps): migrate to Vite dev server with dist-based deployment (#690)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 8cf78b5 commit 328bc56

File tree

26 files changed

+392
-161
lines changed

26 files changed

+392
-161
lines changed

edge-apps/cap-alerting/index.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
<meta charset="utf-8" />
55
<title>CAP Alerting - Screenly Edge App</title>
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<link rel="stylesheet" href="dist/css/style.css" />
87
</head>
98
<body class="m-0 p-0 overflow-hidden">
109
<div id="app" class="w-full h-screen flex items-center justify-center">
@@ -123,6 +122,6 @@
123122
</template>
124123

125124
<script src="screenly.js?version=1"></script>
126-
<script src="dist/js/main.js"></script>
125+
<script type="module" src="/src/main.ts"></script>
127126
</body>
128127
</html>

edge-apps/cap-alerting/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
"scripts": {
77
"prebuild": "bun run type-check",
88
"generate-mock-data": "screenly edge-app run --generate-mock-data",
9-
"predev": "bun run generate-mock-data && edge-apps-scripts build",
10-
"dev": "run-p build:dev edge-app-server cors-proxy-server",
11-
"cors-proxy-server": "bun ../blueprint/scripts/cors-proxy-server.ts",
12-
"edge-app-server": "screenly edge-app run",
9+
"predev": "bun run generate-mock-data",
10+
"dev": "run-p edge-apps-dev cors-proxy-server",
11+
"edge-apps-dev": "edge-apps-scripts dev",
12+
"cors-proxy-server": "bun ../edge-apps-library/scripts/cors-proxy-server.ts",
1313
"build": "edge-apps-scripts build",
1414
"build:dev": "edge-apps-scripts build:dev",
1515
"build:prod": "edge-apps-scripts build",
@@ -18,7 +18,7 @@
1818
"lint": "edge-apps-scripts lint --fix",
1919
"format": "prettier --write src/ README.md index.html",
2020
"format:check": "prettier --check src/ README.md index.html",
21-
"deploy": "bun run build && screenly edge-app deploy",
21+
"deploy": "bun run build && screenly edge-app deploy --path=dist/",
2222
"type-check": "edge-apps-scripts type-check",
2323
"prepare": "cd ../edge-apps-library && bun install && bun run build"
2424
},

edge-apps/clock/index.html

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>Clock - Screenly Edge App</title>
77
<script src="screenly.js?version=1"></script>
8-
<link rel="stylesheet" href="dist/css/style.css" />
98
</head>
109
<body>
1110
<auto-scaler
@@ -38,6 +37,6 @@
3837
</main>
3938
</div>
4039
</auto-scaler>
41-
<script src="dist/js/main.js"></script>
40+
<script type="module" src="/src/main.ts"></script>
4241
</body>
4342
</html>

edge-apps/clock/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
"scripts": {
66
"prebuild": "bun run type-check",
77
"generate-mock-data": "screenly edge-app run --generate-mock-data",
8-
"predev": "bun run generate-mock-data && edge-apps-scripts build",
9-
"dev": "run-p build:dev edge-app-server",
10-
"edge-app-server": "screenly edge-app run",
8+
"predev": "bun run generate-mock-data",
9+
"dev": "edge-apps-scripts dev",
1110
"build": "edge-apps-scripts build",
1211
"build:dev": "edge-apps-scripts build:dev",
1312
"build:prod": "edge-apps-scripts build",
@@ -16,7 +15,7 @@
1615
"lint": "edge-apps-scripts lint --fix",
1716
"format": "prettier --write src/ README.md index.html",
1817
"format:check": "prettier --check src/ README.md index.html",
19-
"deploy": "bun run build && screenly edge-app deploy",
18+
"deploy": "bun run build && screenly edge-app deploy --path=dist/",
2019
"type-check": "edge-apps-scripts type-check",
2120
"prepare": "cd ../edge-apps-library && bun install && bun run build"
2221
},

edge-apps/edge-apps-library/README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ This package provides the `edge-apps-scripts` CLI tool for running shared develo
8484

8585
### Available Commands
8686

87+
#### Development Server
88+
89+
Start the Vite development server with mock data from `screenly.yml` and `mock-data.yml`:
90+
91+
```bash
92+
bun run dev
93+
```
94+
95+
#### Building
96+
97+
```bash
98+
bun run build
99+
```
100+
87101
#### Linting
88102

89103
To lint your Edge App:
@@ -98,12 +112,6 @@ To lint and automatically fix issues:
98112
bun run lint -- --fix
99113
```
100114

101-
#### Building
102-
103-
```bash
104-
bun run build
105-
```
106-
107115
#### Type Checking
108116

109117
Run TypeScript type checking:
@@ -116,18 +124,20 @@ bun run type-check
116124

117125
- This library provides utilities to help with common Edge App tasks.
118126
- The CLI uses the shared ESLint configuration from `@screenly/edge-apps`, so you don't need to maintain your own `eslint.config.ts`
119-
- The build commands assume your Edge App has `src/main.ts` as the entry point
127+
- The build commands assume your Edge App has `index.html` as the entry point
120128
- Build output will be generated in the `dist/` directory
121129

122130
It is recommended to add the following scripts to your Edge App's `package.json`:
123131

124132
```json
125133
{
126134
"scripts": {
127-
"lint": "edge-apps-scripts lint",
135+
"dev": "edge-apps-scripts dev",
128136
"build": "edge-apps-scripts build",
129137
"build:dev": "edge-apps-scripts build:dev",
130-
"type-check": "edge-apps-scripts type-check"
138+
"lint": "edge-apps-scripts lint",
139+
"type-check": "edge-apps-scripts type-check",
140+
"deploy": "bun run build && screenly edge-app deploy --path=dist/"
131141
}
132142
}
133143
```

edge-apps/edge-apps-library/bun.lock

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

edge-apps/edge-apps-library/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@
5454
"test:unit": "bun test --parallel",
5555
"test:coverage": "bun test --coverage --parallel",
5656
"lint": "eslint . --fix",
57-
"format": "prettier --write src/ scripts/ README.md index.html",
58-
"format:check": "prettier --check src/ scripts/ README.md index.html",
57+
"format": "prettier --write src/ scripts/ vite-plugins/ README.md index.html",
58+
"format:check": "prettier --check src/ scripts/ vite-plugins/ README.md index.html",
5959
"type-check": "tsc --noEmit"
6060
},
6161
"prettier": "./.prettierrc.json",
@@ -86,6 +86,7 @@
8686
"prettier": "^3.8.1",
8787
"typescript": "^5.9.3",
8888
"typescript-eslint": "^8.54.0",
89-
"vite": "^7.3.1"
89+
"vite": "^7.3.1",
90+
"yaml": "^2.8.2"
9091
}
9192
}

edge-apps/edge-apps-library/scripts/cli.ts

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* CLI command dispatcher for edge-apps-scripts
44
*/
55

6-
import { execSync, spawn } from 'child_process'
6+
import { execSync, spawn, type ChildProcess } from 'child_process'
77
import path from 'path'
88
import { fileURLToPath } from 'url'
99

@@ -16,6 +16,10 @@ const commands = {
1616
description: 'Run ESLint with shared configuration',
1717
handler: lintCommand,
1818
},
19+
dev: {
20+
description: 'Start Vite development server',
21+
handler: devCommand,
22+
},
1923
build: {
2024
description: 'Build application for production',
2125
handler: buildCommand,
@@ -30,20 +34,74 @@ const commands = {
3034
},
3135
}
3236

37+
/**
38+
* Helper: Get NODE_PATH with library's node_modules included
39+
*/
40+
function getNodePath(): string {
41+
const libraryNodeModules = path.resolve(libraryRoot, 'node_modules')
42+
const existingNodePath = process.env.NODE_PATH || ''
43+
return existingNodePath
44+
? `${libraryNodeModules}${path.delimiter}${existingNodePath}`
45+
: libraryNodeModules
46+
}
47+
48+
/**
49+
* Helper: Setup signal handlers for spawned processes
50+
*/
51+
function setupSignalHandlers(child: ChildProcess): void {
52+
const handleSignal = (signal: string) => {
53+
child.kill(signal as NodeJS.Signals)
54+
child.on('exit', () => process.exit(0))
55+
}
56+
process.on('SIGINT', () => handleSignal('SIGINT'))
57+
process.on('SIGTERM', () => handleSignal('SIGTERM'))
58+
}
59+
60+
/**
61+
* Helper: Spawn a process with common options and signal handling
62+
*/
63+
function spawnWithSignalHandling(
64+
command: string,
65+
args: string[],
66+
errorMessage: string,
67+
): void {
68+
const child = spawn(command, args, {
69+
stdio: 'inherit',
70+
cwd: process.cwd(),
71+
shell: process.platform === 'win32',
72+
env: {
73+
...process.env,
74+
NODE_PATH: getNodePath(),
75+
},
76+
})
77+
78+
child.on('error', (err) => {
79+
console.error(errorMessage, err)
80+
process.exit(1)
81+
})
82+
83+
setupSignalHandlers(child)
84+
}
85+
86+
/**
87+
* Helper: Get Vite binary and config paths
88+
*/
89+
function getVitePaths(): { viteBin: string; configPath: string } {
90+
return {
91+
viteBin: path.resolve(libraryRoot, 'node_modules', '.bin', 'vite'),
92+
configPath: path.resolve(libraryRoot, 'vite.config.ts'),
93+
}
94+
}
95+
3396
async function lintCommand(args: string[]) {
3497
try {
35-
// Get the caller's directory (the app that invoked this script)
36-
const callerDir = process.cwd()
37-
38-
// Get path to eslint binary in the library's node_modules
3998
const eslintBin = path.resolve(
4099
libraryRoot,
41100
'node_modules',
42101
'.bin',
43102
'eslint',
44103
)
45104

46-
// Build eslint command
47105
const eslintArgs = [
48106
'--config',
49107
path.resolve(libraryRoot, 'eslint.config.ts'),
@@ -55,36 +113,36 @@ async function lintCommand(args: string[]) {
55113
`"${eslintBin}" ${eslintArgs.map((arg) => `"${arg}"`).join(' ')}`,
56114
{
57115
stdio: 'inherit',
58-
cwd: callerDir,
116+
cwd: process.cwd(),
59117
},
60118
)
61119
} catch {
62120
process.exit(1)
63121
}
64122
}
65123

66-
async function buildCommand(args: string[]) {
124+
async function devCommand(args: string[]) {
67125
try {
68-
const callerDir = process.cwd()
126+
const { viteBin, configPath } = getVitePaths()
127+
const viteArgs = ['--config', configPath, ...args]
69128

70-
// Build for production using Vite
71-
const viteBin = path.resolve(libraryRoot, 'node_modules', '.bin', 'vite')
72-
const configPath = path.resolve(libraryRoot, 'vite.config.ts')
73-
const viteArgs = ['build', '--config', configPath, ...args]
129+
spawnWithSignalHandling(viteBin, viteArgs, 'Failed to start dev server:')
130+
} catch {
131+
process.exit(1)
132+
}
133+
}
74134

75-
// Set NODE_PATH to include library's node_modules so plugins can resolve dependencies
76-
const libraryNodeModules = path.resolve(libraryRoot, 'node_modules')
77-
const existingNodePath = process.env.NODE_PATH || ''
78-
const nodePath = existingNodePath
79-
? `${libraryNodeModules}${path.delimiter}${existingNodePath}`
80-
: libraryNodeModules
135+
async function buildCommand(args: string[]) {
136+
try {
137+
const { viteBin, configPath } = getVitePaths()
138+
const viteArgs = ['build', '--config', configPath, ...args]
81139

82140
execSync(`"${viteBin}" ${viteArgs.map((arg) => `"${arg}"`).join(' ')}`, {
83141
stdio: 'inherit',
84-
cwd: callerDir,
142+
cwd: process.cwd(),
85143
env: {
86144
...process.env,
87-
NODE_PATH: nodePath,
145+
NODE_PATH: getNodePath(),
88146
},
89147
})
90148
} catch {
@@ -94,11 +152,7 @@ async function buildCommand(args: string[]) {
94152

95153
async function buildDevCommand(args: string[]) {
96154
try {
97-
const callerDir = process.cwd()
98-
99-
// Build for development with watch mode using Vite
100-
const viteBin = path.resolve(libraryRoot, 'node_modules', '.bin', 'vite')
101-
const configPath = path.resolve(libraryRoot, 'vite.config.ts')
155+
const { viteBin, configPath } = getVitePaths()
102156
const viteArgs = [
103157
'build',
104158
'--watch',
@@ -108,50 +162,16 @@ async function buildDevCommand(args: string[]) {
108162
...args,
109163
]
110164

111-
// Set NODE_PATH to include library's node_modules so plugins can resolve dependencies
112-
const libraryNodeModules = path.resolve(libraryRoot, 'node_modules')
113-
const existingNodePath = process.env.NODE_PATH || ''
114-
const nodePath = existingNodePath
115-
? `${libraryNodeModules}${path.delimiter}${existingNodePath}`
116-
: libraryNodeModules
117-
118-
// Use spawn instead of execSync to allow watch mode to run without blocking
119-
const child = spawn(viteBin, viteArgs, {
120-
stdio: 'inherit',
121-
cwd: callerDir,
122-
shell: process.platform === 'win32',
123-
env: {
124-
...process.env,
125-
NODE_PATH: nodePath,
126-
},
127-
})
128-
129-
// Attach an error handler
130-
child.on('error', (err) => {
131-
console.error('Failed to start build process:', err)
132-
process.exit(1)
133-
})
134-
135-
// Handle parent process termination to clean up child process
136-
const handleSignal = (signal: string) => {
137-
child.kill(signal as NodeJS.Signals)
138-
child.on('exit', () => process.exit(0))
139-
}
140-
process.on('SIGINT', () => handleSignal('SIGINT'))
141-
process.on('SIGTERM', () => handleSignal('SIGTERM'))
165+
spawnWithSignalHandling(viteBin, viteArgs, 'Failed to start build process:')
142166
} catch {
143167
process.exit(1)
144168
}
145169
}
146170

147171
async function typeCheckCommand(args: string[]) {
148172
try {
149-
const callerDir = process.cwd()
150-
151-
// Get path to tsc binary in the library's node_modules
152173
const tscBin = path.resolve(libraryRoot, 'node_modules', '.bin', 'tsc')
153174

154-
// Build tsc command
155175
const tscArgs = [
156176
'--noEmit',
157177
'--project',
@@ -161,7 +181,7 @@ async function typeCheckCommand(args: string[]) {
161181

162182
execSync(`"${tscBin}" ${tscArgs.map((arg) => `"${arg}"`).join(' ')}`, {
163183
stdio: 'inherit',
164-
cwd: callerDir,
184+
cwd: process.cwd(),
165185
})
166186
} catch {
167187
process.exit(1)

0 commit comments

Comments
 (0)