Skip to content

feat: Reduce build size by enabling assume_function_wrapper build flag#9921

Open
gonfunko wants to merge 8 commits into
v13from
build-size
Open

feat: Reduce build size by enabling assume_function_wrapper build flag#9921
gonfunko wants to merge 8 commits into
v13from
build-size

Conversation

@gonfunko

@gonfunko gonfunko commented May 21, 2026

Copy link
Copy Markdown
Contributor

The basics

The details

Resolves

Proposed Changes

This is an entirely LLM-generated change. It does seemingly work, and cuts the size of blockly_compressed.js from 1093 KB (216 KB zipped) to 624 KB (176 KB zipped). I split it into two commits; the first is just a formatter run, the second has the actual changes.

@gonfunko gonfunko requested a review from maribethb May 21, 2026 21:22
@gonfunko gonfunko requested a review from a team as a code owner May 21, 2026 21:22
@github-actions github-actions Bot added the PR: feature Adds a feature label May 21, 2026
cpcallen added 5 commits June 8, 2026 15:41
Since the chunk export files are source files to Closure Compiler,
separate the creation of the former from the invocation of the
latter.  Specifically:

- Rename writeChunkExportFiles to buildChunkExporters.
- Invoked as a separate task in the minify series, instead of
  calling it directly from buildCompiled.
- Revert the changes to buildCompiled that made it an async
  function just so it could call writeChunkExportFiles.
The existing code results in each chunk overwriting the same
well-known property ($.__chunkExports__).  Since these properties
are only expected to be read once, in the same chunk's wrapper's
factory function, this isn't strictly wrong - but it made
understanding the minified bundles produced by PR #9912 a bit
confusing.
Note that some comments have been deleted without replacement;
these made statements which are no longer true.
Reorder the new code that generates the chunk exporters, to put
it together with (but before) the code that generates the chunk
wrappers, since the two are closely coupled.
@github-actions github-actions Bot added PR: feature Adds a feature and removed PR: feature Adds a feature labels Jun 8, 2026
For consistency with code and docs, call the files that contain code
which retrieves the chunks' export objects "chunk exporters", since
"chunk exports" better describes the objects being exported.
@cpcallen

cpcallen commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

It took a while to remind myself how the code in build_tasks.mjs works, and a bit longer to remind myself how the code it generates works, but after fairly carefully inspecting the *_compressed.js minified bundles I believe the changes made by the LLM are basically correct, and that the bundled chunks should work properly.

A few notes about Closure Compiler's behaviour:

  • Even with only SIMPLE_OPTIMIZATIONS, the compiler was already renaming method parameters and local variables to short names (a, b, c, etc.). That's great.
  • Additionally, SIMPLE_OPTIMIZATIONS causes Closure Compile to replace many property accesses with variables—e.g. instead of using property foo.bar.baz, it will mostly use var foo$bar$baz internally instead.
    • In order to ensure that this "optimisation" doesn't break external client code, it will emit bookkeeping code like foo.bar.baz=foo$bar$baz as necessary.
    • The long variable names and extra bookkeeping actually bloat the output somewhat—and this bloat got worse when we moved to goog.modules and later to ES modules, as the compiler now gives all those variables a module$build$src$… prefix (hence Remove $build$src from variable names in compressed chunks #7551).
    • Unfortunately, the one bit of bookkeeping code that the compiler doesn't generate is actually the topmost chunk-level export—i.e., it would be very convenient if the blockly_compressed.js chunk included a var Blockly = module$build$src$core$blockly;, and then the factory function in the chunk wrapper for this chunk could just return Blockly;. Fortunately since the mapping from soure filename to internal module variable name was fixed it was easy to do compute this name using the modulePath function, and just have the wrapper return module$build$src$core$blockly;.
  • Enabling --assume-function-wrapper allows the compiler to aggressively rename basically all these variables (as well as related properties on the namespace object), giving them very short (1–3 character) names instead—e.g., where it was already replacing use of foo.bar.baz with var foo$bar$baz, it will now use something like var p instead. This makes the compressed chunks much smaller!
    • Fortunately (and unlike with ADVANCED_OPTIMIZATIONS) Closure Compiler continues to emit the extra bookkeeping, so there will be a statement foo.bar.baz=p; to ensure that external code (i.e., project importing Blockly) still work as expected.
  • The major problem with turning on --assume function-wrapper is that it causes the compiler to rename module$build$src$core$blockly to something short but arbitrary (currently R, as it happens), making it difficult to find and return the correct object from the wrapper's factory function.

To work around this, the LLM proposes adding an extra source file for each chunk to the compiler's input, that imports the chunk and saves the resulting exports (module) object to a well-known location—in this case, on the namespace object that was introduced in #5721. Specifically, for the core chunk, a file blockly_exports.js contains:

import * as exports from `…/core/blockly.js`;
$['__chunkExport__'] = exports;

would be inserted into the source files for the chunk, and then the chunk wrapper's factory function would return $['__chunkExport__'];.

This does indeed work. My main critiques are:

  • Each chunk uses the same property on $, overwriting the value from the previous chunk. Since the property is only used as temporary storage within the factory function that's not a bug, but it did make it harder to understand the code generated by the compiler.
  • It is a bit confusing that these extra source files are called "chunk exports", when that name sounds like it is referring the the chunk's export object.

I have therefore taken the liberty of pushing a few additional commits to this PR, which:

  • Use a separate property, $['__chunk_' + chunk.name], for each chunk's export object.
  • Consistently call the files that create these properties "chunk exporters".
  • Tweak the code in the chunk wrapper so that these properties are not accessed with [] in some cases and . in others, just in case anyone tries feeding the compiled chunks back in to Closure Compiler.
  • Improve naming, code comments and code ordering to improve readability.

I have manually checked to make sure the resulting compiled chunks load locally in the playground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: feature Adds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants