server.ts 5.2 KB
Newer Older
fxy060608's avatar
fxy060608 已提交
1 2 3 4 5 6 7 8 9 10 11
import os from 'os'
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 { hasOwn } from '@vue/shared'
fxy060608's avatar
fxy060608 已提交
12
import { parseManifestJson } from '@dcloudio/uni-cli-shared'
fxy060608's avatar
fxy060608 已提交
13
import { CliOptions } from '.'
fxy060608's avatar
fxy060608 已提交
14
import { addConfigFile, cleanOptions } from './utils'
fxy060608's avatar
fxy060608 已提交
15
import { printHttpServerUrls } from './logger'
fxy060608's avatar
fxy060608 已提交
16 17

export async function createServer(options: CliOptions & ServerOptions) {
fxy060608's avatar
fxy060608 已提交
18 19 20 21 22 23 24 25
  const server = await createViteServer(
    addConfigFile({
      root: process.env.VITE_ROOT_DIR,
      logLevel: options.logLevel,
      clearScreen: options.clearScreen,
      server: cleanOptions(options) as ServerOptions,
    })
  )
fxy060608's avatar
fxy060608 已提交
26
  await server.listen()
fxy060608's avatar
fxy060608 已提交
27 28 29 30 31 32 33 34 35 36 37 38

  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,
    }
  )

  printHttpServerUrls(server.httpServer!, server.config, options)
fxy060608's avatar
fxy060608 已提交
39 40 41 42 43 44 45
}

export async function createSSRServer(options: CliOptions & ServerOptions) {
  const app = express()
  /**
   * @type {import('vite').ViteDevServer}
   */
fxy060608's avatar
fxy060608 已提交
46 47 48 49 50 51 52 53 54 55 56 57 58
  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,
        },
fxy060608's avatar
fxy060608 已提交
59
      },
fxy060608's avatar
fxy060608 已提交
60 61
    })
  )
fxy060608's avatar
fxy060608 已提交
62 63 64 65 66
  // use vite's connect instance as middleware
  app.use(vite.middlewares)

  app.use('*', async (req, res) => {
    try {
fxy060608's avatar
fxy060608 已提交
67 68 69
      const { h5 } = parseManifestJson(process.env.UNI_INPUT_DIR)
      const base = (h5 && h5.router && h5.router.base) || ''
      const url = req.originalUrl.replace(base, '')
fxy060608's avatar
fxy060608 已提交
70 71 72 73 74 75 76 77
      const template = await vite.transformIndexHtml(
        url,
        fs.readFileSync(
          path.resolve(process.env.VITE_ROOT_DIR!, 'index.html'),
          'utf-8'
        )
      )
      const render = (
fxy060608's avatar
fxy060608 已提交
78 79 80
        await vite.ssrLoadModule(
          path.resolve(process.env.UNI_INPUT_DIR, 'entry-server.js')
        )
fxy060608's avatar
fxy060608 已提交
81 82
      ).render

fxy060608's avatar
fxy060608 已提交
83
      const [appHtml, preloadLinks, appContext, title] = await render(url)
fxy060608's avatar
fxy060608 已提交
84

fxy060608's avatar
fxy060608 已提交
85 86 87 88
      const icon = template.includes('rel="icon"')
        ? ''
        : '<link rel="icon" href="data:," />\n'

fxy060608's avatar
fxy060608 已提交
89
      const html = template
fxy060608's avatar
fxy060608 已提交
90
        .replace(/<title>(.*?)<\/title>/, `${icon}<title>${title}</title>`)
fxy060608's avatar
fxy060608 已提交
91 92
        .replace(`<!--preload-links-->`, preloadLinks)
        .replace(`<!--app-html-->`, appHtml)
fxy060608's avatar
fxy060608 已提交
93
        .replace(`<!--app-context-->`, appContext)
fxy060608's avatar
fxy060608 已提交
94
      res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
95
    } catch (e: any) {
fxy060608's avatar
fxy060608 已提交
96 97 98 99 100 101 102 103 104 105 106 107 108
      vite && vite.ssrFixStacktrace(e)
      res.status(500).end(e.stack)
    }
  })

  const logger = createLogger(options.logLevel)
  const serverOptions = vite.config.server || {}
  const protocol = (
    hasOwn(options, 'https') ? options.https : serverOptions.https
  )
    ? 'https'
    : 'http'
  let port = options.port || serverOptions.port || 3000
109
  let hostname: string | undefined
fxy060608's avatar
fxy060608 已提交
110
  if (options.host === 'localhost') {
111 112
    // Use a secure default
    hostname = '127.0.0.1'
fxy060608's avatar
fxy060608 已提交
113
  } else if (options.host === undefined || options.host === true) {
114 115 116 117 118
    // 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
  }
fxy060608's avatar
fxy060608 已提交
119 120 121
  return new Promise((resolve, reject) => {
    const onSuccess = () => {
      const interfaces = os.networkInterfaces()
fxy060608's avatar
fxy060608 已提交
122 123
      const locals: string[] = []
      const networks: string[] = []
fxy060608's avatar
fxy060608 已提交
124 125 126
      Object.keys(interfaces).forEach((key) =>
        (interfaces[key] || [])
          .filter((details) => details.family === 'IPv4')
fxy060608's avatar
fxy060608 已提交
127 128 129 130 131
          .forEach((detail) => {
            if (detail.address.includes('127.0.0.1')) {
              locals.push(detail.address)
            } else {
              networks.push(detail.address)
fxy060608's avatar
fxy060608 已提交
132 133 134
            }
          })
      )
fxy060608's avatar
fxy060608 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
      locals.forEach((host) => {
        const url = `${protocol}://${host}:${chalk.bold(port)}${
          vite.config.base
        }`
        logger.info(`  - Local:    ${chalk.cyan(url)}`)
      })
      const networksLen = networks.length - 1
      networks.forEach((host, index) => {
        const url = `${protocol}://${host}:${chalk.bold(port)}${
          vite.config.base
        }`
        logger.info(
          `  ${index === networksLen ? '-' : '>'} Network:  ${chalk.cyan(url)}`
        )
      })
fxy060608's avatar
fxy060608 已提交
150 151 152 153 154 155 156 157 158
      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...`)
fxy060608's avatar
fxy060608 已提交
159
          app.listen(++port, hostname!, onSuccess).on('error', onError)
fxy060608's avatar
fxy060608 已提交
160 161 162 163 164 165 166 167 168
        }
      } else {
        server.off('error', onError)
        reject(e)
      }
    }
    const server = app.listen(port, hostname!, onSuccess).on('error', onError)
  })
}