Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(preview)!: use base middleware #14818

Merged
merged 3 commits into from Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/guide/migration.md
Expand Up @@ -201,6 +201,9 @@ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'))
There are some changes which only affect plugin/tool creators.

- [[#14119] refactor!: merge `PreviewServerForHook` into `PreviewServer` type](https://github.com/vitejs/vite/pull/14119)
- The `configurePreviewServer` hook now accepts the `PreviewServer` type instead of `PreviewServerForHook` type.
- [[#14818] refactor(preview)!: use base middleware](https://github.com/vitejs/vite/pull/14818)
- Middlewares added from the returned function in `configurePreviewServer` now does not have access to the `base` when comparing the `req.url` value. This aligns the behaviour with the dev server. You can check the `base` from the `configResolved` hook if needed.

Also there are other breaking changes which only affect few users.

Expand Down
39 changes: 16 additions & 23 deletions packages/vite/src/node/preview.ts
Expand Up @@ -15,6 +15,7 @@ import {
} from './http'
import { openBrowser } from './server/openBrowser'
import compression from './server/middlewares/compression'
import { baseMiddleware } from './server/middlewares/base'
import { htmlFallbackMiddleware } from './server/middlewares/htmlFallback'
import { indexHtmlMiddleware } from './server/middlewares/indexHtml'
import { notFoundMiddleware } from './server/middlewares/notFound'
Expand Down Expand Up @@ -164,8 +165,10 @@ export async function preview(

app.use(compression())

const previewBase =
config.base === './' || config.base === '' ? '/' : config.base
// base
if (config.base !== '/') {
app.use(baseMiddleware(config.rawBase, false))
}

// static assets
const headers = config.preview.headers
Expand All @@ -187,36 +190,28 @@ export async function preview(
},
})(...args)

app.use(previewBase, viteAssetMiddleware)
app.use(viteAssetMiddleware)

// html fallback
if (config.appType === 'spa' || config.appType === 'mpa') {
app.use(
previewBase,
htmlFallbackMiddleware(
distDir,
config.appType === 'spa',
previewBase !== '/',
),
)
app.use(htmlFallbackMiddleware(distDir, config.appType === 'spa'))
}

// apply post server hooks from plugins
postHooks.forEach((fn) => fn && fn())

if (config.appType === 'spa' || config.appType === 'mpa') {
// transform index.html
app.use(previewBase, indexHtmlMiddleware(distDir, server))
app.use(indexHtmlMiddleware(distDir, server))

// handle 404s
app.use(previewBase, notFoundMiddleware())
app.use(notFoundMiddleware())
}

const hostname = await resolveHostname(options.host)
const port = options.port ?? DEFAULT_PREVIEW_PORT
const protocol = options.https ? 'https' : 'http'

const serverPort = await httpServerStart(httpServer, {
await httpServerStart(httpServer, {
port,
strictPort: options.strictPort,
host: hostname.host,
Expand All @@ -230,14 +225,12 @@ export async function preview(
)

if (options.open) {
const path = typeof options.open === 'string' ? options.open : previewBase
openBrowser(
path.startsWith('http')
? path
: new URL(path, `${protocol}://${hostname.name}:${serverPort}`).href,
true,
logger,
)
const url = server.resolvedUrls?.local[0] ?? server.resolvedUrls?.network[0]
if (url) {
const path =
typeof options.open === 'string' ? new URL(options.open, url).href : url
openBrowser(path, true, logger)
}
}

return server as PreviewServer
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/index.ts
Expand Up @@ -677,7 +677,7 @@ export async function _createServer(

// base
if (config.base !== '/') {
middlewares.use(baseMiddleware(server))
middlewares.use(baseMiddleware(config.rawBase, middlewareMode))
}

// open in editor support
Expand Down
28 changes: 16 additions & 12 deletions packages/vite/src/node/server/middlewares/base.ts
@@ -1,35 +1,39 @@
import type { Connect } from 'dep-types/connect'
import type { ViteDevServer } from '..'
import { joinUrlSegments, stripBase, withTrailingSlash } from '../../utils'
import {
cleanUrl,
joinUrlSegments,
stripBase,
withTrailingSlash,
} from '../../utils'

// this middleware is only active when (base !== '/')

export function baseMiddleware({
config,
}: ViteDevServer): Connect.NextHandleFunction {
export function baseMiddleware(
rawBase: string,
middlewareMode: boolean,
): Connect.NextHandleFunction {
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
return function viteBaseMiddleware(req, res, next) {
const url = req.url!
const parsed = new URL(url, 'http://vitejs.dev')
const path = parsed.pathname || '/'
const base = config.rawBase
const pathname = cleanUrl(url)
const base = rawBase

if (path.startsWith(base)) {
if (pathname.startsWith(base)) {
// rewrite url to remove base. this ensures that other middleware does
// not need to consider base being prepended or not
req.url = stripBase(url, base)
return next()
}

// skip redirect and error fallback on middleware mode, #4057
if (config.server.middlewareMode) {
if (middlewareMode) {
return next()
}

if (path === '/' || path === '/index.html') {
if (pathname === '/' || pathname === '/index.html') {
// redirect root visit to based url with search and hash
res.writeHead(302, {
Location: base + (parsed.search || '') + (parsed.hash || ''),
Location: base + url.slice(pathname.length),
})
res.end()
return
Expand Down
22 changes: 4 additions & 18 deletions packages/vite/src/node/server/middlewares/htmlFallback.ts
Expand Up @@ -8,21 +8,7 @@ const debug = createDebugger('vite:html-fallback')
export function htmlFallbackMiddleware(
root: string,
spaFallback: boolean,
mounted = false,
): Connect.NextHandleFunction {
// When this middleware is mounted on a route, we need to re-assign `req.url` with a
// leading `.` to signal a relative rewrite. Returning with a leading `/` returns a
// buggy `req.url`. e.g.:
//
// mount /foo/bar:
// req.url = /index.html
// final = /foo/barindex.html
//
// mount /foo/bar:
// req.url = ./index.html
// final = /foo/bar/index.html
const prepend = mounted ? '.' : ''

// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
return function viteHtmlFallbackMiddleware(req, res, next) {
if (
Expand Down Expand Up @@ -51,7 +37,7 @@ export function htmlFallbackMiddleware(
const filePath = path.join(root, pathname)
if (fs.existsSync(filePath)) {
debug?.(`Rewriting ${req.method} ${req.url} to ${url}`)
req.url = prepend + url
req.url = url
sapphi-red marked this conversation as resolved.
Show resolved Hide resolved
return next()
}
}
Expand All @@ -61,7 +47,7 @@ export function htmlFallbackMiddleware(
if (fs.existsSync(filePath)) {
const newUrl = url + 'index.html'
debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`)
req.url = prepend + newUrl
req.url = newUrl
return next()
}
}
Expand All @@ -71,14 +57,14 @@ export function htmlFallbackMiddleware(
if (fs.existsSync(filePath)) {
const newUrl = url + '.html'
debug?.(`Rewriting ${req.method} ${req.url} to ${newUrl}`)
req.url = prepend + newUrl
req.url = newUrl
return next()
}
}

if (spaFallback) {
debug?.(`Rewriting ${req.method} ${req.url} to /index.html`)
req.url = prepend + '/index.html'
req.url = '/index.html'
}

next()
Expand Down