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: use composite builds to speed up compilation #692

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from

Conversation

rflechtner
Copy link
Contributor

@rflechtner rflechtner commented Nov 29, 2022

fixes KILTProtocol/ticket#2331

In this PR I introduce several optimisations to bring down typescript compile times.

  1. Composite builds and package references. This means that packages that did not change will not be touched on a re-build; therefore, if you change a single file the whole project recompiles in a few seconds. The only downside is that in order to make this work we'll need to maintain references in the tsconfig files of each package that mirrors their dependencies on other packages in the monorepo (as indicated by their package.json).
  2. Only build esm files on full builds: I introduced a build:dev command that is sufficient for testing and development. It only builds cjs and type files.
  3. No typings in esm build: We had type definition files being added to both cjs and esm build folders, which I don't think is necessary; the package.json only points to typings in the cjs folder.

How to test:

  • Try building on your machine.
  • Try importing package builds from both esm and cjs code and ts code compiled to cjs and esm.

Checklist:

  • I have verified that the code works
  • I have verified that the code is easy to understand
    • If not, I have left a well-balanced amount of inline comments
  • I have left the code in a better state
  • I have documented the changes (where applicable)

@rflechtner rflechtner self-assigned this Nov 29, 2022
@rflechtner rflechtner added the ♻️ refactoring chore: refactoring label Nov 29, 2022
@ntn-x2
Copy link
Member

ntn-x2 commented Nov 29, 2022

I'll review tomorrow, but I pulled in Tom as well for a review, since I would not rely on my own review for this stuff.

Copy link
Member

@ntn-x2 ntn-x2 left a comment

Choose a reason for hiding this comment

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

I like the improvements! I left few comments/questions, but as I said, I would like someone else's opinion on the changes.

package.json Outdated
"check": "tsc -p tsconfig.json --noEmit",
"build": "yarn workspaces foreach -p -t --exclude '{root-workspace}' run build",
"check": "tsc -b tsconfig.json",
"build": "yarn clean && yarn workspaces foreach -pt --exclude 'root-workspace' run build",
Copy link
Member

Choose a reason for hiding this comment

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

If we run yarn clean before a build, we lose the incremental compilation benefit you mention in the PR description, I guess. So we should probably not clean by default.

Copy link
Contributor Author

@rflechtner rflechtner Nov 30, 2022

Choose a reason for hiding this comment

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

This is by design; yarn build is only meant to be run for production builds essentially (it's also the command run in CI). Running a clean beforehand makes sure we don't get any weird artefacts. For standard builds and rebuilds in development you can simply use build:dev.

Copy link
Member

Choose a reason for hiding this comment

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

As mentioned below, we should then somehow advertise that build:dev is the to-go command then.

"check": "tsc -b tsconfig.json",
"build": "yarn clean && yarn workspaces foreach -pt --exclude 'root-workspace' run build",
"build:dev": "yarn workspaces foreach -pt --exclude 'root-workspace' run build:cjs",
"build:watch": "yarn build:dev && tsc -b tsconfig.json --watch",
Copy link
Member

Choose a reason for hiding this comment

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

What's the benefit of this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It complains as soon as you break something and is more efficient as running yarn check or yarn build:dev repeatedly. Also I would have to double check but potentially you need to have it running if you are using jest in watch mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just checked, you would want to have this running as well if jest is running in watch mode to make sure packages are being rebuild on changes. Otherwise imports from another package may use out of date versions.

Copy link
Member

Choose a reason for hiding this comment

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

So this is supposed to be run before and during tests? And yarn build:dev alone then when?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, this is for watch mode. As in, you keep jest running and it re-executes tests whenever you change a file. So far we didn't have a solution for that. You can also just run yarn build:dev && yarn test whenever you want to test of course. It's a question of preference.

As for the naming, I was considering making yarn build:dev the new yarn build and introducing a yarn build:production instead for release builds. Do you think that's better? I decided to go the other way because there is no harm in running the production build in a development scenario (it's just slower) but it would be bad to release a development build because the esm files are missing.

Copy link
Member

Choose a reason for hiding this comment

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

This makes sense. Rather use the build for development and something else for production.

package.json Outdated
"build": "yarn workspaces foreach -p -t --exclude '{root-workspace}' run build",
"check": "tsc -b tsconfig.json",
"build": "yarn clean && yarn workspaces foreach -pt --exclude 'root-workspace' run build",
"build:dev": "yarn workspaces foreach -pt --exclude 'root-workspace' run build:cjs",
Copy link
Member

