-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
Reduce browser bundle size to be comparable with alternative formatters #12144
Comments
Thanks for considering using Prettier as the JS formatted. Possible way to reduce the file size:
Look at all our js parser, the But in Just a quick response, more details need investigation. |
Thanks for the quick response! I can do a quick peek into |
I did a little investigation of the |
Additionally, while variable names have been optimized down to single letters, object properties have remained unchanged. Maybe something like Closure Compiler could identify properties that are not exposed outside of the file and rename them too? |
@TimvdLippe You can run |
That's an interesting observation. At DevTools, we recently adopted private class fields, which are minified by default by Terser: terser/terser#780 We have seen a consistent 8-10% bundle size win by enabling this option: https://crrev.com/c/2975285 (we later switched to the default behavior in https://crrev.com/c/3158385/3/scripts/build/rollup.config.js)
Thanks, I will take a look! |
Looking at this file you linked, it seems you already have acorn, if it's going to stay, we don't need |
By implementing a custom function for assert, we no longer rely on the Node global polyfill for the assert module. As part of prettier#12144, I discovered that the node global polyfill is a large contributor to the file size. Currently, on main I obtained the following numbers: ``` > yarn build --file=standalone.js --no-minify > perl -le "map { \$sum += -s } @argv; print \$sum" dist/esm/standalone.mjs < 1207295 > yarn build --file=standalone.js > perl -le "map { \$sum += -s } @argv; print \$sum" dist/esm/standalone.mjs < 562931 ``` With this PR, the numbers change to: ``` > yarn build --file=standalone.js --no-minify > perl -le "map { \$sum += -s } @argv; print \$sum" dist/esm/standalone.mjs < 1136137 > yarn build --file=standalone.js > perl -le "map { \$sum += -s } @argv; print \$sum" dist/esm/standalone.mjs < 530682 ``` This is a 5.89% and 5.72% size reduction for respectively unminified and minified standalone bundle output.
I discovered that one of the factors contributing to a large bundle sizes are the node polyfills that are included in the browser bundle. #12145 is a first step in the direction to remove some of these polyfills, which on their own already constitute nearly 6% size reduction. The other polyfill usages should hopefully be possible as well, so I will be working on that next. |
We should also be able to remove |
@j-f1 Yes I spotted that as well, but it wasn't clear to me yet where that is coming from. I think it is coming from the node polyfills, but I am still attempting to dig deeper what is pulling them in. |
I also found https://github.com/btd/esbuild-visualizer, which can produce a visualization of bundle size (main...j-f1:esbuild-visualizer). You’ll need to run a shell script to make The HTML files, though. # fish (bash should be similar)
for name in dist/*.meta.json
npx esbuild-visualizer --metadata $name --filename $name.html &
end |
@j-f1 Oh that is helpful! Interestingly enough, it claims that certain files are included, but I am not sure they are. E.g. in the build process, the |
For reference, with the inclusion of both #12145 and #12146, this is the current bundle layout: EsBuild Visualizer.pdf. |
You can remove other languages from here to see the size with js printer only Lines 5 to 18 in 38b5b98
|
@TimvdLippe Any comment on this? I'm going to add support for |
@fisker Yeah that would be great! In our case, given that we have Acorn in a special location and don't use CommonJS, I think we can integrate our custom parser? But based on https://github.com/prettier/prettier/pull/12172/files#diff-114bc4621fa54fbb70ff0442c6dbc427a114dca4cfcf798f52c11fa5493f23f3 that should be relatively straightforward. With regards to #12144 (comment) I made that change, but sadly it does not reduce the bundle size. I am not sure what is going on, but looking at the visualization report posted on #12144 (comment) I wonder if we can split up these language printers? E.g. can we put these language printers in separate bundles that we can import with dynamic import (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#dynamic_imports)? That way, we can choose ourselves what to load, just like the parsers. |
We can, but it's too hard for other users to use the standalone version of Prettier, it will requires user import prettier from 'prettier/standalone';
import jsPrinter from 'prettier/js-printer';
import babelPlugin from 'prettier/parser-babel';
prettier.format('', {plugins: [jsPrinter, babelPlugin]}) They may use more languages. |
@fisker I see. In that case, can we preserve the existing default standalone bundle, but vendor separate chunks that users (like Chrome DevTools) can use if file size is a concern? The standalone version could then even reuse the new chunks and automatically configure all plugins for users so that the default experience is still easy to consume. |
I tested, comment out those lines reduces the file size. $ yarn build --file=standalone.js --report --print-size
- standalone.js...................................................510 kB DONE
+ standalone.js...................................................372 kB DONE |
Ah yes now I can verify these numbers. Strange, I couldn't before. Sadly Prettier + JS + CSS + HTML printers is around 484 kB (which is a lot better than the original 900kB!), but still a bit above our budget of 100kB. I spotted a couple of more things:
With these improvements, the bundle is down from 484kB to 385kB, so another 100kB off, which seems pretty good progress. |
|
Thanks for the additional context. That made me realize that DevTools might need fewer pieces of Prettier than regular users, since we control the plugins and parser ourselves. Therefore, if we can hook Acorn into Prettier and only use the printer, I think we already cover the use case of DevTools: "Given a well-formed file, format it". To that extent, we would happily ship 50-100 lines of glue code to hookup Acorn/other parsers with Prettier and call the relevant printers and retrieve the end result. Would it therefore be possible to have a separate bundle for the printers that we could import? In that case, how much work do you think it would be to hook these two components together with a minimal amount of options/plugins overhead from Prettier? |
We already have this Line 342 in 9106e7e
I think that's all you need. |
I think we can add a special entry for your case, all you need pass is:
You can still use all our options, but we won't verify them. |
Ah that's good to know. I can tinker next week and see whether it is possible to create a bundle for that (or what would be necessary to make it possible to extract the function with tree-shaking). I wonder whether adopting ES modules (which you were already planning in #10157) would allow us to import only that function and Rollup would tree-shake away all other functionality that we don't use. That would prevent a separate bundle, but would require the standalone bundle to be tree-shakeable (which afaik is not possible with CommonJS, is it?). |
I'll be on vacation(Chinese new year) next week, maybe @sosukesuzuki too? Good luck! |
I don't have any vacation plans. So I can help you 👍 |
this dependency is also one of the bigger "chunks" in Web Dev Server and I look at it at some point in the past... Dependency graph of @babel/code-frame visualized replacing chalk with for example colorette could save around ~16kB Is anyone up for starting a discussion at the babel repo? "Same story" for vnopt => it's only 20kb by "itself" but uses chalk... so it's 40kb the issue with chalk is that it's everywhere... so to get the size bonus you need to replace it everywhere which becomes an organizational nightmare 😅 -- edit: collorette is not 1kb but 3.8kb minified => code save would be ~16kB and not ~19kB as previously written |
Thanks @daKmoR for the suggestion. I opened babel/babel#14213 to that extent 😄 |
Babel maintainer here 👋 Unfortunately we cannot remove Specifically, something like this: // @babel/code-frame
const highlight = require("@babel/highlight");
module.exports = () => highlight.red(); // @babel/highlight
exports.red = () => "RED";
exports.getChalk = function () {
return require("chalk");
}; would it tree-shake chalk away? (I know that rollup supports some CommonJS tree-shaking, but I don't know what exactly). |
And you don't load |
We do, but https://github.com/prettier/prettier/blob/main/scripts/build/shims/babel-highlight.cjs |
Oh awesome, thanks 👍 |
We don't really care about what babel use, we use Chalk ourself. |
While debugging the function main() {
if (true) {
throw new Error('something')
}
try {
console.log('something else');
} catch (e) {
// ignored
}
}
main(); Here, the Line 47 in a0cc4ab
try-catch , esbuild doesn't properly remove it. If you were to write the following, it does correctly remove the console.log :
function main() {
if (true) {
throw new Error('something')
}
console.log('something else');
}
main(); This is reported at esbuild at evanw/esbuild#1317 and listed in their caveats of https://esbuild.github.io/api/#minify-considerations As a result of this behavior, Therefore, would it maybe make sense to move to Terser for minification (https://terser.org/)? Unfortunately, it doesn't appear to be possible to combine Terser and esbuild (https://esbuild.github.io/plugins/#plugin-api-limitations), but maybe we can use esbuild during dev and use Rollup + terser for production builds? We are using Terser at Chrome DevTools and have seen significant size reductions/tree-shaking capabilities compared to other tools. |
We won't switch wo rollup, because esbuild is much faster, but we can add one extra terser call, if you are interest in minify check this #12184 |
With regards to the suggestion in #12144 (comment), I made the following (temporary) changes:
That's below 100 kB and a massive drop (compared to 400 kB) 🎉 This is definitely getting closer to what we are looking for! There are some more optimizations possible, most notably those related to how
The caveat with this approach is that I disabled all languages, to get a baseline value. If I re-enable the
Therefore, we are getting closer to feasibility, but there are some further cleanups required to get below 100kB officially. For that to work, I think migrating to ES Modules (#10157) is the biggest bang for the buck, given that it should (if I understand correctly) allow for more granular tree-shaking with esbuild. |
Environments:
Steps to reproduce:
I would like to include Prettier as part of a browser web page. In this case, I am looking at integrating Prettier into Chrome DevTools, which has a feature to automatically format web content in our sources panel. This feature is currently implemented using custom-written formatters (example).
The current formatters rely on the CodeMirror 5 tokenizers, which I am working on removing: https://bugs.chromium.org/p/chromium/issues/detail?id=1287519 As part of that work, I am analyzing the state of affairs of formatters used on the web and which ones we could potentially use. Since Prettier is an active and popular project, it is one of the first I am looking at.
I have a basic version of Prettier integration working, but I quickly discovered a blocker for us: the bundle size of Prettier greatly exceeds the size budget I have available. At Chromium, we have strict size restrictions for our projects, as users install Chrome on low-space devices. If I include only the standalone bundle + babel parser (required for JS), it clocks in at ~900Kb. I am working with a budget of around 100Kb, as we have both our CodeMirror 5 tokenizers as well as custom implementation of formatting.
At the same time, I looked at alternatives such as js-beautify, which is 40Kb in total (25Kb main bundle, 15Kb for css + html), which is well within our budget. However, js-beautify is less active than Prettier, which is why I initially would prefer to adopt Prettier.
FWIW this problem was previously mentioned at #10157 (comment) and I am not sure how much the ES Modules situation would help reducing the bundle size. Looking at the Skypack size, it shows 185Kb network size (https://cdn.skypack.dev/-/prettier@v2.5.1-2KrfQhZNe1CJFznlAR6m/dist=es2019,mode=imports/unoptimized/esm/standalone.mjs), but that is compressed with brotli. If I download the file and obtain the filesize, it is 720Kb. Since Chromium bundles files and then serves it with brotli, our network size would be more manageable, but unfortunately the disk space limitation is calculated on the original uncompressed size. Hence we are looking at the 720Kb file size rather than the 185Kb network size.
Would it be possible to reduce the bundle size of Prettier to be comparable to alternative formatters and make it feasible for Chrome DevTools to adopt Prettier?
Expected behavior:
The Prettier bundle size is manageable, ideally below 100Kb, similar to js-beautify and alternatives.
Actual behavior:
Using only the standalone bundle + babel parser (e.g. not including CSS or HTML, which we also would need) clocks in at ~900Kb.
The text was updated successfully, but these errors were encountered: