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

Switches the build to ESBuild #2362

Merged
merged 31 commits into from
Jan 26, 2021
Merged

Switches the build to ESBuild #2362

merged 31 commits into from
Jan 26, 2021

Conversation

arcanis
Copy link
Member

@arcanis arcanis commented Jan 12, 2021

What's the problem this PR addresses?

It used to take 28s to build the CLI. With this diff it becomes 6s (on the other hand, went 1.62MB -> 1.92MB).

How did you fix it?

Replaced the bundle building by an esbuild-based config. It only uses the wasm version, so it's a bit slower than it could, but this way we don't have to deal with postinstall scripts.

Checklist

  • I have set the packages that need to be released for my changes to be effective.
  • I will check that all automated PR checks pass before the PR gets reviewed.

@cspotcode
Copy link

Is this intended to eventually replace #2228, or should #2228 be updated / replaced with a PR using esbuild?

esbuild's per-file compilation currently has a lot of overhead but we've investigated some ways to make it faster. I'm not sure how likely those improvements are to land.

Comment on lines -144 to -167
// esprima is only needed for parsing !!js/function, which isn't part of the FAILSAFE_SCHEMA.
// Unfortunately, js-yaml declares it as a hard dependency and requires the entire module,
// which causes webpack to add 0.13 MB of unused code to the bundle.
// Fortunately, js-yaml wraps the require call inside a try / catch block, so we can just ignore it.
// Reference: https://github.com/nodeca/js-yaml/blob/34e5072f43fd36b08aaaad433da73c10d47c41e5/lib/js-yaml/type/js/function.js#L15
new webpack.IgnorePlugin({
resourceRegExp: /^esprima$/,
contextRegExp: /js-yaml/,
}),
Copy link
Member

@merceyz merceyz Jan 12, 2021

Choose a reason for hiding this comment

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

This is most likely why the size went up, would be good if we could still exclude that

Copy link
Member Author

Choose a reason for hiding this comment

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

Esprima isn't in the bundle. I suspect it's because ESBuild uses the browser version of js-yaml rather than the Node one, so Esprima isn't requested. I don't remember what the problem was with the browser version - things seem to work 🤔

Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't let it use the browser version of anything as we can pretty much be certain those are polyfilled into oblivion

@arcanis
Copy link
Member Author

arcanis commented Jan 12, 2021

Is this intended to eventually replace #2228, or should #2228 be updated / replaced with a PR using esbuild?

Good question, I'm not sure yet tbh 🤔

