nuxt-generation-emails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,402 @@
1
+ import { defineNuxtModule, createResolver, addImports, addServerImports, extendPages, addServerHandler } from '@nuxt/kit';
2
+ import fs from 'node:fs';
3
+ import vue from '@vitejs/plugin-vue';
4
+ import { join, relative } from 'pathe';
5
+ import { consola } from 'consola';
6
+ import { parse, compileScript } from 'vue/compiler-sfc';
7
+
8
+ function generateWrapperComponent(emailsLayoutPath, emailComponentPath, extractedProps) {
9
+ const hasProps = extractedProps.props.length > 0;
10
+ const defaultsLiteral = JSON.stringify(extractedProps.defaults, null, 2);
11
+ const propDefsLiteral = JSON.stringify(
12
+ extractedProps.props.map((p) => ({ name: p.name, type: p.type })),
13
+ null,
14
+ 2
15
+ );
16
+ const scriptClose = "<\/script>";
17
+ const templateOpen = "<template>";
18
+ const templateClose = "</template>";
19
+ return `<script setup lang="ts">
20
+ import { reactive, definePageMeta, onMounted } from '#imports'
21
+ import EmailsLayout from '${emailsLayoutPath}'
22
+ import EmailComponent from '${emailComponentPath}'
23
+
24
+ definePageMeta({ layout: false })
25
+ ${hasProps ? `
26
+ const propDefaults = ${defaultsLiteral}
27
+ const propDefinitions = ${propDefsLiteral}
28
+
29
+ // Reactive state derived from the template's withDefaults \u2014 drives both
30
+ // the live preview and the sidebar controls.
31
+ const emailProps = reactive<Record<string, unknown>>({ ...propDefaults })
32
+
33
+ // Hydrate from URL params on mount so shared links restore state.
34
+ onMounted(() => {
35
+ if (typeof window !== 'undefined') {
36
+ const params = new URLSearchParams(window.location.search)
37
+ params.forEach((value, key) => {
38
+ if (key in emailProps) {
39
+ const current = emailProps[key]
40
+ if (typeof current === 'number') {
41
+ const n = Number(value)
42
+ if (!Number.isNaN(n)) emailProps[key] = n
43
+ } else if (typeof current === 'boolean') {
44
+ emailProps[key] = value === 'true'
45
+ } else {
46
+ emailProps[key] = value
47
+ }
48
+ }
49
+ })
50
+ }
51
+ })
52
+ ` : ""}
53
+ ${scriptClose}
54
+
55
+ ${templateOpen}
56
+ <EmailsLayout${hasProps ? ' :email-props="emailProps" :prop-definitions="propDefinitions"' : ""}>
57
+ <EmailComponent${hasProps ? ' v-bind="emailProps"' : ""} />
58
+ </EmailsLayout>
59
+ ${templateClose}
60
+ `;
61
+ }
62
+
63
+ function extractPropsFromSFC(filePath) {
64
+ const source = fs.readFileSync(filePath, "utf-8");
65
+ const { descriptor } = parse(source, { filename: filePath });
66
+ if (!descriptor.scriptSetup) {
67
+ return { props: [], defaults: {} };
68
+ }
69
+ try {
70
+ const compiled = compileScript(descriptor, {
71
+ id: filePath,
72
+ isProd: false
73
+ });
74
+ const props = [];
75
+ const defaults = {};
76
+ const propNames = [];
77
+ if (compiled.bindings) {
78
+ for (const [name, binding] of Object.entries(compiled.bindings)) {
79
+ if (binding === "props") {
80
+ propNames.push(name);
81
+ }
82
+ }
83
+ }
84
+ const propTypes = extractPropTypesFromSource(descriptor.scriptSetup.content);
85
+ for (const name of propNames) {
86
+ const type = propTypes[name] ?? "unknown";
87
+ props.push({ name, type });
88
+ }
89
+ const defaultValues = extractDefaultsFromSource(descriptor.scriptSetup.content);
90
+ for (const [name, value] of Object.entries(defaultValues)) {
91
+ defaults[name] = value;
92
+ const existing = props.find((p) => p.name === name);
93
+ if (existing) {
94
+ existing.default = value;
95
+ }
96
+ }
97
+ return { props, defaults };
98
+ } catch {
99
+ return { props: [], defaults: {} };
100
+ }
101
+ }
102
+ function extractPropTypesFromSource(scriptContent) {
103
+ const result = {};
104
+ const match = scriptContent.match(/defineProps\s*<\s*\{([\s\S]*?)\}\s*>\s*\(/);
105
+ if (!match) return result;
106
+ const typeBody = match[1];
107
+ const propPattern = /(\w+)\s*(?:\?\s*)?:\s*(\w+)/g;
108
+ let propMatch;
109
+ while ((propMatch = propPattern.exec(typeBody)) !== null) {
110
+ const name = propMatch[1];
111
+ const tsType = propMatch[2];
112
+ switch (tsType.toLowerCase()) {
113
+ case "string":
114
+ result[name] = "string";
115
+ break;
116
+ case "number":
117
+ result[name] = "number";
118
+ break;
119
+ case "boolean":
120
+ result[name] = "boolean";
121
+ break;
122
+ default:
123
+ result[name] = "object";
124
+ break;
125
+ }
126
+ }
127
+ return result;
128
+ }
129
+ function extractDefaultsFromSource(scriptContent) {
130
+ const withDefaultsMatch = scriptContent.match(
131
+ /withDefaults\s*\(\s*defineProps\s*<[^>]*>\s*\(\s*\)\s*,\s*(\{[\s\S]*?\})\s*\)/
132
+ );
133
+ if (!withDefaultsMatch) return {};
134
+ const defaultsText = withDefaultsMatch[1];
135
+ if (!defaultsText) return {};
136
+ try {
137
+ const jsonish = defaultsText.replace(/'/g, '"').replace(/(\w+)\s*:/g, '"$1":').replace(/,\s*([\]}])/g, "$1");
138
+ return JSON.parse(jsonish);
139
+ } catch {
140
+ return extractPrimitivesFromObjectLiteral(defaultsText);
141
+ }
142
+ }
143
+ function extractPrimitivesFromObjectLiteral(text) {
144
+ const result = {};
145
+ const linePattern = /(\w+)\s*:\s*(?:'([^']*)'|"([^"]*)"|(\d+(?:\.\d+)?)|(true|false))\s*[,}]?/g;
146
+ let match;
147
+ while ((match = linePattern.exec(text)) !== null) {
148
+ const key = match[1];
149
+ if (match[2] != null) result[key] = match[2];
150
+ else if (match[3] != null) result[key] = match[3];
151
+ else if (match[4] != null) result[key] = Number(match[4]);
152
+ else if (match[5] != null) result[key] = match[5] === "true";
153
+ }
154
+ return result;
155
+ }
156
+
157
+ function addEmailPages(dirPath, pages, options, routePrefix = "") {
158
+ const entries = fs.readdirSync(dirPath);
159
+ for (const entry of entries) {
160
+ const fullPath = join(dirPath, entry);
161
+ const stat = fs.statSync(fullPath);
162
+ if (stat.isDirectory()) {
163
+ addEmailPages(fullPath, pages, options, `${routePrefix}/${entry}`);
164
+ } else if (entry.endsWith(".vue")) {
165
+ const name = entry.replace(".vue", "");
166
+ const routePath = `/__emails${routePrefix}/${name}`;
167
+ const wrapperPath = join(options.buildDir, "email-wrappers", `${routePrefix}/${name}.vue`.replace(/^\//, ""));
168
+ const wrapperDir = join(options.buildDir, "email-wrappers", routePrefix.replace(/^\//, ""));
169
+ if (!fs.existsSync(wrapperDir)) {
170
+ fs.mkdirSync(wrapperDir, { recursive: true });
171
+ }
172
+ const extractedProps = extractPropsFromSFC(fullPath);
173
+ const wrapperContent = generateWrapperComponent(
174
+ options.emailTemplateComponentPath,
175
+ fullPath,
176
+ extractedProps
177
+ );
178
+ fs.writeFileSync(wrapperPath, wrapperContent, "utf-8");
179
+ const pageName = `email${routePrefix.replace(/\//g, "-")}-${name}`.replace(/^-+/, "");
180
+ pages.push({
181
+ name: pageName,
182
+ path: routePath,
183
+ file: wrapperPath
184
+ });
185
+ }
186
+ }
187
+ }
188
+
189
+ function generateApiRoute(emailName, emailPath, examplePayload = "{}") {
190
+ return `/* eslint-disable */
191
+ // This file is generated by nuxt-generation-emails. Do not modify it manually.
192
+
193
+ import { defineEventHandler, readBody, createError } from 'h3'
194
+ import { useNitroApp, getSendGenEmailsHandler } from '#imports'
195
+ import { render } from '@vue-email/render'
196
+ import EmailTemplate from '~/emails/${emailPath}.vue'
197
+
198
+ defineRouteMeta({
199
+ openAPI: {
200
+ tags: ['nuxt-generation-emails'],
201
+ summary: 'Send ${emailName} email',
202
+ description: 'Render and send the ${emailPath} email template.',
203
+ requestBody: {
204
+ required: true,
205
+ content: {
206
+ 'application/json': {
207
+ schema: {
208
+ type: 'object',
209
+ example: ${examplePayload},
210
+ additionalProperties: true,
211
+ description: '${emailName} props payload',
212
+ },
213
+ },
214
+ },
215
+ },
216
+ responses: {
217
+ 200: {
218
+ description: 'Email rendered successfully',
219
+ content: {
220
+ 'application/json': {
221
+ schema: {
222
+ type: 'object',
223
+ properties: {
224
+ success: { type: 'boolean' },
225
+ message: { type: 'string' },
226
+ html: { type: 'string' },
227
+ },
228
+ required: ['success', 'message', 'html'],
229
+ },
230
+ },
231
+ },
232
+ },
233
+ 500: {
234
+ description: 'Failed to render or send email',
235
+ content: {
236
+ 'application/json': {
237
+ schema: {
238
+ type: 'object',
239
+ properties: {
240
+ statusCode: { type: 'number', example: 500 },
241
+ statusMessage: { type: 'string' },
242
+ },
243
+ required: ['statusCode', 'statusMessage'],
244
+ },
245
+ },
246
+ },
247
+ },
248
+ },
249
+ },
250
+ })
251
+
252
+ export default defineEventHandler(async (event) => {
253
+ const body = await readBody<Record<string, unknown>>(event)
254
+
255
+ try {
256
+ const html = await render(EmailTemplate, body, { pretty: true })
257
+
258
+ const nitro = useNitroApp()
259
+ const sendGenEmailsHandler = getSendGenEmailsHandler()
260
+
261
+ if (sendGenEmailsHandler) {
262
+ await sendGenEmailsHandler(html, body)
263
+ }
264
+ else {
265
+ // @ts-ignore - custom hook
266
+ await nitro.hooks.callHook('nuxt-gen-emails:send', { html, data: body })
267
+ }
268
+
269
+ return { success: true, message: 'Email rendered successfully', html }
270
+ }
271
+ catch (error: unknown) {
272
+ const message = error instanceof Error ? error.message : 'Failed to render or send email'
273
+ throw createError({ statusCode: 500, statusMessage: message })
274
+ }
275
+ })
276
+ `;
277
+ }
278
+
279
+ function generateServerRoutes(emailsDir, buildDir) {
280
+ if (!fs.existsSync(emailsDir)) return [];
281
+ const handlersDir = join(buildDir, "email-handlers");
282
+ const handlers = [];
283
+ if (!fs.existsSync(handlersDir)) {
284
+ fs.mkdirSync(handlersDir, { recursive: true });
285
+ }
286
+ function processEmailDirectory(dirPath, routePrefix = "") {
287
+ const entries = fs.readdirSync(dirPath);
288
+ for (const entry of entries) {
289
+ const fullPath = join(dirPath, entry);
290
+ const stat = fs.statSync(fullPath);
291
+ if (stat.isDirectory()) {
292
+ processEmailDirectory(fullPath, `${routePrefix}/${entry}`);
293
+ } else if (entry.endsWith(".vue")) {
294
+ const emailName = entry.replace(".vue", "");
295
+ const emailPath = `${routePrefix}/${emailName}`.replace(/^\//, "");
296
+ const handlerDir = routePrefix ? join(handlersDir, routePrefix.replace(/^\//, "")) : handlersDir;
297
+ if (!fs.existsSync(handlerDir)) {
298
+ fs.mkdirSync(handlerDir, { recursive: true });
299
+ }
300
+ const { defaults } = extractPropsFromSFC(fullPath);
301
+ const examplePayload = Object.keys(defaults).length > 0 ? JSON.stringify(defaults, null, 2) : "{}";
302
+ const handlerFileName = `${emailName}.ts`;
303
+ const handlerFilePath = join(handlerDir, handlerFileName);
304
+ const handlerContent = generateApiRoute(emailName, emailPath, examplePayload);
305
+ fs.writeFileSync(handlerFilePath, handlerContent, "utf-8");
306
+ console.log(`[nuxt-gen-emails] Generated API handler: ${handlerFilePath}`);
307
+ handlers.push({
308
+ route: `/api/emails/${emailPath}`,
309
+ method: "post",
310
+ handlerPath: handlerFilePath
311
+ });
312
+ }
313
+ }
314
+ }
315
+ processEmailDirectory(emailsDir);
316
+ return handlers;
317
+ }
318
+
319
+ const module$1 = defineNuxtModule({
320
+ meta: {
321
+ name: "nuxt-generation-emails",
322
+ configKey: "nuxtGenerationEmails",
323
+ compatibility: {
324
+ // Semver version of supported nuxt versions
325
+ nuxt: ">=4.0.0"
326
+ }
327
+ },
328
+ defaults: {
329
+ emailDir: "emails"
330
+ },
331
+ async setup(options, nuxt) {
332
+ const resolver = createResolver(import.meta.url);
333
+ const configuredEmailDir = options.emailDir ?? "emails";
334
+ const emailsDir = join(nuxt.options.srcDir, configuredEmailDir);
335
+ nuxt.options.runtimeConfig.nuxtGenEmails = {
336
+ emailsDir,
337
+ sendGenEmails: options.sendGenEmails
338
+ };
339
+ addImports([
340
+ {
341
+ name: "encodeStoreToUrlParams",
342
+ from: resolver.resolve("./runtime/utils/url-params")
343
+ },
344
+ {
345
+ name: "generateShareableUrl",
346
+ from: resolver.resolve("./runtime/utils/url-params")
347
+ }
348
+ ]);
349
+ addServerImports([
350
+ {
351
+ name: "encodeStoreToUrlParams",
352
+ from: resolver.resolve("./runtime/utils/url-params")
353
+ },
354
+ {
355
+ name: "getSendGenEmailsHandler",
356
+ from: resolver.resolve("./runtime/server/utils/send-gen-emails")
357
+ }
358
+ ]);
359
+ extendPages((pages) => {
360
+ if (!fs.existsSync(emailsDir)) {
361
+ return;
362
+ }
363
+ addEmailPages(emailsDir, pages, {
364
+ buildDir: nuxt.options.buildDir,
365
+ emailTemplateComponentPath: resolver.resolve("./runtime/pages/__emails.vue")
366
+ });
367
+ });
368
+ const handlers = generateServerRoutes(emailsDir, nuxt.options.buildDir);
369
+ for (const handler of handlers) {
370
+ addServerHandler({
371
+ route: handler.route,
372
+ method: handler.method,
373
+ handler: handler.handlerPath
374
+ });
375
+ }
376
+ const rollupConfig = nuxt.options.nitro?.rollupConfig ?? {};
377
+ const existingPlugins = rollupConfig.plugins ?? [];
378
+ const plugins = Array.isArray(existingPlugins) ? existingPlugins : [existingPlugins];
379
+ rollupConfig.plugins = [...plugins, vue()];
380
+ nuxt.options.nitro = { ...nuxt.options.nitro, rollupConfig };
381
+ if (nuxt.options.dev && fs.existsSync(emailsDir)) {
382
+ const relDir = relative(nuxt.options.rootDir, emailsDir);
383
+ consola.info(`[nuxt-gen-emails] Watching for new templates in ${relDir}/`);
384
+ nuxt.options.watch.push(emailsDir + "/**/*.vue");
385
+ nuxt.hook("builder:watch", (event, relativePath) => {
386
+ const absolutePath = join(nuxt.options.rootDir, relativePath);
387
+ const rel = relative(emailsDir, absolutePath);
388
+ if (event === "add") {
389
+ consola.success(`[nuxt-gen-emails] New template detected: ${rel}`);
390
+ consola.info("[nuxt-gen-emails] Restarting to register new routes...");
391
+ nuxt.callHook("restart");
392
+ } else if (event === "unlink") {
393
+ consola.warn(`[nuxt-gen-emails] Template removed: ${rel}`);
394
+ consola.info("[nuxt-gen-emails] Restarting to update routes...");
395
+ nuxt.callHook("restart");
396
+ }
397
+ });
398
+ }
399
+ }
400
+ });
401
+
402
+ export { module$1 as default };
@@ -0,0 +1,6 @@
1
+ type __VLS_Props = {
2
+ dataObject: Record<string, unknown>;
3
+ };
4
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ declare const _default: typeof __VLS_export;
6
+ export default _default;
@@ -0,0 +1,124 @@
1
+ <script setup>
2
+ import { ref, computed } from "vue";
3
+ import { useRoute } from "#imports";
4
+ const props = defineProps({
5
+ dataObject: { type: Object, required: true }
6
+ });
7
+ const route = useRoute();
8
+ const isLoading = ref(false);
9
+ const response = ref("");
10
+ const responseData = ref(null);
11
+ const error = ref("");
12
+ const copySuccess = ref(false);
13
+ const copyHtmlSuccess = ref(false);
14
+ const apiEndpoint = computed(() => {
15
+ const templatePath = route.path.replace("/__emails/", "");
16
+ return `/api/emails/${templatePath}`;
17
+ });
18
+ const htmlResponse = computed(() => {
19
+ if (!responseData.value || typeof responseData.value !== "object") return "";
20
+ if (!("html" in responseData.value)) return "";
21
+ const html = responseData.value.html;
22
+ return typeof html === "string" ? html : "";
23
+ });
24
+ async function testApi() {
25
+ isLoading.value = true;
26
+ error.value = "";
27
+ response.value = "";
28
+ try {
29
+ const result = await $fetch(apiEndpoint.value, {
30
+ method: "POST",
31
+ body: props.dataObject
32
+ });
33
+ responseData.value = result;
34
+ response.value = JSON.stringify(result, null, 2);
35
+ } catch (err) {
36
+ error.value = err instanceof Error ? err.message : "Failed to test API";
37
+ responseData.value = err;
38
+ response.value = JSON.stringify(err, null, 2);
39
+ } finally {
40
+ isLoading.value = false;
41
+ }
42
+ }
43
+ async function copyResponse() {
44
+ if (!response.value) return;
45
+ try {
46
+ await navigator.clipboard.writeText(response.value);
47
+ copySuccess.value = true;
48
+ setTimeout(() => {
49
+ copySuccess.value = false;
50
+ }, 2e3);
51
+ } catch (error2) {
52
+ console.error("Failed to copy response:", error2);
53
+ }
54
+ }
55
+ async function copyHtml() {
56
+ if (!htmlResponse.value) return;
57
+ try {
58
+ await navigator.clipboard.writeText(htmlResponse.value);
59
+ copyHtmlSuccess.value = true;
60
+ setTimeout(() => {
61
+ copyHtmlSuccess.value = false;
62
+ }, 2e3);
63
+ } catch (error2) {
64
+ console.error("Failed to copy HTML:", error2);
65
+ }
66
+ }
67
+ </script>
68
+
69
+ <template>
70
+ <div class="nge-api-tester">
71
+ <div class="nge-api-tester__header">
72
+ <h4>API Tester</h4>
73
+ <button
74
+ class="nge-api-tester__button"
75
+ :disabled="isLoading"
76
+ @click="testApi"
77
+ >
78
+ {{ isLoading ? "Testing..." : "Test POST Request" }}
79
+ </button>
80
+ </div>
81
+ <div class="nge-api-tester__endpoint">
82
+ <code>POST {{ apiEndpoint }}</code>
83
+ </div>
84
+ <div
85
+ v-if="error"
86
+ class="nge-api-tester__error"
87
+ >
88
+ {{ error }}
89
+ </div>
90
+ <div
91
+ v-if="response"
92
+ class="nge-api-tester__response"
93
+ >
94
+ <div class="nge-api-tester__response-header">
95
+ <span class="nge-api-tester__response-label">Response</span>
96
+ <div class="nge-api-tester__response-actions">
97
+ <button
98
+ v-if="htmlResponse"
99
+ class="nge-api-tester__copy-button"
100
+ @click="copyHtml"
101
+ >
102
+ {{ copyHtmlSuccess ? "\u2713 Copied HTML" : "Copy HTML" }}
103
+ </button>
104
+ <button
105
+ class="nge-api-tester__copy-button"
106
+ @click="copyResponse"
107
+ >
108
+ {{ copySuccess ? "\u2713 Copied" : "Copy" }}
109
+ </button>
110
+ </div>
111
+ </div>
112
+ <textarea
113
+ class="nge-api-tester__output"
114
+ :value="response"
115
+ readonly
116
+ placeholder="Response will appear here..."
117
+ />
118
+ </div>
119
+ </div>
120
+ </template>
121
+
122
+ <style scoped>
123
+ .nge-api-tester{border-top:1px solid #e5e7eb;margin-top:24px;padding-top:24px}.nge-api-tester__header{align-items:center;display:flex;justify-content:space-between;margin-bottom:12px}.nge-api-tester__header h4{color:#111827;font-size:14px;font-weight:600;margin:0}.nge-api-tester__button,.nge-api-tester__header h4{font-family:DM Sans,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;letter-spacing:-.01em}.nge-api-tester__button{background:#047857;border:none;border-radius:6px;color:#fff;cursor:pointer;font-size:12px;font-weight:500;padding:8px 14px;transition:all .2s cubic-bezier(.4,0,.2,1)}.nge-api-tester__button:hover:not(:disabled){background:#065f46;box-shadow:0 2px 8px rgba(4,120,87,.3);transform:translateY(-1px)}.nge-api-tester__button:active:not(:disabled){transform:translateY(0)}.nge-api-tester__button:disabled{cursor:not-allowed;opacity:.6}.nge-api-tester__endpoint{background:#f9fafb;border:1px solid #e5e7eb;border-radius:6px;margin-bottom:12px;padding:8px 12px}.nge-api-tester__endpoint code{color:#374151;font-family:Monaco,Menlo,Consolas,monospace;font-size:11px;word-break:break-all}.nge-api-tester__error{background:#fef2f2;border:1px solid #fecaca;border-radius:6px;color:#dc2626;font-family:DM Sans,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:12px;margin-bottom:12px;padding:10px 12px}.nge-api-tester__response{margin-top:12px}.nge-api-tester__response-header{align-items:center;display:flex;justify-content:space-between;margin-bottom:8px}.nge-api-tester__response-actions{display:flex;gap:8px}.nge-api-tester__response-label{color:#6b7280;font-size:12px}.nge-api-tester__copy-button,.nge-api-tester__response-label{font-family:DM Sans,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-weight:500;letter-spacing:-.01em}.nge-api-tester__copy-button{background:#047857;border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:11px;padding:6px 12px;transition:all .2s cubic-bezier(.4,0,.2,1)}.nge-api-tester__copy-button:hover{background:#065f46}.nge-api-tester__output{background:#f9fafb;border:1px solid #d1d5db;border-radius:6px;box-sizing:border-box;color:#111827;font-family:Monaco,Menlo,Consolas,monospace;font-size:11px;line-height:1.5;max-height:400px;min-height:200px;padding:12px;resize:vertical;width:100%}.nge-api-tester__output:focus{border-color:#047857;box-shadow:0 0 0 3px rgba(4,120,87,.1);outline:none}
124
+ </style>
@@ -0,0 +1,6 @@
1
+ type __VLS_Props = {
2
+ dataObject: Record<string, unknown>;
3
+ };
4
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ declare const _default: typeof __VLS_export;
6
+ export default _default;
@@ -0,0 +1,6 @@
1
+ type __VLS_Props = {
2
+ templates: string[];
3
+ };
4
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
5
+ declare const _default: typeof __VLS_export;
6
+ export default _default;