Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(tsconfig): respect path mapping #136

Closed
Closed
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
62 changes: 57 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const fs = require('fs');
const cabinet = require('filing-cabinet');
const debug = require('debug')('tree');
const Config = require('./lib/Config');
const { createMatchPath } = require('tsconfig-paths')

/**
* Recursively find all dependencies (avoiding circular) traversing the entire dependency tree
Expand All @@ -25,7 +26,7 @@ const Config = require('./lib/Config');
* @param {Boolean} [options.noTypeDefinitions] For TypeScript imports, whether to resolve to `*.js` instead of `*.d.ts`.
* @return {Object}
*/
module.exports = function(options) {
module.exports = function (options) {
const config = new Config(options);

if (!fs.existsSync(config.filename)) {
Expand Down Expand Up @@ -67,7 +68,7 @@ module.exports = function(options) {
*
* Params are those of module.exports
*/
module.exports.toList = function(options) {
module.exports.toList = function (options) {
options.isListForm = true;

return module.exports(options);
Expand All @@ -81,7 +82,7 @@ module.exports.toList = function(options) {
* @param {Config} config
* @return {Array}
*/
module.exports._getDependencies = function(config) {
module.exports._getDependencies = function (config) {
let dependencies;
const precinctOptions = config.detectiveConfig;
precinctOptions.includeCore = false;
Expand All @@ -97,12 +98,22 @@ module.exports._getDependencies = function(config) {
return [];
}


const tsMatchPath = (() => {
if (config.tsConfig) {
const absoluteBaseUrl = path.join(path.dirname(config.tsConfigPath), config.tsConfig.compilerOptions.baseUrl)
// REF: https://github.com/dividab/tsconfig-paths#creatematchpath
return createMatchPath(absoluteBaseUrl, config.tsConfig.compilerOptions.paths)
}
return (alias) => undefined;
})()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

@jjangga0214 jjangga0214 Aug 16, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrjoelkemp That is to prevent using let over const. (A matter of personal preference, so I understand even if you don't like it.)

For example, let's say we should initialize foo by some condition.

// In this example, assume a ternary operator cannot be used, 
// as if-else blocks can be large in real cases.
  let foo
  if (bar) {
    foo = 1
  } else {
    foo = 2
  }

foo is declared as let, so it can be mutated from somewhere else when it should not be.
To prevent this, some (especially functional) languages provides syntax sugar or special approach.
For instance, Rust can treat if-else as an "expression", not a "statement".

// In Rust, pure `let` without `mut` keyword is equivalent to `const` in Javascript.
let foo = if bar {
    1
} else {
    2
};

In Javascript, calling IIFE for variable initialization is a similar pattern.

// foo now becomes `const` !
const foo = (() => {
    if (bar) {
      return 1
    }
    return 2
  })()


const resolvedDependencies = [];

for (let i = 0, l = dependencies.length; i < l; i++) {
const dep = dependencies[i];

const result = cabinet({
let result = cabinet({
partial: dep,
filename: config.filename,
directory: config.directory,
Expand All @@ -114,6 +125,46 @@ module.exports._getDependencies = function(config) {
noTypeDefinitions: config.noTypeDefinitions
});

// Check if the dep is ts path mapping alias, and if so, update the result.
if (!result) {
result = (() => {
// REF: https://github.com/dividab/tsconfig-paths#creatematchpath
const resolvedTsAliasPath = tsMatchPath(dep) // Get absolute path by ts path mapping. `undefined` if non-existent
if (resolvedTsAliasPath) {
const stat = (() => {
try {
// fs.statSync throws an error if path is non-existent
return fs.statSync(resolvedTsAliasPath)
} catch (error) {
return undefined
}
})()
if (stat) {
if (stat.isDirectory()) {
// When directory is imported, index file is resolved
for (const indexFile of ['index.ts', 'index.tsx', 'index.js', 'index.jsx']) {
const filename = path.join(resolvedTsAliasPath, indexFile)
if (fs.existsSync(filename)) {
return filename
}
}
}
// if the path is complete filename
return resolvedTsAliasPath
} else {
// For cases a file extension is omitted when being imported
for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {
const filename = resolvedTsAliasPath + ext
if (fs.existsSync(filename)) {
return filename
}
}
}
}
return undefined
})()
}

if (!result) {
debug('skipping an empty filepath resolution for partial: ' + dep);
config.nonExistent.push(dep);
Expand All @@ -123,6 +174,7 @@ module.exports._getDependencies = function(config) {
const exists = fs.existsSync(result);

if (!exists) {

config.nonExistent.push(dep);
debug('skipping non-empty but non-existent resolution: ' + result + ' for partial: ' + dep);
continue;
Expand Down Expand Up @@ -158,7 +210,7 @@ function traverse(config) {
if (config.filter) {
debug('using filter function to filter out dependencies');
debug('unfiltered number of dependencies: ' + dependencies.length);
dependencies = dependencies.filter(function(filePath) {
dependencies = dependencies.filter(function (filePath) {
return config.filter(filePath, config.filename);
});
debug('filtered number of dependencies: ' + dependencies.length);
Expand Down
5 changes: 4 additions & 1 deletion lib/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ class Config {
if (!this.directory) { throw new Error('directory not given'); }
if (this.filter && typeof this.filter !== 'function') { throw new Error('filter must be a function'); }

// tsConfigPath is needed to calculate absolute baseUrl, which the function `createMatchPath` of the package `tsconfig-paths` requires
this.tsConfigPath = options.tsConfigPath
if ('string' === typeof this.tsConfig) {
debug('preparsing the ts config into an object for performance');
const ts = require('typescript');
const tsParsedConfig = ts.readJsonConfigFile(this.tsConfig, ts.sys.readFile);
const obj = ts.parseJsonSourceFileConfigFileContent(tsParsedConfig, ts.sys, path.dirname(this.tsConfig));
this.tsConfigPath = this.tsConfig
this.tsConfig = obj.raw;
}

Expand All @@ -39,7 +42,7 @@ class Config {
debug('visited: ', this.visited);
}

clone () {
clone() {
return new Config(this);
}
}
Expand Down
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"debug": "^4.3.1",
"filing-cabinet": "^3.0.0",
"precinct": "^8.0.0",
"tsconfig-paths": "^3.10.1",
"typescript": "^3.9.7"
},
"devDependencies": {
Expand Down