Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/sandpack-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"micromatch": "^4.0.4",
"node-fetch": "^2.6.1",
"sandbox-hooks": "0.1.0",
"source-map": "^0.7.3"
"source-map": "^0.7.3",
"strip-json-comments": "3"
}
}
12 changes: 6 additions & 6 deletions packages/sandpack-core/src/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { endMeasure, now } from '@codesandbox/common/lib/utils/metrics';
import DependencyNotFoundError from 'sandbox-hooks/errors/dependency-not-found-error';
import ModuleNotFoundError from 'sandbox-hooks/errors/module-not-found-error';

import { PackageCache, resolveAsync, resolveSync } from './resolver/resolver';
import { ResolverCache, resolveAsync, resolveSync } from './resolver/resolver';
import { generateBenchmarkInterface } from './utils/benchmark';
import { Module } from './types/module';
import {
Expand Down Expand Up @@ -193,7 +193,7 @@ export default class Manager implements IEvaluator {
// All paths are resolved at least twice: during transpilation and evaluation.
// We can improve performance by almost 2x in this scenario if we cache the lookups
cachedPaths: { [path: string]: { [path: string]: string } };
resolverPackageCache: PackageCache;
resolverCache: ResolverCache;

configurations: ParsedConfigurationFiles;

Expand Down Expand Up @@ -225,7 +225,7 @@ export default class Manager implements IEvaluator {
this.stage = 'transpilation';
this.version = options.versionIdentifier;
this.esmodules = new Map();
this.resolverPackageCache = new Map();
this.resolverCache = new Map();

/**
* Contribute the file fetcher, which needs the manager to resolve the files
Expand Down Expand Up @@ -283,7 +283,7 @@ export default class Manager implements IEvaluator {
// Call this whenever the file structure or modules change, so before each compilation...
resetResolverCache() {
this.cachedPaths = {};
this.resolverPackageCache = new Map();
this.resolverCache = new Map();
}

async evaluate(path: string, baseTModule?: TranspiledModule): Promise<any> {
Expand Down Expand Up @@ -830,7 +830,7 @@ export default class Manager implements IEvaluator {
isFile: this.isFile,
readFile: this.readFile,
moduleDirectories: this.getModuleDirectories(),
packageCache: this.resolverPackageCache,
resolverCache: this.resolverCache,
});

endMeasure(measureKey, { silent: true, lastTime: measureStartTime });
Expand Down Expand Up @@ -975,7 +975,7 @@ export default class Manager implements IEvaluator {
isFile: this.isFile,
readFile: this.readFile,
moduleDirectories: this.getModuleDirectories(),
packageCache: this.resolverPackageCache,
resolverCache: this.resolverCache,
});
endMeasure(measureKey, { silent: true, lastTime: measureStartTime });

Expand Down
Empty file.
Empty file.
Empty file.
9 changes: 9 additions & 0 deletions packages/sandpack-core/src/resolver/fixture/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"baseUrl": "src",
"paths": {
"~/*": ["./*"],
"@app/*": ["app/*", "not_app/*"],
"@config/*": ["app_config/*"],
"something-special": ["app/something"]
}
}
42 changes: 42 additions & 0 deletions packages/sandpack-core/src/resolver/resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,4 +480,46 @@ describe('resolve', () => {
expect(normalizeModuleSpecifier('react//test')).toBe('react/test');
});
});

describe('tsconfig', () => {
it('should be able to resolve relative to basePath of tsconfig.json', () => {
const resolved = resolveSync('app', {
filename: '/foo.js',
extensions: ['.ts', '.tsx', '.js', '.jsx'],
isFile,
readFile,
});
expect(resolved).toBe('/src/app/index.js');
});

it('should be able to resolve paths that are simple aliases', () => {
const resolved = resolveSync('something-special', {
filename: '/foo.js',
extensions: ['.ts', '.tsx', '.js', '.jsx'],
isFile,
readFile,
});
expect(resolved).toBe('/src/app/something.js');
});

it('should be able to resolve wildcard paths with single char', () => {
const resolved = resolveSync('~/app_config/test', {
filename: '/foo.js',
extensions: ['.ts', '.tsx', '.js', '.jsx'],
isFile,
readFile,
});
expect(resolved).toBe('/src/app_config/test.js');
});

it('should be able to resolve wildcard paths with name', () => {
const resolved = resolveSync('@app/something', {
filename: '/foo.js',
extensions: ['.ts', '.tsx', '.js', '.jsx'],
isFile,
readFile,
});
expect(resolved).toBe('/src/app/something.js');
});
});
});
68 changes: 61 additions & 7 deletions packages/sandpack-core/src/resolver/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,26 @@ import * as pathUtils from '@codesandbox/common/lib/utils/path';
import { ModuleNotFoundError } from './errors/ModuleNotFound';
import { ProcessedPackageJSON, processPackageJSON } from './utils/pkg-json';
import { isFile, FnIsFile, FnReadFile, getParentDirectories } from './utils/fs';
import {
ProcessedTSConfig,
processTSConfig,
getPotentialPathsFromTSConfig,
} from './utils/tsconfig';

export type PackageCache = Map<string, any>;
export type ResolverCache = Map<string, any>;

export interface IResolveOptionsInput {
filename: string;
extensions: string[];
isFile: FnIsFile;
readFile: FnReadFile;
moduleDirectories?: string[];
packageCache?: PackageCache;
resolverCache?: ResolverCache;
}

interface IResolveOptions extends IResolveOptionsInput {
moduleDirectories: string[];
packageCache: PackageCache;
resolverCache: ResolverCache;
}

function normalizeResolverOptions(opts: IResolveOptionsInput): IResolveOptions {
Expand All @@ -38,7 +43,7 @@ function normalizeResolverOptions(opts: IResolveOptionsInput): IResolveOptions {
isFile: opts.isFile,
readFile: opts.readFile,
moduleDirectories: [...normalizedModuleDirectories],
packageCache: opts.packageCache || new Map(),
resolverCache: opts.resolverCache || new Map(),
};
}

Expand All @@ -55,16 +60,16 @@ function* loadPackageJSON(
const directories = getParentDirectories(filepath, rootDir);
for (const directory of directories) {
const packageFilePath = pathUtils.join(directory, 'package.json');
let packageContent = opts.packageCache.get(packageFilePath);
let packageContent = opts.resolverCache.get(packageFilePath);
if (packageContent === undefined) {
try {
packageContent = processPackageJSON(
JSON.parse(yield* opts.readFile(packageFilePath)),
pathUtils.dirname(packageFilePath)
);
opts.packageCache.set(packageFilePath, packageContent);
opts.resolverCache.set(packageFilePath, packageContent);
} catch (err) {
opts.packageCache.set(packageFilePath, false);
opts.resolverCache.set(packageFilePath, false);
}
}
if (packageContent) {
Expand Down Expand Up @@ -259,6 +264,37 @@ export function normalizeModuleSpecifier(specifier: string): string {
return normalized;
}

const TS_CONFIG_CACHE_KEY = '__root_tsconfig';
function* getTSConfig(
opts: IResolveOptions
): Generator<any, ProcessedTSConfig | false, any> {
const cachedConfig = opts.resolverCache.get(TS_CONFIG_CACHE_KEY);
if (cachedConfig != null) {
return cachedConfig;
}

let config: ProcessedTSConfig | false = false;
try {
const contents = yield* opts.readFile('/tsconfig.json');
const processed = processTSConfig(contents);
if (processed) {
config = processed;
}
} catch (err) {
try {
const contents = yield* opts.readFile('/jsconfig.json');
const processed = processTSConfig(contents);
if (processed) {
config = processed;
}
} catch {
// do nothing
}
}
opts.resolverCache.set(TS_CONFIG_CACHE_KEY, config);
return config;
}

export const resolver = gensync<
(moduleSpecifier: string, inputOpts: IResolveOptionsInput) => string
>(function* resolve(moduleSpecifier, inputOpts): Generator<any, string, any> {
Expand All @@ -267,6 +303,24 @@ export const resolver = gensync<
const modulePath = yield* resolveModule(normalizedSpecifier, opts);

if (modulePath[0] !== '/') {
// This isn't a node module, we can attempt to resolve using a tsconfig/jsconfig
if (!opts.filename.includes('/node_modules')) {
const parsedTSConfig = yield* getTSConfig(opts);
if (parsedTSConfig) {
const potentialPaths = getPotentialPathsFromTSConfig(
modulePath,
parsedTSConfig
);
for (const potentialPath of potentialPaths) {
try {
return yield* resolve(potentialPath, opts);
} catch {
// do nothing, it's probably a node_module in this case
}
}
}
}

try {
return yield* resolveNodeModule(modulePath, opts);
} catch (e) {
Expand Down
49 changes: 49 additions & 0 deletions packages/sandpack-core/src/resolver/utils/tsconfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import stripJsonComments from 'strip-json-comments';
import * as pathUtils from '@codesandbox/common/lib/utils/path';

export interface ProcessedTSConfig {
baseUrl: string;
paths: Record<string, string>;
}

export function processTSConfig(content: string): ProcessedTSConfig | null {
const parsed = JSON.parse(stripJsonComments(content));
if (parsed.baseUrl) {
const paths: ProcessedTSConfig['paths'] = {};
if (parsed.paths) {
for (const p of Object.keys(parsed.paths)) {
paths[p] = parsed.paths[p].map((val: string) => {
return pathUtils.join('/', parsed.baseUrl, val).replace(/\*/g, '');
});
}
}

return {
baseUrl: pathUtils.join('/', parsed.baseUrl),
paths,
};
}
return null;
}

export function getPotentialPathsFromTSConfig(
moduleSpecifier: string,
config: ProcessedTSConfig
): string[] {
const res = [];
for (const p of Object.keys(config.paths)) {
if (p.endsWith('*')) {
const prefix = p.substring(0, p.length - 1);
if (moduleSpecifier.startsWith(prefix)) {
const suffix = moduleSpecifier.substr(prefix.length);
for (const alias of config.paths[p]) {
res.push(alias + suffix);
}
}
} else if (moduleSpecifier === p) {
res.push(...config.paths[p]);
}
}
res.push(pathUtils.join(config.baseUrl, moduleSpecifier));
return res;
}
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -30673,7 +30673,7 @@ [email protected], strip-json-comments@^2.0.0, strip-json-comments@^2.0.
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"

strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
strip-json-comments@3, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
Expand Down