From 799c45e0e2b959ab56a7c8d51d4619325e3cbf47 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Aug 2023 12:29:58 +0100 Subject: [PATCH 1/5] fix(vite): precisely check if files are in dirs --- packages/vite/src/node/optimizer/index.ts | 2 +- packages/vite/src/node/plugins/asset.ts | 4 ++-- packages/vite/src/node/plugins/importAnalysis.ts | 2 +- packages/vite/src/node/plugins/reporter.ts | 2 +- packages/vite/src/node/plugins/resolve.ts | 8 ++++++-- packages/vite/src/node/server/hmr.ts | 2 +- packages/vite/src/node/server/middlewares/base.ts | 2 +- packages/vite/src/node/server/middlewares/static.ts | 2 +- packages/vite/src/node/server/middlewares/transform.ts | 4 ++-- 9 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index ebc7e3c350638f..234dab98864f2a 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -960,7 +960,7 @@ export function createIsOptimizedDepFile( config: ResolvedConfig, ): (id: string) => boolean { const depsCacheDirPrefix = getDepsCacheDirPrefix(config) - return (id) => id.startsWith(depsCacheDirPrefix) + return (id) => id.startsWith(depsCacheDirPrefix + '/') } export function createIsOptimizedDepUrl( diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index ca94dbb2ed5d51..47ee14f5ae7bf4 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -239,7 +239,7 @@ export function checkPublicFile( return } const publicFile = path.join(publicDir, cleanUrl(url)) - if (!publicFile.startsWith(publicDir)) { + if (!publicFile.startsWith(publicDir + '/')) { // can happen if URL starts with '../' return } @@ -267,7 +267,7 @@ function fileToDevUrl(id: string, config: ResolvedConfig) { if (checkPublicFile(id, config)) { // in public dir during dev, keep the url as-is rtn = id - } else if (id.startsWith(config.root)) { + } else if (id.startsWith(config.root + '/')) { // in project root, infer short public path rtn = '/' + path.posix.relative(config.root, id) } else { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 620fa38175d19d..246f5bb097a5ce 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -672,7 +672,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { config.logger.error(e.message, { error: e }) }) } - } else if (!importer.startsWith(clientDir)) { + } else if (!importer.startsWith(clientDir + '/')) { if (!isInNodeModules(importer)) { // check @vite-ignore which suppresses dynamic import warning const hasViteIgnore = hasViteIgnoreRE.test( diff --git a/packages/vite/src/node/plugins/reporter.ts b/packages/vite/src/node/plugins/reporter.ts index bea7d4facd798b..b87bf8c9f1fc1e 100644 --- a/packages/vite/src/node/plugins/reporter.ts +++ b/packages/vite/src/node/plugins/reporter.ts @@ -245,7 +245,7 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { const sizeColor = isLarge ? colors.yellow : colors.dim let log = colors.dim(relativeOutDir + '/') log += - !config.build.lib && entry.name.startsWith(assetsDir) + !config.build.lib && entry.name.startsWith(assetsDir + '/') ? colors.dim(assetsDir) + group.color( entry.name diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index 63841d398c5256..bf273be14d5529 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -228,7 +228,11 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { // URL // /foo -> /fs-root/foo - if (asSrc && id[0] === '/' && (rootInRoot || !id.startsWith(root))) { + if ( + asSrc && + id[0] === '/' && + (rootInRoot || !id.startsWith(root + '/')) + ) { const fsPath = path.resolve(root, id.slice(1)) if ((res = tryFsResolve(fsPath, options))) { debug?.(`[url] ${colors.cyan(id)} -> ${colors.dim(res)}`) @@ -939,7 +943,7 @@ export async function tryOptimizedResolve( } // match by src to correctly identify if id belongs to nested dependency - if (optimizedData.src.startsWith(idPkgDir)) { + if (optimizedData.src.startsWith(idPkgDir + '/')) { return depsOptimizer.getOptimizedDepId(optimizedData) } } diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index e72b1ebdf05338..6db423749b4f87 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -81,7 +81,7 @@ export async function handleHMRUpdate( debugHmr?.(`[file change] ${colors.dim(shortFile)}`) // (dev only) the client itself cannot be hot updated. - if (file.startsWith(normalizedClientDir)) { + if (file.startsWith(normalizedClientDir + '/')) { ws.send({ type: 'full-reload', path: '*', diff --git a/packages/vite/src/node/server/middlewares/base.ts b/packages/vite/src/node/server/middlewares/base.ts index cba4582486f8a9..c29998fa7daef2 100644 --- a/packages/vite/src/node/server/middlewares/base.ts +++ b/packages/vite/src/node/server/middlewares/base.ts @@ -14,7 +14,7 @@ export function baseMiddleware({ const path = parsed.pathname || '/' const base = config.rawBase - if (path.startsWith(base)) { + if (path.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) diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index d1e7073179415b..4a61b06f7de546 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -118,7 +118,7 @@ export function serveStaticMiddleware( } if (redirectedPathname) { // dir is pre-normalized to posix style - if (redirectedPathname.startsWith(dir)) { + if (redirectedPathname.startsWith(dir + '/')) { redirectedPathname = redirectedPathname.slice(dir.length) } } diff --git a/packages/vite/src/node/server/middlewares/transform.ts b/packages/vite/src/node/server/middlewares/transform.ts index 737fb92f7d0fbc..491e744ef5fa67 100644 --- a/packages/vite/src/node/server/middlewares/transform.ts +++ b/packages/vite/src/node/server/middlewares/transform.ts @@ -129,10 +129,10 @@ export function transformMiddleware( // check if public dir is inside root dir const publicDir = normalizePath(server.config.publicDir) const rootDir = normalizePath(server.config.root) - if (publicDir.startsWith(rootDir)) { + if (publicDir.startsWith(rootDir + '/')) { const publicPath = `${publicDir.slice(rootDir.length)}/` // warn explicit public paths - if (url.startsWith(publicPath)) { + if (url.startsWith(publicPath + '/')) { let warning: string if (isImportRequest(url)) { From 2522c5e4f0514222bf0b07af0a10cc1710956c93 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Aug 2023 14:41:27 +0100 Subject: [PATCH 2/5] refactor: extract `withTrailingSlash` and avoid re-normalising base --- packages/vite/src/node/build.ts | 9 +++++++-- packages/vite/src/node/config.ts | 5 +++-- packages/vite/src/node/optimizer/index.ts | 3 ++- packages/vite/src/node/plugins/asset.ts | 5 +++-- .../vite/src/node/plugins/importAnalysis.ts | 5 +++-- .../src/node/plugins/importAnalysisBuild.ts | 3 ++- packages/vite/src/node/plugins/preAlias.ts | 3 ++- packages/vite/src/node/plugins/reporter.ts | 12 +++++++++--- packages/vite/src/node/plugins/resolve.ts | 5 +++-- packages/vite/src/node/server/hmr.ts | 14 +++++++++++--- .../vite/src/node/server/middlewares/base.ts | 6 +++--- .../src/node/server/middlewares/static.ts | 5 +++-- .../src/node/server/middlewares/transform.ts | 5 +++-- packages/vite/src/node/ssr/ssrExternal.ts | 6 +++++- packages/vite/src/node/utils.ts | 19 +++++++++++++------ 15 files changed, 72 insertions(+), 33 deletions(-) diff --git a/packages/vite/src/node/build.ts b/packages/vite/src/node/build.ts index d8935a90b99d78..5183f3777c6a06 100644 --- a/packages/vite/src/node/build.ts +++ b/packages/vite/src/node/build.ts @@ -35,6 +35,7 @@ import { joinUrlSegments, normalizePath, requireResolveFromRootWithFallback, + withTrailingSlash, } from './utils' import { manifestPlugin } from './plugins/manifest' import type { Logger } from './logger' @@ -712,7 +713,7 @@ function prepareOutDir( for (const outDir of nonDuplicateDirs) { if ( fs.existsSync(outDir) && - !normalizePath(outDir).startsWith(config.root + '/') + !normalizePath(outDir).startsWith(withTrailingSlash(config.root)) ) { // warn if outDir is outside of root config.logger.warn( @@ -1238,5 +1239,9 @@ export const toOutputFilePathInHtml = toOutputFilePathWithoutRuntime function areSeparateFolders(a: string, b: string) { const na = normalizePath(a) const nb = normalizePath(b) - return na !== nb && !na.startsWith(nb + '/') && !nb.startsWith(na + '/') + return ( + na !== nb && + !na.startsWith(withTrailingSlash(nb)) && + !nb.startsWith(withTrailingSlash(na)) + ) } diff --git a/packages/vite/src/node/config.ts b/packages/vite/src/node/config.ts index 916af948368d9b..450e0a63ac05fb 100644 --- a/packages/vite/src/node/config.ts +++ b/packages/vite/src/node/config.ts @@ -39,6 +39,7 @@ import { mergeConfig, normalizeAlias, normalizePath, + withTrailingSlash, } from './utils' import { createPluginHookUtils, @@ -680,7 +681,7 @@ export async function resolveConfig( ), inlineConfig, root: resolvedRoot, - base: resolvedBase.endsWith('/') ? resolvedBase : resolvedBase + '/', + base: withTrailingSlash(resolvedBase), rawBase: resolvedBase, resolve: resolveOptions, publicDir: resolvedPublicDir, @@ -856,7 +857,7 @@ assetFileNames isn't equal for every build.rollupOptions.output. A single patter ) { resolved.logger.warn( colors.yellow(` -(!) Experimental legacy.buildSsrCjsExternalHeuristics and ssr.format: 'cjs' are going to be removed in Vite 5. +(!) Experimental legacy.buildSsrCjsExternalHeuristics and ssr.format: 'cjs' are going to be removed in Vite 5. Find more information and give feedback at https://github.com/vitejs/vite/discussions/13816. `), ) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 234dab98864f2a..934b7327a00e35 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -23,6 +23,7 @@ import { normalizePath, removeLeadingSlash, tryStatSync, + withTrailingSlash, } from '../utils' import { transformWithEsbuild } from '../plugins/esbuild' import { ESBUILD_MODULES_TARGET } from '../constants' @@ -960,7 +961,7 @@ export function createIsOptimizedDepFile( config: ResolvedConfig, ): (id: string) => boolean { const depsCacheDirPrefix = getDepsCacheDirPrefix(config) - return (id) => id.startsWith(depsCacheDirPrefix + '/') + return (id) => id.startsWith(withTrailingSlash(depsCacheDirPrefix)) } export function createIsOptimizedDepUrl( diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 47ee14f5ae7bf4..6264cd8a2549eb 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -23,6 +23,7 @@ import { joinUrlSegments, normalizePath, removeLeadingSlash, + withTrailingSlash, } from '../utils' import { FS_PREFIX } from '../constants' @@ -239,7 +240,7 @@ export function checkPublicFile( return } const publicFile = path.join(publicDir, cleanUrl(url)) - if (!publicFile.startsWith(publicDir + '/')) { + if (!publicFile.startsWith(withTrailingSlash(publicDir))) { // can happen if URL starts with '../' return } @@ -267,7 +268,7 @@ function fileToDevUrl(id: string, config: ResolvedConfig) { if (checkPublicFile(id, config)) { // in public dir during dev, keep the url as-is rtn = id - } else if (id.startsWith(config.root + '/')) { + } else if (id.startsWith(withTrailingSlash(config.root))) { // in project root, infer short public path rtn = '/' + path.posix.relative(config.root, id) } else { diff --git a/packages/vite/src/node/plugins/importAnalysis.ts b/packages/vite/src/node/plugins/importAnalysis.ts index 246f5bb097a5ce..814aa9187a9ce0 100644 --- a/packages/vite/src/node/plugins/importAnalysis.ts +++ b/packages/vite/src/node/plugins/importAnalysis.ts @@ -44,6 +44,7 @@ import { timeFrom, transformStableResult, unwrapId, + withTrailingSlash, wrapId, } from '../utils' import { getDepOptimizationConfig } from '../config' @@ -335,7 +336,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { // normalize all imports into resolved URLs // e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'` - if (resolved.id.startsWith(root + '/')) { + if (resolved.id.startsWith(withTrailingSlash(root))) { // in root: infer short absolute path from root url = resolved.id.slice(root.length) } else if ( @@ -672,7 +673,7 @@ export function importAnalysisPlugin(config: ResolvedConfig): Plugin { config.logger.error(e.message, { error: e }) }) } - } else if (!importer.startsWith(clientDir + '/')) { + } else if (!importer.startsWith(withTrailingSlash(clientDir))) { if (!isInNodeModules(importer)) { // check @vite-ignore which suppresses dynamic import warning const hasViteIgnore = hasViteIgnoreRE.test( diff --git a/packages/vite/src/node/plugins/importAnalysisBuild.ts b/packages/vite/src/node/plugins/importAnalysisBuild.ts index 1d046eb49f581e..1889790463dae8 100644 --- a/packages/vite/src/node/plugins/importAnalysisBuild.ts +++ b/packages/vite/src/node/plugins/importAnalysisBuild.ts @@ -16,6 +16,7 @@ import { isInNodeModules, moduleListContains, numberToPos, + withTrailingSlash, } from '../utils' import type { Plugin } from '../plugin' import { getDepOptimizationConfig } from '../config' @@ -271,7 +272,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin { // normalize all imports into resolved URLs // e.g. `import 'foo'` -> `import '/@fs/.../node_modules/foo/index.js'` - if (resolved.id.startsWith(root + '/')) { + if (resolved.id.startsWith(withTrailingSlash(root))) { // in root: infer short absolute path from root url = resolved.id.slice(root.length) } else { diff --git a/packages/vite/src/node/plugins/preAlias.ts b/packages/vite/src/node/plugins/preAlias.ts index 931db79d231bf7..cd86bac770d823 100644 --- a/packages/vite/src/node/plugins/preAlias.ts +++ b/packages/vite/src/node/plugins/preAlias.ts @@ -14,6 +14,7 @@ import { isInNodeModules, isOptimizable, moduleListContains, + withTrailingSlash, } from '../utils' import { getDepsOptimizer } from '../optimizer' import { tryOptimizedResolve } from './resolve' @@ -114,7 +115,7 @@ function matches(pattern: string | RegExp, importee: string) { if (importee === pattern) { return true } - return importee.startsWith(pattern + '/') + return importee.startsWith(withTrailingSlash(pattern)) } function getAliasPatterns( diff --git a/packages/vite/src/node/plugins/reporter.ts b/packages/vite/src/node/plugins/reporter.ts index b87bf8c9f1fc1e..3e0c19634725cf 100644 --- a/packages/vite/src/node/plugins/reporter.ts +++ b/packages/vite/src/node/plugins/reporter.ts @@ -4,7 +4,12 @@ import { promisify } from 'node:util' import colors from 'picocolors' import type { Plugin } from 'rollup' import type { ResolvedConfig } from '../config' -import { isDefined, isInNodeModules, normalizePath } from '../utils' +import { + isDefined, + isInNodeModules, + normalizePath, + withTrailingSlash, +} from '../utils' import { LogLevels } from '../logger' const groups = [ @@ -243,9 +248,10 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin { group.name === 'JS' && entry.size / 1000 > chunkLimit if (isLarge) hasLargeChunks = true const sizeColor = isLarge ? colors.yellow : colors.dim - let log = colors.dim(relativeOutDir + '/') + let log = colors.dim(withTrailingSlash(relativeOutDir)) log += - !config.build.lib && entry.name.startsWith(assetsDir + '/') + !config.build.lib && + entry.name.startsWith(withTrailingSlash(assetsDir)) ? colors.dim(assetsDir) + group.color( entry.name diff --git a/packages/vite/src/node/plugins/resolve.ts b/packages/vite/src/node/plugins/resolve.ts index bf273be14d5529..26c30b9a9c9edc 100644 --- a/packages/vite/src/node/plugins/resolve.ts +++ b/packages/vite/src/node/plugins/resolve.ts @@ -36,6 +36,7 @@ import { safeRealpathSync, slash, tryStatSync, + withTrailingSlash, } from '../utils' import { optimizedDepInfoFromFile, optimizedDepInfoFromId } from '../optimizer' import type { DepsOptimizer } from '../optimizer' @@ -231,7 +232,7 @@ export function resolvePlugin(resolveOptions: InternalResolveOptions): Plugin { if ( asSrc && id[0] === '/' && - (rootInRoot || !id.startsWith(root + '/')) + (rootInRoot || !id.startsWith(withTrailingSlash(root))) ) { const fsPath = path.resolve(root, id.slice(1)) if ((res = tryFsResolve(fsPath, options))) { @@ -943,7 +944,7 @@ export async function tryOptimizedResolve( } // match by src to correctly identify if id belongs to nested dependency - if (optimizedData.src.startsWith(idPkgDir + '/')) { + if (optimizedData.src.startsWith(withTrailingSlash(idPkgDir))) { return depsOptimizer.getOptimizedDepId(optimizedData) } } diff --git a/packages/vite/src/node/server/hmr.ts b/packages/vite/src/node/server/hmr.ts index 6db423749b4f87..f708b1114ed3cd 100644 --- a/packages/vite/src/node/server/hmr.ts +++ b/packages/vite/src/node/server/hmr.ts @@ -5,7 +5,13 @@ import colors from 'picocolors' import type { Update } from 'types/hmrPayload' import type { RollupError } from 'rollup' import { CLIENT_DIR } from '../constants' -import { createDebugger, normalizePath, unique, wrapId } from '../utils' +import { + createDebugger, + normalizePath, + unique, + withTrailingSlash, + wrapId, +} from '../utils' import type { ViteDevServer } from '..' import { isCSSRequest } from '../plugins/css' import { getAffectedGlobModules } from '../plugins/importMetaGlob' @@ -38,7 +44,9 @@ export interface HmrContext { } export function getShortName(file: string, root: string): string { - return file.startsWith(root + '/') ? path.posix.relative(root, file) : file + return file.startsWith(withTrailingSlash(root)) + ? path.posix.relative(root, file) + : file } export async function handleHMRUpdate( @@ -81,7 +89,7 @@ export async function handleHMRUpdate( debugHmr?.(`[file change] ${colors.dim(shortFile)}`) // (dev only) the client itself cannot be hot updated. - if (file.startsWith(normalizedClientDir + '/')) { + if (file.startsWith(withTrailingSlash(normalizedClientDir))) { ws.send({ type: 'full-reload', path: '*', diff --git a/packages/vite/src/node/server/middlewares/base.ts b/packages/vite/src/node/server/middlewares/base.ts index c29998fa7daef2..c6af2302aef3d9 100644 --- a/packages/vite/src/node/server/middlewares/base.ts +++ b/packages/vite/src/node/server/middlewares/base.ts @@ -1,6 +1,6 @@ import type { Connect } from 'dep-types/connect' import type { ViteDevServer } from '..' -import { joinUrlSegments, stripBase } from '../../utils' +import { joinUrlSegments, stripBase, withTrailingSlash } from '../../utils' // this middleware is only active when (base !== '/') @@ -14,7 +14,7 @@ export function baseMiddleware({ const path = parsed.pathname || '/' const base = config.rawBase - if (path.startsWith(base + '/')) { + if (path.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) @@ -36,7 +36,7 @@ export function baseMiddleware({ } else if (req.headers.accept?.includes('text/html')) { // non-based page visit const redirectPath = - url + '/' !== base ? joinUrlSegments(base, url) : base + withTrailingSlash(url) !== base ? joinUrlSegments(base, url) : base res.writeHead(404, { 'Content-Type': 'text/html', }) diff --git a/packages/vite/src/node/server/middlewares/static.ts b/packages/vite/src/node/server/middlewares/static.ts index 4a61b06f7de546..8acc9e681b4e37 100644 --- a/packages/vite/src/node/server/middlewares/static.ts +++ b/packages/vite/src/node/server/middlewares/static.ts @@ -19,6 +19,7 @@ import { removeLeadingSlash, shouldServeFile, slash, + withTrailingSlash, } from '../../utils' const knownJavascriptExtensionRE = /\.[tj]sx?$/ @@ -118,7 +119,7 @@ export function serveStaticMiddleware( } if (redirectedPathname) { // dir is pre-normalized to posix style - if (redirectedPathname.startsWith(dir + '/')) { + if (redirectedPathname.startsWith(withTrailingSlash(dir))) { redirectedPathname = redirectedPathname.slice(dir.length) } } @@ -129,7 +130,7 @@ export function serveStaticMiddleware( resolvedPathname[resolvedPathname.length - 1] === '/' && fileUrl[fileUrl.length - 1] !== '/' ) { - fileUrl = fileUrl + '/' + fileUrl = withTrailingSlash(fileUrl) } if (!ensureServingAccess(fileUrl, server, res, next)) { return diff --git a/packages/vite/src/node/server/middlewares/transform.ts b/packages/vite/src/node/server/middlewares/transform.ts index 491e744ef5fa67..f85b12a46202f4 100644 --- a/packages/vite/src/node/server/middlewares/transform.ts +++ b/packages/vite/src/node/server/middlewares/transform.ts @@ -16,6 +16,7 @@ import { removeImportQuery, removeTimestampQuery, unwrapId, + withTrailingSlash, } from '../../utils' import { send } from '../send' import { ERR_LOAD_URL, transformRequest } from '../transformRequest' @@ -129,10 +130,10 @@ export function transformMiddleware( // check if public dir is inside root dir const publicDir = normalizePath(server.config.publicDir) const rootDir = normalizePath(server.config.root) - if (publicDir.startsWith(rootDir + '/')) { + if (publicDir.startsWith(withTrailingSlash(rootDir))) { const publicPath = `${publicDir.slice(rootDir.length)}/` // warn explicit public paths - if (url.startsWith(publicPath + '/')) { + if (url.startsWith(withTrailingSlash(publicPath))) { let warning: string if (isImportRequest(url)) { diff --git a/packages/vite/src/node/ssr/ssrExternal.ts b/packages/vite/src/node/ssr/ssrExternal.ts index 9a5bc28b39b9ee..99001f3614f330 100644 --- a/packages/vite/src/node/ssr/ssrExternal.ts +++ b/packages/vite/src/node/ssr/ssrExternal.ts @@ -13,6 +13,7 @@ import { isInNodeModules, lookupFile, normalizePath, + withTrailingSlash, } from '../utils' import type { Logger, ResolvedConfig } from '..' import { resolvePackageData } from '../packages' @@ -340,7 +341,10 @@ export function cjsShouldExternalizeForSSR( } // deep imports, check ext before externalizing - only externalize // extension-less imports and explicit .js imports - if (id.startsWith(e + '/') && (!path.extname(id) || id.endsWith('.js'))) { + if ( + id.startsWith(withTrailingSlash(e)) && + (!path.extname(id) || id.endsWith('.js')) + ) { return true } }) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 397369eac7286d..50c615ee35e9f4 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -123,7 +123,9 @@ export function moduleListContains( moduleList: string[] | undefined, id: string, ): boolean | undefined { - return moduleList?.some((m) => m === id || id.startsWith(m + '/')) + return moduleList?.some( + (m) => m === id || id.startsWith(withTrailingSlash(m)), + ) } export function isOptimizable( @@ -224,6 +226,13 @@ export function fsPathFromUrl(url: string): string { return fsPathFromId(cleanUrl(url)) } +export function withTrailingSlash(path: string): string { + if (path[path.length - 1] !== '/') { + return `${path}/` + } + return path +} + /** * Check if dir is a parent of file * @@ -234,9 +243,7 @@ export function fsPathFromUrl(url: string): string { * @returns true if dir is a parent of file */ export function isParentDirectory(dir: string, file: string): boolean { - if (dir[dir.length - 1] !== '/') { - dir = `${dir}/` - } + dir = withTrailingSlash(dir) return ( file.startsWith(dir) || (isCaseInsensitiveFS && file.toLowerCase().startsWith(dir.toLowerCase())) @@ -647,7 +654,7 @@ export function ensureWatchedFile( if ( file && // only need to watch if out of root - !file.startsWith(root + '/') && + !file.startsWith(withTrailingSlash(root)) && // some rollup plugins use null bytes for private resolved Ids !file.includes('\0') && fs.existsSync(file) @@ -1225,7 +1232,7 @@ export function stripBase(path: string, base: string): string { if (path === base) { return '/' } - const devBase = base[base.length - 1] === '/' ? base : base + '/' + const devBase = base[base.length - 1] === '/' ? base : withTrailingSlash(base) return path.startsWith(devBase) ? path.slice(devBase.length - 1) : path } From 2d1f9498387493e1df65aaa038fc4385e589fa82 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Aug 2023 14:44:09 +0100 Subject: [PATCH 3/5] chore: simplify --- packages/vite/src/node/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vite/src/node/utils.ts b/packages/vite/src/node/utils.ts index 50c615ee35e9f4..2aeae79302e067 100644 --- a/packages/vite/src/node/utils.ts +++ b/packages/vite/src/node/utils.ts @@ -1232,7 +1232,7 @@ export function stripBase(path: string, base: string): string { if (path === base) { return '/' } - const devBase = base[base.length - 1] === '/' ? base : withTrailingSlash(base) + const devBase = withTrailingSlash(base) return path.startsWith(devBase) ? path.slice(devBase.length - 1) : path } From 039d4a205012aae9d5ac1bbaf1eb6a7b6ca6a884 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Aug 2023 14:56:08 +0100 Subject: [PATCH 4/5] fix: remove check for optimizer --- packages/vite/src/node/optimizer/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/vite/src/node/optimizer/index.ts b/packages/vite/src/node/optimizer/index.ts index 934b7327a00e35..ebc7e3c350638f 100644 --- a/packages/vite/src/node/optimizer/index.ts +++ b/packages/vite/src/node/optimizer/index.ts @@ -23,7 +23,6 @@ import { normalizePath, removeLeadingSlash, tryStatSync, - withTrailingSlash, } from '../utils' import { transformWithEsbuild } from '../plugins/esbuild' import { ESBUILD_MODULES_TARGET } from '../constants' @@ -961,7 +960,7 @@ export function createIsOptimizedDepFile( config: ResolvedConfig, ): (id: string) => boolean { const depsCacheDirPrefix = getDepsCacheDirPrefix(config) - return (id) => id.startsWith(withTrailingSlash(depsCacheDirPrefix)) + return (id) => id.startsWith(depsCacheDirPrefix) } export function createIsOptimizedDepUrl( From 6026981a21cabce40f4f8fe426150260990bbc43 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Wed, 30 Aug 2023 15:21:22 +0100 Subject: [PATCH 5/5] fix: normalise windows paths --- packages/vite/src/node/plugins/asset.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/vite/src/node/plugins/asset.ts b/packages/vite/src/node/plugins/asset.ts index 6264cd8a2549eb..9a91234f9378f6 100644 --- a/packages/vite/src/node/plugins/asset.ts +++ b/packages/vite/src/node/plugins/asset.ts @@ -240,7 +240,11 @@ export function checkPublicFile( return } const publicFile = path.join(publicDir, cleanUrl(url)) - if (!publicFile.startsWith(withTrailingSlash(publicDir))) { + if ( + !normalizePath(publicFile).startsWith( + withTrailingSlash(normalizePath(publicDir)), + ) + ) { // can happen if URL starts with '../' return }