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

feat(css): allow scoping css to importers exports #16058

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Conversation

bluwy
Copy link
Member

@bluwy bluwy commented Feb 29, 2024

Description

closes #4389
closes #15497 (doesn't exactly fix it, but unblocks downstream to fix this)

Frameworks like Vue, Svelte, and Astro has the concept of scoped CSS. Internally, they are transformed as imports to a virtual CSS module. However, the general CSS bundling flow will assume the CSS to be global, so even if the framework component is used but treeshaken, the CSS will still be kept because Vite assumes its global.

To resolve this, Vite plugins can mark certain CSS to be scoped to certain modules' exports. For example, /src/Foo.vue?vue&type=style&lang.css is scoped to /src/App.vue's default export.

The API this PR exposes is via meta.vite.cssScopeTo['/src/Foo.vue'] = ['default'] set on the /src/Foo.vue?vue&type=style&lang.css modules in Vite plugins. Now, if Foo.vue is not used, the styles are also treeshaken.

Additional context

This only works in build, in dev it will still loaded as ESM will load it as usual (we dont treeshake in dev). However, this shouldn't be an issue in dev because the CSS should be scoped and not affect anything globally.

I can't figure out a general API that works outside of Vite plugins without magic comments. Import attributes or queries don't seem feasible because they annotate the "import statement" rather than the "rollup module" itself.

I've also tested this manually in a Svelte app. From some rough implementations, it's easiest to set the meta on the resolveId hook when resolving the virtual CSS module. (implementation detail)


What is the purpose of this pull request?

  • Bug fix
  • New Feature
  • Documentation update
  • Other

Before submitting the PR, please make sure you do the following

  • Read the Contributing Guidelines, especially the Pull Request Guidelines.
  • Check that there isn't already a PR that solves the problem the same way to avoid creating a duplicate.
  • Provide a description in this PR that addresses what the PR is solving, or reference the issue that it solves (e.g. fixes #123).
  • Update the corresponding documentation if needed.
  • Ideally, include relevant tests that fail without this PR but pass with it.

@bluwy bluwy added feat: css p2-to-be-discussed Enhancement under consideration (priority) labels Feb 29, 2024
Copy link

stackblitz bot commented Feb 29, 2024

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

@sapphi-red
Copy link
Member

Another way I can think of is to add a default export as a placeholder. The corresponding CSS will be dropped if the placeholder is dropped, and vice versa.

const plugin = {
  name: 'css-post-post',
  transform(css, id) {
    if (!(id.endsWith('.css') && id.includes('&scoped'))) return

    css += '\nexport default ""'

    return { code: css, moduleSideEffects: null /* not sure if this can override the value returned by vite:css-post plugin */ }
  }
}
// compiled main file
import placeholder from './foo.vue?vue&scoped&lang.css'

export default /* #__PURE__*/ (() => {
  const component = {}
  component.styles = [placeholder] // this is required and will affect the bundle size slightly
  return component
})

@bluwy
Copy link
Member Author

bluwy commented Feb 29, 2024

Yeah that was also an option while figuring this out. Like some sort of marker that can be injected and we remove it post-render. But the caveats I think are:

  1. It's not always possible to inject it somewhere. (Or tricky if you have no control of the compiled code)
  2. If you want to scope to multiple exports, it could get tricky.
  3. I also wanted it to work for "CSS Modules" where they can tie to specific exports if they want (e.g. multiple components in a single file). CSS Modules already export something.

But it certainly seems like the nicer API for end-users though, but ideally we only introduce a single API.

@bluwy bluwy mentioned this pull request Feb 29, 2024
9 tasks
@bluwy
Copy link
Member Author

bluwy commented Mar 1, 2024

Did another test today, and I found that it didn't have the best code-splitting if multiple entrypoints import from the barrel file. The multiple entrypoints will import a shared CSS asset that contains all the scoped CSS from the barrel file, because Rollup will initially think that the scoped CSS are all side-effect-full, so it groups them together. I guess the only correct fix is an API like Sapphi proposed.

I don't think it's a dealbreaker though since extra-included scoped CSS are harmless, but it could work better with a different strategy with its own set of trade-offs too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat: css p2-to-be-discussed Enhancement under consideration (priority)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Svelte CSS treeshaking not working CSS files cannot be treeshaken with side effects
2 participants