Skip to content

Commit

Permalink
Merge branch 'develop' of github.com:privatenumber/fs-require
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber committed Oct 24, 2021
2 parents 00c7a7f + d2aa13c commit 342b36b
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 222 deletions.
99 changes: 15 additions & 84 deletions src/fs-require.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@ import vm from 'vm';
import path from 'path';
import Module from 'module';
import {
Options,
FileSystemLike,
fsRequire,
Loaders,
implicitExtensions,
loaderTypes,
ModuleCache,
} from './types';
import {
isFilePathPattern,
hasValidExtensionPattern,
getBareSpecifier,
isDirectory,
} from './utils';
import { isBareSpecifier, resolveBareSpecifier } from './utils/resolve-bare-specifier';
import { resolveModule } from './utils/resolve-module';

export type { FileSystemLike as FileSystem };

Expand Down Expand Up @@ -53,53 +48,8 @@ loaders['.json'] = function (newModule, sourceCode) {
newModule.exports = JSON.parse(sourceCode);
};

type Resolved = {
extension: (typeof loaderTypes)[number];
filePath: string;
}

function resolve(
fs: FileSystemLike,
filePath: string,
): Resolved | null {
// Exact match
if (fs.existsSync(filePath)) {
if (isDirectory(fs, filePath)) {
return (
resolve(fs, path.join(filePath, 'index.js'))
|| resolve(fs, path.join(filePath, 'index.json'))
);
}

const extension = (filePath.match(hasValidExtensionPattern)?.[0] ?? '') as Resolved['extension'];
return {
extension,
filePath,
};
}

// Try extensions
for (const extension of implicitExtensions) {
const filePathWithExtension = filePath + extension;
if (fs.existsSync(filePathWithExtension)) {
return {
extension,
filePath: filePathWithExtension,
};
}
}

return null;
}

const realRequire = require;

let idCounter = 0;

type Options = {
fs?: boolean | FileSystemLike;
};

export const createFsRequire = (
mfs: FileSystemLike,
options?: Options,
Expand All @@ -109,41 +59,21 @@ export const createFsRequire = (
const moduleCache: ModuleCache = Object.create(null);

function makeRequireFunction(parentModule: Module): fsRequire {
const require = (modulePath: string) => {
if (!isFilePathPattern.test(modulePath)) {
const [moduleName, moduleSubpath] = getBareSpecifier(modulePath) ?? [];

if (moduleName === 'fs') {
const { fs } = options ?? {};

// If true, use native fs (can still be truthy)
if (fs !== true) {
const shimFs = fs || mfs;

if (!moduleSubpath) {
return shimFs;
}

if (moduleSubpath === '/promises' && ('promises' in shimFs)) {
return shimFs.promises;
}

throw new Error(`Cannot find module '${modulePath}'`);
}
}

return realRequire(modulePath);
const resolve = (modulePath: string) => {
if (isBareSpecifier(modulePath)) {
return modulePath;
}
const resolved = resolveModule(mfs, parentModule, modulePath);
return resolved.filePath;
};

let filePath = path.resolve(path.dirname(parentModule.filename), modulePath);

const resolvedPath = resolve(mfs, filePath);

if (!resolvedPath) {
throw new Error(`Cannot find module '${modulePath}'`);
const require: fsRequire = (modulePath: string) => {
if (isBareSpecifier(modulePath)) {
return resolveBareSpecifier(mfs, modulePath, options);
}

filePath = resolvedPath.filePath;
const resolvedPath = resolveModule(mfs, parentModule, modulePath);
const { filePath } = resolvedPath;

let importedModule = moduleCache[filePath];

Expand All @@ -167,6 +97,7 @@ export const createFsRequire = (
};

require.id = fsRequireId;
require.resolve = resolve;
require.cache = moduleCache;

return require;
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Module from 'module';

export type Options = {
fs?: boolean | FileSystemLike;
};

// These are the only methods fs-require needs to use
export interface FileSystemLike {
readFileSync: (
Expand All @@ -16,6 +20,7 @@ export interface FileSystemLike {
export type ModuleCache = Record<string, Module>;
export type fsRequire = {
(modulePath: string): any;
resolve: (modulePath: string) => string;
id: number;
cache: ModuleCache;
};
Expand Down
22 changes: 0 additions & 22 deletions src/utils.ts

This file was deleted.

14 changes: 14 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FileSystemLike, implicitExtensions } from '../types';

export const hasValidExtensionPattern = new RegExp(
`(${
implicitExtensions
.map(extension => extension.replace(/\./g, '\\$&'))
.join('|')
})$`,
);

export const isDirectory = (
fs: FileSystemLike,
directoryPath: string,
) => fs.lstatSync(directoryPath).isDirectory();
40 changes: 40 additions & 0 deletions src/utils/resolve-bare-specifier.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Options, FileSystemLike } from '../types';

const isFilePathPattern = /^[./]/;
export const isBareSpecifier = (modulePath: string) => !isFilePathPattern.test(modulePath);

const specifierPattern = /^(?:node:)?((?:@[\da-z][\w.-]+\/)?[\da-z][\w.-]+)(\/.+)?$/;
const parseBareSpecifier = (
modulePath: string,
) => modulePath.match(specifierPattern)?.slice(1, 3);

const realRequire = require;

export function resolveBareSpecifier(
mfs: FileSystemLike,
modulePath: string,
options?: Options,
) {
const [moduleName, moduleSubpath] = parseBareSpecifier(modulePath) ?? [];

if (moduleName === 'fs') {
const { fs } = options ?? {};

// If true, use native fs (can still be truthy)
if (fs !== true) {
const shimFs = fs || mfs;

if (!moduleSubpath) {
return shimFs;
}

if (moduleSubpath === '/promises' && ('promises' in shimFs)) {
return shimFs.promises;
}

throw new Error(`Cannot find module '${modulePath}'`);
}
}

return realRequire(modulePath);
}
68 changes: 68 additions & 0 deletions src/utils/resolve-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import path from 'path';
import Module from 'module';
import {
FileSystemLike,
implicitExtensions,
loaderTypes,
} from '../types';
import {
hasValidExtensionPattern,
isDirectory,
} from '.';

type Resolved = {
extension: (typeof loaderTypes)[number];
filePath: string;
}

function resolveModuleSafe(
mfs: FileSystemLike,
parentModule: Module,
modulePath: string,
): Resolved | null {
// Absolute path
modulePath = path.resolve(path.dirname(parentModule.filename), modulePath);

// Exact match
if (mfs.existsSync(modulePath)) {
if (isDirectory(mfs, modulePath)) {
return (
resolveModuleSafe(mfs, parentModule, path.join(modulePath, 'index.js'))
|| resolveModuleSafe(mfs, parentModule, path.join(modulePath, 'index.json'))
);
}

const extension = (modulePath.match(hasValidExtensionPattern)?.[0] ?? '') as Resolved['extension'];
return {
extension,
filePath: modulePath,
};
}

// Try extensions
for (const extension of implicitExtensions) {
const filePathWithExtension = modulePath + extension;
if (mfs.existsSync(filePathWithExtension)) {
return {
extension,
filePath: filePathWithExtension,
};
}
}

return null;
}

export function resolveModule(
mfs: FileSystemLike,
parentModule: Module,
modulePath: string,
) {
const resolved = resolveModuleSafe(mfs, parentModule, modulePath);

if (!resolved) {
throw new Error(`Cannot find module '${modulePath}'`);
}

return resolved;
}
Loading

0 comments on commit 342b36b

Please sign in to comment.