-
-
Notifications
You must be signed in to change notification settings - Fork 9.4k
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
Vite: Replace vite-plugin-externals with custom plugin #20698
Conversation
I've confirmed this approach does avoid duplicate imports, and HMR works correctly. I'm a bit nervous that the simple find/replace might be a bit too fragile, but it's building which I think means we're okay. We might consider changing back to |
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.
Needs test, needs perf improvements.
I can work on this if needed.
imports.forEach(({ n: path, ss: startPosition, se: endPosition }) => { | ||
const packageName = path as SingleGlobalName | undefined; | ||
const packageAndDelimiters = new RegExp(`.${packageName}.`); | ||
if (packageName && globalsList.includes(packageName)) { | ||
src.update( | ||
startPosition, | ||
endPosition, | ||
src | ||
.slice(startPosition, endPosition) | ||
.replace('import ', 'const ') | ||
.replace('import{', 'const {') | ||
.replaceAll(' as ', ': ') | ||
.replace(' from ', ' = ') | ||
.replace('}from', '} = ') | ||
.replace(packageAndDelimiters, globals[packageName]) | ||
); | ||
} | ||
}); |
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 am scared, without a proper test-suite. ... we have tests for this?
We should extract this code into a function and test the flip out of it.
I would also recommend writing a single regex + a single replace, that's easier to grok than 6 separate replace commands.?
Thanks, I completely agree. Just wanted to prove out that the approach would work first, and it seems like it will. If you have time to work on it that would be great, otherwise I'll try to take a shot at it tonight. Or maybe @Dschungelabenteuer would like to? |
@IanVS I can try while you take some rest until you feel better :) Eventually I'll probably need some guidance around tests, though! |
I can also help on anything here if needed. |
Don't mean to derail the efforts here, but it's also possible to instead more simply wrap the original plugin within a custom plugin, allowing bug fixes, swapping out certain parts, or extending, etc. Could be easier to go with this than to maintain a new custom plugin. import type { Plugin } from 'vite';
import { globals } from '@storybook/preview/globals';
import { viteExternalsPlugin } from 'vite-plugin-externals';
export async function externalsPlugin() {
const plugin = viteExternalsPlugin(globals, { useWindow: false });
const config = plugin.config as Extract<
ReturnType<typeof viteExternalsPlugin>['config'],
(...args: any[]) => any
>;
return {
...plugin,
name: 'storybook:externals-plugin',
async config(...configArgs) {
const conf = await config(...configArgs);
return {
resolve: {
alias: conf?.resolve?.alias,
},
};
},
} satisfies Plugin;
} Note on the following part: Extract<
ReturnType<typeof viteExternalsPlugin>['config'],
(...args: any[]) => any
> We have to narrow the type here to the function portion of the union since the original plugin returns |
@Dschungelabenteuer Do you want @stevezhu to take over? I think, with a bit of cleanup this PR is good to go. |
I'm still at work so if @stevezhu wants to give it a shot, please feel free :) |
High priority means, "preferably this week" 😄 |
@stevezhu It might be interesting to compare the performance of the two approaches, is that something that you would be interested in trying out, to see if there's much of a difference? |
PR for the suggestion here: #20770 @IanVS Simple performance test in one of the comments #20770 (comment) |
Thanks @stevezhu. I wanted to get a bit more granular perf numbers for just the plugin, so I pushed up two branches, https://github.com/storybookjs/storybook/tree/timing/external-vite-plugin and https://github.com/storybookjs/storybook/tree/timing/internal-vite-plugin. I added timings to the external (wrapped dependency):
internal:
This surprised me, since the config hooks are nearly identical between the two. But, the external plugin was using a newer version of At any rate, it does seem like the approach in this PR is slightly more performant, and we should keep in mind that the sandbox is a small project, and we could expect the transform time to grow with the number of files being transformed. There's also something nice about controlling our own destiny and not relying on another maintainer to merge fixes, though the workaround in #20770 is clever and would indeed do what we need. In summary, I think either approach would be fine, the internal plugin is a bit faster (after the fs-extra upgrade), but if we don't feel comfortable with its robustness we could use the wrapped dependency instead. |
The fs-extra performance was a great find! Ultimately up to you on what you think will be the better solution; just wanted to offer up the other option. Seeing as how the external plugin is relatively unmaintained and with the performance gains, I'm definitely leaning towards having an internal plugin as well. |
Nice insight from both of you guys, I was quite curious about the transform hook which seemed like the most relevant concern, especially because it looks like candidate files are being traversed twice (first by Do we have a clear idea of the amount of files served by Vite that are actually transformed by the plugin? I mean, there are the obvious virtual files such as |
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.
A few small comments
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 think this is really close, but there are a couple of cases we should handle (and test).
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.
This looks good to me, I tested it out in a new sandbox and yarn storybook
starts fine and HMR still works. I'd love if we can get this merged and released ASAP since it fixes a pretty large issue for vite (and especially pnpm) users. Thanks for all the great work here @Dschungelabenteuer and for the help/reviews/ideas @stevezhu. 🙌 🚀
@ndelangen how's it look to you?
I'm seeing an issue in relation to this change in beta.36:
|
@dryton it looks like you might be hitting vitejs/vite#9986 or some variation on it. Are you able to reproduce it reliably, or does it only happen occasionally? If you can reproduce it, can you please open an issue with a link to a reproduction? |
@dryton as mentioned in this discussion, I think we'll have to upgrade the minimum expected |
Following #20698, [some users reported an error thrown around Magic String's `update` method](#20698 (comment)). ## What I did It turns out the `update` method was introduced in [version `0.26.6`](https://github.com/Rich-Harris/magic-string/blob/master/CHANGELOG.md#0266-2022-10-05), but Storybook packages [actually set it to `^0.26.1`](https://github.com/storybookjs/storybook/blob/next/code/lib/builder-vite/package.json#L62). I've opted for 0.27.0 instead of 0.26.6 because it does not seem to have any breaking changes and we could benefit from performance improvements they made. ## How to test This should be pretty straight-forward. I think the issue does not occur on clean installs because it should already pick `magic-string`'s latest patch. Let's see if CI passes! ## Checklist <!-- Please check (put an "x" inside the "[ ]") the applicable items below to make sure your PR is ready to be reviewed. --> - [ ] Make sure your changes are tested (stories and/or unit, integration, or end-to-end tests) - [ ] Make sure to add/update documentation regarding your changes - [ ] If you are deprecating/removing a feature, make sure to update [MIGRATION.MD](https://github.com/storybookjs/storybook/blob/next/MIGRATION.md) #### Maintainers - [ ] If this PR should be tested against many or all sandboxes, make sure to add the `ci:merged` or `ci:daily` GH label to it. - [X] Make sure this PR contains **one** of the labels below. `["cleanup", "BREAKING CHANGE", "feature request", "bug", "documentation", "maintenance", "dependencies", "other"]` <!-- Everybody: Please submit all PRs to the `next` branch unless they are specific to the current release. Storybook maintainers cherry-pick bug and documentation fixes into the `main` branch as part of the release process, so you shouldn't need to worry about this. For additional guidance: https://storybook.js.org/docs/react/contribute/how-to-contribute -->
Issue: #20643
What I did
vite-plugin-externals
breaks HMR for vite-based projects.What I'm not sure I checked correctly
What I'm sure I didn't checked
How to test
[vite] Internal server error: Failed to resolve import "@storybook/preview-api" from "../../../../../../virtual:/@storybook/builder-vite/vite-app.js".
@storybook/builder-vite
package from the source branch of this PRdist
output of@storybook/builder-vite
to replace the one in your pnpm workspaceChecklist
MIGRATION.MD
Maintainers
make sure to add the
ci:merged
orci:daily
GH label to it.["cleanup", "BREAKING CHANGE", "feature request", "bug", "documentation", "maintenance", "dependencies", "other"]