Files
App.k12.Parents/serve-web.js
T
2026-10-29 17:41:25 +08:00

158 lines
4.4 KiB
JavaScript

const http = require('http')
const fs = require('fs')
const path = require('path')
const { URL } = require('url')
const projectRoot = __dirname
const defaultWebRoot = path.join(projectRoot, 'unpackage', 'dist', 'build', 'web')
const defaultHost = '127.0.0.1'
const defaultPort = 5500
const mimeTypes = {
'.css': 'text/css; charset=utf-8',
'.gif': 'image/gif',
'.html': 'text/html; charset=utf-8',
'.ico': 'image/x-icon',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.js': 'text/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.map': 'application/json; charset=utf-8',
'.png': 'image/png',
'.svg': 'image/svg+xml',
'.ttf': 'font/ttf',
'.txt': 'text/plain; charset=utf-8',
'.webp': 'image/webp',
'.woff': 'font/woff',
'.woff2': 'font/woff2'
}
function getOption(name, fallback) {
const prefix = `${name}=`
const argument = process.argv.find((item) => item.startsWith(prefix))
return argument ? argument.slice(prefix.length) : fallback
}
function getPort() {
const value = getOption('--port', process.env.PORT || String(defaultPort))
const port = Number(value)
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error(`端口无效:${value}`)
}
return port
}
function getWebRoot() {
const configuredRoot = getOption('--dir', defaultWebRoot)
return path.resolve(projectRoot, configuredRoot)
}
function isInsideRoot(root, target) {
const relativePath = path.relative(root, target)
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath))
}
function sendText(response, statusCode, message) {
response.writeHead(statusCode, {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'no-store'
})
response.end(message)
}
function sendFile(response, filePath) {
const extension = path.extname(filePath).toLowerCase()
const contentType = mimeTypes[extension] || 'application/octet-stream'
response.writeHead(200, {
'Content-Type': contentType,
'Cache-Control': extension === '.html' ? 'no-cache' : 'public, max-age=3600'
})
fs.createReadStream(filePath).pipe(response)
}
function tryServeFile(response, webRoot, pathname) {
const decodedPath = decodeURIComponent(pathname)
const relativePath = decodedPath === '/' ? 'index.html' : decodedPath.slice(1)
const filePath = path.resolve(webRoot, relativePath)
if (!isInsideRoot(webRoot, filePath)) {
sendText(response, 403, 'Forbidden')
return true
}
if (!fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
return false
}
sendFile(response, filePath)
return true
}
function createServer(webRoot) {
return http.createServer((request, response) => {
if (request.method !== 'GET' && request.method !== 'HEAD') {
sendText(response, 405, 'Method Not Allowed')
return
}
let requestUrl
try {
requestUrl = new URL(request.url, 'http://localhost')
} catch {
sendText(response, 400, 'Bad Request')
return
}
try {
if (tryServeFile(response, webRoot, requestUrl.pathname)) {
return
}
// Web 发行项目使用前端路由时,未知路径回退到入口页面。
if (!path.extname(requestUrl.pathname) && tryServeFile(response, webRoot, '/index.html')) {
return
}
sendText(response, 404, 'Not Found')
} catch (error) {
console.error(`[serve-web] ${error.message}`)
sendText(response, 500, 'Internal Server Error')
}
})
}
function printUsage() {
console.log('用法:node serve-web.js [--port=5500] [--host=127.0.0.1] [--dir=unpackage/dist/build/web]')
}
function start() {
if (process.argv.includes('--help') || process.argv.includes('-h')) {
printUsage()
return
}
const webRoot = getWebRoot()
const host = getOption('--host', process.env.HOST || defaultHost)
const port = getPort()
if (!fs.existsSync(webRoot)) {
console.error(`未找到 Web 文件目录:${webRoot}`)
console.error('请先在 HBuilderX 中发行 Web,或使用 --dir 指定已打包的目录。')
process.exitCode = 1
return
}
const server = createServer(webRoot)
server.listen(port, host, () => {
console.log(`Web 文件目录:${webRoot}`)
console.log(`本机访问:http://localhost:${port}`)
console.log(`局域网访问:http://<本机IP>:${port}`)
console.log('按 Ctrl+C 停止服务器')
})
}
start()