sanity
Version:
Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches
124 lines (104 loc) • 3.81 kB
text/typescript
import {type UserViteConfig} from '@sanity/cli'
import chalk from 'chalk'
import fs from 'fs/promises'
import path from 'path'
import {type InlineConfig, preview} from 'vite'
import {debug as serverDebug} from './debug'
import {extendViteConfigWithUserConfig} from './getViteConfig'
import {sanityBasePathRedirectPlugin} from './vite/plugin-sanity-basepath-redirect'
const debug = serverDebug.extend('preview')
export interface PreviewServer {
urls: {local: string[]; network: string[]}
close(): Promise<void>
}
export interface PreviewServerOptions {
root: string
cwd: string
httpPort: number
httpHost?: string
vite?: UserViteConfig
}
export async function startPreviewServer(options: PreviewServerOptions): Promise<PreviewServer> {
const {httpPort, httpHost, root, vite: extendViteConfig} = options
const startTime = Date.now()
const indexPath = path.join(root, 'index.html')
let basePath: string | undefined
try {
const index = await fs.readFile(indexPath, 'utf8')
basePath = tryResolveBasePathFromIndex(index)
} catch (err) {
if (err.code !== 'ENOENT') {
throw err
}
const error = new Error(
`Could not find a production build in the '${root}' directory.\nTry building your studio app with 'sanity build' before starting the preview server.`,
)
error.name = 'BUILD_NOT_FOUND'
throw error
}
const mode = 'production'
let previewConfig: InlineConfig = {
root,
base: basePath || '/',
plugins: [sanityBasePathRedirectPlugin(basePath)],
configFile: false,
preview: {
port: httpPort,
host: httpHost,
strictPort: true,
},
// Needed for vite to not serve `root/dist`
build: {
outDir: root,
},
mode,
}
// Extend Vite configuration with user-provided config
if (extendViteConfig) {
previewConfig = await extendViteConfigWithUserConfig(
{command: 'serve', mode},
previewConfig,
extendViteConfig,
)
}
debug('Creating vite server')
const server = await preview(previewConfig)
const warn = server.config.logger.warn
const info = server.config.logger.info
const url = server.resolvedUrls.local[0]
if (typeof basePath === 'undefined') {
warn('Could not determine base path from index.html, using "/" as default')
} else if (basePath && basePath !== '/') {
info(`Using resolved base path from static build: ${chalk.cyan(basePath)}`)
}
const startupDuration = Date.now() - startTime
info(
`Sanity Studio ` +
`using ${chalk.cyan(`vite@${require('vite/package.json').version}`)} ` +
`ready in ${chalk.cyan(`${Math.ceil(startupDuration)}ms`)} ` +
`and running at ${chalk.cyan(url)} (production preview mode)`,
)
return {
urls: server.resolvedUrls,
close: () =>
new Promise((resolve, reject) =>
server.httpServer.close((err) => (err ? reject(err) : resolve())),
),
}
}
function tryResolveBasePathFromIndex(index: string): string | undefined {
// <script ... src="/some-base-path/static/sanity-a3cc3d86.js"></script>
const basePath = index.match(/<script[^>]+src="(.*?)\/static\/sanity-/)?.[1]
// We _expect_ to be able to find the base path. If we can't, we should warn.
// Note that we're checking for `undefined` here, since an empty string is a
// valid base path.
if (typeof basePath === 'undefined') {
return undefined
}
// In the case of an empty base path, we still want to return `/` to indicate
// that we _found_ the basepath - it just happens to be empty. Eg:
// <script ... src = "/static/sanity-a3cc3d86.js"></script>
// Which differs from not being able to find the script tag at all, in which
// case we'll want to show a warning to indicate that it is an abnormality.
return basePath === '' ? '/' : basePath
}