Building a Vue SFC Compiler as a Hand-Rolled Rollup Plugin
Understanding how a Rollup plugin decomposes and reassembles Vue SFCs demystifies the build step that Vite and other tooling abstract away. Developers who need custom SFC processing, non-standard blocks, or framework-agnostic compilation can adapt this pattern directly.
A custom Rollup plugin is built from scratch to compile `.vue` single-file components, using `@vue/compiler-sfc` to parse, compile, and reassemble template, script, and style blocks. The template becomes a render function, `<script setup>` macros and TypeScript are transformed into standard JavaScript, and scoped styles are compiled into plain CSS strings. Virtual modules with query parameters (`?vue&type=script`) and a `Map`-based cache let Rollup treat each SFC block as a separate module, avoiding recompilation. For styles, a runtime `styleInject` function creates a `<style>` tag and appends the compiled CSS to the document head. The assembled output is a valid ESM component that mounts and renders correctly in a Vite dev server, confirming the plugin works end-to-end.
The plugin's architecture mirrors how Vite's own Vue plugin works, proving that production-grade SFC compilation is a straightforward composition of the compiler-sfc APIs and Rollup's virtual module mechanism.
Using a `Map` for caching inside a plugin is a simple but effective pattern that avoids re-parsing and re-compiling the same SFC block when Rollup processes the virtual module imports.
The path-escaping bug caused by Rollup's internal handling of absolute paths is a subtle detail that can break virtual module resolution; `JSON.stringify` is the documented fix.
Runtime CSS injection via `styleInject` is a pragmatic choice for development and simple builds, but a production plugin would likely extract CSS into a separate file for better performance and caching.