mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-14 16:36:18 -08:00
refactor(tooling): [MOD-DEV-01] own dev server lifecycle and report port failures
This commit is contained in:
1 parent
401d4cdd65
commit
772d79a671
24 files changed
+1249
-132
No files matched your search
+113
-57
@@ -1,73 +1,129 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import servor from 'servor'
|
||||
import process from 'node:process'
|
||||
import openBrowser from 'servor/utils/openBrowser.js'
|
||||
import { build as viteBuild } from 'vite'
|
||||
import { getViteBuildConfig } from './config.ts'
|
||||
import { getGlobalName } from './names.ts'
|
||||
import { getEntryFile, getProjects, selectProjects } from './projects.ts'
|
||||
import { createRebuildQueue } from './rebuild.ts'
|
||||
import { startDevServer } from './server.ts'
|
||||
|
||||
async function develop(projectPath: string, name: string, open: boolean, port: number) {
|
||||
const uncompiledPath = path.resolve(`docs/uncompiled/${name}`)
|
||||
let browserOpened = false
|
||||
|
||||
// Servor enumerates existing directories when installing its Linux watchers.
|
||||
fs.mkdirSync(uncompiledPath, { recursive: true })
|
||||
|
||||
const { url } = await servor({
|
||||
root: 'docs',
|
||||
fallback: 'index.html',
|
||||
reload: true,
|
||||
port,
|
||||
})
|
||||
|
||||
async function buildBundle() {
|
||||
const startTime = Date.now()
|
||||
try {
|
||||
const config = getViteBuildConfig({
|
||||
entry: getEntryFile(projectPath),
|
||||
outDir: uncompiledPath,
|
||||
name: getGlobalName(name),
|
||||
format: 'iife',
|
||||
fileName: 'index.js',
|
||||
minify: false,
|
||||
emptyOutDir: true,
|
||||
})
|
||||
config.define['process.env.NODE_ENV'] = JSON.stringify('development')
|
||||
|
||||
await viteBuild({ root: projectPath, ...config })
|
||||
console.log(`[${name}] ✅ Built in ${Date.now() - startTime}ms`)
|
||||
|
||||
if (open && !browserOpened) {
|
||||
browserOpened = true
|
||||
openBrowser(url)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`[${name}] ❌ Build error:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
const rebuild = createRebuildQueue(buildBundle)
|
||||
await rebuild()
|
||||
console.log(`[${name}] Demo: ${url}/?libs=./uncompiled/${name}/index.js`)
|
||||
|
||||
const srcPath = path.join(projectPath, 'src')
|
||||
console.log(`[${name}] 👀 Watching ${srcPath}...`)
|
||||
|
||||
fs.watch(srcPath, { recursive: true }, async (_, filename) => {
|
||||
if (filename) {
|
||||
console.log(`[${name}] 📝 Changed: ${filename}`)
|
||||
await rebuild()
|
||||
}
|
||||
})
|
||||
export interface DevelopmentOptions {
|
||||
root?: string
|
||||
port?: number
|
||||
signal?: AbortSignal
|
||||
}
|
||||
|
||||
export async function runDevelop({ port = 8082 }: { port?: number } = {}): Promise<void> {
|
||||
export async function startDevelopment(projectPath: string, name: string, open: boolean, { root = process.cwd(), port = 8082, signal }: DevelopmentOptions = {}) {
|
||||
const uncompiledPath = path.resolve(root, `docs/uncompiled/${name}`)
|
||||
let browserOpened = false
|
||||
let stopped = false
|
||||
let failure: Error | undefined
|
||||
let watcher: fs.FSWatcher | undefined
|
||||
let activeBuild = Promise.resolve()
|
||||
let closing: Promise<void> | undefined
|
||||
let finish!: () => void
|
||||
let reject!: (error: Error) => void
|
||||
const done = new Promise<void>((resolve, failed) => {
|
||||
finish = resolve
|
||||
reject = failed
|
||||
})
|
||||
// Startup can fail before the caller receives the session; retain rejection for its owner.
|
||||
void done.catch(() => {})
|
||||
const startup = startDevServer({
|
||||
root: path.join(root, 'docs'),
|
||||
port,
|
||||
ignoredDirectory: uncompiledPath,
|
||||
onError: fatal,
|
||||
})
|
||||
function fatal(error: Error) {
|
||||
if (stopped)
|
||||
return
|
||||
failure ??= error
|
||||
void close().catch(() => {})
|
||||
}
|
||||
function aborted() {
|
||||
void close().catch(() => {})
|
||||
}
|
||||
function close(): Promise<void> {
|
||||
if (!closing) {
|
||||
stopped = true
|
||||
watcher?.close()
|
||||
signal?.removeEventListener('abort', aborted)
|
||||
closing = (async () => {
|
||||
const server = await startup.catch(() => undefined)
|
||||
await server?.close()
|
||||
await activeBuild
|
||||
if (failure)
|
||||
throw failure
|
||||
})()
|
||||
void closing.then(finish, reject)
|
||||
}
|
||||
return closing
|
||||
}
|
||||
signal?.addEventListener('abort', aborted, { once: true })
|
||||
try {
|
||||
const server = await startup
|
||||
if (signal?.aborted || stopped) {
|
||||
await close()
|
||||
return { url: server.url, close, done }
|
||||
}
|
||||
fs.mkdirSync(uncompiledPath, { recursive: true })
|
||||
async function buildBundle() {
|
||||
if (stopped)
|
||||
return
|
||||
const startTime = Date.now()
|
||||
try {
|
||||
const config = getViteBuildConfig({ entry: getEntryFile(projectPath), outDir: uncompiledPath, name: getGlobalName(name), format: 'iife', fileName: 'index.js', minify: false, emptyOutDir: true })
|
||||
config.define['process.env.NODE_ENV'] = JSON.stringify('development')
|
||||
await viteBuild({ root: projectPath, ...config })
|
||||
if (stopped)
|
||||
return
|
||||
console.log(`[${name}] ✅ Built in ${Date.now() - startTime}ms`)
|
||||
server.reload()
|
||||
if (open && !browserOpened) {
|
||||
browserOpened = true
|
||||
openBrowser(server.url)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
if (!stopped)
|
||||
console.error(`[${name}] ❌ Build error:`, error)
|
||||
}
|
||||
}
|
||||
const queue = createRebuildQueue(buildBundle)
|
||||
function rebuild() {
|
||||
if (!stopped)
|
||||
activeBuild = queue()
|
||||
return activeBuild
|
||||
}
|
||||
const srcPath = path.join(projectPath, 'src')
|
||||
watcher = fs.watch(srcPath, { recursive: true }, (_, filename) => {
|
||||
if (filename && !stopped) {
|
||||
console.log(`[${name}] 📝 Changed: ${filename}`)
|
||||
void rebuild().catch(fatal)
|
||||
}
|
||||
})
|
||||
watcher.on('error', fatal)
|
||||
void rebuild().then(() => {
|
||||
if (!stopped) {
|
||||
console.log(`[${name}] Demo: ${server.url}/?libs=./uncompiled/${name}/index.js`)
|
||||
console.log(`[${name}] 👀 Watching ${srcPath}...`)
|
||||
}
|
||||
}, fatal)
|
||||
return { url: server.url, close, done }
|
||||
}
|
||||
catch (error) {
|
||||
await close()
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export async function runDevelop({ port = 8082, signal }: DevelopmentOptions = {}) {
|
||||
const projects = getProjects()
|
||||
const { names, open } = await selectProjects(projects, 'dev')
|
||||
const name = names[0]
|
||||
if (name)
|
||||
await develop(projects[name]!, name, open, port)
|
||||
return startDevelopment(projects[name]!, name, open, { port, signal })
|
||||
}
|
||||
Reference in new issue
Block a user