fuse-box
Version:
Fuse-Box a bundler that does it right
148 lines (147 loc) • 7.51 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fileLookup = exports.resolveIfExists = void 0;
const fs = require("fs");
const path = require("path");
const parseTypescriptConfig_1 = require("../compilerOptions/parseTypescriptConfig");
const utils_1 = require("../utils/utils");
const JS_EXTENSIONS = ['.js', '.jsx', '.mjs'];
const TS_EXTENSIONS = ['.ts', '.tsx'];
const TS_EXTENSIONS_FIRST = [...TS_EXTENSIONS, ...JS_EXTENSIONS];
const JS_EXTENSIONS_FIRST = [...JS_EXTENSIONS, ...TS_EXTENSIONS];
function isFileSync(path) {
return utils_1.fileExists(path) && fs.lstatSync(path).isFile();
}
// A SubPathResolver that simply checks the actual filesystem
exports.resolveIfExists = (base, target = '', type, props = {}) => {
const absPath = path.join(base, target);
switch (type) {
case 'dir':
return ((utils_1.fileExists(absPath) &&
fs.lstatSync(absPath).isDirectory() && Object.assign({ absPath, extension: path.extname(absPath), fileExists: true }, props)) ||
undefined);
case 'exists':
case undefined:
return ((utils_1.fileExists(absPath) && Object.assign({ absPath, extension: path.extname(absPath), fileExists: true }, props)) || undefined);
case 'file':
return ((utils_1.fileExists(absPath) &&
fs.lstatSync(absPath).isFile() && Object.assign({ absPath, extension: path.extname(absPath), fileExists: true }, props)) ||
undefined);
default:
// never
return undefined;
}
};
function fileLookup(props) {
if (!props.fileDir && !props.filePath) {
throw new Error('Failed to lookup. Provide either fileDir or filePath');
}
const jsFirst = props.javascriptFirst && !props.typescriptFirst;
const base = path.normalize(props.filePath ? path.dirname(props.filePath) : props.fileDir);
const subpathResolver = props.subPathResolver || exports.resolveIfExists;
const target = props.target && path.normalize(props.target);
return resolveSubmodule(base, target, subpathResolver, true, jsFirst, props.isBrowserBuild);
}
exports.fileLookup = fileLookup;
function resolveSubmodule(base, target, resolveSubpath, checkPackage, jsFirst, isBrowserBuild) {
// If an exact file exists, return it
const exactFile = resolveSubpath(base, target, 'file');
if (exactFile) {
return exactFile;
}
// If a file exists after adding an extension, return it
const extensions = jsFirst ? JS_EXTENSIONS_FIRST : TS_EXTENSIONS_FIRST;
for (const extension of extensions) {
const withExtension = resolveSubpath(base, `${target}${extension}`, 'file');
if (withExtension) {
return withExtension;
}
}
// If we should check for a package.json, do that
// We don't always check because we might be here by following the "main" field from a previous package.json
// and no further package.json files should be followed after that
if (checkPackage) {
const packageJSONPath = path.join(base, target, 'package.json');
if (isFileSync(packageJSONPath)) {
const packageJSON = JSON.parse(utils_1.readFile(packageJSONPath));
if (isBrowserBuild && packageJSON['browser'] && typeof packageJSON['browser'] === 'string') {
const browser = path.join(target, packageJSON['browser']);
const subresolution = resolveSubmodule(base, browser, resolveSubpath, false, jsFirst, isBrowserBuild);
return subresolution;
}
// NOTE: the implementation of "local:main" has a couple of flaws
// 1. the isLocal test is fragile and won't work in Yarn 2
// 2. it is incorrect to simply assume that the tsconfig at the root of the package is the right one
// a more robust solution would be to use tsconfig references which can map outputs to inputs
// and then you can just "main" instead of "local:main" and the output will be mapped to the input
// and tsconfig references also solve the "which tsconfig to use?" problem
const isLocal = !/node_modules/.test(packageJSONPath);
if (isLocal && packageJSON['local:main']) {
const localMain = path.join(target, packageJSON['local:main']);
const subresolution = resolveSubmodule(base, localMain, resolveSubpath, false, jsFirst, isBrowserBuild);
// TODO: not used?
const submodules = path.resolve(base, path.join(target, 'node_modules'));
const monorepoModulesPaths = utils_1.fileExists(submodules) ? submodules : undefined;
return Object.assign(Object.assign({}, subresolution), { customIndex: true, isDirectoryIndex: true,
// TODO: not used?
monorepoModulesPaths, tsConfigAtPath: loadTsConfig(path.join(base, target)) });
}
if (packageJSON['ts:main']) {
const tsMain = path.join(target, packageJSON['ts:main']);
const subresolution = resolveSubmodule(base, tsMain, resolveSubpath, false, jsFirst, isBrowserBuild);
if (subresolution.fileExists) {
return Object.assign(Object.assign({}, subresolution), { customIndex: true, isDirectoryIndex: true });
}
}
if (packageJSON['module']) {
const mod = path.join(target, packageJSON['module']);
const subresolution = resolveSubmodule(base, mod, resolveSubpath, false, jsFirst, isBrowserBuild);
if (subresolution.fileExists) {
return Object.assign(Object.assign({}, subresolution), { customIndex: true, isDirectoryIndex: true });
}
}
if (packageJSON['main']) {
const main = path.join(target, packageJSON['main']);
const subresolution = resolveSubmodule(base, main, resolveSubpath, false, jsFirst, isBrowserBuild);
if (subresolution.fileExists) {
return Object.assign(Object.assign({}, subresolution), { customIndex: true, isDirectoryIndex: true });
}
}
// We do not look for index.js (etc.) here
// Because we always look for those whether we are checking package.json or not
// So that's done outside the if.
}
}
// If an index file exists return it
for (const extension of extensions) {
const asIndex = resolveSubpath(base, path.join(target, `index${extension}`), 'file', {
isDirectoryIndex: true,
});
if (asIndex) {
return asIndex;
}
}
const asJson = resolveSubpath(base, `${target}.json`, 'file', {
customIndex: true,
});
if (asJson) {
return asJson;
}
return {
absPath: path.join(base, target),
fileExists: false,
};
}
function loadTsConfig(packageDir) {
const tsConfig = path.resolve(packageDir, 'tsconfig.json');
if (isFileSync(tsConfig)) {
const tsConfigParsed = parseTypescriptConfig_1.parseTypescriptConfig(tsConfig);
if (!tsConfigParsed.error) {
return {
absPath: packageDir,
compilerOptions: tsConfigParsed.config.compilerOptions,
tsconfigPath: tsConfig,
};
}
}
}