From 4825f19e305081d340fb68629a5e5ba813555f46 Mon Sep 17 00:00:00 2001 From: Kristian Jones Date: Mon, 30 Mar 2020 15:10:45 -0500 Subject: [PATCH] fix(Resovler): Refactor resolver and module loader to prevent loading of URLs and already discovered javascript files (#28) --- .devcontainer.json | 5 +++- .vscode/settings.json | 2 +- Testing/Runner/Utils/runTests.ts | 4 ++- src/findFiles.ts | 4 +-- src/index.ts | 46 +++++++++++++++++++++----------- 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/.devcontainer.json b/.devcontainer.json index 157e090..5d09890 100644 --- a/.devcontainer.json +++ b/.devcontainer.json @@ -10,10 +10,13 @@ "SHELL": "/bin/sh" }, - "extensions": ["esbenp.prettier-vscode"], + "extensions": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"], + "remoteUser": "node", + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", "workspaceFolder": "/workspace", + "mounts": [ "source=ts-esnode-modules,target=/workspace/node_modules,type=volume" ], diff --git a/.vscode/settings.json b/.vscode/settings.json index 3662b37..25fa621 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { "typescript.tsdk": "node_modules/typescript/lib" -} \ No newline at end of file +} diff --git a/Testing/Runner/Utils/runTests.ts b/Testing/Runner/Utils/runTests.ts index 74d9be0..8e77c49 100644 --- a/Testing/Runner/Utils/runTests.ts +++ b/Testing/Runner/Utils/runTests.ts @@ -9,7 +9,9 @@ export interface Result { } export function runTest(test: Test): Promise { - const worker = spawnWorker(test.path, {}); + const worker = spawnWorker(test.path, { + helloWorld: ['test', 'test2'], + }); return new Promise((resolve, reject) => { worker.on('exit', (exitCode) => { diff --git a/src/findFiles.ts b/src/findFiles.ts index 46b31aa..53a7916 100644 --- a/src/findFiles.ts +++ b/src/findFiles.ts @@ -3,8 +3,6 @@ import { promises as fs } from 'fs'; import { resolve as resolvePath } from 'path'; import { pathToFileURL, URL } from 'url'; -const JS_EXTS = ['.js', '.jsx']; - interface FileRule { fileName: string; extensions: string[]; @@ -24,7 +22,7 @@ async function findFile( if (directoryEntry.name.includes(fileName)) { if (directoryEntry.isDirectory()) return true; - for (let extension of [...extensions, ...JS_EXTS]) { + for (let extension of extensions) { if (directoryFileName === fileName + extension) { return true; } diff --git a/src/index.ts b/src/index.ts index 5a63f08..00065bf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,26 +2,26 @@ import { createRequire } from 'module'; import { basename, dirname } from 'path'; import ts from 'typescript'; -import { fileURLToPath, pathToFileURL, URL, format } from 'url'; +import { fileURLToPath, pathToFileURL } from 'url'; import { findFiles } from './findFiles'; import { + ModuleFormat, ResolveContext, ResolveResponse, Source, TransformContext, TransformResponse, - ModuleFormat, } from './types'; import { getTSConfig } from './Utils'; const rootModulePath = `${process.cwd()}/`; const baseURL = pathToFileURL(rootModulePath).href; -const relativePathRegex = /^\.{0,2}[/]/; +const relativePathRegex = /^\.{1,2}[/]/; // TODO: Allow customization of extensions const extensions = ['.ts', '.tsx']; -const extensionsRegex = new RegExp(`\\${extensions.join('$|\\')}$`); +const extensionsRegex = new RegExp(`\\${extensions.join('$|\\')}`); // Custom resolver to allow `.ts` and `.tsx` extensions, along with finding files if no extension is provided. export async function resolve( @@ -31,8 +31,11 @@ export async function resolve( ): Promise { const { parentURL = baseURL } = context; + const resolvedUrl = new URL(specifier, parentURL); + const fileName = basename(resolvedUrl.pathname); + // If we can already see a `.ts` or `.tsx` extensions then we can create a File URL - if (extensionsRegex.test(specifier)) { + if (extensionsRegex.test(fileName)) { // Node.js normally errors on unknown file extensions, so return a URL for // specifiers ending in the TypeScript file extensions. return { @@ -43,14 +46,14 @@ export async function resolve( /** * If no extension is passed and is a relative import then let's try to find a `.ts` or `.tsx` file at the path */ - if (relativePathRegex.test(specifier) && !specifier.startsWith('file:')) { - const fileURL = new URL(specifier, parentURL); - const filePath = fileURLToPath(fileURL); + if (relativePathRegex.test(specifier)) { + const filePath = fileURLToPath(resolvedUrl); const file = await findFiles(dirname(filePath), { - fileName: basename(filePath), + fileName, extensions, }); + return { url: file.href, }; @@ -65,13 +68,19 @@ export async function resolve( * @param url fileURL given by Node.JS */ export async function dynamicInstantiate(url: string) { - // Create a Node.JS Require using the `node_modules` folder as the base URL. - const require = createRequire( - `${url.split('/node_modules/')[0].replace('file://', '')}/node_modules/`, + const moduleUrl = new URL(url); + + const [nodeModulesBase, specifier] = moduleUrl.pathname.split( + 'node_modules/', ); + const nodeModuleUrl = new URL('node_modules', pathToFileURL(nodeModulesBase)); + + // Create a Node.JS Require using the `node_modules` folder as the base URL. + const require = createRequire(nodeModuleUrl); + // Import the module file path - let dynModule = require(url.replace(/.*\/node_modules\//, '')); + let dynModule = require(specifier); /** * This is needed to allow for default exports in CommonJS modules. @@ -105,8 +114,11 @@ export async function getFormat( let format = formatCache.get(url); if (format) return { format }; + const resolvedUrl = new URL(url); + const fileName = basename(resolvedUrl.pathname); + // If it's a TypeScript extension then force `module` mode. - if (extensionsRegex.test(url)) format = 'module'; + if (extensionsRegex.test(fileName)) format = 'module'; if (!format) { const defaultResolve = defaultGetFormat(url, context, defaultGetFormat) as { @@ -136,8 +148,11 @@ export async function transformSource( context: TransformContext, defaultTransformSource: Function, ): Promise { + const resolvedUrl = new URL(context.url); + const fileName = basename(resolvedUrl.pathname); + // Only transform TypeScript Modules - if (extensionsRegex.test(context.url)) { + if (extensionsRegex.test(fileName)) { const sourceFilePath = fileURLToPath(context.url); // Load the closest `tsconfig.json` to the source file @@ -149,7 +164,6 @@ export async function transformSource( reportDiagnostics: true, }); - // TODO: Actually "check" the TypeScript Code. return { source: transpiledModule.outputText, };