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

Add builtin optimize/bundle support (powered by esbuild!) #1615

Merged
merged 1 commit into from
Nov 19, 2020

Conversation

FredKSchott
Copy link
Owner

@FredKSchott FredKSchott commented Nov 15, 2020

Background

Changes

experiments.optimize: {
  /** Your application file entrypoints: HTML if they exist in your build, but JS also supported for Rails, server-side, etc. */
  entrypoints: 'auto' | string[] | (({files: string[]}) => string[]);
  /** Add preload tags to flatten deep import waterfalls (entrypoints must be HTML) */
  preload: boolean;
  /** Bundle your application JS & CSS */
  bundle: boolean; 
  /** Minify the final build (JS, CSS, and HTML (todo)) */
  minify: boolean;
  /** Transpile the final build to a specific target, for older browsers */
  target: 'es2020' | 'es2019' | 'es2018' | 'es2017';
  /** Add a build-manifest.json file to your final build */
  manifest: boolean;
}
  • This support will be experimental! It will hopefully leave the experiments scope as a part of v3.0.
  • This isn't replacing our Webpack or Rollup plugins: this will still be experimental as esbuild continues to undergo development. If you need something more stable, we'll still recommended more stable bundlers.
  • Some of the more aspirational features of Built-in production optimizations (incl. bundling) #1276 are left out of this PR, for future enhancements.

snowpack build Results

Before (2.9sec load over 3G)

Screen Shot 2020-11-15 at 2 53 30 PM

preload: true (2.3sec load over 3G, no observable impact on build speed)

(Notice the waterfall is gone)

Screen Shot 2020-11-15 at 2 57 25 PM

bundle: true (2.0sec load over 3G, no observable impact on build speed)

Screen Shot 2020-11-15 at 2 56 11 PM

Thoughts on esbuild

  • TODO: want to write out some thoughts here about betting on esbuild for our first class bundling support (vs. rollup or webpack)

Testing

  • TODO: Want to get some early feedback before testing.

Docs

  • TODO

@vercel
Copy link

vercel bot commented Nov 15, 2020

This pull request is being automatically deployed with Vercel (learn more).
To see the status of your deployment, click below or on the icon next to each commit.

🔍 Inspect: https://vercel.com/pikapkg/snowpack/5d62dq1c8
✅ Preview: https://snowpack-git-wip-optimize.pikapkg.vercel.app

@FredKSchott
Copy link
Owner Author

Snowpack builds all assets (including npm packages) to ESM JS ahead of time, so we take care of a lot of the ecosystem weirdness before the code ever gets to esbuild. We only need esbuild to bundle valid ESM (all import URLs fully resolved) and CSS. Only the js and css loaders are needed/used on our end.

@evanw would love to get your thoughts on this & if you think esbuild is ready for this, stability- & timing-wise. This would land in Snowpack as an experiment at first but the plan would be to fully support in Snowpack v3.0 in a couple of months.

I have some feedback from the integration that I'd love to share as well, will create a couple follow up issues on your repo tonight/tomorrow.

@evanw
Copy link

evanw commented Nov 16, 2020

@evanw would love to get your thoughts on this & if you think esbuild is ready for this, stability- & timing-wise. This would land in Snowpack as an experiment at first but the plan would be to fully support in Snowpack v3.0 in a couple of months.

Here's what I'd call out:

  • The JS bundler has matured a lot and should be good to use for something like this.

  • CSS support is much newer and still a work in progress. Basic use of @import to join CSS files together should be fine though, and should continue to work. The CSS/JS integration points are primitive and buggy and will change in the future, but it sounds like you don't need those? There also isn't yet support for down-leveling CSS to the target browser environment but I'm guessing you don't need that either. What do you need out of esbuild for CSS, if anything?

  • If you're feeling conservative, I'd recommend pinning the entire version of esbuild to make sure nothing breaks unless you explicitly upgrade esbuild and it passes your test suite. Otherwise please pin both the major and minor versions so that just the patch version varies. I'm using minor versions for breaking API changes because esbuild is pre-1.0.0.

  • Code splitting is still a work in progress. I wouldn't rely on it yet. It only works for ESM output and there is a known ordering bug with cross-chunk imports that I haven't fixed yet.

  • I'm sure you're aware of this but just in case: esbuild can't yet lower most ES6+ syntax to ES5. So you'll either have to post-process or pre-process that syntax away yourself.

I have some feedback from the integration that I'd love to share as well, will create a couple follow up issues on your repo tonight/tomorrow.

Sounds great! Thanks.

@FredKSchott
Copy link
Owner Author

FredKSchott commented Nov 16, 2020

Sounds good to all of the above! Appreciate the response.

What do you need out of esbuild for CSS, if anything?

