Created
February 2, 2026 08:58
-
-
Save ewmb7701/d14a8bc2cea5d50bd6ccd3df121f1452 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { glob } from "glob"; | |
| import { mkdir, copyFile } from "node:fs/promises"; | |
| import { dirname } from "node:path"; | |
| import { fileURLToPath, pathToFileURL } from "node:url"; | |
| import { Module, ModuleEmitter, ModuleResolver, type API } from "swooce"; | |
| /** | |
| * Module without any content. | |
| * | |
| * Useful for eg, | |
| */ | |
| export class VoidModule extends Module {} | |
| /** | |
| * Module with content. | |
| */ | |
| export abstract class ContentModule<TContent> extends Module { | |
| /** | |
| * Fetch the content of the src file of this module. | |
| */ | |
| abstract fetch(api: API): Promise<TContent>; | |
| } | |
| /** | |
| * Creates a module resolver which resolves the "sidecar file" {@link VoidModule} as a {@link VoidModule}. | |
| * | |
| * Example usage: | |
| * ```ts | |
| * export default SidecarFileVoidModuleResolver(import.meta.url); | |
| * ``` | |
| * | |
| * @param resolverImportMetaURL `import.meta.url` of the resolver. ie, the `import.meta.url` from the esmodule that called this. | |
| */ | |
| export function SidecarFileVoidModuleResolver<TModule extends Module>( | |
| resolverImportMetaURL: string, | |
| moduleFactory: (srcModuleFileURL: URL) => TModule, | |
| ) { | |
| if (resolverImportMetaURL.match(/\.[^/.]+$/)) { | |
| throw new Error( | |
| `Sidecar resolver file has no extension: ${resolverImportMetaURL}`, | |
| ); | |
| } | |
| return class extends ModuleResolver<TModule> { | |
| override async resolve(_api: API) { | |
| const url = new URL(resolverImportMetaURL); | |
| // remove the last extension | |
| url.pathname = url.pathname.replace(/\.[^/.]+$/, ""); | |
| return moduleFactory(url); | |
| } | |
| }; | |
| } | |
| /** | |
| * Creates a module resolver which resolves some files in the sidecar directory as {@link VoidModule}. | |
| * | |
| * # Example usage: | |
| * | |
| * ## Import all .png and .svg images in the sidecar dir | |
| * ```ts | |
| * // src/site/page/post.ts | |
| * export default SidecarDirModuleResolver( | |
| * import.meta.url, // resolver file URL | |
| * "*.md", // glob pattern to match all post markdowns | |
| * (url) => new PostPageModule(url), // factory for each matched file | |
| * ); | |
| * ``` | |
| * | |
| * @param resolverImportMetaURL `import.meta.url` of the resolver. ie, the `import.meta.url` from the esmodule that called this. | |
| */ | |
| export function SidecarDirModuleResolver<T extends Module>( | |
| resolverImportMetaURL: string, | |
| pattern: string, | |
| moduleFactory: (srcModuleFileURL: URL) => T, | |
| ) { | |
| return class extends ModuleResolver<Module> { | |
| override async resolve(_api: API): Promise<Module[]> { | |
| // strip last extension ==> directory name | |
| const sidecarDirURL = new URL( | |
| resolverImportMetaURL.replace(/\.[^/.]+$/, "") + "/", | |
| resolverImportMetaURL, | |
| ); | |
| const sidecarDirPath = fileURLToPath(sidecarDirURL); | |
| const sidecarDirFileMatches = await glob(pattern, { | |
| cwd: sidecarDirPath, | |
| nodir: true, | |
| posix: true, | |
| }); | |
| return sidecarDirFileMatches.map((sidecarDirFileRelativePath) => | |
| moduleFactory(new URL(sidecarDirFileRelativePath, sidecarDirURL)), | |
| ); | |
| } | |
| }; | |
| } | |
| /** | |
| * Creates a module resolver which resolves some files in the sidecar directory. | |
| * | |
| * ie, resolves the resolvers in a sidecar dir. | |
| * | |
| * # Example usage: | |
| * | |
| * ## Import all .png and .svg images in sidecar dir | |
| * ```ts | |
| * // src/assets/images.ts | |
| * export default sidecarDirBarrelModuleResolver(import.meta.url, "*.{png,svg}); | |
| * ``` | |
| * | |
| * @param resolverImportMetaURL `import.meta.url` of the resolver. ie, the `import.meta.url` from the esmodule that called this. | |
| */ | |
| export function SidecarDirBarrelModuleResolver( | |
| resolverImportMetaURL: string, | |
| pattern: string, | |
| ) { | |
| return class extends ModuleResolver<Module> { | |
| override async resolve(api: API): Promise<Module[]> { | |
| // strip last extension ==> directory name | |
| const sidecarDirURL = new URL( | |
| resolverImportMetaURL.replace(/\.[^/.]+$/, "") + "/", | |
| resolverImportMetaURL, | |
| ); | |
| const sidecarDirPath = fileURLToPath(sidecarDirURL); | |
| const sidecarDirFileMatches = await glob(pattern, { | |
| cwd: sidecarDirPath, | |
| nodir: true, | |
| posix: true, | |
| }); | |
| const modules: Module[] = []; | |
| for (const iSidecarDirFileRelativePath of sidecarDirFileMatches) { | |
| const resolverFileURL = pathToFileURL( | |
| fileURLToPath(sidecarDirURL + iSidecarDirFileRelativePath), | |
| ); | |
| // dynamic import of resolver | |
| const imported = await import(resolverFileURL.href); | |
| const ResolverClass = | |
| imported.default as new () => ModuleResolver<Module>; | |
| const resolverInstance = new ResolverClass(); | |
| const resolvedModules = await resolverInstance.resolve(api); | |
| if (Array.isArray(resolvedModules)) { | |
| modules.push(...resolvedModules); | |
| } else { | |
| modules.push(resolvedModules); | |
| } | |
| } | |
| return modules; | |
| } | |
| }; | |
| } | |
| /** | |
| * Module emitter which copies the module src file to its target file path using {@link API}. | |
| */ | |
| export class CopyModuleEmitter extends ModuleEmitter<Module> { | |
| #mkdir: boolean; | |
| async emit(api: API, module: Module): Promise<void> { | |
| const targetFileURL = api.paths.resolveModuleTargetFileURL(api, module); | |
| const srcPath = fileURLToPath(module.srcFileURL); | |
| const targetPath = fileURLToPath(targetFileURL); | |
| if (this.#mkdir) { | |
| await mkdir(dirname(targetPath), { recursive: true }); | |
| } | |
| await copyFile(srcPath, targetPath); | |
| } | |
| constructor(mkdir: boolean) { | |
| super(); | |
| this.#mkdir = mkdir; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment