Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 30 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A Vite plugin for handling CSS sourcemaps. This plugin ensures that CSS sourcema
- Supports custom sourcemap file locations
- Configurable sourcemap URL generation
- Works with Vite's build process
- Compatible with Vite 5.x and 6.x
- Compatible with Vite 5.x through 8.x

## Installation

Expand Down Expand Up @@ -55,17 +55,22 @@ cssSourcemap({

// Custom function to generate sourcemap URLs
getURL: (fileName) => `sourcemaps/${fileName}`,

// Keep Vite's CSS minification on (default: false, i.e. minification is
// disabled while the plugin is active)
disableCssMinify: true,
});
```

### Options

| Option | Type | Default | Description |
| ------------ | ------------------------------ | ------------------------ | ------------------------------------------ |
| `enabled` | `boolean` | `true` | Enable or disable the plugin |
| `extensions` | `string[]` | `['.css', '.scss']` | File extensions to process |
| `folder` | `string` | `''` | Custom folder for sourcemap files |
| `getURL` | `(fileName: string) => string` | `(fileName) => fileName` | Custom function to generate sourcemap URLs |
| Option | Type | Default | Description |
| ------------------ | ------------------------------ | ------------------------ | ---------------------------------------------------------- |
| `enabled` | `boolean` | `true` | Enable or disable the plugin |
| `extensions` | `string[]` | `['.css', '.scss']` | File extensions to process |
| `folder` | `string` | `''` | Custom folder for sourcemap files |
| `getURL` | `(fileName: string) => string` | `(fileName) => fileName` | Custom function to generate sourcemap URLs |
| `disableCssMinify` | `boolean` | `true` | Disable Vite's CSS minification while the plugin is active |

## Examples

Expand Down Expand Up @@ -140,17 +145,32 @@ This plugin hooks into Vite's build process to:

The plugin works by:

1. Using the `transform` hook to process CSS files and generate sourcemaps
2. Using the `generateBundle` hook to ensure sourcemaps are properly emitted
3. It observes `vite:css-post` plugin, specifically the `augmentChunkHash` hook to obtain the future id of the file.
1. Using the `transform` hook to capture each stylesheet after Vite has
compiled it, along with whatever sourcemap the preprocessor produced
2. Using the `generateBundle` hook, ordered after `vite:css-post`, to find
where each stylesheet was placed inside the concatenated CSS asset
3. Translating each stylesheet's mappings into the asset's coordinate space and
emitting the combined sourcemap alongside it
4. Allows configuring the sourcemap URL based on the provided options

### Why CSS minification is disabled

Vite minifies a CSS asset after this plugin has recorded where each stylesheet
landed inside it. Minifying collapses the asset onto a handful of lines, which
invalidates those positions and produces a sourcemap that resolves every
position back to the first source.

The plugin therefore turns CSS minification off by default. If you would rather
keep it on and forgo accurate sourcemaps, set `disableCssMinify: false`.

## Compatibility

This plugin is compatible with:

- Vite 5.x
- Vite 6.x
- Vite 7.x
- Vite 8.x (including the Rolldown-based build)

## License

Expand Down
13 changes: 1 addition & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 2 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@
"access": "public"
},
"peerDependencies": {
"vite": ">= 5.0.0",
"sass": ">= 1.0.0",
"sass-embedded": ">= 1.0.0"
"sass-embedded": ">= 1.0.0",
"vite": ">= 5.0.0"
},
"peerDependenciesMeta": {
"sass": {
Expand Down Expand Up @@ -100,8 +100,5 @@
"typescript": "^5.2.2",
"vite": "^7.3.0",
"vitest": "^3.1.1"
},
"dependencies": {
"merge-source-map": "^1.1.0"
}
}
122 changes: 15 additions & 107 deletions playground-scss-entrypoint/README.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,34 @@
# SCSS Entrypoint Playground

This playground tests the scenario where SCSS is used as a direct rollup entrypoint, with `@import`ed partials that should all appear in the sourcemap.
This playground is a raw SCSS Rollup input (`styles: 'styles/main.scss'`). Vite
does not expose a combined preprocessor map for that shape, so the plugin
attributes the compiled CSS to `main.scss` rather than compiling Sass a second
time outside Vite's resolver.

## The Issue

In traditional server-rendered projects, you might have a Vite config like this:

```js
export default defineConfig({
build: {
rollupOptions: {
input: {
main: 'javascript/main.js',
styles: 'styles/main.scss', // SCSS as direct entrypoint
},
},
},
});
```

The `styles/main.scss` file uses `@import` to pull in multiple partials:

```scss
@import 'partials/variables';
@import 'partials/reset';
@import 'partials/buttons';
// ... etc
```

**Expected:** The generated sourcemap should include mappings for ALL SCSS files.

**Actual:** The sourcemap only covers `main.scss`, not the imported partials.
SCSS imported through Vite's CSS pipeline (`import './main.scss'` from JS) still
gets whatever map Vite already built, including partials when Sass provided one.

## Structure

```
playground-scss-entrypoint/
├── vite.config.ts # Vite config with SCSS as rollup input
├── javascript/
│ └── main.js # JS entrypoint (minimal)
├── styles/
│ ├── main.scss # SCSS entrypoint (uses @import)
│ └── partials/
│ ├── _variables.scss
│ ├── _reset.scss
│ ├── _layout.scss
│ ├── _buttons.scss
│ ├── _cards.scss
│ ├── _forms.scss
│ └── _utilities.scss
└── index.html # Test page
├── vite.config.ts
├── javascript/main.js
├── styles/main.scss
└── styles/partials/…
```

## Running

```bash
# From the root of the project
cd playground-scss-entrypoint
npx vite build

