You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When skipDuplicates is on (the default), a file whose content is byte-identical to an already-imported file is skipped, even when its at-rules resolve paths relative to the file's own location. Byte equality is not semantic equality for such files: two identical files at different paths express different targets. The skip produces no warning, so the failure is invisible until someone notices output is missing and bisects their imports.
Output contains one @source (from a/); b/sources.css is dropped. Each file's pattern resolves against its own directory, so the second import is not a duplicate of the first. Controls: with skipDuplicates: false both survive, and making the files differ by one byte also makes both survive, so the content hash in parse-styles.js is the exact mechanism. Reproduced on 16.1.1 and 15.1.0.
@source here is Tailwind's directive, but nothing in this report is specific to Tailwind: any at-rule with path-relative semantics that postcss-import does not own hits the same skip. The reason this lands on postcss-import rather than the at-rule's owner is ordering: the file is dropped during import inlining, before any downstream plugin runs, so no downstream plugin can recover it. Downstream report: vitejs/vite#22386 (Vite bundles postcss-import for @import inlining; the Vite team's position is that this belongs on the postcss-import or consumer side).
Prior art in this repo
This class of bug was found and fixed here in 2015. #67 reported byte-identical proxy files (containing only @import statements) being wrongly skipped, on the argument that their relative imports resolve per-location. The fix (1706c1a, "Not skip files containing import rules") is the hasImport exemption that still guards the dedup today. That exemption encodes the right principle: files whose at-rules carry path-relative side effects must not be content-deduped. It just enumerates the path-dependent at-rules that existed in 2015, which was only @import.
Options
I'm happy to PR whichever direction you prefer, with fixture tests and a README update. In rough order of how much they change:
Warn on the skip. When a content-duplicate containing at-rules outside the CSS specs is skipped, emit result.warn(). No behavior change; the failure stops being silent and points at skipDuplicates: false as the escape hatch.
Split the two kinds of dedup. In File containing same content shouldn't be skipped #67, the same day skipDuplicates shipped as a boolean, it was pointed out that it conflates skipping the same file twice with skipping different files that share content, and that the two have different use cases. Accepting a value that keeps same-file dedup but disables same-content dedup would let consumers (bundlers included) opt out of the unsafe half without losing the safe half. No default change.
Generalize the File containing same content shouldn't be skipped #67 exemption. Extend hasImport to also exempt files containing at-rules that are not defined by CSS, on the reasoning that postcss-import cannot know their semantics are location-independent, so deduping them is not a safe optimization. The failure direction is conservative: under-deduping costs some output size, over-deduping silently drops semantics. The affected set is nearly identical to the files that are broken today. This deliberately does not name any third-party at-rules, in line with the position in Fix conflict with tailwindcss when using @tailwind before @import #449; the check would be against the CSS-defined at-rule names, not a vendor list. Cost: that list needs occasional maintenance as specs add at-rules, and it is a change to default output for the affected files.
Options 1 and 2 compose, and either can ship without 3.
When
skipDuplicatesis on (the default), a file whose content is byte-identical to an already-imported file is skipped, even when its at-rules resolve paths relative to the file's own location. Byte equality is not semantic equality for such files: two identical files at different paths express different targets. The skip produces no warning, so the failure is invisible until someone notices output is missing and bisects their imports.Reproduction
Output contains one
@source(froma/);b/sources.cssis dropped. Each file's pattern resolves against its own directory, so the second import is not a duplicate of the first. Controls: withskipDuplicates: falseboth survive, and making the files differ by one byte also makes both survive, so the content hash inparse-styles.jsis the exact mechanism. Reproduced on 16.1.1 and 15.1.0.@sourcehere is Tailwind's directive, but nothing in this report is specific to Tailwind: any at-rule with path-relative semantics that postcss-import does not own hits the same skip. The reason this lands on postcss-import rather than the at-rule's owner is ordering: the file is dropped during import inlining, before any downstream plugin runs, so no downstream plugin can recover it. Downstream report: vitejs/vite#22386 (Vite bundles postcss-import for@importinlining; the Vite team's position is that this belongs on the postcss-import or consumer side).Prior art in this repo
This class of bug was found and fixed here in 2015. #67 reported byte-identical proxy files (containing only
@importstatements) being wrongly skipped, on the argument that their relative imports resolve per-location. The fix (1706c1a, "Not skip files containing import rules") is thehasImportexemption that still guards the dedup today. That exemption encodes the right principle: files whose at-rules carry path-relative side effects must not be content-deduped. It just enumerates the path-dependent at-rules that existed in 2015, which was only@import.Options
I'm happy to PR whichever direction you prefer, with fixture tests and a README update. In rough order of how much they change:
Warn on the skip. When a content-duplicate containing at-rules outside the CSS specs is skipped, emit
result.warn(). No behavior change; the failure stops being silent and points atskipDuplicates: falseas the escape hatch.Split the two kinds of dedup. In File containing same content shouldn't be skipped #67, the same day
skipDuplicatesshipped as a boolean, it was pointed out that it conflates skipping the same file twice with skipping different files that share content, and that the two have different use cases. Accepting a value that keeps same-file dedup but disables same-content dedup would let consumers (bundlers included) opt out of the unsafe half without losing the safe half. No default change.Generalize the File containing same content shouldn't be skipped #67 exemption. Extend
hasImportto also exempt files containing at-rules that are not defined by CSS, on the reasoning that postcss-import cannot know their semantics are location-independent, so deduping them is not a safe optimization. The failure direction is conservative: under-deduping costs some output size, over-deduping silently drops semantics. The affected set is nearly identical to the files that are broken today. This deliberately does not name any third-party at-rules, in line with the position in Fix conflict with tailwindcss when using @tailwind before @import #449; the check would be against the CSS-defined at-rule names, not a vendor list. Cost: that list needs occasional maintenance as specs add at-rules, and it is a change to default output for the affected files.Options 1 and 2 compose, and either can ship without 3.