-
-
Notifications
You must be signed in to change notification settings - Fork 408
Strip <template> indentation by default #1121
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
Merged
Merged
Changes from 14 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
0d2aaed
Template invisible character minification by default
NullVoxPopuli 302f9ff
meta
NullVoxPopuli 5eb4e0f
Show an example
NullVoxPopuli e50dc64
Fix headers
NullVoxPopuli 64ee595
Clarity
NullVoxPopuli df2c3f2
Another note
NullVoxPopuli 13492ac
oof
NullVoxPopuli 2fbf205
Updates
NullVoxPopuli 7dd447b
Links
NullVoxPopuli edb3447
More
NullVoxPopuli ee058d5
Remove inconsistency
NullVoxPopuli 7932099
Remove extraneous phrasings
NullVoxPopuli e584c96
Update 1121-extraneous-invisible-character-stripping.md
NullVoxPopuli 9c417c2
Clarify invisible character stripping rules
NullVoxPopuli aa9c651
Update 1121-extraneous-invisible-character-stripping.md
NullVoxPopuli ecef172
ope
NullVoxPopuli 3a6873c
Merge branch 'master' into nvp/minification-by-default
ef4 b307d16
Add notes for communication
NullVoxPopuli 62e4664
change drawback section
NullVoxPopuli 27140cd
ope
NullVoxPopuli ab732bc
Update text/1121-extraneous-invisible-character-stripping.md
kategengler 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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| --- | ||
| stage: accepted | ||
| start-date: 2025-07-09T00:00:00.000Z | ||
| release-date: # In format YYYY-MM-DDT00:00:00.000Z | ||
| release-versions: | ||
| teams: | ||
| - framework | ||
| prs: | ||
| accepted: https://github.com/emberjs/rfcs/pull/1121 | ||
| project-link: | ||
| suite: | ||
| --- | ||
|
|
||
| # Default Template invisible character Minification | ||
|
|
||
| ## Summary | ||
|
|
||
| Integrate template invisible character minification directly into Ember's template compiler, making the behavior of the ember-hbs-minifier addon available by default for all Ember applications. This will automatically remove unnecessary invisible character from templates while preserving semantic meaning and visual layout, resulting in smaller bundle sizes and improved performance without requiring developer intervention. | ||
|
|
||
| ## Motivation | ||
|
|
||
| HTML templates in Ember applications often contain significant amounts of invisible character that exists purely for developer readability but serves no semantic or visual purpose in the rendered output. This invisible character includes: | ||
|
|
||
| - Leading and trailing spaces, tabs, and newlines in text nodes | ||
| - Excessive invisible character between elements that gets collapsed by browsers anyway | ||
| - Formatting indentation that increases bundle size without benefit | ||
| - This is exacerbated by the adoption of gjs/gts, which has yet an another extra indentation level | ||
|
|
||
| Currently, developers must manually install and configure the ember-hbs-minifier addon to achieve optimal template output, and the instructions for doing so in v2 addons and vite apps are not yet available on that repo[^hbs-minifier-maintain]. However, invisible character minification is a widely applicable optimization that benefits virtually all applications and has well-understood safety rules. | ||
|
|
||
| [^hbs-minifier-maintain]: ember-hbs-minifier _is_ a third party library, so it can only be as maintained as the maintainers have time for. Here is an issue on their repo for how to [use with a babel config](https://github.com/mainmatter/ember-hbs-minifier/issues/744) (for reference). | ||
|
|
||
| The HTML specification clearly defines how browsers handle invisible character: | ||
| - Multiple consecutive invisible character characters are collapsed to a single space | ||
| - Leading and trailing invisible character in block-level elements is typically ignored | ||
| - invisible character-only text nodes between block elements are often not rendered | ||
|
|
||
| By understanding these rules, we can safely remove extraneous invisible character while preserving: | ||
| - Intentional spacing in inline content | ||
| - Preformatted content (like `<pre>` tags) | ||
| - Content where invisible character has semantic meaning | ||
|
|
||
| Making this optimization available by default will: | ||
| 1. Reduce bundle sizes for all Ember applications | ||
| 2. Improve parsing and rendering performance | ||
| 3. Eliminate the need for developers to discover and configure an additional addon / babebl plugin | ||
| 4. Provide a consistent, optimized template output across the ecosystem | ||
|
|
||
| ## Detailed design | ||
|
|
||
| Strip the overall indent of a gjs/gts template including the leading / trailing line breaks. -- this is similar to `stripIndent` from [common-tags](https://www.npmjs.com/package/common-tags) | ||
|
|
||
| ---- | ||
|
|
||
| It's not possible to safely strip all extraneous invisible characters, as the definition of extraneous is dependent on active CSS. | ||
|
|
||
| The governing documentation on these rules are official standards documents: | ||
| - [from MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace) | ||
| - [from CSS WG: White Space Processing & Control Characters](https://drafts.csswg.org/css-text-3/#white-space-processing) | ||
| - [from CSS WG: Collapsible White Space](https://drafts.csswg.org/css-text-3/#collapsible-white-space) | ||
|
|
||
|
|
||
| But there is a safe transform that we can do only on gjs/gts | ||
| - The leading and trailing invisible characters for opening `<template>` and closing `</template>` in gjs/gts files should be stripped. | ||
| - This helps determine the "indentation level" which would also be stripped from each line -- effectively de-denting templates to what developers were used to before we started embedding templates in JS/TS. | ||
| - How to determine indentation: | ||
| - scan all lines after the line with `<template>` and before the line with `</template>` | ||
| - indentation is determined by the smallest number of invisible characters for each line | ||
| - skip lines with 0 characters | ||
| - ideally all other lines have the same or greater indentation | ||
| - if spaces and tabs are mixed, we can ignore tabs, and determine indentation from spaces and strip space-indentation | ||
|
NullVoxPopuli marked this conversation as resolved.
Outdated
|
||
| - stripping tab indentation can only happen if all lines are indented with tabs | ||
|
|
||
|
|
||
| ### Examples | ||
|
|
||
| > [!NOTE] | ||
| > These examples use the format described in [RFC #931](https://github.com/emberjs/rfcs/pull/931) for brevity. | ||
|
|
||
| > [!IMPORTANT] | ||
| > Every invisible character and line break unneededly adds to the app-compiled wire-format, which leads to more runtime code for end-users to deal with. The more extraneous invisible characters we can eliminate, the better. | ||
| > For _aggressive_ invisible character stripping, [ember-hbs-minifier](https://github.com/mainmatter/ember-hbs-minifier/) is still a viable option. | ||
|
|
||
| <table> | ||
| <thead> | ||
| <th>Input</th> | ||
| <th>Output (today)</th> | ||
| <th>Output (desired)</th> | ||
| </thead> | ||
| <tbody> | ||
| <tr><td> | ||
|
|
||
|
|
||
| ```gjs | ||
| <template> | ||
| <span>x</span> | ||
| </template> | ||
| ``` | ||
|
|
||
| </td><td> | ||
|
|
||
| ```js | ||
| export default template( | ||
| '\n <span>x</span>\n' | ||
| ); | ||
| ``` | ||
|
|
||
| </td><td> | ||
|
|
||
| ```js | ||
| export default template( | ||
| '<span>x</span>' | ||
| ) | ||
| ``` | ||
|
|
||
| </td></tr> | ||
| <tr><td> | ||
|
|
||
| ```gjs | ||
| const MyTemplate = <template> | ||
| <span>x</span> | ||
| </template> | ||
|
|
||
| <template> | ||
| <MyTemplate /> | ||
| </template> | ||
| ``` | ||
|
|
||
| </td><td> | ||
|
|
||
| ```js | ||
| const MyTemplate = template( | ||
| '\n <span>x</span>\n' | ||
| ); | ||
|
|
||
| export default template( | ||
| '\n <MyTemplate>\n', | ||
| { scope: () => ({ MyTemplate })} | ||
| ) | ||
| ``` | ||
|
|
||
| </td><td> | ||
|
|
||
| ```js | ||
| const MyTemplate = template( | ||
| '<span>x</span>' | ||
| ); | ||
|
|
||
| export default template( | ||
| '<MyTemplate>', | ||
| { scope: () => ({ MyTemplate })} | ||
| ) | ||
| ``` | ||
|
|
||
| </td></tr> | ||
| <tr><td> | ||
|
|
||
|
|
||
| ```gjs | ||
| <template> | ||
| Hello | ||
| <span>there</span>. | ||
| <p> | ||
| <span>how are you</span> | ||
| </p> | ||
| </template> | ||
| ``` | ||
|
|
||
| </td><td> | ||
|
|
||
| ```js | ||
| export default template( | ||
| '\n Hello\n <span>there</span>.\n <p>\n <span>how are you</span>\n </p>\n' | ||
| ); | ||
| ``` | ||
|
|
||
| </td><td> | ||
|
|
||
| ```js | ||
| export default template( | ||
| 'Hello <span>there</span>.\n<p>\n <span>how are you</span>\n</p>' | ||
| ) | ||
| ``` | ||
|
|
||
| </td></tr> | ||
| </tbody> | ||
| </table> | ||
|
|
||
| ### Configuration | ||
|
|
||
| There will be no configuration, as this can be enabled with 0 breaking changes to existing behavior. | ||
|
|
||
| ### Backward Compatibility | ||
|
|
||
| - Existing applications using ember-hbs-minifier can continue to do so -- this RFC does not affect the behavior of those transforms. | ||
|
|
||
| ## How we teach this | ||
|
|
||
| This feature requires minimal teaching as it operates transparently. It requires more teaching now to explain all teh excessive indentation we have in gjs / gts. | ||
|
|
||
| ## Drawbacks | ||
|
|
||
| we lose the ability to `white-space: pre` on any content. | ||
|
|
||
| ## Alternatives | ||
|
|
||
| - This RFC could be considered a bugfix. | ||
|
|
||
| - Keep ember-hbs-minifier as an optional addon | ||
| - Pros: No breaking changes, opt-in behavior | ||
| - Cons: Many applications miss this optimization, ecosystem fragmentation, we want ember to be a cohesive out-of-the-box framework | ||
|
|
||
| - Support opt-in: | ||
| ```gjs | ||
| <template minifiy> | ||
| <span>x</span> | ||
| </template> | ||
| ``` | ||
| Which could alleviate the edge case where folks are using `white-space: pre` on arbitrary contents. | ||
|
|
||
| - Breaking change for some CSS use cases: enable all ember-hbs-minifier optimization. | ||
|
|
||
| ## Unresolved Questions | ||
|
|
||
| n/a | ||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have to be careful about
<pre>blockswhich I guess are in a weird state already due to increased indentation from a migration
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea pre blocks aren't affected here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but they might be by the stripping of the indentation
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you have an example where something would be problematic?
The way I see it:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in your second example the indentation between the open and close tags matters and is reproduced by the browser exactly
it would be incorrect to touch it
indentation outside pre should be stripped even though pre doesn't align with the whitespace count of outside of it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't agree -- it would mean we have to parse the template contents to determine if we are in a pre tag and have a different set of rules.
if someone is going to put their pre-indentation all the way to the left like that, the whole template gets no indentation, per:
We could later change that, but I don't want implementation to hard.
as is, implementation would just be line-by-line iteration -- no parsing of the contents needed.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but you have to do that anyway as it would be a bug to strip whitespace from a pre tag even if the indentation rule passes the check