| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- import type * as m from 'monaco-editor'
- import type { CompilerError } from '@vue/compiler-dom'
- import { compile } from '@vue/compiler-dom'
- import {
- type CompilerOptions,
- compile as vaporCompile,
- } from '@vue/compiler-vapor'
- // import { compile as ssrCompile } from '@vue/compiler-ssr'
- import {
- compilerOptions,
- defaultOptions,
- initOptions,
- ssrMode,
- vaporMode,
- } from './options'
- import { toRaw, watchEffect } from '@vue/runtime-dom'
- import { SourceMapConsumer } from 'source-map-js'
- import theme from './theme'
- declare global {
- interface Window {
- monaco: typeof m
- _deps: any
- init: () => void
- }
- }
- interface PersistedState {
- src: string
- ssr: boolean
- options: CompilerOptions
- }
- const sharedEditorOptions: m.editor.IStandaloneEditorConstructionOptions = {
- fontSize: 14,
- scrollBeyondLastLine: false,
- renderWhitespace: 'selection',
- minimap: {
- enabled: false,
- },
- }
- window.init = () => {
- const monaco = window.monaco
- monaco.editor.defineTheme('my-theme', theme)
- monaco.editor.setTheme('my-theme')
- let persistedState: PersistedState | undefined
- try {
- let hash = window.location.hash.slice(1)
- try {
- hash = escape(atob(hash))
- } catch (e) {}
- persistedState = JSON.parse(
- decodeURIComponent(hash) || localStorage.getItem('state') || `{}`,
- )
- } catch (e: any) {
- // bad stored state, clear it
- console.warn(
- 'Persisted state in localStorage seems to be corrupted, please reload.\n' +
- e.message,
- )
- localStorage.clear()
- }
- if (persistedState) {
- // functions are not persistable, so delete it in case we sometimes need
- // to debug with custom nodeTransforms
- delete persistedState.options?.nodeTransforms
- ssrMode.value = persistedState.ssr
- Object.assign(compilerOptions, persistedState.options)
- }
- let lastSuccessfulCode: string
- let lastSuccessfulMap: SourceMapConsumer | undefined = undefined
- function compileCode(source: string): string {
- console.clear()
- try {
- const errors: CompilerError[] = []
- const compileFn = /* ssrMode.value ? ssrCompile : */ (
- vaporMode.value ? vaporCompile : compile
- ) as typeof vaporCompile
- const start = performance.now()
- const { code, ast, map } = compileFn(source, {
- ...compilerOptions,
- prefixIdentifiers:
- compilerOptions.prefixIdentifiers ||
- compilerOptions.mode === 'module' ||
- compilerOptions.ssr,
- filename: 'ExampleTemplate.vue',
- sourceMap: true,
- onError: err => {
- errors.push(err)
- },
- })
- console.log(`Compiled in ${(performance.now() - start).toFixed(2)}ms.`)
- monaco.editor.setModelMarkers(
- editor.getModel()!,
- `@vue/compiler-dom`,
- errors.filter(e => e.loc).map(formatError),
- )
- console.log(`AST: `, ast)
- console.log(`Options: `, toRaw(compilerOptions))
- lastSuccessfulCode = code + `\n\n// Check the console for the AST`
- lastSuccessfulMap = new SourceMapConsumer(map!)
- lastSuccessfulMap!.computeColumnSpans()
- } catch (e: any) {
- lastSuccessfulCode = `/* ERROR: ${e.message} (see console for more info) */`
- console.error(e)
- }
- return lastSuccessfulCode
- }
- function formatError(err: CompilerError) {
- const loc = err.loc!
- return {
- severity: monaco.MarkerSeverity.Error,
- startLineNumber: loc.start.line,
- startColumn: loc.start.column,
- endLineNumber: loc.end.line,
- endColumn: loc.end.column,
- message: `Vue template compilation error: ${err.message}`,
- code: String(err.code),
- }
- }
- function reCompile() {
- const src = editor.getValue()
- // every time we re-compile, persist current state
- const optionsToSave = {}
- let key: keyof CompilerOptions
- for (key in compilerOptions) {
- const val = compilerOptions[key]
- if (typeof val !== 'object' && val !== defaultOptions[key]) {
- // @ts-expect-error
- optionsToSave[key] = val
- }
- }
- const state = JSON.stringify({
- src,
- ssr: ssrMode.value,
- options: optionsToSave,
- } as PersistedState)
- localStorage.setItem('state', state)
- window.location.hash = btoa(unescape(encodeURIComponent(state)))
- const res = compileCode(src)
- if (res) {
- output.setValue(res)
- }
- }
- const editor = monaco.editor.create(document.getElementById('source')!, {
- value: persistedState?.src || `<div>Hello World</div>`,
- language: 'html',
- ...sharedEditorOptions,
- wordWrap: 'bounded',
- })
- editor.getModel()!.updateOptions({
- tabSize: 2,
- })
- const output = monaco.editor.create(document.getElementById('output')!, {
- value: '',
- language: 'javascript',
- readOnly: true,
- ...sharedEditorOptions,
- })
- output.getModel()!.updateOptions({
- tabSize: 2,
- })
- // handle resize
- window.addEventListener('resize', () => {
- editor.layout()
- output.layout()
- })
- // update compile output when input changes
- editor.onDidChangeModelContent(debounce(reCompile))
- // highlight output code
- let prevOutputDecos: string[] = []
- function clearOutputDecos() {
- prevOutputDecos = output.deltaDecorations(prevOutputDecos, [])
- }
- editor.onDidChangeCursorPosition(
- debounce(e => {
- clearEditorDecos()
- if (lastSuccessfulMap) {
- const pos = lastSuccessfulMap.generatedPositionFor({
- source: 'ExampleTemplate.vue',
- line: e.position.lineNumber,
- column: e.position.column - 1,
- })
- if (pos.line != null && pos.column != null) {
- prevOutputDecos = output.deltaDecorations(prevOutputDecos, [
- {
- range: new monaco.Range(
- pos.line,
- pos.column + 1,
- pos.line,
- pos.lastColumn ? pos.lastColumn + 2 : pos.column + 2,
- ),
- options: {
- inlineClassName: `highlight`,
- },
- },
- ])
- output.revealPositionInCenter({
- lineNumber: pos.line,
- column: pos.column + 1,
- })
- } else {
- clearOutputDecos()
- }
- }
- }, 100),
- )
- let previousEditorDecos: string[] = []
- function clearEditorDecos() {
- previousEditorDecos = editor.deltaDecorations(previousEditorDecos, [])
- }
- output.onDidChangeCursorPosition(
- debounce(e => {
- clearOutputDecos()
- if (lastSuccessfulMap) {
- const pos = lastSuccessfulMap.originalPositionFor({
- line: e.position.lineNumber,
- column: e.position.column - 1,
- })
- if (
- pos.line != null &&
- pos.column != null &&
- !(
- // ignore mock location
- (pos.line === 1 && pos.column === 0)
- )
- ) {
- const translatedPos = {
- column: pos.column + 1,
- lineNumber: pos.line,
- }
- previousEditorDecos = editor.deltaDecorations(previousEditorDecos, [
- {
- range: new monaco.Range(
- pos.line,
- pos.column + 1,
- pos.line,
- pos.column + 1,
- ),
- options: {
- isWholeLine: true,
- className: `highlight`,
- },
- },
- ])
- editor.revealPositionInCenter(translatedPos)
- } else {
- clearEditorDecos()
- }
- }
- }, 100),
- )
- initOptions()
- watchEffect(reCompile)
- }
- function debounce<T extends (...args: any[]) => any>(
- fn: T,
- delay: number = 300,
- ): T {
- let prevTimer: number | null = null
- return ((...args: any[]) => {
- if (prevTimer) {
- clearTimeout(prevTimer)
- }
- prevTimer = window.setTimeout(() => {
- fn(...args)
- prevTimer = null
- }, delay)
- }) as T
- }
|