Skip to content

Commit ec96362

Browse files
Merge branch 'develop' of https://github.com/live-codes/livecodes into i18n/develop
2 parents 3d8bf0e + 8053bcf commit ec96362

10 files changed

Lines changed: 159 additions & 36 deletions

File tree

docs/docs/languages/vue.mdx

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ import Counter from 'https://raw.githubusercontent.com/hatemhosny/simple-vue-cou
257257

258258
Please note that extensionless imports are not supported. However, you may customize the import URL using import maps as described in [module resolution](../features/module-resolution.mdx#custom-module-resolution) section.
259259

260-
This is an example of importing a Vue SFC, which in turn imports other Vue SFCs and extensionless imports, that are customized using importmap:
260+
Example:
261261

262262
```json title="Custom Settings"
263263
{
@@ -298,7 +298,62 @@ import App from 'https://raw.githubusercontent.com/hatemhosny/vue3-samples/maste
298298
</a>
299299
</div>
300300

301-
<LiveCodes config={importExternalWithImportMap}></LiveCodes>
301+
### Importing Data URLs
302+
303+
You may want to import other SFCs without having to host them on a server.
304+
These components can be encoded as [data URLs](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data) and imported as usual.
305+
306+
The data URL has to start with `data:text/vue` to be recognized as a Vue SFC. Any imports in the imported URLs (even if they are also data URLs) are resolved and compiled recursively.
307+
308+
:::info
309+
310+
The code in any code editor can be encoded as data URL from the LiveCodes UI using the "Copy code as data URL" button below the code editor.
311+
312+
:::
313+
314+
This is the previous demo that uses data URLs (with nested imports) instead of external URLs:
315+
316+
export const importDataUrls = {
317+
activeEditor: 'script',
318+
script: {
319+
language: 'vue',
320+
content: `<script setup>
321+
import Counter from 'data:text/vue;charset=UTF-8;base64,PHNjcmlwdCBzZXR1cCBsYW5nPSJ0cyI+DQppbXBvcnQgeyByZWYgfSBmcm9tICJ2dWUiOw0KaW1wb3J0IFByaW1hcnlCdXR0b24gZnJvbSAiZGF0YTp0ZXh0L3Z1ZTtjaGFyc2V0PVVURi04O2Jhc2U2NCxQSE5qY21sd2RDQnpaWFIxY0NCc1lXNW5QU0owY3lJK0RRcGtaV1pwYm1WUWNtOXdjeWg3RFFvZ0lIUnBkR3hsT2lCN0RRb2dJQ0FnZEhsd1pUb2dVM1J5YVc1bkxBMEtJQ0FnSUdSbFptRjFiSFE2SUNKQ2RYUjBiMjRpTEEwS0lDQjlMQTBLZlNrN0RRbzhMM05qY21sd2RENE5DZzBLUEhSbGJYQnNZWFJsUGcwS0lDQThZblYwZEc5dURRb2dJQ0FnWTJ4aGMzTTlJblJsZUhRdGJXUWdabTl1ZEMxdFpXUnBkVzBnWW1jdFozSmhlUzAxTURBZ2FHOTJaWEk2WW1jdFozSmhlUzAyTURBZ2RISmhibk5wZEdsdmJpQndlUzB4SUhCNExUUWdkR1Y0ZEMxM2FHbDBaU0J5YjNWdVpHVmtJR1J5YjNBdGMyaGhaRzkzTFhoc0lnMEtJQ0ErRFFvZ0lDQWdlM3NnZEdsMGJHVWdmWDBOQ2lBZ1BDOWlkWFIwYjI0K0RRbzhMM1JsYlhCc1lYUmxQZz09IjsNCmltcG9ydCBEYW5nZXJCdXR0b24gZnJvbSAiZGF0YTp0ZXh0L3Z1ZTtjaGFyc2V0PVVURi04O2Jhc2U2NCxQSE5qY21sd2RDQnpaWFIxY0NCc1lXNW5QU0owY3lJK0RRcGtaV1pwYm1WUWNtOXdjeWg3RFFvZ0lIUnBkR3hsT2lCN0RRb2dJQ0FnZEhsd1pUb2dVM1J5YVc1bkxBMEtJQ0FnSUdSbFptRjFiSFE2SUNKQ2RYUjBiMjRpTEEwS0lDQjlMQTBLZlNrN0RRbzhMM05qY21sd2RENE5DZzBLUEhSbGJYQnNZWFJsUGcwS0lDQThZblYwZEc5dURRb2dJQ0FnWTJ4aGMzTTlJblJsZUhRdGJXUWdabTl1ZEMxdFpXUnBkVzBnWW1jdGNtVmtMVFV3TUNCb2IzWmxjanBpWnkxeVpXUXROakF3SUhSeVlXNXphWFJwYjI0Z2NIa3RNU0J3ZUMwMElIUmxlSFF0ZDJocGRHVWdjbTkxYm1SbFpDQmtjbTl3TFhOb1lXUnZkeTE0YkNJTkNpQWdQZzBLSUNBZ0lIdDdJSFJwZEd4bElIMTlEUW9nSUR3dlluVjBkRzl1UGcwS1BDOTBaVzF3YkdGMFpUND0iOw0KDQpjb25zdCBjb3VudCA9IHJlZigwKTsNCjwvc2NyaXB0Pg0KDQo8dGVtcGxhdGU+DQogIDxkaXYgY2xhc3M9InctZnVsbCBtdC04IGZsZXgganVzdGlmeS1jZW50ZXIiPg0KICAgIDxzcGFuIHJlZj0iY291bnRlciIgY2xhc3M9InRleHQtM3hsIGZvbnQtYm9sZCI+e3sgY291bnQgfX08L3NwYW4+DQogIDwvZGl2Pg0KICA8ZGl2IGNsYXNzPSJ3LWZ1bGwgbXQtNCBmbGV4IGZsZXgtcm93IHNwYWNlLXgtNCBqdXN0aWZ5LWNlbnRlciI+DQogICAgPHByaW1hcnktYnV0dG9uIHJlZj0iZGVjcmVtZW50QnV0dG9uIiB0aXRsZT0iLSIgQGNsaWNrPSJjb3VudC0tIiAvPg0KICAgIDxwcmltYXJ5LWJ1dHRvbiByZWY9ImluY3JlbWVudEJ1dHRvbiIgdGl0bGU9IisiIEBjbGljaz0iY291bnQrKyIgLz4NCiAgPC9kaXY+DQogIDxkaXYgY2xhc3M9InctZnVsbCBtdC00IGZsZXggZmxleC1yb3cgc3BhY2UteC00IGp1c3RpZnktY2VudGVyIj4NCiAgICA8ZGFuZ2VyLWJ1dHRvbiByZWY9InJlc2V0QnV0dG9uIiB0aXRsZT0iUmVzZXQiIEBjbGljaz0iY291bnQgPSAwIiAvPg0KICA8L2Rpdj4NCjwvdGVtcGxhdGU+';
322+
</script>
323+
324+
<template>
325+
<Counter />
326+
</template>
327+
`
328+
},
329+
style: {
330+
language: 'css',
331+
content: '@import "tailwindcss";\n',
332+
},
333+
processors: ['tailwindcss'],
334+
}
335+
336+
<div style={{ marginBottom: '1em' }}>
337+
<RunInLiveCodes config={importDataUrls} style={{ display: 'inline' }}></RunInLiveCodes>
338+
</div>
339+
340+
<LiveCodes config={importDataUrls}></LiveCodes>
341+
342+
In the above demo, this component is imported:
343+
344+
```
345+
data:text/vue;charset=UTF-8;base64,PHNjcmlwdCBzZXR1cCBsYW5nPSJ0cyI+DQppbXBvcnQgeyByZWYgfSBmcm9tICJ2dWUiOw0KaW1wb3J0IFByaW1hcnlCdXR0b24gZnJvbSAiZGF0YTp0ZXh0L3Z1ZTtjaGFyc2V0PVVURi04O2Jhc2U2NCxQSE5qY21sd2RDQnpaWFIxY0NCc1lXNW5QU0owY3lJK0RRcGtaV1pwYm1WUWNtOXdjeWg3RFFvZ0lIUnBkR3hsT2lCN0RRb2dJQ0FnZEhsd1pUb2dVM1J5YVc1bkxBMEtJQ0FnSUdSbFptRjFiSFE2SUNKQ2RYUjBiMjRpTEEwS0lDQjlMQTBLZlNrN0RRbzhMM05qY21sd2RENE5DZzBLUEhSbGJYQnNZWFJsUGcwS0lDQThZblYwZEc5dURRb2dJQ0FnWTJ4aGMzTTlJblJsZUhRdGJXUWdabTl1ZEMxdFpXUnBkVzBnWW1jdFozSmhlUzAxTURBZ2FHOTJaWEk2WW1jdFozSmhlUzAyTURBZ2RISmhibk5wZEdsdmJpQndlUzB4SUhCNExUUWdkR1Y0ZEMxM2FHbDBaU0J5YjNWdVpHVmtJR1J5YjNBdGMyaGhaRzkzTFhoc0lnMEtJQ0ErRFFvZ0lDQWdlM3NnZEdsMGJHVWdmWDBOQ2lBZ1BDOWlkWFIwYjI0K0RRbzhMM1JsYlhCc1lYUmxQZz09IjsNCmltcG9ydCBEYW5nZXJCdXR0b24gZnJvbSAiZGF0YTp0ZXh0L3Z1ZTtjaGFyc2V0PVVURi04O2Jhc2U2NCxQSE5qY21sd2RDQnpaWFIxY0NCc1lXNW5QU0owY3lJK0RRcGtaV1pwYm1WUWNtOXdjeWg3RFFvZ0lIUnBkR3hsT2lCN0RRb2dJQ0FnZEhsd1pUb2dVM1J5YVc1bkxBMEtJQ0FnSUdSbFptRjFiSFE2SUNKQ2RYUjBiMjRpTEEwS0lDQjlMQTBLZlNrN0RRbzhMM05qY21sd2RENE5DZzBLUEhSbGJYQnNZWFJsUGcwS0lDQThZblYwZEc5dURRb2dJQ0FnWTJ4aGMzTTlJblJsZUhRdGJXUWdabTl1ZEMxdFpXUnBkVzBnWW1jdGNtVmtMVFV3TUNCb2IzWmxjanBpWnkxeVpXUXROakF3SUhSeVlXNXphWFJwYjI0Z2NIa3RNU0J3ZUMwMElIUmxlSFF0ZDJocGRHVWdjbTkxYm1SbFpDQmtjbTl3TFhOb1lXUnZkeTE0YkNJTkNpQWdQZzBLSUNBZ0lIdDdJSFJwZEd4bElIMTlEUW9nSUR3dlluVjBkRzl1UGcwS1BDOTBaVzF3YkdGMFpUND0iOw0KDQpjb25zdCBjb3VudCA9IHJlZigwKTsNCjwvc2NyaXB0Pg0KDQo8dGVtcGxhdGU+DQogIDxkaXYgY2xhc3M9InctZnVsbCBtdC04IGZsZXgganVzdGlmeS1jZW50ZXIiPg0KICAgIDxzcGFuIHJlZj0iY291bnRlciIgY2xhc3M9InRleHQtM3hsIGZvbnQtYm9sZCI+e3sgY291bnQgfX08L3NwYW4+DQogIDwvZGl2Pg0KICA8ZGl2IGNsYXNzPSJ3LWZ1bGwgbXQtNCBmbGV4IGZsZXgtcm93IHNwYWNlLXgtNCBqdXN0aWZ5LWNlbnRlciI+DQogICAgPHByaW1hcnktYnV0dG9uIHJlZj0iZGVjcmVtZW50QnV0dG9uIiB0aXRsZT0iLSIgQGNsaWNrPSJjb3VudC0tIiAvPg0KICAgIDxwcmltYXJ5LWJ1dHRvbiByZWY9ImluY3JlbWVudEJ1dHRvbiIgdGl0bGU9IisiIEBjbGljaz0iY291bnQrKyIgLz4NCiAgPC9kaXY+DQogIDxkaXYgY2xhc3M9InctZnVsbCBtdC00IGZsZXggZmxleC1yb3cgc3BhY2UteC00IGp1c3RpZnktY2VudGVyIj4NCiAgICA8ZGFuZ2VyLWJ1dHRvbiByZWY9InJlc2V0QnV0dG9uIiB0aXRsZT0iUmVzZXQiIEBjbGljaz0iY291bnQgPSAwIiAvPg0KICA8L2Rpdj4NCjwvdGVtcGxhdGU+
346+
```
347+
348+
which imports these:
349+
350+
```
351+
data:text/vue;charset=UTF-8;base64,PHNjcmlwdCBzZXR1cCBsYW5nPSJ0cyI+DQpkZWZpbmVQcm9wcyh7DQogIHRpdGxlOiB7DQogICAgdHlwZTogU3RyaW5nLA0KICAgIGRlZmF1bHQ6ICJCdXR0b24iLA0KICB9LA0KfSk7DQo8L3NjcmlwdD4NCg0KPHRlbXBsYXRlPg0KICA8YnV0dG9uDQogICAgY2xhc3M9InRleHQtbWQgZm9udC1tZWRpdW0gYmctZ3JheS01MDAgaG92ZXI6YmctZ3JheS02MDAgdHJhbnNpdGlvbiBweS0xIHB4LTQgdGV4dC13aGl0ZSByb3VuZGVkIGRyb3Atc2hhZG93LXhsIg0KICA+DQogICAge3sgdGl0bGUgfX0NCiAgPC9idXR0b24+DQo8L3RlbXBsYXRlPg==
352+
```
353+
354+
```
355+
data:text/vue;charset=UTF-8;base64,PHNjcmlwdCBzZXR1cCBsYW5nPSJ0cyI+DQpkZWZpbmVQcm9wcyh7DQogIHRpdGxlOiB7DQogICAgdHlwZTogU3RyaW5nLA0KICAgIGRlZmF1bHQ6ICJCdXR0b24iLA0KICB9LA0KfSk7DQo8L3NjcmlwdD4NCg0KPHRlbXBsYXRlPg0KICA8YnV0dG9uDQogICAgY2xhc3M9InRleHQtbWQgZm9udC1tZWRpdW0gYmctcmVkLTUwMCBob3ZlcjpiZy1yZWQtNjAwIHRyYW5zaXRpb24gcHktMSBweC00IHRleHQtd2hpdGUgcm91bmRlZCBkcm9wLXNoYWRvdy14bCINCiAgPg0KICAgIHt7IHRpdGxlIH19DQogIDwvYnV0dG9uPg0KPC90ZW1wbGF0ZT4=
356+
```
302357

303358
### Root Element
304359

src/livecodes/compiler/import-map.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -144,25 +144,35 @@ export const isScriptImport = (mod: string) =>
144144
mod.toLowerCase().endsWith('.vue') ||
145145
mod.toLowerCase().endsWith('.svelte')));
146146

147+
const modulesCache: Record<string, string> = {};
148+
const fetchModule = async (mod: string) => {
149+
if (modulesCache[mod]) {
150+
return modulesCache[mod];
151+
}
152+
const res = await fetch(mod);
153+
const content = await res.text();
154+
modulesCache[mod] = content;
155+
return content;
156+
};
157+
147158
export const replaceSFCImports = async (
148159
code: string,
149160
{
150161
filename,
151162
config,
152-
sfcExtension,
163+
isSfc,
153164
getLanguageByAlias,
154165
compileSFC,
155166
external,
156167
}: {
157168
config: Config;
158169
filename: string;
159-
sfcExtension: string;
170+
isSfc: (mod: string) => boolean;
160171
getLanguageByAlias: (alias: string) => Language | undefined;
161172
compileSFC: (code: string, options: { filename: string; config: Config }) => Promise<string>;
162173
external?: string;
163174
},
164175
) => {
165-
const isSfc = (mod: string) => mod.toLowerCase().endsWith(sfcExtension);
166176
const isExtensionless = (mod: string) =>
167177
mod.startsWith('.') && !mod.split('/')[mod.split('/').length - 1].includes('.');
168178
const sfcImports = getImports(code).filter(
@@ -196,8 +206,7 @@ export const replaceSFCImports = async (
196206
? new URL(mod, filename).href
197207
: modulesService.getUrl(mod));
198208

199-
const res = await fetch(url);
200-
const content = await res.text();
209+
const content = await fetchModule(url);
201210
const compiled = isSfc(mod)
202211
? await compileSFC(content, { filename: url, config })
203212
: await replaceSFCImports(
@@ -208,7 +217,7 @@ export const replaceSFCImports = async (
208217
config,
209218
)
210219
).code,
211-
{ filename: url, config, sfcExtension, getLanguageByAlias, compileSFC, external },
220+
{ filename: url, config, isSfc, getLanguageByAlias, compileSFC, external },
212221
);
213222
if (!compiled) return;
214223
const dataUrl = toDataUrl(compiled);

src/livecodes/editor/codemirror/codemirror.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export const createEditor = async (options: EditorOptions): Promise<CodeEditor>
7878
editorLanguages[language]?.() || (editorLanguages.html?.() as Promise<LanguageSupport>);
7979

8080
const mapLanguage = (lang: Language) => {
81-
if (['vue', 'vue3', 'vue2'].includes(lang)) return 'vue';
81+
if (lang.startsWith('vue')) return 'vue';
8282
return options.mapLanguage?.(lang) || lang;
8383
};
8484

src/livecodes/editor/monaco/monaco.ts

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
monacoBaseUrl,
2222
monacoEmacsUrl,
2323
monacoVimUrl,
24+
monacoVolarUrl,
2425
vendorsBaseUrl,
2526
} from '../../vendors';
2627
import { getImports } from '../../compiler/import-map';
@@ -41,6 +42,8 @@ let codeiumProvider: { dispose: () => void } | undefined;
4142
// track editors for providing context for AI
4243
let editors: Monaco.editor.IStandaloneCodeEditor[] = [];
4344
let tailwindcssConfig: any;
45+
let vueRegistered = false;
46+
let shikiThemes: Record<string, string> = {};
4447

4548
export const createEditor = async (options: EditorOptions): Promise<CodeEditor> => {
4649
const {
@@ -55,11 +58,15 @@ export const createEditor = async (options: EditorOptions): Promise<CodeEditor>
5558
getFormatterConfig,
5659
getFontFamily,
5760
} = options;
61+
let language = options.language;
62+
5863
if (!container) throw new Error('editor container not found');
5964

6065
const loadMonaco = () => import(monacoBaseUrl + 'monaco.js');
6166

6267
let editorMode: any | undefined;
68+
let currentTheme = theme;
69+
let currentEditorTheme = editorTheme;
6370

6471
const convertOptions = (opt: EditorConfig): Options => ({
6572
fontFamily: getFontFamily(opt.fontFamily),
@@ -81,9 +88,11 @@ export const createEditor = async (options: EditorOptions): Promise<CodeEditor>
8188
? 'coffeescript'
8289
: ['rescript', 'reason', 'ocaml'].includes(language)
8390
? 'csharp'
84-
: ['vue', 'svelte', 'malina', 'riot'].includes(language)
85-
? ('razor' as Language) // avoid mixing code between markup & script editors when formatting
86-
: mapLanguage(language);
91+
: language.startsWith('vue')
92+
? 'vue'
93+
: ['svelte', 'malina', 'riot'].includes(language)
94+
? ('razor' as Language) // avoid mixing code between markup & script editors when formatting
95+
: mapLanguage(language);
8796

8897
try {
8998
(window as any).monaco = (window as any).monaco || (await loadMonaco()).monaco;
@@ -126,7 +135,9 @@ export const createEditor = async (options: EditorOptions): Promise<CodeEditor>
126135

127136
const setTheme = (theme: Theme, editorTheme: Config['editorTheme']) => {
128137
loadTheme(theme, editorTheme).then((newTheme) => {
129-
monaco.editor.setTheme(newTheme);
138+
monaco.editor.setTheme(shikiThemes[newTheme] ?? newTheme);
139+
currentTheme = theme;
140+
currentEditorTheme = editorTheme;
130141
});
131142
};
132143

@@ -263,14 +274,21 @@ export const createEditor = async (options: EditorOptions): Promise<CodeEditor>
263274
configureTypeScriptFeatures();
264275
};
265276

266-
let language = options.language;
267-
268277
const editor = monaco.editor.create(container, {
269278
...editorOptions,
270279
language: monacoMapLanguage(language),
271280
});
272281
setModel(editor, options.value, language);
273282

283+
const getOrCreateModel = (value: string, lang: string | undefined, uri: Monaco.Uri) => {
284+
const model = monaco.editor.getModel(uri);
285+
if (model) {
286+
model.setValue(value);
287+
return model;
288+
}
289+
return monaco.editor.createModel(value, lang, uri);
290+
};
291+
274292
const contentEditors: Array<EditorOptions['editorId']> = ['markup', 'style', 'script', 'tests'];
275293
if (contentEditors.includes(editorId)) {
276294
editors.push(editor);
@@ -299,7 +317,24 @@ export const createEditor = async (options: EditorOptions): Promise<CodeEditor>
299317
config?: Monaco.languages.LanguageConfiguration;
300318
tokens?: Monaco.languages.IMonarchLanguage;
301319
}
320+
321+
const addVueSupport = async () => {
322+
if (vueRegistered) return;
323+
vueRegistered = true;
324+
const { registerVue, registerHighlighter } = await import(monacoVolarUrl);
325+
const tsCompilerOptions = { ...getCompilerOptions('vue'), jsx: 'preserve' };
326+
await registerVue({ editor, monaco, tsCompilerOptions, silent: true });
327+
shikiThemes = registerHighlighter(monaco);
328+
shikiThemes['custom-vs-light'] = shikiThemes.vs;
329+
shikiThemes['custom-vs-dark'] = shikiThemes['vs-dark'];
330+
setTheme(currentTheme, currentEditorTheme);
331+
};
332+
302333
const loadMonacoLanguage = async (lang: Language) => {
334+
if (monacoMapLanguage(lang) === 'vue') {
335+
await addVueSupport();
336+
return;
337+
}
303338
const langUrl = customLanguages[lang];
304339
if (langUrl && !monaco.languages.getLanguages().find((l) => l.id === lang)) {
305340
const mod: CustomLanguageDefinition = (await import(langUrl)).default;
@@ -367,24 +402,25 @@ export const createEditor = async (options: EditorOptions): Promise<CodeEditor>
367402
});
368403
if (!scriptEditor) return;
369404
const ext = scriptLanguage === 'typescript' ? 'tsx' : 'jsx';
370-
const createModel = () => {
371-
scriptModel = monaco.editor.createModel(
372-
scriptEditor.getValue(),
373-
scriptLanguage,
374-
monaco.Uri.parse('script.' + ext),
375-
);
376-
};
377-
if (scriptModel) {
378-
scriptModel.dispose();
379-
setTimeout(() => {
380-
createModel();
381-
}, 300);
382-
} else {
383-
createModel();
384-
}
405+
scriptModel = getOrCreateModel(
406+
scriptEditor.getValue(),
407+
scriptLanguage,
408+
monaco.Uri.parse('script.' + ext),
409+
);
385410
};
386411
createScriptModel();
387412

413+
const addDeclarations = () => {
414+
if (editorId !== 'script') return;
415+
const declarations = `
416+
declare module 'https://*';
417+
declare module 'data:*';
418+
declare module './*';
419+
`;
420+
getOrCreateModel(declarations, undefined, monaco.Uri.parse('file:///declarations.d.ts'));
421+
};
422+
addDeclarations();
423+
388424
const clearTypes = (allTypes = true) => {
389425
scriptModel?.dispose();
390426
if (editorId === 'tests') return;
@@ -404,8 +440,18 @@ export const createEditor = async (options: EditorOptions): Promise<CodeEditor>
404440
const setLanguage = (lang: Language, value?: string) => {
405441
language = lang;
406442
clearTypes(false);
407-
setModel(editor, value ?? editor.getValue(), language);
408-
loadMonacoLanguage(lang);
443+
const valueToIsert = value ?? editor.getValue();
444+
if (monacoMapLanguage(lang) === 'vue') {
445+
// avoid race condition of value changing while valor is loading
446+
setValue(valueToIsert);
447+
} else {
448+
setModel(editor, valueToIsert, language);
449+
}
450+
loadMonacoLanguage(lang).then(() => {
451+
if (monacoMapLanguage(lang) === 'vue') {
452+
setModel(editor, editor.getValue(), language);
453+
}
454+
});
409455
};
410456

411457
const focus = () => editor.focus();

src/livecodes/editor/ts-compiler-options.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,17 @@ export const hasJsx = [
1313
'flow',
1414
'solid',
1515
'solid.tsx',
16+
'stencil',
1617
'react-native',
1718
'react-native-tsx',
19+
'vue',
1820
];
1921

2022
export const getCompilerOptions = (language: Language): CompilerOptions => {
2123
const JSLangs = ['javascript', 'jsx', 'react', 'flow', 'solid', 'react-native'];
2224
const isJSLang = JSLangs.includes(language);
2325
const isJsx = hasJsx.includes(language);
24-
const nonReactJsx = ['solid', 'solid.tsx', 'stencil'].includes(language);
26+
const nonReactJsx = ['solid', 'solid.tsx', 'stencil', 'vue'].includes(language);
2527

2628
const settings: CompilerOptions = {
2729
allowJs: true,

src/livecodes/languages/svelte/lang-svelte-compiler.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ import { getCompileResult } from '../../compiler';
2020
}
2121
if (!code) return getCompileResult('');
2222

23+
const isSfc = (mod: string) =>
24+
mod.toLowerCase().endsWith('.svelte') || mod.toLowerCase().startsWith('data:text/svelte');
25+
2326
const fullCode = await replaceSFCImports(code, {
2427
config,
2528
filename,
2629
getLanguageByAlias,
27-
sfcExtension: '.svelte',
30+
isSfc,
2831
compileSFC: async (
2932
code: string,
3033
{ config, filename }: { config: Config; filename: string },

src/livecodes/languages/vue/lang-vue-compiler.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,14 @@ import { getLanguageByAlias } from '../utils';
3737
}
3838
if (!code.trim()) return;
3939

40+
const isSfc = (mod: string) =>
41+
mod.toLowerCase().endsWith('.vue') || mod.toLowerCase().startsWith('data:text/vue');
42+
4043
code = await replaceSFCImports(code, {
4144
filename,
4245
config,
43-
sfcExtension: '.vue',
4446
getLanguageByAlias,
47+
isSfc,
4548
compileSFC: async (code, { filename, config }) => {
4649
const compiled = (await compileVueSFC(code, { filename, config }))?.js || '';
4750
importedContent += `\n${filename}\n\n${compiled}\n`;

src/livecodes/languages/vue/lang-vue.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const vue: LanguageSpecs = {
2525
},
2626
extensions: ['vue', 'vue3'],
2727
editor: 'script',
28+
editorLanguage: 'html',
2829
};
2930

3031
export const vueApp: LanguageSpecs = {

src/livecodes/templates/starter/vue-sfc-starter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import Counter from './Component.vue';
3434
const align = 'center';
3535
3636
// define inline component
37-
function Greeting(props: {name: string}) {
37+
function Greeting(props: {name?: string}) {
3838
return <h1>Hello, { props.name || 'World' }!</h1>
3939
}
4040
</script>

src/livecodes/vendors.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,10 @@ export const monacoThemesBaseUrl = /* @__PURE__ */ getUrl('monaco-themes@0.4.4/t
293293

294294
export const monacoVimUrl = /* @__PURE__ */ getUrl('monaco-vim@0.4.1/dist/monaco-vim.js');
295295

296+
export const monacoVolarUrl = /* @__PURE__ */ getUrl(
297+
'@live-codes/monaco-volar@0.1.0/dist/index.js',
298+
);
299+
296300
export const mustacheUrl = /* @__PURE__ */ getUrl('mustache@4.2.0/mustache.js');
297301

298302
export const ninjaKeysUrl = /* @__PURE__ */ getUrl('@hatemhosny/ninja-keys@1.14.0/bundle/index.js');

0 commit comments

Comments
 (0)