-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
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: enable dependencies discovery and pre-bundling in ssr environments #18358
feat: enable dependencies discovery and pre-bundling in ssr environments #18358
Conversation
|
5e9488e
to
23d6d9d
Compare
This comment was marked as off-topic.
This comment was marked as off-topic.
b90f742
to
ec8060b
Compare
This comment was marked as outdated.
This comment was marked as outdated.
431d438
to
ec7f400
Compare
packages/vite/src/node/external.ts
Outdated
if ( | ||
!environment.config.dev.optimizeDeps.noDiscovery && | ||
!environment.config.dev.optimizeDeps.exclude?.includes(id) | ||
) { | ||
// If there is server side pre-bundling and the module is not | ||
// in the `exclude` config then it is not external | ||
return false | ||
} |
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 one is interesting. We were trying to avoid conflating optimizeDeps.include/exclude with external/noExternal in the past but we will need this check for noDiscovery
to make sense. This condition should be move to line 140 though.
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 see, I understand trying not to conflate them, although it does feel like these concepts start to overlap here 🤔
I hope this is not a huge concern but yeah it does feel like this check is needed since we do not want prebundled modules to be run through runExternalModule
In any case I've moved the check 🙂👍
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.
If a dependency is marked as external, I guess we should not optimized it even if it is not excluded from optimizeDeps. I mean dropping this whole if block so that only no-externalized files would be pre-bundled.
Otherwise, for the following dependency graph:
- app -> pkg-a (external: true, not excluded) -> pkg-b
- app -> pkg-c (external: true, manually excluded) -> pkg-b
pkg-a will be prebundled with pkg-b, and pkg-a will use the bundled pkg-b. But pkg-c will use the non-bundled pkg-b because pkg-c uses a normal import
and Vite cannot intercept that. This means there will be a problem similar to dual package hazard.
I think this is difficult to understand by users.
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.
pkg-a will be prebundled with pkg-b, and pkg-a will use the bundled pkg-b. But pkg-c will use the non-bundled pkg-b because pkg-c uses a normal import and Vite cannot intercept that. This means there will be a problem similar to dual package hazard.
Yes I understand this issue, although I imagine that you can always end up in similar situations even today (without environments) if you have multiple packages with the same dependency and exclude one of them, no?
I think this boils down to trusting users to provide correct/sensible configs, if someone does try to use more advanced features such as optimizeDeps
they should be able to understand the potential risks and implications I think? 🤔
Anyways I don't fully understand your suggestion, dropping this if block means that all the external modules will not be pre-bundled, doesn't it? but that's the whole point of this PR; Allowing environments to get opt-in externals pre-bundling without having to specify all the various dependencies to pre-bundle (since that's something we can do already today).
So I'm not sure what your suggestion is, unless I am missing something?
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.
Yes I understand this issue, although I imagine that you can always end up in similar situations even today (without environments) if you have multiple packages with the same dependency and exclude one of them, no?
Oh, yes, it will happen. But the difference is that we can fix it in future for non-externalized deps since Vite can intercept them and that's not possible for externalized deps. If we add support for prebundling externalized deps, we cannot fix that issue for them. (That said, I'm not so much against keeping this block because I don't think it will be necessarily fixed.)
dropping this if block means that all the external modules will not be pre-bundled, doesn't it?
Yes.
but that's the whole point of this PR; Allowing environments to get opt-in externals pre-bundling without having to specify all the various dependencies to pre-bundle (since that's something we can do already today).
I thought the point was to make noDiscovery: false
work in SSR and allow users / environments to enable it. That would make no-externalized CJS deps work in dev (It should be working in build already). Given that all files has to be bundled for a output for workerd, I thought the ideal way is to set noExternal: true
(also set external: ['buffer' /* list supported node and cloudflare builtins */]
if supported) so that all files will be processed by Vite in both dev and build. Do you have any other reason to avoid ssr.noExternal: true
other than this CJS dep problem?
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.
@sapphi-red sorry I simply didn't consider noExternal: true
+ external: [...]
, it does seems like a valid solution to me, hoping that the maintenance of the external
list doesn't become too cumbersome 🤔
Yes I agree that this is probably the most correct solution 👍
But, do you think that this could be confusing/easy to get wrong from a user's perspective?
That just setting noDiscovery: false
is not enough but you also need to set noExternal: true
for it to take effect (especially considering that this is not required for client environments)
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.
(PS: I've removed the if
block 👍)
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.
hoping that the maintenance of the
external
list doesn't become too cumbersome 🤔
Looking at it again, it seems to work without setting external
.
vite/packages/vite/src/node/plugins/resolve.ts
Lines 459 to 480 in 802839d
if (currentEnvironmentOptions.consumer === 'server') { | |
if ( | |
options.webCompatible && | |
options.noExternal === true && | |
// if both noExternal and external are true, noExternal will take the higher priority and bundle it. | |
// only if the id is explicitly listed in external, we will externalize it and skip this error. | |
(options.external === true || !options.external.includes(id)) | |
) { | |
let message = `Cannot bundle Node.js built-in "${id}"` | |
if (importer) { | |
message += ` imported from "${path.relative( | |
process.cwd(), | |
importer, | |
)}"` | |
} | |
message += `. Consider disabling environments.${environmentName}.noExternal or remove the built-in dependency.` | |
this.error(message) | |
} | |
return options.idOnly | |
? id | |
: { id, external: true, moduleSideEffects: false } |
Maybe It just works only with
noExternal: true
.
But, do you think that this could be confusing/easy to get wrong from a user's perspective?
That just settingnoDiscovery: false
is not enough but you also need to setnoExternal: true
for it to take effect
If the runtime does not support running that module natively, then the module should be no-externalized. In other words, if that module can run natively, then it should work without pre-bundle. So I think the user would add the module to noExternal
.
In future, I guess we'd want to set noDiscovery: false
by default.
/ecosystem-ci run |
📝 Ran ecosystem CI on
✅ analogjs, histoire, ladle, laravel, marko, nuxt, previewjs, quasar, qwik, rakkas, redwoodjs, storybook, unocss, vite-environment-examples, vite-plugin-pwa, vite-plugin-react, vite-plugin-react-swc, vite-plugin-svelte, vite-plugin-vue, vite-setup-catalogue, vitepress, vuepress |
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.
There are less reds because the PR isn't based on main, and there were some changes lately.
Thanks for working this out @dario-piotrowicz! Let's wait for another approval before we merge this. I think it would be useful to move forward to let environments experiment with this
packages/vite/src/node/external.ts
Outdated
if ( | ||
!environment.config.dev.optimizeDeps.noDiscovery && | ||
!environment.config.dev.optimizeDeps.exclude?.includes(id) | ||
) { | ||
// If there is server side pre-bundling and the module is not | ||
// in the `exclude` config then it is not external | ||
return false | ||
} |
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.
If a dependency is marked as external, I guess we should not optimized it even if it is not excluded from optimizeDeps. I mean dropping this whole if block so that only no-externalized files would be pre-bundled.
Otherwise, for the following dependency graph:
- app -> pkg-a (external: true, not excluded) -> pkg-b
- app -> pkg-c (external: true, manually excluded) -> pkg-b
pkg-a will be prebundled with pkg-b, and pkg-a will use the bundled pkg-b. But pkg-c will use the non-bundled pkg-b because pkg-c uses a normal import
and Vite cannot intercept that. This means there will be a problem similar to dual package hazard.
I think this is difficult to understand by users.
Awesome work! |
Wouldn't this be equivalent to an edit event where a user added a new import in a file that caused a full-reload? This should work or we have a bug, no? |
I think the manual edit case is different and it should work fine since request handling starts from importing a fresh entry module. My demo is to show what happens when there is re-optimization in the middle of request handling. |
Ah, you're right. In the browser, it doesn't matter because the full-reload would end up doing the request again so the modules being transformed can be discarded (we throw an outdated request exception in this case in the vite:optimized-deps plugin). We catch this in the transform middleware and send a 504. I don't know what would be the equivalent when using the server. A framework could catch the exception and issue a full-reload in the client if there isn't one in fly? (cc @sheremet-va, maybe you have some thoughts about this) |
92f90b5
to
3d8d634
Compare
Co-authored-by: 翠 / green <[email protected]>
3d8d634
to
2c7afed
Compare
(PS: I had to rebase because of a merge conflict, so since I was already at it I squashed all my commits, sorry if this causes any inconvenience in reviewing the PR 🙇) |
set `scan: true` instead of adding relative prefix in `resolvePath` Co-authored-by: 翠 / green <[email protected]>
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!
I wasn't able to make this work in Vitest because there is no mechanism for retrying to load a file again in For regular environments, I guess they would have to handle this themselves? I think loading modules in the same global scope has the same side effects as it does in the browser. So, if you use an external process, you can just reload it, but if you are running in the same process (like with the default ssr), what do you do? We can reset the cache of inlined modules, but we can't reset the cache of external modules. We also cannot clean up their side effects. |
Description
This PR adds pre-bundling support for server environments