English | 简体中文
vue-sfc-component
is a library designed for compiling and mounting Vue Single File Components (SFCs) directly in the browser. It simplifies the process of rendering Vue components from various sources, making it ideal for quick prototyping and educational purposes.
-
Front-End Compilation: All compilations are completed in the browser, eliminating the need for a build step.
-
TypeScript:
vue-sfc-component
provides limited TypeScript support (does not check types, only removes type annotations). Simply use the<script lang="ts">
tag in your.vue
files or import.ts
files. -
Setup Feature: Supports the
<script setup>
syntactic sugar. -
Multiple Ways to Load Dependencies: Allows compiled components to use dependencies from the current project or to directly load dependencies via URLs.
-
Scoped CSS Support: You can use Scoped CSS to isolate component styles and prevent them from leaking into the global scope.
-
Seamless Integration with Existing Vue Projects:
vue-sfc-component
can compile a series of files into a Vue component and usedefineAsyncComponent
for integration into existing Vue projects.
Install vue-sfc-component
using npm:
npm install vue-sfc-component
Or with yarn:
yarn add vue-sfc-component
import { createApp, defineAsyncComponent } from 'vue'
import { defineSFC } from 'vue-sfc-component';
const files = {
'App.vue': `
<template>
<div>
<h1>{{ msg }}</h1>
<button @click="msg = msg.split('').reverse().join('')">
Reverse
</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const msg = ref("Hello World!")
</script>
<style scoped>
h1 {
color: red;
}
</style>`
}
const app = createApp({
components: {
'sfc-component': defineAsyncComponent(() => defineSFC('App.vue', { files })),
},
template: `<sfc-component></sfc-component>`
});
app.mount('#app')
In some cases, compiled components may lose reactivity. For example:
<template>
<button @click="reverse">{{ msg }}</button>
</template>
<script setup>
import { ref } from 'vue'
const msg = ref('Hello World!')
const reverse = () => {
msg.value = msg.value.split('').reverse().join('')
}
</script>
At this time, clicking the button will call the function, but the button text does not update correctly.
In this situation, it is necessary to manually import Vue and pass it to the defineSFC function.
import * as vue from 'vue'
defineSFC('App.vue', {
files,
imports: {
vue
}
});
The reason for this issue has not yet been identified.
defineSFC
is the main function of vue-sfc-component
. It takes a *.vue
file name and then returns a Vue component. This function also accepts an optional configuration object, allowing you to customize the component loading process.
type MaybePromise<T> = T | Promise<T>;
type FileContent = string | ArrayBuffer | Blob | Response;
export async function defineSFC(
mainfile: string,
options?: {
imports?: Record<string, any>;
files?: Record<string, FileContent | URL>;
getFile?: (path: string) => MaybePromise<FileContent | URL>;
renderStyles?: (css: string) => MaybePromise<(() => void)>;
catch?: (errors: Array<string | Error>) => MaybePromise<void>;
fileConvertRule?: (file: File) => MaybePromise<void>;
cache?: boolean;
}
) : Promise<Component>;
The imports
parameter in vue-sfc-component
enhances the module importing experience by allowing you to simplify the import process. You can use already imported modules or specify URLs for direct imports.
Here’s how you can use it:
import lodash from 'lodash'
import * as axios from 'axios';
defineSFC('App.vue', {
files,
imports: {
lodash: {
default: lodash
},
axios,
moment: "https://esm.sh/moment"
}
});
With this configuration, you can use imports in your SFCs like this:
<script setup lang="ts">
import _ from 'lodash'
import moment from 'moment'
import axios, {isCancel, AxiosError} from 'axios'
console.log(_.camelCase('hello world'))
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'))
axios('https://jsonplaceholder.typicode.com/todos/1')
.then(console.log)
.catch((error: AxiosError) => {
if (isCancel(error)) {
console.log('Request canceled', error.message)
} else {
console.log(error)
}
})
</script>
Alternatively, you can directly use a URL for importing within your SFC code:
<script setup>
import moment from 'https://esm.sh/moment'
console.log(moment().format('MMMM Do YYYY, h:mm:ss a'))
</script>
Note that if you choose to import the module via URL, it will be cached locally by default. If you want to disable caching, you can set cache to false.
defineSFC('App.vue', {
files,
imports: {
moment: "https://esm.sh/moment"
},
cache: false
});
vue-sfc-component
offers the flexibility to develop components using multiple files, supporting various file types such as .vue
, .js
, .ts
, .css
, and .json
. This feature allows you to structure your component just like in a standard Vue project, providing a familiar and intuitive development experience.
Please note that using .css
files will apply styles globally, which might inadvertently affect other components. It's recommended to use scoped
styles to prevent such style contamination.
There are two approaches to utilize multi-file components:
// Approach 1: Using the `files` parameter
defineSFC('App.vue', {
files: {
'App.vue': "xxx"
// ... other files
}
});
// Approach 2: Using the `getFile` callback
defineSFC('App.vue', {
async getFile(path) {
const res = await fetch(path);
return await res.text();
}
});
These approaches can be mixed. The files
object is checked first for the specified files, and if not found, the getFile
callback is used to retrieve the file content.
The type of file content can be string
, ArrayBuffer
, Blob
, Response
, or URL
.
-
String
,ArrayBuffer
,Blob
, andResponse
: These types are directly used as the content of your component files. -
URL: For
vue
,css
,javascript
,typescript
orjson
, the library will utilizefetch
to retrieve the content from the specified URL.defineSFC('App.vue', { files: { 'App.vue': new URL('http://example.com/path/to/your/App.vue') // ... other files } });
For other file types not listed above, the provided URL will be used directly. See more details in the Other Files section.
You can directly import Vue SFCs in your components. For instance:
<script setup>
import Foo from './Foo.vue'
</script>
Imports and exports for JavaScript and TypeScript files follow the ECMAScript module (ESM) standard.
When the imported files are in files
, you can omit the file extension. For example:
<script setup>
import foo from './foo'
</script>
The resolution order is: original filename > foo.ts
> foo.js
.
However, when using the getFile
approach, you must specify the file extension.
To ensure CSS styles are properly processed and applied, explicit importation of CSS files is required.
Importing CSS in Script Tag
You can import CSS files directly within the <script setup>
tag. This approach applies the styles globally across your application.
Example:
<script setup>
import './style.css'
</script>
Scoped CSS Import
For component-scoped styles, you should import CSS within a <style scoped>
tag. Scoped styles ensure that CSS rules only apply to the current component, avoiding unwanted global side effects.
Examples:
<style scoped>
@import './style.css';
/* or */
@import url('./style.css');
/* or */
@import './style.css' screen and (min-width: 500px);
</style>
Importing External CSS
You can also import CSS files hosted externally. However, when importing external CSS resources, you should do it within a <style>
tag, as importing them in a <script>
tag would treat them as JavaScript files.
Example:
<style>
@import 'https://example.com/style.css';
</style>
JSON files are imported as objects, allowing you to use JSON data directly within your SFC.
Other file types are exported as strings representing the URL of the file.
The type of url depends on the type of file content you provide. If string
, ArrayBuffer
, Blob
, or Response
is passed, it will be a Blob URL
. If a URL
type is provided, it will be that specific URL
.
For example:
<script setup>
import a from './a.png' // a is a Blob URL
import b from './b.png' // b is new URL('./b.png', window.location.href).toString()
</script>
<template>
<img :src="a">
<img :src="b">
</template>
defineSFC('App.vue', files, {
async getFile(path) {
if (path === './a.png') {
return await fetch(path);
} else if (path === './b.png') {
return new URL(path, window.location.href);
}
throw new Error('File not found');
}
});
By default, vue-sfc-component
automatically renders the component's styles into the head
tag of the document. If you wish to customize the behavior of how styles are rendered, you can use the renderStyles
parameter.
defineSFC('App.vue', {
files,
renderStyles(styles) {
// 'styles' is a string containing all the component's styles
// You can decide how to render these styles, but remember to clear previous styles if necessary
return () => {
// This function is called when the component is unmounted
// You can use this function to clear the styles you previously rendered
}
}
});
By default, compilation errors are logged to the console. If you prefer to handle errors in a custom way, you can use the catch
parameter.
defineSFC('App.vue', {
files,
catch(err) {
// 'err' is an array containing errors, which can be either Error objects or strings
// Custom error handling logic goes here
}
});
If you have specific requirements that necessitate modifying file contents before compilation, you can achieve this using the fileConvertRule
parameter.
defineSFC('App.vue', {
files,
fileConvertRule(file) {
console.log(file.filename);
console.log(file.content);
console.log(file.language); // 'vue' | 'javascript' | 'typescript' | 'json' | 'css' | 'other'
console.log(file.mimetype); // Only present when file.language is 'other'
file.content = "xxx"; // Modify the file content
file.language = 'vue'; // Treat as a Vue file
// file.mime = 'text/plain'; // Set MIME type for 'other' language files
}
});
This project is licensed under the MIT License - see the LICENSE file for details.