) => FC
+
+export declare const applyStyle: ApplyStyle
+{{/if}}
diff --git a/packages/vite-loader/src/ts-parser.ts b/packages/vite-loader/src/ts-parser.ts
new file mode 100644
index 0000000..539e6cc
--- /dev/null
+++ b/packages/vite-loader/src/ts-parser.ts
@@ -0,0 +1,64 @@
+import * as R from 'ramda'
+import type {MsaProperty, MsaVariable} from './types'
+
+const isOptional = (value: MsaProperty | MsaVariable) => (
+ (Array.isArray(value) && value[2]) || (!Array.isArray(value) && value[`@isOptional`]) ? `?` : ``
+)
+
+const isBoolean = R.ifElse(
+ R.test(/^(true|false)$/),
+ R.always(`boolean`),
+ R.F
+)
+
+const isNumber = R.ifElse(
+ R.test(/^\d+$/),
+ R.always(`number`),
+ R.F
+)
+
+const toLiteral = R.pipe(
+ R.keys,
+ R.reject(R.equals(`@isOptional`)),
+ R.map((value: string) => `'${value}'`),
+ R.join(` | `),
+)
+
+const extractName = R.pipe(
+ R.keys,
+ R.reject(R.equals(`@isOptional`)),
+ R.head,
+)
+
+function definePropertyType(value: MsaProperty) {
+ const varName = extractName(value) as string
+ return (
+ isBoolean(varName) ||
+ isNumber(varName) ||
+ toLiteral(value)
+ )
+}
+
+const defineVariableType = R.pipe(
+ R.head,
+ (value: string) => isBoolean(value) || isNumber(value) || `string`,
+)
+
+const parseProperty = R.compose(
+ R.when(R.isEmpty, R.always(``)),
+ R.join(`\n`),
+ R.map(([key, value]: [string, MsaProperty]) =>
+ `${key}${isOptional(value)}: ${definePropertyType(value)}`
+ ),
+ R.toPairs
+)
+
+const parseVariable = R.compose(
+ R.join(`\n`),
+ R.map(([key, value]: [string, MsaVariable]) =>
+ `${key}${isOptional(value)}: ${defineVariableType(value)}`
+ ),
+ R.toPairs,
+)
+
+export {parseProperty, parseVariable}
diff --git a/packages/vite-loader/src/ts.ts b/packages/vite-loader/src/ts.ts
new file mode 100644
index 0000000..39b5847
--- /dev/null
+++ b/packages/vite-loader/src/ts.ts
@@ -0,0 +1,70 @@
+import type {ResultCacheValue} from "./types"
+import {resultCache} from "./index.js"
+import type {Plugin} from "vite"
+import {parseProperty, parseVariable} from "./ts-parser.js"
+import {writeFile, readFileSync} from 'node:fs'
+import {dirname, basename, join} from 'node:path'
+import Handlebars from "handlebars"
+import {createFilter} from '@rollup/pluginutils'
+
+/** @ts-ignore support both CommonJS and ESM */
+const currentDirname = typeof __dirname !== `undefined` ? __dirname : import.meta.dirname
+
+const template = readFileSync(join(currentDirname, `template.hbs`), `utf8`)
+const toDTS = Handlebars.compile(template, {noEscape: true})
+
+const filter = createFilter(/\.module\.s?css$/)
+const shouldTransform = (id: string) => filter(id) && resultCache.has(id)
+
+const nameFile = (path: string): string => {
+ const dirName = dirname(path)
+ const baseName = basename(path)
+ return join(dirName, `${baseName}.d.ts`)
+}
+
+function generateDeclarationFile(options: { id: string; onError?: (error: Error) => void }) {
+ const {id, onError} = options
+ const {msa} = resultCache.get(id) as ResultCacheValue
+ const exports = msa.map(({componentName, className, properties, tagName, variables}) => {
+ const parsedProperties = parseProperty(properties)
+ const parsedVariables = parseVariable(variables)
+ return {
+ className,
+ componentName,
+ isExtended: tagName && (parsedProperties || parsedVariables) && true,
+ isStyled: !tagName,
+ properties: parsedProperties,
+ propsType: `${componentName}Props`,
+ styledPropsType: `Styled${componentName}Props`,
+ tagName,
+ variables: parsedVariables,
+ }
+ })
+ const isRestyled = exports.some(({isStyled}) => isStyled)
+
+ writeFile(
+ nameFile(id),
+ toDTS({isRestyled, exports}),
+ error => error && onError && onError(error)
+ )
+}
+
+export default function tsLoader(): Plugin {
+ return {
+ name: `vite-ts-stylin`,
+
+ transform(code, id) {
+ if (shouldTransform(id)) {
+ generateDeclarationFile({id, onError: this.error})
+ }
+ },
+
+ handleHotUpdate(ctx) {
+ const id = ctx.file
+
+ if (shouldTransform(id)) {
+ generateDeclarationFile({id, onError: console.error})
+ }
+ },
+ }
+}
diff --git a/packages/vite-loader/src/types.ts b/packages/vite-loader/src/types.ts
new file mode 100644
index 0000000..ccd738c
--- /dev/null
+++ b/packages/vite-loader/src/types.ts
@@ -0,0 +1,22 @@
+export type MsaVariable = [string, string, boolean] // [default_value, css_var_name, is_optional]
+export type MsaProperty = Record