Skip to content

Tahul/sfc-composer

Repository files navigation

sfc-composer

npm version npm downloads Github Actions

πŸ‘¨β€πŸ”¬ Pre-compiler helpers for Single File Components

Currently supports: .vue, .svelte, .astro

πŸ•ΉοΈ Playground

Usage

Install package:

pnpm install sfc-composer
import { MagicSFC as MagicVueSFC } from 'sfc-composer/vue'
import { MagicSFC as MagicSvelteSFC } from 'sfc-composer/svelte'
import { MagicSFC as MagicAstroSFC } from 'sfc-composer/astro'

Internals

βš™οΈ MagicSFC class

MagicSFC<T> is the root interface supplied to be extended by framework-specific child classes.

  • scripts: MagicBlock<T>[]

    Referring to <script> or any JavaScript/TypeScript contexts of SFCs.

  • templates: MagicBlock<T>[]

    Referring to <template> parts of SFCs.

  • styles: MagicBlock<T>[]

    Referring to <style> parts of SFCs.

  • customs: MagicBlock<T>[]

    Custom blocks from frameworks parsers supporting that feature.

  • getSourcemap(options?: SourceMapOptions): SourceMap

    Generates a version 3 sourcemap like MagicString.

  • getTransformResult(): TransformResult

    Compatible with Vite transform() hook result.

  • parse(): Promise<MagicSFC>

    Uses the parser to update MagicSFC blocks.

Should be implemented by child classes.

Learn more about all the usages by looking at the tests!

βš™οΈ How MagicSFC works

When using the parse function, MagicSFC will split the supplied component using your framework native tooling.

The parsing result will be splitted into a standard format recognizing templates, scripts and styles blocks.

Each of these MagicBlock will preserve the original shape from the parser, but will also expose all the relevant MagicString functions.

Each of these function, when called, will both apply your changes on the local block.

You also get access to _loc and _source on every MagicBlock, which are standard copies of the block positioning and content.

When calling result, you will get the code as a string, and an appropriate SourceMap object.

Look at the implementation.

βš™οΈ createSFC functions

Frameworks exports a createSfc function that makes generating SFCs programatically easier.

They all support the same input aguments:

import { createSFC as createVueSFC } from 'sfc-composer/vue'
import { createSFC as createSvelteSFC } from 'sfc-composer/svelte'
import { createSFC as createAstroSFC } from 'sfc-composer/astro'

const writeableSFC = {
  templates: [
    {
      content: '<div>Hello World!</div>'
    }
  ],
  scripts: [
    {
      content: 'console.log(`Hello World!`)'
    }
  ],
  styles: [
    {
      content: 'div { color: red; }'
    }
  ]
}

// Will output a valid Svelte SFC
createSvelteSFC(writeableSFC)

// Will output a valid Astro SFC
createAstroSFC(writeableSFC)

// Will output a valid Vue SFC
createVueSFC({
  ...writeableSFC,
  // Vue also natively supports `customs` block in its parser.
  customs: [
    {
      type: 'i18n',
      content: '{ "fr": "Bonjour!", "en": "Hello!" }',
    }
  ]
})

MagicVueSFC

Example code
import { MagicSFC as MagicVueSFC } from 'sfc-composer/vue'
import { parse } from 'vue/compiler-sfc'

async function transformVueSFC() {
  const sfc = new MagicVueSFC(
    '<template><div>Hello World!</div></template>'
    + '<script setup>let test: string</script>'
    + '<style scoped>div { color: red; }</style>',
    {
      parser: parse
    }
  )

  // Process the SFC code through the parser
  await sfc.parse()

  // Append to the <script> tag
  sfc.scripts[0].append(' = `Hello World`')

  return sfc.result()

  // {
  //    code: '<template><div>Hello World!</div></template>\n\n<script setup>let test: string = `Hello World`</script>\n\n<style scoped>div { color: red; }</style>'
  //    map: SourceMap
  // }
}

Learn more by looking at the tests.

createVueSFC

Example code
import { createSFC as createVueSFC } from 'sfc-composer/vue'

