-
Notifications
You must be signed in to change notification settings - Fork 606
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
Fix treeshaking #2232
Fix treeshaking #2232
Conversation
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: redallen The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Hi @redallen. Thanks for your PR. I'm waiting for a openshift member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
/ok-to-test |
This doesn't seem right to me to have |
Yeah, this one is actually looking more complicated than I thought a few hours ago. Currently, this PR results in bundles like this: where the vendors~main chunk seems to be correctly shaken down from 10MB to 5.33MB. However, there's now additional chunks that just contain those modules that should be tree shaken, so I thought this was a fix too soon. This issue can most prevalently be seen by In a test webpack project, patternfly-react is tree shaken just fine using JS. I'll slowly build my minimum test case scenario to have a configuration like yours. |
@redallen is there an issue tracking this with specific details as to what modules are affected and captures the difference between the current and the expected modules to include to some extent? d3 is being used by |
I just opened #2251 to track this.
|
adcdd1c
to
e6ea06b
Compare
070c34b
to
6b59eef
Compare
Giving this another shot since kubevirt/web-ui-components#536 and kubevirt/web-ui-components#539 were merged. Bundle size isn't as good as I hoped for since |
With code splitting, I wouldn't expect any kubevirt dependency to appear in the main vendor bundle. We should try to track down what's pulling it in. |
@redallen: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
@@ -331,7 +332,7 @@ export const CreateOperandForm: React.FC<CreateOperandFormProps> = (props) => { | |||
} | |||
if (field.capabilities.includes(SpecCapability.booleanSwitch)) { | |||
return <AsyncComponent | |||
loader={() => import('patternfly-react').then(m => m.Switch)} | |||
loader={() => Switch} |
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.
No reason to use AsyncComponent
now. Are we certain this change is needed?
We might just switch to the PF4 switch.
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.
It's needed or we need to make a Webpack chunk for it so all of patternfly-react
isn't included.
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.
It's needed or we need to make a Webpack chunk for it so all of
patternfly-react
isn't included.
Right, I see it now 👍
@@ -56,7 +57,7 @@ const BooleanSwitch: React.FC<SpecCapabilityProps> = (props) => { | |||
|
|||
return <div className="co-spec-descriptor--switch"> | |||
<AsyncComponent | |||
loader={() => import('patternfly-react').then(m => m.Switch)} | |||
loader={() => Switch} |
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.
No reason to use AsyncComponent
any longer if we're already importing Switch
@@ -39,7 +36,7 @@ const config: webpack.Configuration = { | |||
{ test: /\.glsl$/, loader: 'raw!glslify' }, | |||
{ | |||
test: /(\.jsx?)|(\.tsx?)$/, | |||
exclude: /node_modules/, | |||
exclude: /node_modules\/(?!(@console)\/)/, |
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.
Are we sure this is right?
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'm sure this is the magic line. The current exclude
excludes everything since all @console/*
references actually are in node_modules/@console/*
.
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'm sure this is the magic line. The current
exclude
excludes everything since all@console/*
references actually are innode_modules/@console/*
.
I'm still not sure. If I only bump kubevirt-web-ui-components with no other changes, I get a main vendor bundle size of 7.52MB, which is the same as #2232 (comment)
/cc @vojtechszocs |
@redallen: PR needs rebase. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
Thanks for your work on this! I think the right approach is to make these changes separately and check how each impacts bundle sizes. This PR has several different changes, and it's hard to evaluate what impact each has in a single PR. I hope you don't mind, but I opened #2383 specifically for the I suspect the two big wins will be:
I looked at (2). It's getting pulled in for the inventory card on the dashboard. Unfortunately, it needs kubevirt-web-ui-components for In general, we should look at code splitting static plugins. Ideally, inactive plugins should not increase the vendor bundle size. I also want to add a check in CI that prevents us from accidentally blowing up the main vendor bundle. It's far too easy to do, and it's difficult to track back and find the change that caused the problem initially. |
I'll create a PR to move
|
@spadgett It depends on what you mean by "active" plugin. The current definition of "active" plugin is:
Arguably, the above definition feels closer to "bundled" or "loaded" plugin, so maybe we should rename that. Anyway, a Console plugin contributes N extensions, each of which may be gated by feature flags (based on #2269). It's perfectly possible to have one extension not gated by anything and another one gated by something. However, flags used to gate (potentially) every extension can only be resolved after you load the plugin's entry module. @spadgett Can you please elaborate on the "inactive" plugin concern? |
By active, I mean a plugin that has at least one extension point whose required flag is set.
That's currently true. Can there be a static declaration that says only load the plugin if at least one of these flags is set? Ideally no plugin code runs until we know it's needed. Otherwise, we need a rule that a plugin entry module cannot pull in any 3rd party dependencies, otherwise they're included in the main vendor bundle even when not needed. I'm not sure how to enforce this, though. |
This can be solved by loading plugins at build time, which is already possible, e.g. in loadActivePlugins(resolvePluginPackages()) Some extensions are always-on (*), the rest is gate-able by flags via (*) At Console startup, we can load the extensions which are either 1, always-on or 2, whose
We could extend Console plugin definition in "consolePlugin": {
"entry": "src/plugin.ts",
"entryFlags": ["FOO", "BAR"]
} so that the plugin as a whole would be loaded only when However, this way, we exclude extensions which are either 1, always-on or 2, whose A much better approach IMO is, at build time, to read/process extensions into logical categories, treating "Console plugin" only as a developer / build-time representation of extensions.
I'd add that rule regardless of the above. It makes perfect sense. Plugin entry module should only contribute (export) extension objects. This module on its own shouldn't have any external dependencies or trigger any side-effects. I've talked about this with @christianvogt some time ago, having the plugin entry module as JSON file would be ideal from extension definition perspective (but less practical from code perspective). To enforce this, we can use ESLint rules that forbid static or dynamic imports. |
There's a reason plugin frameworks typically use a declarative, non-code, approach. |
https://jira.coreos.com/browse/CONSOLE-1651
Let's go down a rabbit hole. Currently, our vendor bundle has a > 10MB stat size and looks like this:
It should NOT include all of
patternfly-react
since we don't use all ofpatternfly-react
. Likewise, it should not include the hugec3
dependency ofpatternfly-react
's charts, which are unused in any files under thefrontend
folder. It also shouldn't include all of@patternfly/react-core
and numerous other sections of packages.This critical line of the webpack config is causing NONE of our code to be treeshaken:
exclude: /node_modules/,
. This is because of yarn workspaces -- it can most obviously be seen right in the entrypoint of the webpack config:entry: [...'@console/app',]
. Well whoops,@console/app
is undernode_modules
, if only by a symlink topackages/console-app
so it will never be tree shaken.This is easily solvable with a regex like
/node_modules\/(?!@console\/)/
, but new problems arise:loader={() => import('patternfly-react').then(m => m.Switch)}
of which Webpack only sees theimport
and then (ironically) loads the whole module synchronously for the bundle. This can be solved.kubevirt-web-ui-components
, which only has ajs
export and can't be tree shaken. Ifesm
modules are provided, the bundle size decreases by half to 5.5MB:I'm currently venturing upstream to kubevirt-web-ui-components to provide some ESM exports. @suomiy @mareklibra .