Skip to content
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

refactor: simplify resolveSSRExternal #5544

Merged
merged 6 commits into from
Nov 8, 2021

Conversation

benmccann
Copy link
Collaborator

@benmccann benmccann commented Nov 4, 2021

This is a rebased version of #5210 from @aleclarson, which looks good to me

Description

  1. add collectExternals function for recursive tracing
  2. apply config.ssr just once, after all externals are collected
  3. support packages with deep imports but no main entry

To clarify #​3, a package might allow imports like foo/bar but not foo alone. Before this PR, packages like this were never marked external (unless done so explicitly in user config), because the call to tryNodeResolve would throw. As a workaround, you can add the package's name to ssr.external or add an empty index.js module to the package.

- add `collectExternals` function for recursive tracing
- apply `config.ssr` just once, after all externals are collected
- support packages with deep imports but no main entry
- remove `knownImports` argument

fix(ssr): re-add `knownImports` argument to `resolveSSRExternal`

This argument is necessary to ensure transient dependencies in node_modules are marked as external.
aleclarson
aleclarson previously approved these changes Nov 4, 2021
@aleclarson
Copy link
Member

aleclarson commented Nov 4, 2021

Thanks for rebasing. I will let @patak-js merge when he's ready

Shinigami92
Shinigami92 previously approved these changes Nov 4, 2021
@patak-dev
Copy link
Member

I'll bring this PR to our next team meeting for discussion 👍🏼

yyx990803
yyx990803 previously approved these changes Nov 6, 2021
Copy link
Member

@yyx990803 yyx990803 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's get this out in a beta and test in relevant SSR-reliant projects.

/cc @ElMassimo @frandiox @brillout @FredKSchott

@brillout
Copy link
Contributor

brillout commented Nov 6, 2021

👍 (Super looking forward to this.)

@benmccann @aleclarson I'm confused about this block:

else if (/\.m?js$/.test(entry)) {
if (pkg.type === "module" || entry.endsWith('.mjs')) {
ssrExternals.add(id)
continue
}
// check if the entry is cjs
const content = fs.readFileSync(entry, 'utf-8')
if (/\bmodule\.exports\b|\bexports[.\[]|\brequire\s*\(/.test(content)) {
ssrExternals.add(id)
}
}

It basically checks: if dep is ESM => externalize and if dep is CJS => externalize. Doesn't that mean that the dep will always be externalized? I.e. why can't we simply do the following instead?

 else if (/\.m?js$/.test(entry)) { 
   ssrExternals.add(id) 
 } 

@brillout
Copy link
Contributor

brillout commented Nov 6, 2021

Also, I'm wondering: why does Vite preemptively resolves all SSR externals at once by reading a bunch of package.json#dependencies, instead of evaluating whether an import should be externalized as Vite stumbles upon new import statements? I'm probably missing something here but AFAICT the latter would be much simpler.

@brillout
Copy link
Contributor

brillout commented Nov 6, 2021

Maybe we can add some more comments? I find it difficult to comprehend resolveSSRExternal() and its intention. (Btw. I find Vite's source code actually supringsly and refreshingly easy to read 😊, especially considering the inherent complexities of bundlers.)

 -    entry = tryNodeResolve(
+     esmEntry = tryNodeResolve(
        id,
        undefined,
        resolveOptions,
+       // We set `targetWeb` to `true` to get the ESM entry
        true,
        undefined,
        true
      )?.id
// assume external if not yet seen

Why?

ssrExternals.delete('vite')

Why? I would have assumed that Vite is actually a good candidate to externalize.

@benmccann
Copy link
Collaborator Author

benmccann commented Nov 6, 2021

Doesn't that mean that the dep will always be externalized? I.e. why can't we simply do the following instead?

I guess the only other case would be if there's some other JS format like AMD or SystemJS. I don't know how common those are though. I'd be fine assuming we should always externalize JS files, but am not sure I'm the right person to make that call

ssrExternals.delete('vite')
Why? I would have assumed that Vite is actually a good candidate to externalize.

I added comments the other places you suggested. I'm not sure about this one though, so will leave that one to @aleclarson to explain

@aleclarson
Copy link
Member

Doesn't that mean that the dep will always be externalized? I.e. why can't we simply do the following instead?

I guess the only other case would be if there's some other JS format like AMD or SystemJS.

In that case, we should add a code comment. I wonder if, instead of checking for CommonJS, we should check for signs of AMD or SystemJS..?

 

ssrExternals.delete('vite')

Why? I would have assumed that Vite is actually a good candidate to externalize.

See 578c591 and #1865

 

Also, I'm wondering: why does Vite preemptively resolves all SSR externals at once by reading a bunch of package.json#dependencies, instead of evaluating whether an import should be externalized as Vite stumbles upon new import statements?

While making this PR, I tried removing the knownImports argument of resolveSSRExternal, but I ran into some bugs, so I added it back without really understanding why transitive imports needed to be included.

In regard to package.json crawling, I didn't really question it. Now that I am... it seems like we could refactor it to take more of a lazy approach, since only the vite:import-analysis plugin currently uses server._ssrExternals, and #5220 will also use it once merged. For now though, the current approach gets the job done.

@benmccann
Copy link
Collaborator Author

I added a comment about why vite is removed from externals. I feel like the other stuff is bigger and would be better to do in separate PRs

@brillout
Copy link
Contributor

brillout commented Nov 7, 2021

I'd be fine assuming we should always externalize JS files

Since we'll release this PR as a beta, I'd say that it is a good opportunity to try this out.

It would get us closer to the vision of:

  1. Always externalize SSR deps by default.
  2. Only transpile user code to CJS if the user's package.json#type is not module.

LGTM otherwise, thanks for the comments super helpful.

Copy link
Member

@patak-dev patak-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pushing this forward. This PR is already quite involved, let's get this in and test it during the next few days (we'll release 2.7.0-beta.2 tomorrow including it). We can then continue to build on top of it on other PRs so it is easier to discuss and track each proposal.

@patak-dev patak-dev merged commit f663348 into vitejs:main Nov 8, 2021
@benmccann benmccann deleted the ssr/externals-fix-rebased branch November 8, 2021 16:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants