-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Resolve @import
in core
#14446
Resolve @import
in core
#14446
Conversation
95d9682
to
c3f7963
Compare
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.
Dude this looks 💯 — a handful of questions and comments about stuff but dang this is some epic work.
packages/@tailwindcss-postcss/src/fixtures/example-project/src/relative-import.css
Outdated
Show resolved
Hide resolved
|
||
// Find all `@theme` declarations | ||
let theme = new Theme() | ||
let customVariants: ((designSystem: DesignSystem) => void)[] = [] | ||
let customUtilities: ((designSystem: DesignSystem) => void)[] = [] | ||
let firstThemeRule: Rule | null = null | ||
let keyframesRules: Rule[] = [] | ||
let globs: { origin?: string; pattern: string }[] = [] | ||
let globs: { base: string; pattern: string }[] = [] |
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 makes me happy 🥳
Also was thinking but you can eliminate the 2nd walk by making use of object identity. Basically you'll eagerly create the context node, store functions that load the stylesheet, parse it, etc… and then replace the nodes / metadata of the context node when it's done loading. Then you call all the promises after you've done the walk and once they've resolved the AST has been updated. let ctx = context({ base: '.' }, [])
replaceWith(ctx)
if (promises.has(uri)) return
promises.set(uri, async () => {
const imported = await loadStylesheet(uri, base)
let root = CSS.parse(imported.content)
await substituteAtImports(root, imported.base, loadStylesheet)
if (layer !== null) {
root = [rule('@layer ' + layer, root)]
}
if (media !== null) {
root = [rule('@media ' + media, root)]
}
if (supports !== null) {
root = [rule(`@supports ${supports[0] === '(' ? supports : `(${supports})`}`, root)]
}
ctx.context = { base: imported.base }
ctx.nodes = root
}) |
A realization I just had while prepping Prettier for this change — the current definition of For example, for a missing plugin I can provide an empty function and for a missing config just an empty object will do. This is necessary because I need intellisense to actually boot even in the face of errors. Once it's up and running we can offer diagnostics pointing out that something is broken / missing. The Prettier plugin is a similar situation — I think it'd be useful for the majority of things to work even if something can't be found — though I could be convinced that for Prettier at least it doesn't matter as much. But at the very least this is pretty critical for Intellisense DX. |
@thecrypticace Ah this makes sense, what if we pass a third argument that is either |
Ahh good idea with the mutation! yep this makes sense and I think would make things a bit simpler 👍 |
I think |
@thecrypticace regarding this comment:
Is this a concern for Intellisense as well? i.e do you introspect the CSS file to determine if it's a likely Tailwind root? Might make it a higher priority to add this sooner rather than later. I guess nothing will break just now though since we still use |
Yep it is but nothing will break because it uses |
70bd9bb
to
2f1dbcc
Compare
671e234
to
f463ead
Compare
1188963
to
60ed8bb
Compare
a499e52
to
dac6cd5
Compare
4b92247
to
9dd29d0
Compare
…1058) Related to tailwindlabs/tailwindcss#14446 We'll be handling `@import` resolution in core with the appropriate hooks to ensure that all I/O is done outside of the core package. This PR preps for that.
This PR brings
@import
resolution into Tailwind CSS core. This means that our clients (PostCSS, Vite, and CLI) no longer need to depend onpostcss
andpostcss-import
to resolve@import
. Furthermore this simplifies the handling of relative paths for@source
,@plugin
, or@config
in transitive CSS files (where the relative root should always be relative to the CSS file that contains the directive). This PR also fixes a plugin resolution bug where non-relative imports (e.g. directly importing node modules like@plugin '@tailwindcss/typography';
) would not work in CSS files that are based in a different npm package.Resolving
@import
The core of the
@import
resolution is insidepackages/tailwindcss/src/at-import.ts
. There, to keep things performant, we do a two-step process to resolve imports. Imagine the following input CSS file:Since our AST walks are synchronous, we will do a first traversal where we start a loading request for each
@import
directive. Once all loads are started, we will await the promise and do a second walk where we actually replace the AST nodes with their resolved stylesheets. All of this is recursive, so that@import
-ed files can again@import
other files.The core
@import
resolver also includes extensive test cases for various combinations of media query and supports conditionals as well als layered imports.When the same file is imported multiple times, the AST nodes are duplicated but duplicate I/O is avoided on a per-file basis, so this will only load one file, but include the
@theme
rules twice:Adding a new
context
node to the ASTOne limitation we had when working with the
postcss-import
plugin was the need to do an additional traversal to rewrite relative@source
,@plugin
, and@config
directives. This was needed because we want these paths to be relative to the CSS file that defines the directive but when flattening a CSS file, this information is no longer part of the stringifed CSS representation. We worked around this by rewriting the content of these directives to be relative to the input CSS file, which resulted in added complexity and caused a lot of issues with Windows paths in the beginning.Now that we are doing the
@import
resolution in core, we can use a different data structure to persist this information. This PR adds a newcontext
node so that we can store arbitrary context like this inside the Ast directly. This allows us to share information with the sub tree while doing the Ast walk.Here's an example of how the new
context
node can be used to share information with subtrees:In core, we use this new Ast node specifically to persist the
base
path of the current CSS file. We put the input CSS filebase
at the root of the Ast and then overwrite thebase
on every@import
substitution.Removing the dependency on
postcss-import
Now that we support
@import
resolution in core, our clients no longer need a dependency onpostcss-import
. Furthermore, most dependencies also don't need to know aboutpostcss
at all anymore (except the PostCSS client, of course!).This also means that our workaround for rewriting
@source
, thepostcss-fix-relative-paths
plugin, can now go away as a shared dependency between all of our clients. Note that we still have it for the PostCSS plugin only, where it's possible that users already havepostcss-import
running before the@tailwindcss/postcss
plugin.Here's an example of the changes to the dependencies for our Vite client ✨ :
Performance
Since our Vite and CLI clients now no longer need to use
postcss
at all, we have also measured a significant improvement to the initial build times. For a small test setup that contains only a hand full of files (nothing super-complex), we measured an improvement in the 3.5x range:The code for this is in the commit history if you want to reproduce the results. The test was based on the Vite client.
Caveats
One thing to note is that we previously relied on finding specific symbols in the input CSS to bail out of Tailwind processing completely. E.g. if a file does not contain a
@tailwind
or@apply
directive, it can never be a Tailwind file.Since we no longer have a string representation of the flattened CSS file, we can no longer do this check. However, the current implementation was already inconsistent with differences on the allowed symbol list between our clients. Ideally, Tailwind CSS should figure out wether a CSS file is a Tailwind CSS file. This, however, is left as an improvement for a future API since it goes hand-in-hand with our planned API changes for the core
tailwindcss
package.