# Check the sourcemap
cat dist/assets/styles.css.map | jq '.sources'
```

## What to Verify

1. Build succeeds
2. `dist/assets/styles.css` is generated
3. `dist/assets/styles.css.map` is generated
4. The sourcemap's `sources` array should include ALL partials, not just `main.scss`

## Current Behavior (Fixed)

After the fix, the sourcemap includes all SCSS partials:

```json
{
"sources": [
"file:///path/to/styles/main.scss",
"file:///path/to/styles/partials/_reset.scss",
"file:///path/to/styles/partials/_variables.scss",
"file:///path/to/styles/partials/_layout.scss",
"file:///path/to/styles/partials/_buttons.scss",
"file:///path/to/styles/partials/_cards.scss",
"file:///path/to/styles/partials/_forms.scss",
"file:///path/to/styles/partials/_utilities.scss"
],
"sourcesContent": ["/* Original SCSS source for each file */"]
}
```

When debugging in browser DevTools, styles correctly point to the actual partial files like `_buttons.scss`, `_forms.scss`, etc.

## Fixes Applied

### Fix 1: Asset Name Matching

The plugin now tries multiple key formats when looking up source files in `generateBundle`:

- The full filename with extension (e.g., `assets/styles.css`)
- The filename without extension (e.g., `assets/styles`)
- The fallback template path (e.g., `assets/main`)

### Fix 2: SCSS Compilation for Sourcemaps

When `getCombinedSourcemap()` returns an empty sourcemap (which happens when SCSS is a direct rollup entrypoint), the plugin now:

1. Detects that the file is SCSS/Sass
2. Dynamically loads `sass-embedded` or `sass` if available
3. Compiles the SCSS file to extract the proper sourcemap with all `@import`ed partials
4. Falls back to identity sourcemap if no Sass compiler is available

## Requirements

For SCSS sourcemaps to work correctly, you need either `sass-embedded` or `sass` installed:

```bash
npm install -D sass-embedded
# or
npm install -D sass
```

## Related
## What to verify

This playground was created to test SCSS sourcemap support when SCSS is used as a direct rollup entrypoint.
1. Build succeeds and emits `styles.css` + `styles.css.map`.
2. `sources` includes `main.scss`.
3. Partials appear only if Vite's combined map listed them — this playground
typically does not, and the plugin does not invent them.
3 changes: 3 additions & 0 deletions playground/src/assets/dot.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions playground/src/assets/sprite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions playground/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ import './styles/components/modal.css';
// Utility styles
import './styles/utilities.css';
import './styles/animations.css';
import './styles/a11y.css';
import './styles/print.css';

// Styles whose leading at-rule is hoisted to the top of the asset
import './styles/fonts.css';

// Styles that reference an emitted asset
import './styles/hero.css';
import './styles/sprite.css';

// Original styles
import './styles/main.css';
Expand Down
3 changes: 3 additions & 0 deletions playground/src/split/a-only.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.only-a {
margin: 1px;
}
3 changes: 3 additions & 0 deletions playground/src/split/alpha.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.shared-widget {
color: #abcdef;
}
3 changes: 3 additions & 0 deletions playground/src/split/b-only.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.only-b {
margin: 2px;
}
3 changes: 3 additions & 0 deletions playground/src/split/beta.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.shared-widget {
color: #abcdef;
}
4 changes: 4 additions & 0 deletions playground/src/split/entry-a.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import './a-only.css';
import './alpha.css';

console.log('split entry a');
4 changes: 4 additions & 0 deletions playground/src/split/entry-b.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import './b-only.css';
import './beta.css';

console.log('split entry b');
10 changes: 10 additions & 0 deletions playground/src/styles/a11y.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.skip-link {
left: -999px;
}

/* Duplicated verbatim from print.css, which makes that stylesheet's compiled
CSS a substring of this one's. */
.visually-hidden {
position: absolute;
clip: rect(0 0 0 0);
}
9 changes: 9 additions & 0 deletions playground/src/styles/fonts.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@import url('https://fonts.example/css2?family=Inter:wght@400;700');

.font-body {
font-family: Inter, sans-serif;
}

.font-heading {
font-weight: 800;
}
11 changes: 11 additions & 0 deletions playground/src/styles/hero.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* Referencing an asset means this stylesheet still holds a Vite placeholder
when the plugin captures it, and the real URL is substituted afterwards. */
.hero {
background-image: url('../assets/dot.svg');
padding: 2rem;
}

.hero-caption {
font-size: 0.875rem;
color: #666;
}
4 changes: 4 additions & 0 deletions playground/src/styles/print.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.visually-hidden {
position: absolute;
clip: rect(0 0 0 0);
}
11 changes: 11 additions & 0 deletions playground/src/styles/sprite.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* A reference carrying a fragment is held in a different shape of Vite
placeholder than a bare one. */
.sprite {
background-image: url('../assets/sprite.svg#star');
width: 1rem;
height: 1rem;
}

.sprite-label {
font-weight: 700;
}
Loading