-
Notifications
You must be signed in to change notification settings - Fork 393
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
feat(compiler): add disableSyntheticShadowSupport option #3229
Conversation
@@ -32,3 +32,4 @@ export default { | |||
- `experimentalDynamicComponent` (type: `DynamicComponentConfig`, default: `null`) - The configuration to pass to `@lwc/compiler`. | |||
- `enableLwcSpread` (type: `boolean`, default: `false`) - The configuration to pass to the `@lwc/template-compiler`. | |||
- `enableScopedSlots` (type: `boolean`, default: `false`) - The configuration to pass to `@lwc/template-compiler` to enable scoped slots feature. | |||
- `disableSyntheticShadowSupport` (type: `boolean`, default: `false`) - Set to true if synthetic shadow DOM support is not needed, which can result in smaller output. |
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 could have put this boolean on the stylesheetConfig
, but that didn't seem right to me since this optimization could apply to the template compiler (and maybe the component compiler?) as well.
In this PR, though, I've only implemented the optimization for the style compiler.
buffer += ` var ${USE_NATIVE_DIR_PSEUDOCLASS} = true;\n`; | ||
} else { | ||
buffer += `function ${STYLESHEET_IDENTIFIER}(${TOKEN}, ${USE_ACTUAL_HOST_SELECTOR}, ${USE_NATIVE_DIR_PSEUDOCLASS}) {\n`; | ||
} |
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.
Arguably we could do something much fancier by modifying serialize.ts
to avoid outputting any of these variables in the first place. But that seemed unnecessary to me since Terser is already perfectly capable of removing unused variables.
@@ -81,6 +84,7 @@ commands: | |||
<<# parameters.enable_native_custom_element_lifecycle >> ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 <</ parameters.enable_native_custom_element_lifecycle >> \ | |||
<<# parameters.enable_scoped_custom_element_registry >> ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY=1 <</ parameters.enable_scoped_custom_element_registry >> \ | |||
<<# parameters.disable_aria_reflection_polyfill >> DISABLE_ARIA_REFLECTION_POLYFILL=1 <</ parameters.disable_aria_reflection_polyfill >> \ | |||
<<# parameters.disable_synthetic_shadow_support_in_compiler >> DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER=1 <</ parameters.disable_synthetic_shadow_support_in_compiler >> \ |
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.
Feedback from Ravi: we can use the |
Just occurred to me that using
It doesn't seem unsolvable, but maybe it's best to save for a future PR. |
Another issue with |
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 did a quick test with CSS keyframes and noticed that terser doesn't quite return the CSS directly as a string. The full example can be found here.
This is the CSS:
.fix-notify_toast_animation {
animation-name: fadeIn;
animation-duration: 0.4s;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
Here's the output:
function stylesheet() {
var token;
var useActualHostSelector = true;
var useNativeDirPseudoclass = true;
var shadowSelector = token ? ("[" + token + "]") : "";
var hostSelector = token ? ("[" + token + "-host]") : "";
return ".fix-notify_toast_animation" + shadowSelector + " {animation-name: fadeIn" + (shadowSelector ? ('-' + shadowSelector.substring(1, shadowSelector.length - 1)) : '') + ";animation-duration: 0.4s;}@keyframes fadeIn" + (shadowSelector ? ('-' + shadowSelector.substring(1, shadowSelector.length - 1)) : '') + " {0% {opacity: 0;}100% {opacity: 1;}}";
/*LWC compiler vX.X.X*/
}
There are a few lines with shadowSelector.substring(1, shadowSelector.length - 1)
in the returned value of the stylesheet
function and the output from the terser playground (you'll have to copy and paste the stylesheet
function) also has the call to substring
.
Terser output:
export default[function(){var n="";return".fix-notify_toast_animation"+n+" {animation-name: fadeIn"+(n?"-"+n.substring(1,n.length-1):"")+";animation-duration: 0.4s;}@keyframes fadeIn"+(n?"-"+n.substring(1,n.length-1):"")+" {0% {opacity: 0;}100% {opacity: 1;}}"}];
Looks like the call to substring
is added here because keyframes are transformed as part of the postCssLwcPlugin
.
Probably not be a big deal since in the end it will evaluate to the same thing but wanted to mention this observation.
If we wanted to return the exactly CSS string in this case, we could add a check in the postCssLwcPlugin
to look for the disableSyntheticShadowSupport
compiler option and prevent it from making transformations.
There might also be a terser setting that takes care of these types of transformations for us.
Ah, that is a good find. I think we could fix this by removing the |
I played around with Terser a bit. It's not quite as smart as I expected (SWC neither) – some cases I found it behaved differently if you ran the content twice through Terser rather than once. In any case, I think I found a system that works. Input: .foo {
animation-name: fadeIn;
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
} Output: function stylesheet() {
var token;
var useActualHostSelector = true;
var useNativeDirPseudoclass = true;
var shadowSelector = token ? ("[" + token + "]") : "";
var hostSelector = token ? ("[" + token + "-host]") : "";
var suffixToken = token ? ("-" + token) : "";
return ".foo" + shadowSelector + " {animation-name: fadeIn" + suffixToken + ";}@keyframes fadeIn" + suffixToken + " {0% {opacity: 0;}100% {opacity: 1;}}";
/*LWC compiler vX.X.X*/
}
export default [stylesheet]; Minified: function stylesheet() {
return ".foo {animation-name: fadeIn;}@keyframes fadeIn {0% {opacity: 0;}100% {opacity: 1;}}"
}
export default [stylesheet]; |
Another option I thought of: we could have a "magic string" inside of CSS files like: /* lwc-native-shadow-only */ If this string were detected, then the file would be treated as native-shadow-only. This would be a contract between the component author and LWC that a certain CSS file will only ever use native shadow. Interestingly, this should work even for |
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.
These changes look good to me!
There's one more thing I think we might want to address. I was looking at #3266 and found that the custom properties transformations will always be applied to the stylesheet
function.
CSS:
div { color: var(--lwc-color); }
div { color: var(--lwc-color, black); }
div { color: var(--lwc-color) important; }
Output:
import varResolver from "custom-properties-resolver";
function stylesheet() {
var token;
var useActualHostSelector = true;
var useNativeDirPseudoclass = true;
var shadowSelector = token ? ("[" + token + "]") : "";
var hostSelector = token ? ("[" + token + "-host]") : "";
return "div" + shadowSelector + " {color: " + (varResolver("--lwc-color")) + ";}div" + shadowSelector + " {color: " + (varResolver("--lwc-color","black")) + ";}div" + shadowSelector + " {color: " + (varResolver("--lwc-color")) + " important;}";
/*LWC compiler vX.X.X*/
}
export default [stylesheet];
Terser output:
import o from"custom-properties-resolver";export default[function(){return"div {color: "+o("--lwc-color")+";}div {color: "+o("--lwc-color","black")+";}div {color: "+o("--lwc-color")+" important;}"}];
Based on #3266, it sounds like this is something that could also be tackled using terser. The call to varResolver
is
generated here, I think we could add a check to skip the transformation for native shadow.
What are your thoughts on this @nolanlawson? (We might need to do this in a separate PR since this kind of change is marked as a breaking change in #3266).
@jmsjtu I think this is different. This is not about synthetic vs native shadow, but instead about whether the browser supports CSS custom properties or not. The |
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.
Thanks for the clarification, LGTM!
Random thought: I wonder if it would be worth it to detect if a native-only stylesheet is loaded in synthetic shadow. Might be an edge case, but we could at least warn the developer. I might save that for a future PR though. |
Details
Partially addresses #3228.
Introduces a new option to the compiler:
disableSyntheticShadowSupport
. This option is plumbed through from@lwc/rollup-plugin
to@lwc/compiler
and then to@lwc/style-compiler
. When set totrue
, the outputstylesheet
functions are much smaller when minified.Example input:
Output using
master
(minified with Terser):Click to see unminified
Output with the option enabled (minified with Terser):
Click to see unminified
With the option enabled, Terser is able to strip away all the synthetic-specific code and have a simple function that returns a string.
Note this only works for unscoped styles, because scoped styles (in both light DOM and shadow DOM) have various runtime-specific parameters passed in. (Not just the scope token – light DOM in scoped mode also transforms
:host
into an identifier corresponding to the component root tag.)Does this pull request introduce a breaking change?
Does this pull request introduce an observable change?
GUS work item
W-12130393