const MagicVueSFC = createVueSFC({
  templates: [
    {
      content: '<div>{{ msg }}</div>',
    }
  ],
  script: [
    {
      content: 'export default { data() { return { msg: "Hello, world!" } } }'
    },
    {
      content: 'const setupMsg = "Hello from setup!"',
    }
  ],
  styles: [
    {
      lang: 'scss',
      scoped: true,
      content: '.text { color: red; }',
    },
  ],
})

πŸ–¨οΈ Will output

<script>
export default { data() { return { msg: "Hello, world!" } }
</script>

<script setup>
const setupMsg = "Hello from setup!"
</script>

<template>
<div>{{ msg }}</div>
</template>

<style scoped lang="scss">
.text { color: red; }
</style>

Learn more by looking at the tests!

MagicSvelteSFC

Example code
import { MagicSFC as MagicSvelteSFC } from 'sfc-composer/svelte'
import { preprocess } from 'svelte/compiler'

async function transformSvelteSFC() {
  const sfc = new MagicSvelteSFC(
    '<script>let test: string</script>\n\n'
    + '<div>Hello World!</div>\n\n'
    + '<style>div { color: red; }</style>',
    {
      parser: preprocess
    }
  )

  // Process the SFC code through the parser
  await sfc.parse()

  // Append to the <script> tag
  sfc.scripts[0].append(' = `Hello World`')

  return sfc.result()

  // {
  //    code: '<script>let test: string = `Hello World`</script>\n\n<div>Hello World!</div>\n\n<style>div { color: red; }</style>'
  //    map: SourceMap
  // }
}

Learn more by looking at the tests!

createSvelteSFC

Example code
import { createSFC as createSvelteSFC } from 'sfc-composer/svelte'

const MagicVueSFC = createSvelteSFC({
  templates: [
    {
      content: '<div>{msg}</div>',
    }
  ],
  script: [
    {
      content: 'let test = `Hello World!`;'
    }
  ],
  styles: [
    {
      content: '.text { color: red; }',
    },
  ],
})

πŸ–¨οΈ Will output

<script>
let test = `Hello World!`;
</script>

<script setup>
const setupMsg = "Hello from setup!"
</script>

<div>{msg}</div>

<style>
.text { color: red; }
</style>

Learn more by looking at the tests!

MagicAstroSFC

Example code
import { MagicSFC as MagicAstroSFC } from 'sfc-composer/astro'
import { preprocess } from '@astrojs/compiler'

async function transformAstroSFC() {
  const sfc = new MagicAstroSFC(
    '---\nlet test: string\n---\n\n'
    + '<div>Hello World!</div>\n\n'
    + '<style>div { color: red; }</style>',
    {
      parser: preprocess
    }
  )

  // Process the SFC code through the parser
  await sfc.parse()

  // Append to the <script> tag
  sfc.scripts[0].append('test = `Hello World`')

  return sfc.result()

  // {
  //    code: '---\nlet test: string\ntest = `Hello World`\n---\n\n<div>Hello World!</div>\n\n<style>div { color: red; }</style>'
  //    map: SourceMap
  // }
}

Learn more by looking at the tests!

createAstroSFC

Example code
import { createSFC as createAstroSFC } from 'sfc-composer/astro'

const MagicVueSFC = createAstroSFC({
  templates: [
    {
      content: '<div>{msg}</div>',
    }
  ],
  script: [
    {
      content: 'let test = `Hello World!`;',
      attrs: {
        frontmatter: true
      }
    },
    {
      content: 'let secondTest = `Hello World`;'
    }
  ],
  styles: [
    {
      content: '.text { color: red; }',
    },
  ],
})

πŸ–¨οΈ Will output

---
let test = `Hello World!`;
---

<script>
let secondTest = `Hello World`;
</script>

<div>{msg}</div>

<style>
.text { color: red; }
</style>

Learn more by looking at the tests!

Development

  • Clone this repository
  • Install latest LTS version of Node.js
  • Enable Corepack using corepack enable
  • Install dependencies using pnpm install
  • Run interactive tests using pnpm dev

License

Made with πŸ’š

Published under MIT License.