I could have clarified: we do allow import './foo.css' so there is an interop between JS & CSS that we'll need to rely on. No need for anything more than what's supported today, but if there are any relevant issues to keep an eye on please lmk.

Code splitting is still a work in progress.

Ah right, I saw a mention of that. Will keep an eye out, but yup that's fine for now given the experimental status of things on our end. Webpack & Rollup plugins will still exist for more production-ready use.

@lewisl9029
Copy link
Contributor

I have no opinion on the bundling feature itself as I generally prefer the unbundled preloading approach, but I noticed in the preload benchmark you have some gray bars in the network inspector, which probably means you're running into the tcp connection limit and are not on http2.

If you run the same test using http2 I think the preload version would match/beat the bundled version (my bet is on beat 🙂).

@FredKSchott
Copy link
Owner Author

you're right! I just spun that up on local, we should replace with some real-world examples (H2/H3 + more files).

"cacache": "^15.0.0",
"cachedir": "^2.3.0",
"cheerio": "^1.0.0-rc.3",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Loose suggestion: if you‘re only using cheerio to find specific elements like <script> or <link>, hypertag works well for this (and even parses attributes). It could be a faster & much lighter-weight alternative. But if you need cheerio for something heavier in the future, it‘s fine

Copy link
Owner Author

Choose a reason for hiding this comment

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

yea, the only reason I switched it out for cheerio was because we do a lot more with it. You can see in the file we're querying, modifying, adding and removing different elements as we optimize.

/**
* Given any set of user-input as entrypoints,
*/
async function resolveEntrypoints(
Copy link
Collaborator

Choose a reason for hiding this comment

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

More of a DX observation than anything: for my project, I have src/index.js. So in my config, I specified entrypoints: ['./src/index.js']. But it threw a “not found“ error. I guess I have to specify ./_dist_/index.js instead here?

Copy link
Collaborator

@drwpow drwpow Nov 18, 2020

Choose a reason for hiding this comment

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

Ah I see—it wants HTML entrypoints. Hm, similar thing, though—if I specify ['./public/index.html'] or ['./index.html'] both throw a “not found“ error, so I’m not sure how to use this

Error: ENOENT: no such file or directory, open './public/index.html'

Copy link
Owner Author

Choose a reason for hiding this comment

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

It should work with both JS & HTML entrypoints, but it looks like there's some bug here. since the 'auto' scanner returns absolute paths, i guess it needs absolute file paths to the final build/ output files? Either way, will fix to support relative files 👍

Copy link
Collaborator

Choose a reason for hiding this comment

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

It should work with both JS & HTML entrypoints, but it looks like there's some bug here. since the 'auto' scanner returns absolute paths, i guess it needs absolute file paths to the final build/ output files? Either way, will fix to support relative files 👍

Gotcha. Yeah if I specify .js I get a “only HTML is supported” message right now

Copy link
Owner Author

Choose a reason for hiding this comment

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

Also: need to fix the "seeing modulepreloads when only trying to bundle" issue

@drwpow
Copy link
Collaborator

drwpow commented Nov 18, 2020

In this PR we aren‘t pulling styles out of css.proxy.js files anymore, which could lead to FOUC and just a little bit of delayed TTI (not significant for small sites, but might be with enough CSS files). What are your thoughts on that issue?


interface ESBuildMetaInput {
bytes: number;
imports: {path: string}[];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Love this build manifest format 👍🏻 . Seeing the imports and size is really cool

platform: 'browser',
metafile: path.join(config.buildOptions.out, 'build-manifest.json'),
publicPath: config.buildOptions.baseUrl,
minify: config.experiments.optimize!.minify,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just a note: I’m seeing minify work for JS and CSS, but not HTML. Is that intentional?

Copy link
Owner Author

Choose a reason for hiding this comment

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

intentional for this PR, but agreed that we should tackle eventually. My hope is that esbuild supports HTML one day, and then we'd get it for free. But, until then we may want to ship some popular html minifier.

I couldn't find one on NPM that I really liked, @drwpow any favorites that you've used in the past?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not really. Fine for waiting to see if esbuild supports it as you said

return allBuildFiles.filter((f) => f.endsWith('.html'));
}
if (Array.isArray(entrypoints)) {
return entrypoints;
Copy link
Collaborator

Choose a reason for hiding this comment

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

WDYT about something like this?

  if (Array.isArray(entrypoints)) {
-    return entrypoints;
+    return entrypoints.map(file => file.startsWith(path.sep) ? file : path.join(cwd, file))

Basically, use an absolute path if provided by the user, or assume it‘s relative to the project directory (see above comment for more context)

Copy link
Owner Author

Choose a reason for hiding this comment

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

Done, added handling for relative paths to support both final build directory and source directory.

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.

Built-in production optimizations (incl. bundling)
4 participants