file abstraction

This commit is contained in:
Matthew Phillips 2023-03-09 15:17:25 -05:00
parent 3f6a0888b0
commit 63e379c146
3 changed files with 209 additions and 10 deletions

View file

@ -0,0 +1,113 @@
import type { AstroConfig } from '../../@types/astro';
import { pathToFileURL, fileURLToPath } from 'node:url';
import { normalizePath } from 'vite';
import { prependForwardSlash } from '../path.js';
import { unwrapId, viteID } from '../util.js';
export type PathType =
/**
* file:///Users/name/projects/todos/src/pages/index.astro
*/
'url' |
/**
* /Users/name/projects/todos/src/pages/index.astro
*/
'absolute' |
/**
* /@fs/Users/name/projects/todos/src/pages/index.astro
*/
'vite-fs-path' |
/**
* /src/pages/index.astro
*/
'root-relative' |
/**
* We don't know
*/
'unknown';
class File {
public raw: string;
public root: URL;
public type: PathType;
constructor(raw: string | URL, rootOrConfig: URL | Pick<AstroConfig, 'root'>) {
this.raw = typeof raw === 'string' ? raw : raw.toString();
this.root = rootOrConfig instanceof URL ? rootOrConfig : rootOrConfig.root;
this.type = File.getPathType(this.raw, this.root);
}
/**
* Convert a raw path to a File URL
*/
toFileURL(): URL {
switch(this.type) {
case 'url': return new URL(this.raw);
case 'absolute': return pathToFileURL(this.raw);
case 'vite-fs-path': {
const fsPath = this.raw.slice('/@fs'.length);
return pathToFileURL(fsPath);
}
case 'root-relative': {
return new URL('.' + this.raw, this.root);
}
default: {
throw new Error(`Cannot create file URL for an unknown path type: ${this.raw}`);
}
}
}
/**
* Converts to a path that is relative to the root, for use in browser paths
*/
toRootRelativePath() {
const url = this.toFileURL();
const id = unwrapId(viteID(url));
return prependForwardSlash(id.slice(normalizePath(fileURLToPath(this.root)).length));
}
/**
* Converts to the absolute (string) path. Uses the platform's path separator.
*/
toAbsolutePath() {
return fileURLToPath(this.toFileURL());
}
/**
* Converts to a path for use in browser, contains the `/@fs` prefix understood by Vite.
*/
toViteFSPath() {
const abs = prependForwardSlash(normalizePath(this.toAbsolutePath()));
return '/@fs' + abs;
}
static getPathType(raw: string, root: URL): PathType {
if(raw.startsWith('/@fs')) {
return 'vite-fs-path';
}
if(raw.startsWith('/')) {
const normalRoot = normalizePath(fileURLToPath(root))
if(raw.startsWith(normalRoot)) {
return 'absolute';
} else {
return 'root-relative';
}
}
if(raw.startsWith('file://')) {
return 'url';
}
// Windows drive
if(/[A-Z]:/.test(raw)) {
return 'absolute';
}
return 'unknown';
}
}
export {
File,
File as default
}

View file

@ -151,16 +151,6 @@ export function relativeToSrcDir(config: AstroConfig, idOrUrl: URL | string) {
return id.slice(slash(fileURLToPath(config.srcDir)).length);
}
export function rootRelativePath(config: AstroConfig, idOrUrl: URL | string) {
let id: string;
if (typeof idOrUrl !== 'string') {
id = unwrapId(viteID(idOrUrl));
} else {
id = idOrUrl;
}
return prependForwardSlash(id.slice(normalizePath(fileURLToPath(config.root)).length));
}
export function emoji(char: string, fallback: string) {
return process.platform !== 'win32' ? char : fallback;
}

View file

@ -0,0 +1,96 @@
import { expect } from 'chai';
import { fileURLToPath, pathToFileURL } from 'node:url';
import File from '../../../dist/core/file/index.js';
describe('File abstraction', () => {
describe('constructor', () => {
it('can take a file url', () => {
let root = new URL('./', import.meta.url);
let file = new File(import.meta.url, root);
expect(file.type).to.equal('url');
});
it('can take an absolute path', () => {
let root = new URL('./', import.meta.url);
let path = fileURLToPath(import.meta.url);
let file = new File(path, root);
expect(file.type).to.equal('absolute');
});
it('can take a Vite fs path', () => {
let root = new URL('./', import.meta.url);
let path = '/@fs/some/path';
let file = new File(path, root);
expect(file.type).to.equal('vite-fs-path');
});
it('can take a path relative to the root', () => {
let root = new URL('./', import.meta.url);
let path = '/src/pages/index.astro';
let file = new File(path, root);
expect(file.type).to.equal('root-relative');
});
it('Defaults to unknown', () => {
let root = new URL('./', import.meta.url);
let path = 'some-virtual-id';
let file = new File(path, root);
expect(file.type).to.equal('unknown');
});
it('Can take an AstroConfig as the second argument', () => {
let root = new URL('./', import.meta.url);
let path = fileURLToPath(import.meta.url);
let file = new File(path, { root });
expect(file.type).to.equal('absolute');
});
});
describe('toFileURL', () => {
it('converts from a file URL', () => {
let root = new URL('./', import.meta.url);
let file = new File(import.meta.url, root);
let url = file.toFileURL();
expect(url.toString()).to.equal(import.meta.url);
});
it('converts from an absolute path', () => {
let root = new URL('./', import.meta.url);
let path = fileURLToPath(import.meta.url);
let file = new File(path, root);
let url = file.toFileURL();
expect(url.toString()).to.equal(pathToFileURL(path).toString());
});
it('converts from an Vite fs path', () => {
let root = new URL('./', import.meta.url);
let path = '/@fs/some/path';
let file = new File(path, root);
expect(file.toFileURL().toString()).to.equal('file:///some/path');
});
it('converts from a path relative to the root', () => {
let root = new URL('./', import.meta.url);
let path = '/src/pages/index.astro';
let file = new File(path, root);
let expected = new URL('.' + path, root).toString();
expect(file.toFileURL().toString()).to.equal(expected);
});
it('Throws converting an unknown', () => {
let root = new URL('./', import.meta.url);
let path = 'some-virtual-id';
let file = new File(path, root);
try {
file.toFileURL();
expect(false).to.equal(true);
} catch(err) {
expect(err).to.be.an.instanceOf(Error);
expect(err.message.startsWith('Cannot create file URL')).to.equal(true);
}
});
})
});