-
Notifications
You must be signed in to change notification settings - Fork 57
tree shaking #1643
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
Open
iObject
wants to merge
48
commits into
master
Choose a base branch
from
183-tree-shaking
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
tree shaking #1643
Changes from 11 commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
f5b045a
Base tree shaking implementation
iObject 022dd84
bsconfig control
iObject f04302e
Change from annotation to comments
iObject 0ab9294
spec update
iObject adf11e7
Potential fix for pull request finding
iObject b6aed2e
Potential fix for pull request finding
iObject 5d18b91
correct tests
iObject 208d9d6
remove only
iObject e690a39
bsconfig.schema.json update
iObject 0d70977
readme updates
iObject b001258
spec rename
iObject e226ecd
documentation fix
iObject c58c812
runscreensaver
iObject 7ee5624
optimizations
iObject 19f13f8
Potential fix for pull request finding
iObject 6b6a080
Merge branch 'master' into 183-tree-shaking
iObject def4082
Potential fix for pull request finding
iObject e5d83aa
resolve issues
iObject 421d77b
fix shorthand conflict
iObject 1f32d43
Potential fix for pull request finding
iObject 4879eb7
Potential fix for pull request finding
iObject 18f7fd5
Potential fix for pull request finding
iObject cf64d4c
fix bug
iObject de7dece
Windows src path normalization
iObject 0569d01
Tree shaking now lives in Program.beforeProgramTranspile
iObject 5ca9b74
remove space
iObject 4296778
Potential fix for pull request finding
iObject ff2d8df
Move TreeShaker
iObject 949f67b
Merge branch '183-tree-shaking' of github.com:rokucommunity/brighters…
iObject edd9cea
Updated comment
iObject 301a919
Potential fix for pull request finding
iObject 12643fd
logging
iObject b6d9d57
fix(tree-shaker): retain .brs library functions called via namespace …
iObject bbc8752
move logging to single output
iObject 83e0f6b
tests for excludes brs
iObject b6f036a
Move treeShaker back into BscPlugin.ts
iObject 7672891
remove whitespace
iObject 5986f59
readme update
iObject c4d231d
omit fully protected files from transpiler
iObject a9b0de5
Fix issue
iObject e3717f1
pattern fix
iObject 94f9c9d
treeShaking normalization tests
iObject 8d6d302
changes map for any compiled rule that ends up with no criteria
iObject 9267d14
Merge branch 'master' into 183-tree-shaking
iObject 84a4da2
readme update
iObject ac3d832
customizations support
iObject 45a03cf
Merge branch '183-tree-shaking' of github.com:rokucommunity/brighters…
iObject 41bcae3
test for function passed by reference as an argument to another function
iObject File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,224 @@ | ||
| # Tree Shaking | ||
|
|
||
| Tree shaking is BrighterScript's dead code elimination feature. When enabled, any function that is never called — directly or indirectly — is removed from the transpiled output, reducing the size of your deployed channel. | ||
|
|
||
| Tree shaking is **disabled by default**. You must explicitly opt in. | ||
|
|
||
| ## Enabling Tree Shaking | ||
|
|
||
| Add a `treeShaking` section to your `bsconfig.json`: | ||
|
|
||
| ```json | ||
| { | ||
| "treeShaking": { | ||
| "enabled": true | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| That's the minimal configuration. With only `enabled: true`, any function that cannot be reached from a known entry point will be removed. | ||
|
|
||
iObject marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ## How It Works | ||
|
|
||
| BrighterScript performs a two-pass analysis across the entire program before transpiling: | ||
|
|
||
| **Pass 1 — collect definitions.** Every `sub` and `function` statement in every `.bs`/`.brs` file is recorded, along with its source file and its transpiled (BrightScript) name. `bs:keep` comments are also detected in this pass (see below). Functions declared in XML `<interface>` elements and `onChange` callbacks are collected from `.xml` component files. | ||
|
|
||
| **Pass 2 — collect references.** The AST of every file is walked to find: | ||
| - Direct call expressions (`doSomething()`, `myNamespace.helper()`) | ||
| - String literals that look like identifiers — conservatively retained to support dynamic dispatch patterns like `observeField("field", "onMyFieldChanged")` and `callFunc` | ||
| - Variable expressions that reference a known function name (function-by-reference patterns such as `m.observe(node, "field", onContentChanged)`) | ||
| - `@.` callFunc shorthand expressions | ||
|
|
||
| After both passes, any function that has no references and is not a protected entry point is removed from the transpiled output by replacing its statement with an empty node. | ||
|
|
||
| ### Protected Entry Points | ||
|
|
||
| The following Roku framework callbacks are **always kept** regardless of whether they appear in any call expression: | ||
|
|
||
| | Name | Context | | ||
| |---|---| | ||
| | `main` | Channel entry point | | ||
| | `init` | SceneGraph component lifecycle | | ||
| | `onKeyEvent` | Remote key handling | | ||
| | `onMessage` | Task/port message handling | | ||
| | `runUserInterface` | UI task entry point | | ||
| | `runTask` | Background task entry point | | ||
iObject marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ## `bs:keep` Comments | ||
|
|
||
| A `bs:keep` comment tells the tree shaker to unconditionally keep a specific function, even if it has no detectable callers. This is useful for functions that are invoked dynamically at runtime in ways the static analysis cannot see. | ||
|
|
||
| ### Same-Line | ||
|
|
||
| Place the comment on the same line as the `sub` or `function` keyword: | ||
|
|
||
| ```brightscript | ||
| sub onMyDynamicCallback() ' bs:keep | ||
| ' ... | ||
| end sub | ||
| ``` | ||
|
|
||
| ### Above the Function | ||
|
|
||
| Place the comment anywhere between the end of the previous function and the start of the next one: | ||
|
|
||
| ```brightscript | ||
| end sub | ||
|
|
||
| ' bs:keep | ||
| sub onMyDynamicCallback() | ||
| ' ... | ||
| end sub | ||
| ``` | ||
|
|
||
| Multiple lines of other comments or blank lines between `bs:keep` and the function are fine — the comment applies to the next function that follows it. | ||
|
|
||
| ### First Function in a File | ||
|
|
||
| For the very first function in a file, `bs:keep` can appear anywhere before it (since there is no previous function to bound the region): | ||
|
|
||
| ```brightscript | ||
| ' This file's public API — prevent tree shaking | ||
| ' bs:keep | ||
| sub publicEntry() | ||
| ' ... | ||
| end sub | ||
| ``` | ||
|
|
||
| ### `rem` Syntax | ||
|
|
||
| Both `'` and `rem` comment starters are supported: | ||
|
|
||
| ```brightscript | ||
| rem bs:keep | ||
| sub legacyEntryPoint() | ||
| ' ... | ||
| end sub | ||
| ``` | ||
|
|
||
| ### What `bs:keep` Does NOT Do | ||
|
|
||
| - A `bs:keep` comment placed **inside** a function body does not protect that function. | ||
| - A `bs:keep` comment does not automatically keep the functions that the annotated function calls — only the annotated function itself. If those callees are also unused by the rest of the program, they will still be removed. Use [`treeShaking.keep`](#treeshakingkeep-rules) rules with dependency closure, or annotate each callee individually, if you need to retain an entire call graph. | ||
iObject marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| ## `treeShaking.keep` Rules | ||
|
|
||
| For coarser-grained control — keeping entire files, namespaces, or pattern-matched sets of functions — use the `keep` array in `bsconfig.json`. Each entry is either a plain string (exact function name) or a rule object. | ||
|
|
||
| ### Plain String | ||
|
|
||
| A plain string matches the exact transpiled (BrightScript) function name, case-insensitively: | ||
|
|
||
| ```json | ||
| { | ||
| "treeShaking": { | ||
| "enabled": true, | ||
| "keep": [ | ||
| "myPublicFunction", | ||
| "myNamespace_helperFunction" | ||
| ] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| For namespaced BrighterScript functions, use the transpiled underscore form. For example, `namespace myNamespace` + `function helperFunction()` transpiles to `myNamespace_helperFunction`. | ||
|
|
||
| ### Rule Objects | ||
|
|
||
| A rule object can filter by any combination of `functions`, `matches`, `src`, and `dest`. All fields present in a single rule must match simultaneously (AND semantics). Rules in the array are evaluated independently and a function is kept if **any** rule matches (OR semantics). | ||
|
|
||
| #### `functions` — exact name list | ||
|
|
||
| ```json | ||
| { | ||
| "keep": [ | ||
| { "functions": "myNamespace_init" }, | ||
| { "functions": ["analyticsTrack", "analyticsFlush"] } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| #### `matches` — glob/wildcard against the function name | ||
|
|
||
| ```json | ||
| { | ||
| "keep": [ | ||
| { "matches": "analytics_*" }, | ||
| { "matches": ["debug_*", "test_*"] } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| #### `src` — glob against the source file path | ||
|
|
||
| The pattern is resolved relative to `rootDir` unless it is an absolute path. | ||
|
|
||
| ```json | ||
| { | ||
| "keep": [ | ||
| { "src": "source/public/**/*.bs" }, | ||
| { "src": ["source/api.bs", "source/auth.bs"] } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| #### `dest` — glob against the package-relative destination path | ||
|
|
||
| This matches the path the file will have inside the deployed zip (e.g. `pkg:/source/utils.brs`). | ||
|
|
||
iObject marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ```json | ||
| { | ||
| "keep": [ | ||
| { "dest": "source/public/**/*.brs" } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| #### Combining Fields (AND within a rule) | ||
|
|
||
| Keep only functions whose name starts with `api_` **and** that live in a specific file: | ||
|
|
||
| ```json | ||
| { | ||
| "keep": [ | ||
| { | ||
| "src": "source/api.bs", | ||
| "matches": "api_*" | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| ### Dependency Closure | ||
|
|
||
| Keep rules do not automatically pull in the transitive dependencies of a matched function. If `api_login` calls `crypto_hash` and only `api_login` is matched by a keep rule, `crypto_hash` will still be removed if nothing else calls it. | ||
|
|
||
| To retain the entire reachable graph of a kept function, either: | ||
| - Add a `bs:keep` comment to each function you want to preserve, or | ||
| - Add additional keep rules (e.g. a `src` rule that covers the whole file containing the helpers) | ||
|
|
||
iObject marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ## Configuration Reference | ||
|
|
||
| ```json | ||
| { | ||
| "treeShaking": { | ||
| "enabled": false, | ||
| "keep": [] | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| | Field | Type | Default | Description | | ||
| |---|---|---|---| | ||
| | `enabled` | `boolean` | `false` | Must be `true` to activate tree shaking | | ||
| | `keep` | `(string \| KeepRule)[]` | `[]` | Functions matching any entry are always retained | | ||
|
|
||
| **KeepRule fields** (all optional; at least one required): | ||
|
|
||
| | Field | Type | Description | | ||
| |---|---|---| | ||
| | `functions` | `string \| string[]` | Exact transpiled function name(s), case-insensitive | | ||
| | `matches` | `string \| string[]` | Glob pattern(s) matched against the transpiled function name | | ||
| | `src` | `string \| string[]` | Glob pattern(s) matched against the source file path | | ||
| | `dest` | `string \| string[]` | Glob pattern(s) matched against the package-relative destination path | | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.