Choose a reason for hiding this comment

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

I guess this is to be used before unit/integration tests? Can we mention it somewhere? Otherwise I am 99% sure people will still be running yarn build.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, I haven't updated READMEs and such. Could also add comments above the respective scripts

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay so we don't have a section on development in the README and we can't add comments to the package.json, where do you think we should put that info?

Copy link
Member

Choose a reason for hiding this comment

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

I guess the README then? I don't see any other place where this could go, personally. Maybe @arty-name has some prior experience for this?

@rflechtner
Copy link
Contributor Author

I'll review tomorrow, but I pulled in Tom as well for a review, since I would not rely on my own review for this stuff.

Agree, would be a great idea to get @arty-name's review on this as well.

@arty-name
Copy link
Collaborator

I barely have experience with managing monorepos, but I do often see how they invite piling on workarounds and complexity. I am still of the opinion that the SDK should not be a monorepo, and everything will become much simpler and way, way quicker.

Composite builds? Never heard of them, frankly.

@ntn-x2
Copy link
Member

ntn-x2 commented Dec 1, 2022

@arty-name just out of curiosity, how would a non-monorepo SDK look like? Still all packages in the same repo, I guess? And then each with its own package.json and without a workspace linking them?

@arty-name
Copy link
Collaborator

Why would they even need their own package.json?

An example: move /packages/core/src/* to /src/core/. That’s it!

@arty-name
Copy link
Collaborator

An example of increasing complexity: now you need to add extra knowledge to the readme, and there are multiple places that "must be in sync with @KILTprotocol imports in package.json". And then you might be adding in the readme on how to diagnose and solve weird errors that happen when you forget to manually update that thing that must be in sync.

@ntn-x2
Copy link
Member

ntn-x2 commented Dec 1, 2022

An example: move /packages/core/src/* to /src/core/. That’s it!

I see. So it would become one big package instead of multiple smaller ones, right?

@arty-name
Copy link
Collaborator

Yes, making something a monorepo is splitting a big package into multiple smaller ones, while removing a monorepo is joining multiple smaller packages into a single bigger one. I have been reporting for quite some time that the SDK’s experiment with a monorepo has failed, in my opinion, and should be reverted.

@rflechtner
Copy link
Contributor Author

I don't really see the end-the-monorepo discussion as related to this change. I'm not saying we shouldn't do it, but it's a different thing. It would completely change the surface of the sdk, while this is just an optimisation of internals.
On the question of added complexity: Essentially, this change introduces a "development build mode" that differs from the current build strategy in two aspects:

  1. Don't clean before build; make sure to rebuild a package only if files changed.
  2. Don't build esm files, you don't need them if you don't publish.

That brings down rebuild times from a whopping 2-3 minutes to less than 10 seconds. This is vital for me to be able to work efficiently.
Everything else is an add-on; if we don't need watch mode or efficient yarn check runs we could also get rid of project references and then we're back to the original complexity, with the difference that I have an extra build command that lets me run tests without a coffee break waiting for the project to build.

@arty-name
Copy link
Collaborator

I don't really see the end-the-monorepo discussion as related to this change.

According to the PR title, this change is to speed up things, and the "no monorepo" achieves the same goal of 10 seconds. At least I had that a couple of months ago in a quick test.

It would completely change the surface of the sdk

As far as I can remember, using sub-packages was never recommended, and the surface of @kiltprotocol/sdk-js will not change.

@rflechtner
Copy link
Contributor Author

According to the PR title, this change is to speed up things, and the "no monorepo" achieves the same goal of 10 seconds. At least I had that a couple of months ago in a quick test.

That's great, but we simply do not know what would break if we discontinue 10 packages we have published to npm, and 'not having recommended to use them' does not really change anything about that. This is going to take a bit of evaluation and deliberation and is not going to happen tomorrow.
Optimising ts compilation in the current setup has little to do with that, and that's the thing I want to get feedback on. So still think we are sidetracking here.

@arty-name
Copy link
Collaborator

As I have said in my first comment on this PR #692 (comment) I cannot really contribute here, sorry. I guess the PR achieves its goals 🤷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
♻️ refactoring chore: refactoring
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants