Skip to content

Commit 6de0d07

Browse files
committed
fix: cross platform file paths
1 parent feef223 commit 6de0d07

File tree

3 files changed

+80
-3
lines changed

3 files changed

+80
-3
lines changed

packages/vite/helpers/css-tree.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import path from 'path';
22
import { __dirname } from './project.js';
33

44
export const aliasCssTree = [
5-
// Node.js built-ins and mdn-data polyfills for css-tree
65
{
76
find: 'module',
87
replacement: path.resolve(__dirname, '../polyfills/module.js'),
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it, expect } from 'vitest';
2+
import path from 'path';
3+
import { pathToFileURL } from 'url';
4+
5+
// Reproduce the path logic from vite-plugin.ts in a testable helper
6+
function computeClientImport(options: { projectRoot: string; clientFsPath: string }) {
7+
const { projectRoot, clientFsPath } = options;
8+
let clientImport = clientFsPath;
9+
try {
10+
const rel = path.relative(projectRoot, clientFsPath);
11+
const relPosix = rel.replace(/\\/g, '/');
12+
13+
if (path.isAbsolute(rel)) {
14+
clientImport = pathToFileURL(clientFsPath).toString();
15+
} else {
16+
clientImport = (relPosix.startsWith('.') ? relPosix : `/${relPosix}`).replace(/\/+/g, '/');
17+
}
18+
} catch {
19+
clientImport = clientFsPath.replace(/\\/g, '/');
20+
}
21+
return clientImport;
22+
}
23+
24+
describe('ns-hmr-client vite plugin path handling', () => {
25+
it('keeps a clean project-relative POSIX path on POSIX-like roots', () => {
26+
const projectRoot = '/Users/test/app';
27+
const clientFsPath = '/Users/test/app/node_modules/@nativescript/vite/hmr/client/index.js';
28+
29+
const result = computeClientImport({ projectRoot, clientFsPath });
30+
31+
expect(result).toBe('/node_modules/@nativescript/vite/hmr/client/index.js');
32+
});
33+
34+
it('falls back to file URL when relative becomes absolute (Windows-like different drive)', () => {
35+
// Simulate a scenario where projectRoot and clientFsPath are on different drives.
36+
// On real Windows, path.relative('C:/proj', 'D:/lib/file.js') is an absolute path
37+
// starting with the target drive (e.g. 'D:/lib/file.js').
38+
const projectRoot = 'C:/project/root';
39+
const clientFsPath = 'D:/ns-vite-demo/node_modules/@nativescript/vite/hmr/client/index.js';
40+
41+
const result = computeClientImport({ projectRoot, clientFsPath });
42+
43+
// On non-Windows hosts, Node's path.relative may not simulate the
44+
// cross-drive behavior. The important contract is: when the computed
45+
// relative path is absolute, we do NOT generate an import like '/D:/...'
46+
// that would later resolve to 'D:\\D:\\...'. In that case we use a
47+
// file URL; otherwise we leave the relative specifier alone.
48+
if (path.sep === '\\') {
49+
// Windows: expect file URL behavior
50+
expect(result.startsWith('file://')).toBe(true);
51+
}
52+
expect(result.includes('nativescript/vite/hmr/client/index.js')).toBe(true);
53+
});
54+
55+
it('handles Windows-style same-drive paths as project-relative POSIX', () => {
56+
const projectRoot = 'D:/ns-vite-demo';
57+
const clientFsPath = 'D:/ns-vite-demo/node_modules/@nativescript/vite/hmr/client/index.js';
58+
59+
const result = computeClientImport({ projectRoot, clientFsPath });
60+
61+
// When on the same drive, relative path should be node_modules/... and we normalize to POSIX.
62+
expect(result).toBe('/node_modules/@nativescript/vite/hmr/client/index.js');
63+
});
64+
});

packages/vite/hmr/server/vite-plugin.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Plugin, ResolvedConfig } from 'vite';
22
import { createRequire } from 'node:module';
33
import path from 'path';
4+
import { pathToFileURL } from 'url';
45
const require = createRequire(import.meta.url);
56

67
const VIRTUAL_ID = 'virtual:ns-hmr-client';
@@ -32,8 +33,21 @@ export function nsHmrClientVitePlugin(opts: { platform: string; verbose?: boolea
3233
const projectRoot = config?.root || process.cwd();
3334
let clientImport = clientFsPath;
3435
try {
35-
const rel = path.relative(projectRoot, clientFsPath).replace(/\\/g, '/');
36-
clientImport = (rel.startsWith('.') ? rel : `/${rel}`).replace(/\/+/g, '/');
36+
// Compute a project-relative POSIX path when possible. When `path.relative`
37+
// returns an absolute path (this can occur on Windows if roots differ or
38+
// when path.relative returns a drive-letter-prefixed path), avoid creating
39+
// a specifier like `/D:/...` which later gets resolved to `D:\D:\...`.
40+
const rel = path.relative(projectRoot, clientFsPath);
41+
const relPosix = rel.replace(/\\/g, '/');
42+
43+
// If `rel` is absolute (e.g. starts with a drive letter on Windows),
44+
// use a file:// URL for the import so Vite/Rollup do not prepend the
45+
// project root and cause duplicated drive prefixes.
46+
if (path.isAbsolute(rel)) {
47+
clientImport = pathToFileURL(clientFsPath).toString();
48+
} else {
49+
clientImport = (relPosix.startsWith('.') ? relPosix : `/${relPosix}`).replace(/\/+/g, '/');
50+
}
3751
} catch {
3852
// On any failure, keep the original path but normalize to POSIX
3953
clientImport = clientFsPath.replace(/\\/g, '/');

0 commit comments

Comments
 (0)