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

feat: generate TypeScript project references #343

Merged
merged 12 commits into from
Jan 3, 2019

Conversation

rix0rrr
Copy link
Contributor

@rix0rrr rix0rrr commented Jan 2, 2019

Include TypeScript project references in the generated tsconfig.json and properly pass them to the inline compiler as well.

Creating project references enables quick (and more correct) full-repo
rebuilds, as well as allowing Visual Studio to do Find References across
all packages in the repository.

BREAKING CHANGE: all handwritten TypeScript projects that generated projects are mixed
with need composite: true configured, and a root-level index of all possible packages has to be
generated as well.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

Copy link
Contributor

@eladb eladb left a comment

Choose a reason for hiding this comment

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

Goodness!

* "composite: true" we consider them project references.
*
* Unfortunately it doesn't seem like the TypeScript compiler itself
* resolves transitive references in a way that
Copy link
Contributor

Choose a reason for hiding this comment

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

that...?


// If there's no package file, don't do anything
if (!await fs.pathExists(packageJsonPath)) { return []; }
const pkg = require(packageJsonPath);
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe we are loading package.json like 10 times already. If not too hard, maybe we can just pass that into the function?

const pkg = require(packageJsonPath);

const ret = new Array<string>();
for (const dependencyMap of [pkg.dependencies, pkg.devDependencies]) {
Copy link
Contributor

Choose a reason for hiding this comment

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

peerDependencies should also be in that list I think

if (dependencyMap === undefined) { continue; }

for (const depName of Object.keys(dependencyMap)) {
let tsconfigFile = path.join('node_modules', depName, 'tsconfig.json');
Copy link
Contributor

Choose a reason for hiding this comment

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

That's not necessarily going to work (for example, if npm hoists modules to an upper "node_modules").
You should use require.resolve(xxx, { paths: [ dir ] })

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is still not how that function works I believe, and all instances where we have it in our code only work because of fairy dust.

The { paths } argument to require.resolve() needs to be a list of search directories, not a list of directories from which the search algorithm will be started. In other words, it already needs to be this list:

[ '/home/local/ANT/huijbers/Temp/node_modules',
  '/home/local/ANT/huijbers/node_modules',
  '/home/local/ANT/node_modules',
  ...
]

I think you're supposed to use require.resolve.paths() to build this list, but for some reason that doesn't work on my machine. Investigating.

Copy link
Contributor Author

@rix0rrr rix0rrr Jan 2, 2019

Choose a reason for hiding this comment

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

Ah yes, and require.resolve.paths(package) returns

the list of paths where we would search for package

if we were to search for package

from the JS file that contains the require.resolve.paths() command.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In fact, this seems to be what we need except it's Node 10.12+.

Going to have to construct this by hand.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would say that we don't want project references for globally installed modules

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed

* This makes it so that running 'tsc' and running 'jsii' has the same behavior.
*/
private async determineSources(files: string[]): Promise<void> {
this.rootFiles.splice(0);
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make more sense to just return the list of files instead of mutate rootFiles? Seems unnecessarily convoluted.

Rico Huijbers added 3 commits January 2, 2019 12:00
Brings the dependency loading phase of jsii compilation for the
'aws-sns' package on my machine down from 30s -> 2s.
- Don't load package.json again
- Also crawl peerDependencies
- Better NodeJS resolution mechanism
- determineSources no longer compiles in-place
try {
dep = require.resolve(depName, { paths });
} catch (e) {
// We failed to 'require' the given dependency. This might be valid if the package
Copy link
Contributor

Choose a reason for hiding this comment

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

You should always be able to resolve pkg/package.json (but ignoring is also fine)

Copy link
Contributor Author

@rix0rrr rix0rrr Jan 3, 2019

Choose a reason for hiding this comment

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

cdk-build-tools for example does not point "main" to a valid JS file, so cannot be require()d.

Copy link
Contributor

Choose a reason for hiding this comment

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

Why not? require('cdk-build-tools/package.json') should just return the contents of it's package.json without loading the main file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that's how require() works, but let me double-check.

Copy link
Contributor Author

@rix0rrr rix0rrr Jan 3, 2019

Choose a reason for hiding this comment

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

Oh !@#%$ it is T_T

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well in that case I can probably go require("pkg/tsconfig.json") and save myself some hassle.


/**
* Compiles the configured program.
*
* @param files can be specified to override the standard source code location logic. Useful for example when testing "negatives".
*/
public async emit(...files: string[]): Promise<EmitResult | never> {
await this.buildTypeScriptConfig();
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason these are two separate functions?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. They're two different operations.

Copy link
Contributor

Choose a reason for hiding this comment

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

Are they used elsewhere in the core individually?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, but I don't think this is a very good argument.

Do you propose to not make helper functions at all and just inline all code of every project into a 1000-line main() function monstrosity, just because it so happens that every function is only called once?

They're separate because determining what the configuration should be, and writing the configuration to disk are two distinct operations.


/**
* Compiles the configured program.
*
* @param files can be specified to override the standard source code location logic. Useful for example when testing "negatives".
*/
public async emit(...files: string[]): Promise<EmitResult | never> {
await this.buildTypeScriptConfig();
await this.writeTypeScriptConfig();
this.rootFiles = await this.determineSources(files);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need rootFiles as a member? Can we make this a local variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Because I wanted to put information about the state of compilation on the Compiler object on purpose.

@rix0rrr rix0rrr merged commit 5eec5dc into master Jan 3, 2019
@rix0rrr rix0rrr deleted the huijbers/generate-project-references branch January 3, 2019 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants