In a Vue single-file component, how do you write a block in TypeScript, SCSS or Pug, and what actually processes the lang attribute?
answer
- one attribute per block
- the compiler delegates to preprocessors
- packages you install yourself
- TypeScript output still needs transpiling
basics
~20 sAdd a lang attribute to the block, such as <script setup lang="ts">, <style lang="scss"> or <template lang="pug">. The SFC compiler hands style and template content to the matching preprocessor, which you install, and leaves TypeScript for the toolchain to transpile.
solid answer
~40 sEvery SFC block accepts `lang`. `<script lang="ts">` or `<script setup lang="ts">` makes the SFC compiler parse the block as TypeScript, which is also what lets type-based macros work, but its output **still contains TypeScript**; the build toolchain strips it. `<style lang="scss">` (also `sass`, `less`, `stylus`) runs the content through the matching CSS preprocessor before Vue's own style transforms, such as scoped rewriting; the preprocessor package, for example `sass`, must be installed in the project. `<template lang="pug">` renders Pug to HTML before template compilation. Script blocks in languages other than JS, JSX, TS or TSX are passed through unprocessed. A normal `<script>` and `<script setup>` must use the same `lang`.
code
vue · 22 lines<script setup lang="ts">
interface Item {
id: number
label: string
}
const props = defineProps<{ items: Item[] }>()
</script>
<template>
<ul class="list">
<li v-for="item in props.items" :key="item.id">{{ item.label }}</li>
</ul>
</template>
<style scoped lang="scss">
$gap: 8px;
.list {
display: grid;
gap: $gap;
li { list-style: none; }
}
</style>go deeper
Recall that lang on a block selects its language, with lang="ts" for scripts and lang="scss" for styles as the everyday cases.
Explain the pipeline: preprocessor first, then Vue's transforms; TypeScript parsed but not checked or stripped by Vue; same lang for both script blocks.
Diagnose preprocessing failures by separating Vue's role from the toolchain's, and keep type-checking as an explicit build step since lang="ts" alone checks nothing.
Standardise block languages across a codebase, weighing Pug or Stylus against the tooling, hiring and editor support cost of non-default languages.
## The `lang` attribute The SFC specification lets any block declare a **pre-processor language** with `lang`. The most common case is TypeScript in a script block, followed by Sass in a style block: ```vue <script setup lang="ts"> const count: number = 0 </script> <template lang="pug"> p {{ count }} </template> <style lang="scss"> $primary: #333; .title { color: $primary; } </style> ``` `lang` is an instruction to the compiler pipeline, not a runtime feature. What happens depends on the block. ## Per block, who does the work | Block and `lang` | What `@vue/compiler-sfc` does | What else is needed | |---|---|---| | `<script lang="ts">` / `"tsx"` | parses TypeScript, rewrites macros, keeps type annotations in the output | the toolchain transpiles the TypeScript away | | `<script lang="js">` / `"jsx"` or none | parses JavaScript | nothing special | | `<script lang="other">` | returns the block unprocessed | a tool for that language | | `<style lang="scss">`, `"sass"`, `"less"`, `"styl"`, `"stylus"` | loads the preprocessor, then applies Vue's style transforms | the preprocessor package installed | | `<template lang="pug">` | renders the content to HTML through a template-engine adapter, then compiles it | the engine package installed | Points to state carefully: 1. **TypeScript is parsed, not type-checked or stripped by Vue.** The SFC compiler must understand TypeScript to read type-based `defineProps<...>()` declarations, and its output keeps type annotations. Type-checking is done by a separate tool, and transpiling by the build toolchain. 2. **CSS preprocessing comes first.** The Sass or Less output is ordinary CSS, and only then does Vue apply its own transforms, such as `scoped` attribute rewriting and `v-bind()` variables. That order is why Sass nesting works inside `<style scoped>`. 3. **Preprocessors are your dependencies.** `compiler-sfc` loads `sass`, `less` or `stylus` from the project; if the package is missing, style compilation fails. 4. **Template preprocessing in the browser build** needs an explicitly supplied in-browser preprocessor; in a normal build it is handled on the build side. ## Rules that involve `lang` - **Same language for both scripts.** When a file has a normal `<script>` and a `<script setup>`, they must have the same `lang`; the compiler throws an error that they must have the same language type. - **One `lang` per block.** Two style blocks may use different languages, for example one `scss` and one plain CSS. - **Toolchain differences.** The Vue docs note that integration with pre-processors may differ by toolchain, so the exact configuration lives in the build tool's documentation. ## Why this design - The SFC format stays language-agnostic: Vue does not ship Sass or Pug, it defines where they plug in. - Each block keeps its native tooling: Sass variables, TypeScript types and Pug syntax work as they would in standalone files. - Vue's own transforms run on a normalised input, plain CSS or HTML, so scoped styles and template compilation do not need to understand every preprocessor. ## Diagnosing a failing block 1. **Read which block failed.** Build errors name the block type; a style failure is almost always a preprocessor or CSS syntax problem, not a Vue one. 2. **Check the dependency.** A missing `sass`, `less` or `stylus` package, or a template engine for `lang="pug"`, produces a load error from the compiler. 3. **Check the script pair.** A mismatch between `<script lang="ts">` and `<script setup>` is reported before anything else compiles. 4. **Check the toolchain for TypeScript.** If TypeScript syntax reaches the browser or the bundler chokes on a type annotation, the toolchain's TypeScript transform is missing, because Vue left the annotations in place. ## Common mistakes - Expecting `lang="ts"` to catch type errors during the build; it enables parsing, not checking. - Forgetting to install `sass` and reading the resulting error as a Vue bug. - Mixing `lang="ts"` on `<script setup>` with a plain `<script>` in the same file. ## Summary `lang` tells the SFC compiler which language a block is written in. Style and template content goes through an installed preprocessor before Vue's transforms; TypeScript is parsed by Vue and transpiled by the toolchain; and both script blocks of one file must agree on their language.
- Does <script setup lang="ts"> make the Vue build fail on a type error?No. The SFC compiler parses TypeScript so it can read type-based macros, and the toolchain strips the types, but neither step type-checks. Type errors in scripts and templates are reported by a separate type-checking step that you run in your editor or CI.
- In <style scoped lang="scss">, which runs first, Sass or Vue's scoping?Sass runs first and produces plain CSS; Vue's style compiler then applies the scoped attribute rewrite to that CSS. That order is why Sass nesting and variables work normally inside a scoped block.
saying these in an interview costs you the question
- Vue bundles Sass, so lang="scss" works without installing anything.
- lang="ts" makes the SFC compiler type-check the component during the build.
- Vue applies scoped rewriting before the Sass compiler runs.
- <script lang="ts"> and a plain <script setup> can be mixed in one file.
- The lang attribute only works on <script> blocks.