import fs from 'fs' import path from 'path' import chalk from 'chalk' import { createLogger, createServer as createViteServer, ServerOptions, } from 'vite' import express from 'express' import { printHttpServerUrls } from 'vite' import { parseManifestJson } from '@dcloudio/uni-cli-shared' import { CliOptions } from '.' import { addConfigFile, cleanOptions } from './utils' export async function createServer(options: CliOptions & ServerOptions) { const server = await createViteServer( addConfigFile({ root: process.env.VITE_ROOT_DIR, logLevel: options.logLevel, clearScreen: options.clearScreen, server: cleanOptions(options) as ServerOptions, }) ) await server.listen() const info = server.config.logger.info info( chalk.cyan(`\n vite v${require('vite/package.json').version}`) + chalk.green(` dev server running at:\n`), { clear: !server.config.logger.hasWarned, } ) server.printUrls() } export async function createSSRServer(options: CliOptions & ServerOptions) { const app = express() /** * @type {import('vite').ViteDevServer} */ const vite = await createViteServer( addConfigFile({ root: process.env.VITE_ROOT_DIR, logLevel: options.logLevel, clearScreen: options.clearScreen, server: { middlewareMode: true, watch: { // During tests we edit the files too fast and sometimes chokidar // misses change events, so enforce polling for consistency usePolling: true, interval: 100, }, }, }) ) // use vite's connect instance as middleware app.use(vite.middlewares) app.use('*', async (req, res) => { try { const { h5 } = parseManifestJson(process.env.UNI_INPUT_DIR) const base = (h5 && h5.router && h5.router.base) || '' const url = req.originalUrl.replace(base, '') const template = await vite.transformIndexHtml( url, fs.readFileSync( path.resolve(process.env.VITE_ROOT_DIR!, 'index.html'), 'utf-8' ) ) const render = ( await vite.ssrLoadModule( path.resolve(process.env.UNI_INPUT_DIR, 'entry-server.js') ) ).render const [appHtml, preloadLinks, appContext, title] = await render(url) const icon = template.includes('rel="icon"') ? '' : '\n' const html = template .replace(/(.*?)<\/title>/, `${icon}<title>${title}`) .replace(``, preloadLinks) .replace(``, appHtml) .replace(``, appContext) res.status(200).set({ 'Content-Type': 'text/html' }).end(html) } catch (e: any) { vite && vite.ssrFixStacktrace(e) res.status(500).end(e.stack) } }) const logger = createLogger(options.logLevel) const serverOptions = vite.config.server || {} let port = options.port || serverOptions.port || 3000 let hostname: string | undefined if (options.host === 'localhost') { // Use a secure default hostname = '127.0.0.1' } else if (options.host === undefined || options.host === true) { // probably passed --host in the CLI, without arguments hostname = undefined // undefined typically means 0.0.0.0 or :: (listen on all IPs) } else { hostname = options.host as string } return new Promise((resolve, reject) => { const onSuccess = () => { printHttpServerUrls(server, vite.config) resolve(server) } const onError = (e: Error & { code?: string }) => { if (e.code === 'EADDRINUSE') { if (options.strictPort) { server.off('error', onError) reject(new Error(`Port ${port} is already in use`)) } else { logger.info(`Port ${port} is in use, trying another one...`) app.listen(++port, hostname!, onSuccess).on('error', onError) } } else { server.off('error', onError) reject(e) } } const server = app.listen(port, hostname!, onSuccess).on('error', onError) }) }