I would probably tend to prefer using the exact same transpiler on both branches, if only to make sure that they have very similar behaviours (can matter with things like initialization order, etc, or simply supported features). What do you think?

}),
new webpack.BannerPlugin({
entryOnly: true,
banner: `#!/usr/bin/env node\n/* eslint-disable */`,
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't we continue telling eslint to ignore the bundle? (and add prettier-ignore as well)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep 👍

@cspotcode
Copy link

cspotcode commented Jan 12, 2021

I would probably tend to prefer using the exact same transpiler on both branches, if only to make sure that they have very similar behaviours (can matter with things like initialization order, etc, or simply supported features).

Agreed, using the same one makes the most sense.

The per-file compilation overhead is caused by esbuild launching as a new external process for each sync compiler call. (async can share a single background process)

evanw/esbuild#590 and evanw/esbuild#612 are about using a worker_thread to talk to a shared esbuild process for all sync compiler calls. It still needs some work. I haven't had much time for open-source over the last month.

I suppose, alternatively, the worker_thread stuff could be implemented as a third-party library that wraps esbuild. But that adds extra complexity to yarn's tooling.

@7rulnik
Copy link
Contributor

7rulnik commented Jan 12, 2021

Have you seen webpack's persistent-caching? Not sure that build time would be the same as esbuild, but I think it was an option too.

@arcanis
Copy link
Member Author

arcanis commented Jan 12, 2021

@7rulnik Would be interesting to check, I'm probably doing something wrong - after upgrading from Webpack 5.1 to 5.13, the build time went from 28s to 39s, and the cache didn't appear in .yarn/cache.

@7rulnik
Copy link
Contributor

7rulnik commented Jan 12, 2021

@arcanis I can check it tomorrow. I tried it in my work project and build time went from 12 to 2 minutes

@7rulnik
Copy link
Contributor

7rulnik commented Jan 13, 2021

@arcanis I debugged a bit webpack caching by adding this options:

cache: {
    type: `filesystem`,
},
infrastructureLogging: {
    debug: /webpack\.cache/,
},

and got two errors:

  [webpack.cache.PackFileCacheStrategy] No pack exists at /Users/7rulnik/projects/opensource/berry/packages/yarnpkg-cli/.yarn/.cache/webpack/default-production.pack: Error: ENOENT: no such file or directory, open '/Users/7rulnik/projects/opensource/berry/packages/yarnpkg-cli/.yarn/.cache/webpack/default-production/index.pack'
[webpack.cache.PackFileCacheStrategy] Pack got invalid because of write to: ResolverCachePlugin|normal|dependencyType=|esm|path=|/Users/7rulnik/projects/opensource/berry/packages/yarnpkg-cli|request=|./sources/cli.ts

So seems that you were looking in the wrong place for the cache. But more importantly, it was never created because of sources/cli.ts. Not sure that it's the real reason, but magic with val-loader seems strange for me.

Then I tried to move webpack config to plain webpack.config.js and dropped out resolve.alias and val-loader. As I understand, with this configuration it bundles only yarn itself, without plugins. So after that cache was created. The cold run took 16s and warm (with cache) to 505ms. I think we should investigate what is wrong when we run webpack via API.

@merceyz
Copy link
Member

merceyz commented Jan 14, 2021

The build fails on Windows but since the CI doesn't build on Windows it wont catch it. The output also shows that we're not handling failures correctly as it still prints the "success" path

➤ YN0000: ┌ Building the CLI
➤ YN0001: │ Error: Build failed with 1 error:
error: Invalid path: C:\berry\packages\yarnpkg-cli/bundles/yarn.js
    at failureErrorWithLog (C:\berry\.yarn\cache\esbuild-wasm-npm-0.8.32-7de142bcb8-fe6ae67708.zip\node_modules\esbuild-wasm\lib\main.js:969:15)
    at buildResponseToResult (C:\berry\.yarn\cache\esbuild-wasm-npm-0.8.32-7de142bcb8-fe6ae67708.zip\node_modules\esbuild-wasm\lib\main.js:767:32)
    at C:\berry\.yarn\cache\esbuild-wasm-npm-0.8.32-7de142bcb8-fe6ae67708.zip\node_modules\esbuild-wasm\lib\main.js:819:20
    at handleIncomingPacket (C:\berry\.yarn\cache\esbuild-wasm-npm-0.8.32-7de142bcb8-fe6ae67708.zip\node_modules\esbuild-wasm\lib\main.js:566:9)
    at Socket.readFromStdout (C:\berry\.yarn\cache\esbuild-wasm-npm-0.8.32-7de142bcb8-fe6ae67708.zip\node_modules\esbuild-wasm\lib\main.js:482:7)
    at Socket.emit (events.js:315:20)
    at addChunk (internal/streams/readable.js:309:12)
    at readableAddChunk (internal/streams/readable.js:284:9)
    at Socket.Readable.push (internal/streams/readable.js:223:10)
    at Pipe.onStreamRead (internal/stream_base_commons.js:188:23)
➤ YN0000: └ Completed in 0s 679ms

➤ YN0000: ✓ Done building the CLI!
➤ YN0000: ? Bundle path: C:\berry\packages\yarnpkg-cli/bundles/yarn.js
➤ YN0000: ? Bundle size: 1.62 MB

➤ YN0000: → @yarnpkg/plugin-essentials
➤ YN0000: → @yarnpkg/plugin-compat
➤ YN0000: → @yarnpkg/plugin-dlx
➤ YN0000: → @yarnpkg/plugin-file
➤ YN0000: → @yarnpkg/plugin-git
➤ YN0000: → @yarnpkg/plugin-github
➤ YN0000: → @yarnpkg/plugin-http
➤ YN0000: → @yarnpkg/plugin-init
➤ YN0000: → @yarnpkg/plugin-link
➤ YN0000: → @yarnpkg/plugin-node-modules
➤ YN0000: → @yarnpkg/plugin-npm
➤ YN0000: → @yarnpkg/plugin-npm-cli
➤ YN0000: → @yarnpkg/plugin-pack
➤ YN0000: → @yarnpkg/plugin-patch
➤ YN0000: → @yarnpkg/plugin-pnp

cc @evanw

@arcanis
Copy link
Member Author

arcanis commented Jan 21, 2021

As a quick status update, this is currently blocked on evanw/esbuild#687

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.

4 participants