nksaraf / vinxi

The Full Stack JavaScript SDK
https://vinxi.vercel.app
MIT License
1.33k stars 57 forks source link

Fix changes from #310 to correctly collect imported CSS files #316

Closed XiNiHa closed 1 week ago

XiNiHa commented 2 weeks ago

This tries to land changes from #310 (that got rolled back in #312) without re-introducing dev FOUCs. The problem was that even CSS imports don't have staticImportedUrls, which makes those files not collected as a dependency. This PR adds a small check to see if nodes are CSS modules or not, to ensure all CSS imports are collected.

codesandbox[bot] commented 2 weeks ago

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders
Open Preview

changeset-bot[bot] commented 2 weeks ago

⚠️ No Changeset found

Latest commit: fa2fc31520eadd3eae744bebcf82740cf0aa2d77

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

vercel[bot] commented 2 weeks ago

@XiNiHa is attempting to deploy a commit to the Nikhil Saraf's projects Team on Vercel.

A member of the Team first needs to authorize it.

katywings commented 2 weeks ago

Why do you see the logic I wrote before was flawed?

I'll answer it here :D. Correct me if I am wrong, but from what I found (by debugging) staticImportedUrls does not relate to dynamic imports in any way shape or form πŸ˜…, even so the name might suggest that . staticImportedUrls just includes a list of all urls that are imported via code (including dynamic imports). With this information in mind your code would translate as follows:

Code: if (module.staticImportedUrls?.size || module.url.endsWith(".css")) Translation: if [a file imports any other files] or [its url ends with .css] then walk through the file

If I am not completely wrong here, this logic does not solve the goal of excluding dynamically imported modules in the css crawling.


And here is the data from which I am basing on my understanding:

A module that is dynamically imported by its parent:

{
  url: '/src/components/box2.tsx',
  id: '/project/src/components/box2.tsx',
  file: '/project/src/components/box2.tsx',
  type: 'js',
  info: undefined,
  meta: undefined,
  importers: [
    '/project/src/routes/index.tsx?pick=default&pick=$css',
    '/project/src/routes/index.tsx'
  ],
  clientImportedModules: [
    '/node_modules/.vinxi/client/deps/solid-js_web.js?v=42960f6f',
    '/@solid-refresh',
    '/src/components/box2.module.css'
  ],
  ssrImportedModules: Set(0) {},
  acceptedHmrDeps: Set(0) {},
  acceptedHmrExports: null,
  importedBindings: null,
  isSelfAccepting: true,
  ssrTransformResult: null,
  ssrModule: null,
  ssrError: null,
  lastHMRTimestamp: 0,
  lastHMRInvalidationReceived: false,
  lastInvalidationTimestamp: 0,
  invalidationState: undefined,
  ssrInvalidationState: undefined,
  staticImportedUrls: Set(3) {
    '/node_modules/.vinxi/client/deps/solid-js_web.js?v=42960f6f',
    '/@solid-refresh',
    '/src/components/box2.module.css'
  },
  importedModules: [
    '/node_modules/.vinxi/client/deps/solid-js_web.js?v=42960f6f',
    '/@solid-refresh',
    '/src/components/box2.module.css'
  ]
}

A route component, that dynamically imports box2. Notice how staticImportedUrls includes box2 even though it is dynamically imported:

{
  url: '/project/src/routes/index.tsx?pick=default&pick=$css',
  id: '/project/src/routes/index.tsx?pick=default&pick=$css',
  file: '/project/src/routes/index.tsx',
  type: 'js',
  info: undefined,
  meta: undefined,
  importers: [],
  clientImportedModules: [
    '/node_modules/.vinxi/client/deps/solid-js_web.js?v=42960f6f',
    '/@solid-refresh',
    '/node_modules/.vinxi/client/deps/solid-js.js?v=42960f6f',
    '/src/components/box.tsx',
    '/src/routes/index.css',
    '/src/routes/index.module.css',
    '/src/components/box2.tsx'
  ],
  ssrImportedModules: Set(0) {},
  acceptedHmrDeps: Set(0) {},
  acceptedHmrExports: null,
  importedBindings: null,
  isSelfAccepting: true,
  ssrTransformResult: null,
  ssrModule: null,
  ssrError: null,
  lastHMRTimestamp: 0,
  lastHMRInvalidationReceived: false,
  lastInvalidationTimestamp: 0,
  invalidationState: undefined,
  ssrInvalidationState: undefined,
  staticImportedUrls: Set(7) {
    '/node_modules/.vinxi/client/deps/solid-js_web.js?v=42960f6f',
    '/@solid-refresh',
    '/node_modules/.vinxi/client/deps/solid-js.js?v=42960f6f',
    '/src/components/box.tsx',
    '/src/routes/index.css',
    '/src/routes/index.module.css',
    '/src/components/box2.tsx'
  },
  importedModules: [
    '/node_modules/.vinxi/client/deps/solid-js_web.js?v=42960f6f',
    '/@solid-refresh',
    '/node_modules/.vinxi/client/deps/solid-js.js?v=42960f6f',
    '/src/components/box.tsx',
    '/src/routes/index.css',
    '/src/routes/index.module.css',
    '/src/components/box2.tsx'
  ]
}
katywings commented 2 weeks ago

