From 63e379c14627cc2eba4955200bb4954b506788e3 Mon Sep 17 00:00:00 2001 From: Matthew Phillips Date: Thu, 9 Mar 2023 15:17:25 -0500 Subject: [PATCH] file abstraction --- packages/astro/src/core/file/index.ts | 113 ++++++++++++++++++++ packages/astro/src/core/util.ts | 10 -- packages/astro/test/units/file/file.test.js | 96 +++++++++++++++++ 3 files changed, 209 insertions(+), 10 deletions(-) create mode 100644 packages/astro/src/core/file/index.ts create mode 100644 packages/astro/test/units/file/file.test.js diff --git a/packages/astro/src/core/file/index.ts b/packages/astro/src/core/file/index.ts new file mode 100644 index 000000000..1e20ad80c --- /dev/null +++ b/packages/astro/src/core/file/index.ts @@ -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) { + 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 +} diff --git a/packages/astro/src/core/util.ts b/packages/astro/src/core/util.ts index 51abe62c1..a7152e41a 100644 --- a/packages/astro/src/core/util.ts +++ b/packages/astro/src/core/util.ts @@ -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; } diff --git a/packages/astro/test/units/file/file.test.js b/packages/astro/test/units/file/file.test.js new file mode 100644 index 000000000..f9170d18f --- /dev/null +++ b/packages/astro/test/units/file/file.test.js @@ -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); + } + }); + }) +});