跪拜 Guibai
← All articles
Frontend · JavaScript · Vue.js

Building a Vue SFC Compiler as a Hand-Rolled Rollup Plugin

By Cobyte ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

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.

Summary

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.

Takeaways
`@vue/compiler-sfc`'s `parse` method splits a `.vue` file into descriptor objects for template, script, scriptSetup, and styles without compiling their content.
`compileScript` transforms `<script setup>` macros (defineProps, defineEmits) and TypeScript into standard JavaScript wrapped in a `setup` function.
`compileTemplate` converts the template block into a render function that imports Vue helpers like `openBlock` and `createElementBlock`.
Virtual module IDs with query strings (`App.vue?vue&type=script`) let a single Rollup plugin handle each SFC block as an independent module.
A `Map` cache keyed by virtual module ID stores compiled script, template, and style results so the `load` hook can return them without recompiling.
`JSON.stringify` must wrap virtual module paths in import statements to prevent Rollup from misinterpreting special characters in absolute file paths.
`compileStyle` handles scoped attribute hashing, `v-bind` in CSS, and `:deep()` selectors, outputting a plain CSS string.
Compiled CSS is injected at runtime by a `styleInject` function that creates a `<style>` element and appends it to `document.head`.
The final assembled output imports the compiled script and render function, assigns `script.render = render`, and exports the component as a default export.
Conclusions

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.

Concepts & terms
Virtual Module
A module that does not exist on disk but is created dynamically by a plugin during the build. In this Rollup plugin, virtual modules with query strings like `App.vue?vue&type=script` represent the compiled script, template, or style blocks of an SFC.
SFC Descriptor
The object returned by `@vue/compiler-sfc`'s `parse` method, containing the separated template, script, scriptSetup, and styles blocks of a `.vue` file, each with its raw content and attributes like `lang` or `scoped`.
Scoped CSS
A Vue feature where styles are automatically scoped to a component by adding a unique data attribute (e.g., `data-v-xxxxx`) to the component's elements and CSS selectors, preventing style leakage.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