Ping @XiNiHa, see my answer above (I forgot to ping you ':)

XiNiHa commented 2 weeks ago

@katywings I see, I also checked that behavior by myself. Looks like we should just use what Vite provides (like deps and dynamicDeps included in the result of ViteDevServer.transformRequest?) I wonder why @nksaraf initially implemented this using module graph traversal (maybe for performance reasons) and want to know whether that still applies.

katywings commented 2 weeks ago

@XiNiHa Basically vite transforms the modules for server (ssr) and client differently. From what I can gather, vite only includes deps and dynamicDeps in the result of the ssr transform. As you can see on line https://github.com/nksaraf/vinxi/blob/02a32700e50457e5ab38e3e15792dbb2fd1e8c1d/packages/vinxi/lib/manifest/collect-styles.js#L104, we already use deps on the ssr transform and exclude dynamicDeps. The client branch on the other hand uses a different tactic, because its transformResult doesnt have deps.

A look into the vite internals:

Now I don't know, why vite doesn't include deps in the client transform. I also don't know if we could somehow use the results from the ssr transform in the non-ssr branch, or if this would lead into bugs and flakiness πŸ˜….

XiNiHa commented 2 weeks ago

I believe using results from SSR branch will have many problems in edge cases (DCE with import.meta.env.SSR, different entries per export conditions, probably more?) so should be avoided IMO.

katywings commented 2 weeks ago

Yupp thats also what I assume β˜ΊοΈπŸ‘. So we still don't know a way to identify and exclude dynamic imports in the client transform πŸ˜…, or do you have a different idea :)?

nksaraf commented 2 weeks ago

if we get to know in prod, maybe in dev its okay that the css is eagerly prefetched, (since we don't know how to exclude it). I am surprised tho that the client side doesn't have any information.

XiNiHa commented 2 weeks ago

Actually it was the dev server performance that was problematic with me; I have a file that includes dynamic imports for all MDX files in the project, and importing that file resulted in transforming all the MDX files, which was very critical to dev performance.

XiNiHa commented 2 weeks ago

I think using es-module-lexer by ourselves can be a last resort, but it'd be better to look for more efficient approaches first :eyes: looks like there are none! :innocent:

katywings commented 2 weeks ago

@XiNiHa Since you mentioned that it is a very specific file resulting in a performance degradation for you, how about this: instead of us trying to identify dynamic imports, how about we add a config to vinxi, where the user can exclude files from dev css crawling? So you could just exclude that one big file and we wouldnt have to analyze all nodes with the lexer πŸ˜….

XiNiHa commented 2 weeks ago

That sounds very temporary :joy: I think the fundamental fix would be creating a patch on Vite to store static and dynamic dependencies separately and utilizing it. However I'm open to doing both!

Besides that, I'd also prefer to have some kind of flag comments (something like // @vinxi-ignore style-collection on the top?) instead of having them inside the config, since that would clearly show what is happening when looking at the file.

katywings commented 2 weeks ago

@nksaraf What would you prefer?

Personally I am in favor of B, because:

nksaraf commented 1 week ago

I like B, always favor these explicit hacks that give precise control. Is it safe/easy to do?

And yes, maybe A is ideal, but lets be practical and solve our problems on our end. Upstreaming is always an unpredictable process

katywings commented 1 week ago

@XiNiHa Would you be open to implement the file exclusion (via config or comment - whichever you prefer) and do you think its easy, or do you see risks?

If you will do it, can you make it in a new PR so that we can close this one πŸ™‚?

Let me know if you wanna hear my arguments between config and comment 😁.

XiNiHa commented 1 week ago

I'd be happy to implement the feature, and I also want to know your thoughts between config and comment! I see both options can be implemented pretty easily (fast-glob for configs, regexes for comments) I'll close this PR and continue with a new one, but let's continue the conversation here until we move on!

katywings commented 1 week ago

@XiNiHa Awesome 🀩, thank you!

Regarding config vs comments I would say:

Comments:

Config:

Both have their advantages and I see no clear winner. Whats your opinion @nksaraf ?

katywings commented 1 week ago

@XiNiHa Nikhil answered me personally: He prefers the exclusion via comment, in the same spirit as // @ts-ignore.

XiNiHa commented 1 week ago

πŸ‘ I'll go with // @vinxi-ignore-style-collection unless you prefer anything else!

katywings commented 5 days ago

@XiNiHa Sounds good to me πŸ‘ and I guess we still can change it during the PR review, if Nikhil prefers something else :).