diff --git a/README.md b/README.md
index b529b203..5f790799 100644
--- a/README.md
+++ b/README.md
@@ -51,6 +51,22 @@ import Monaco from '@vue/repl/monaco-editor'
```
+### With Luna Console (4.5.2)
+
+Console UI to print console logs.
+**Be aware**: Because we are getting logs that pass into a `postMessage` they are some types/logs that are currently not supported such as elements html, declared functions, `console.trace()`, etc... More of the supported types [here](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#things_that_dont_work_with_structured_clone).
+
+```vue
+
+
+
+
+
+```
+
## Advanced Usage
Customize the behavior of the REPL by manually initializing the store.
diff --git a/package.json b/package.json
index ab1f9233..a02536bd 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,11 @@
"import": "./dist/codemirror-editor.js",
"require": null
},
+ "./luna-console": {
+ "types": "./dist/luna-console.d.ts",
+ "import": "./dist/luna-console.js",
+ "require": null
+ },
"./core": {
"types": "./dist/core.d.ts",
"import": "./dist/core.js",
@@ -110,6 +115,9 @@
"vite-plugin-dts": "^4.5.0",
"vscode-uri": "^3.1.0",
"vue": "^3.5.13",
- "vue-tsc": "~2.2.2"
+ "vue-tsc": "~2.2.2",
+ "luna-console": "^1.3.5",
+ "luna-data-grid": "^1.3.0",
+ "luna-object-viewer": "^0.3.1"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8d25d3a8..ce24aef1 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -77,6 +77,15 @@ importers:
lint-staged:
specifier: ^15.4.3
version: 15.4.3
+ luna-console:
+ specifier: ^1.3.5
+ version: 1.3.5(luna-data-grid@1.4.1)(luna-dom-viewer@1.8.2)(luna-object-viewer@0.3.1)
+ luna-data-grid:
+ specifier: ^1.3.0
+ version: 1.4.1
+ luna-object-viewer:
+ specifier: ^0.3.1
+ version: 0.3.1
monaco-editor-core:
specifier: ^0.52.2
version: 0.52.2
@@ -1671,6 +1680,9 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
+ licia@1.48.0:
+ resolution: {integrity: sha512-bBWiT5CSdEtwuAHiYTJ74yItCjIFdHi4xiFk6BRDfKa+sdCpkUHp69YKb5udNOJlHDzFjNjcMgNZ/+wQIHrB8A==}
+
lilconfig@3.1.3:
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
engines: {node: '>=14'}
@@ -1715,6 +1727,22 @@ packages:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
+ luna-console@1.3.5:
+ resolution: {integrity: sha512-Ac4g4y5AwTABPZpw/y5TEVpZxA7yA9w/BRmWKCdO3Qr0M/BTGxbjjH6UTWPcKIUfu3QO+cBsmK6+qL+OBZQb9A==}
+ peerDependencies:
+ luna-data-grid: ^1.0.0
+ luna-dom-viewer: ^1.4.0
+ luna-object-viewer: ^0.3.1
+
+ luna-data-grid@1.4.1:
+ resolution: {integrity: sha512-Blo7PP+86LBaiCie5hNeXxQgpodMQGD2ReczFvn2NXZhV6FVKLQC3TL2avSqCsFmIXon09w1zzJVhdh+fVhpCQ==}
+
+ luna-dom-viewer@1.8.2:
+ resolution: {integrity: sha512-37yiSYLBeu2TyHp1iQMVrYsAkH4X5Hkn0UYIaTlvTTEc6uSdu5aBI67zHnuXV2FVmOoa0kJhFK/GsUKsFibaCQ==}
+
+ luna-object-viewer@0.3.1:
+ resolution: {integrity: sha512-byynxixA8aAzSg9LymFDzPPrhKCPBOiELnOGxuztAXmes7W7OFqGkiLSyV8ECDvRimF1WewVmyOdxyhsLVquFg==}
+
magic-string@0.30.17:
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
@@ -4226,6 +4254,8 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
+ licia@1.48.0: {}
+
lilconfig@3.1.3: {}
lines-and-columns@1.2.4: {}
@@ -4285,6 +4315,25 @@ snapshots:
dependencies:
yallist: 4.0.0
+ luna-console@1.3.5(luna-data-grid@1.4.1)(luna-dom-viewer@1.8.2)(luna-object-viewer@0.3.1):
+ dependencies:
+ licia: 1.48.0
+ luna-data-grid: 1.4.1
+ luna-dom-viewer: 1.8.2
+ luna-object-viewer: 0.3.1
+
+ luna-data-grid@1.4.1:
+ dependencies:
+ licia: 1.48.0
+
+ luna-dom-viewer@1.8.2:
+ dependencies:
+ licia: 1.48.0
+
+ luna-object-viewer@0.3.1:
+ dependencies:
+ licia: 1.48.0
+
magic-string@0.30.17:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.0
diff --git a/src/Repl.vue b/src/Repl.vue
index 66138ec7..17fd97b3 100644
--- a/src/Repl.vue
+++ b/src/Repl.vue
@@ -2,24 +2,28 @@
import SplitPane from './SplitPane.vue'
import Output from './output/Output.vue'
import { type Store, useStore } from './store'
-import { computed, provide, toRefs, useTemplateRef } from 'vue'
+import { computed, provide, toRefs, useTemplateRef, watchEffect } from 'vue'
import {
+ type ConsoleComponentType,
type EditorComponentType,
injectKeyPreviewRef,
injectKeyProps,
} from './types'
import EditorContainer from './editor/EditorContainer.vue'
+
import type * as monaco from 'monaco-editor-core'
export interface Props {
theme?: 'dark' | 'light'
previewTheme?: boolean
editor: EditorComponentType
+ console?: ConsoleComponentType
store?: Store
autoResize?: boolean
showCompileOutput?: boolean
showImportMap?: boolean
showTsConfig?: boolean
+ showConsole?: boolean
clearConsole?: boolean
layout?: 'horizontal' | 'vertical'
layoutReverse?: boolean
@@ -55,6 +59,7 @@ const props = withDefaults(defineProps(), {
showCompileOutput: true,
showImportMap: true,
showTsConfig: true,
+ showConsole: false,
clearConsole: true,
layoutReverse: false,
ssr: false,
@@ -67,6 +72,15 @@ const props = withDefaults(defineProps(), {
if (!props.editor) {
throw new Error('The "editor" prop is now required.')
}
+watchEffect(() => {
+ if (!!props.showConsole && !props.console)
+ throw new Error(
+ 'If you want to enable a console "console" prop is required.',
+ )
+})
+const consoleWrapper = computed(
+ () => props.console ?? (() => ({})),
+)
const outputRef = useTemplateRef('output')
@@ -77,6 +91,7 @@ const outputSlotName = computed(() => (props.layoutReverse ? 'left' : 'right'))
provide(injectKeyProps, {
...toRefs(props),
+ console: consoleWrapper,
autoSave,
})
provide(
@@ -104,6 +119,7 @@ defineExpose({ reload })
@@ -127,8 +143,9 @@ defineExpose({ reload })
margin: 0;
overflow: hidden;
font-size: 13px;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
- Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ font-family:
+ -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
+ Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
background-color: var(--bg-soft);
}
diff --git a/src/output/LunaConsole.vue b/src/output/LunaConsole.vue
new file mode 100644
index 00000000..75e3dca9
--- /dev/null
+++ b/src/output/LunaConsole.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
diff --git a/src/output/Output.vue b/src/output/Output.vue
index 62085293..931b5470 100644
--- a/src/output/Output.vue
+++ b/src/output/Output.vue
@@ -1,7 +1,9 @@
-
+
@@ -152,50 +153,51 @@
)
}
})
-
- let previous = { level: null, args: null }
-
- ;['clear', 'log', 'info', 'dir', 'warn', 'error', 'table'].forEach(
- (level) => {
- const original = console[level]
- console[level] = (...args) => {
- const msg = args[0]
- if (typeof msg === 'string') {
- if (
- msg.includes('You are running a development build of Vue') ||
- msg.includes('You are running the esm-bundler build of Vue')
- ) {
- return
- }
- }
-
- original(...args)
-
- const stringifiedArgs = stringify(args)
+ const errorColorStr = 'color: #fb2c36'
+ const errorMsg = 'please open the devtool to see correctly this log'
+ ;[
+ 'clear',
+ 'log',
+ 'info',
+ 'dir',
+ 'warn',
+ 'error',
+ 'table',
+ 'debug',
+ 'time',
+ 'timeLog',
+ 'timeEnd',
+ 'assert',
+ 'count',
+ 'countReset',
+ ].forEach((level) => {
+ const original = console[level]
+ console[level] = (...args) => {
+ const msg = args[0]
+ if (typeof msg === 'string') {
if (
- previous.level === level &&
- previous.args &&
- previous.args === stringifiedArgs
+ msg.includes('You are running a development build of Vue') ||
+ msg.includes('You are running the esm-bundler build of Vue')
) {
- parent.postMessage(
- { action: 'console', level, duplicate: true },
- '*',
- )
- } else {
- previous = { level, args: stringifiedArgs }
-
- try {
- parent.postMessage({ action: 'console', level, args }, '*')
- } catch (err) {
- parent.postMessage(
- { action: 'console', level, args: args.map(toString) },
- '*',
- )
- }
+ return
}
}
- },
- )
+ original(...args)
+ const postMessage = () =>
+ parent.postMessage({ action: 'console', level, args }, '*')
+ try {
+ postMessage()
+ } catch {
+ args = args.map(toString)
+ if (!args?.[0]?.startsWith('[Vue warn]'))
+ args = [
+ `[vue-repl]: %c Cannot clone the message, ${errorMsg}. Supported types are listed here: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#javascript_types`,
+ errorColorStr,
+ ]
+ postMessage()
+ }
+ }
+ })
;[
{ method: 'group', action: 'console_group' },
{ method: 'groupEnd', action: 'console_group_end' },
@@ -208,119 +210,17 @@
original(label)
}
})
-
- const timers = new Map()
- const original_time = console.time
- const original_timelog = console.timeLog
- const original_timeend = console.timeEnd
-
- console.time = (label = 'default') => {
- original_time(label)
- timers.set(label, performance.now())
- }
- console.timeLog = (label = 'default') => {
- original_timelog(label)
- const now = performance.now()
- if (timers.has(label)) {
- parent.postMessage(
- {
- action: 'console',
- level: 'system-log',
- args: [`${label}: ${now - timers.get(label)}ms`],
- },
- '*',
- )
- } else {
- parent.postMessage(
- {
- action: 'console',
- level: 'system-warn',
- args: [`Timer '${label}' does not exist`],
- },
- '*',
- )
+ ;['profile', 'profileEnd', 'trace', 'dirXml'].forEach((level) => {
+ const original = console[level]
+ console[level] = () => {
+ original()
+ const args = [
+ `[vue-repl]: %c Cannot handle "${level}" log, ${errorMsg}.`,
+ errorColorStr,
+ ]
+ parent.postMessage({ action: 'console', level: 'log', args }, '*')
}
- }
- console.timeEnd = (label = 'default') => {
- original_timeend(label)
- const now = performance.now()
- if (timers.has(label)) {
- parent.postMessage(
- {
- action: 'console',
- level: 'system-log',
- args: [`${label}: ${now - timers.get(label)}ms`],
- },
- '*',
- )
- } else {
- parent.postMessage(
- {
- action: 'console',
- level: 'system-warn',
- args: [`Timer '${label}' does not exist`],
- },
- '*',
- )
- }
- timers.delete(label)
- }
-
- const original_assert = console.assert
- console.assert = (condition, ...args) => {
- if (condition) {
- const stack = new Error().stack
- parent.postMessage(
- { action: 'console', level: 'assert', args, stack },
- '*',
- )
- }
- original_assert(condition, ...args)
- }
-
- const counter = new Map()
- const original_count = console.count
- const original_countreset = console.countReset
-
- console.count = (label = 'default') => {
- counter.set(label, (counter.get(label) || 0) + 1)
- parent.postMessage(
- {
- action: 'console',
- level: 'system-log',
- args: `${label}: ${counter.get(label)}`,
- },
- '*',
- )
- original_count(label)
- }
-
- console.countReset = (label = 'default') => {
- if (counter.has(label)) {
- counter.set(label, 0)
- } else {
- parent.postMessage(
- {
- action: 'console',
- level: 'system-warn',
- args: `Count for '${label}' does not exist`,
- },
- '*',
- )
- }
- original_countreset(label)
- }
-
- const original_trace = console.trace
-
- console.trace = (...args) => {
- const stack = new Error().stack
- parent.postMessage(
- { action: 'console', level: 'trace', args, stack },
- '*',
- )
- original_trace(...args)
- }
+ })
function toString(value) {
if (value instanceof Error) {
diff --git a/src/store.ts b/src/store.ts
index 2508b679..6af3a79c 100644
--- a/src/store.ts
+++ b/src/store.ts
@@ -16,7 +16,7 @@ import type {
SFCScriptCompileOptions,
SFCTemplateCompileOptions,
} from 'vue/compiler-sfc'
-import type { OutputModes } from './types'
+import type { LogPayload, OutputModes } from './types'
import type { editor } from 'monaco-editor-core'
import { type ImportMap, mergeImportMap, useVueImportMap } from './import-map'
@@ -353,6 +353,8 @@ export function useStore(
}
activeFilename ||= ref(mainFile.value)
const activeFile = computed(() => files.value[activeFilename.value])
+ const executeLog = ref()
+ const clearConsole = ref()
applyBuiltinImportMap()
@@ -389,6 +391,8 @@ export function useStore(
deserialize,
getFiles,
setFiles,
+ executeLog,
+ clearConsole,
})
return store
}
@@ -463,6 +467,8 @@ export interface ReplStore extends UnwrapRef {
deserialize(serializedState: string, checkBuiltinImportMap?: boolean): void
getFiles(): Record
setFiles(newFiles: Record, mainFile?: string): Promise
+ executeLog?(payload: LogPayload): void
+ clearConsole?(): void
}
export type Store = Pick<
@@ -487,6 +493,8 @@ export type Store = Pick<
| 'renameFile'
| 'getImportMap'
| 'getTsConfig'
+ | 'executeLog'
+ | 'clearConsole'
>
export class File {
diff --git a/src/transform.ts b/src/transform.ts
index 91e2599b..4aae3a21 100644
--- a/src/transform.ts
+++ b/src/transform.ts
@@ -317,7 +317,7 @@ async function doCompileScript(
return [code, compiledScript.bindings]
} else {
// @ts-expect-error TODO remove when 3.6 is out
- const vaporFlag = descriptor.vapor ? '__vapor: true' :''
+ const vaporFlag = descriptor.vapor ? '__vapor: true' : ''
return [`\nconst ${COMP_IDENTIFIER} = { ${vaporFlag} }`, undefined]
}
}
diff --git a/src/types.ts b/src/types.ts
index 19b8c609..c20eb86b 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -8,11 +8,40 @@ export interface EditorProps {
readonly?: boolean
mode?: EditorMode
}
+export type LogLevel =
+ | 'clear'
+ | 'log'
+ | 'error'
+ | 'warn'
+ | 'group'
+ | 'groupCollapsed'
+ | 'groupEnd'
+ | 'assert'
+ | 'count'
+ | 'countReset'
+ | 'debug'
+ | 'dir'
+ | 'info'
+ | 'table'
+ | 'time'
+ | 'timeEnd'
+ | 'timeLog'
+ | 'warn'
+
+export interface LogPayload {
+ logLevel: LogLevel
+ data?: any[]
+}
export interface EditorEmits {
(e: 'change', code: string): void
}
+export interface SandboxEmits {
+ (e: 'log', payload: LogPayload): void
+}
export type EditorComponentType = Component
+export type ConsoleComponentType = Component
+
export type OutputModes = 'preview' | EditorMode
export const injectKeyProps: InjectionKey<
diff --git a/test/main.ts b/test/main.ts
index 85cce24f..8a5dcde7 100644
--- a/test/main.ts
+++ b/test/main.ts
@@ -60,6 +60,7 @@ const App = {
editor: MonacoEditor,
// layout: 'vertical',
ssr: true,
+ showConsole: true,
sfcOptions: {
script: {
// inlineTemplate: false
diff --git a/vite.config.ts b/vite.config.ts
index 1b007b87..9036bf3b 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -60,6 +60,7 @@ export default mergeConfig(base, {
core: './src/core.ts',
'monaco-editor': './src/editor/MonacoEditor.vue',
'codemirror-editor': './src/editor/CodeMirrorEditor.vue',
+ 'luna-console': './src/output/LunaConsole.vue',
},
formats: ['es'],
fileName: () => '[name].js',