workflow: improve template explorer
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import * as m from 'monaco-editor'
|
||||
import { compile } from '@vue/compiler-dom'
|
||||
import { compile, CompilerError } from '@vue/compiler-dom'
|
||||
import { compilerOptions, initOptions } from './options'
|
||||
import { watch } from '@vue/runtime-dom'
|
||||
import { SourceMapConsumer } from 'source-map'
|
||||
|
||||
const self = window as any
|
||||
|
||||
@@ -9,30 +12,64 @@ self.init = () => {
|
||||
decodeURIComponent(window.location.hash.slice(1)) ||
|
||||
`<div>{{ foo + bar }}</div>`
|
||||
|
||||
self.compilerOptions = {
|
||||
mode: 'module',
|
||||
prefixIdentifiers: true,
|
||||
hoistStatic: true
|
||||
}
|
||||
|
||||
let lastSuccessfulCode: string = `/* See console for error */`
|
||||
let lastSuccessfulMap: SourceMapConsumer | undefined = undefined
|
||||
function compileCode(source: string): string {
|
||||
console.clear()
|
||||
try {
|
||||
const { code, ast } = compile(source, self.compilerOptions)
|
||||
|
||||
console.log(ast)
|
||||
return code
|
||||
const { code, ast, map } = compile(source, {
|
||||
filename: 'template.vue',
|
||||
...compilerOptions,
|
||||
sourceMap: true,
|
||||
onError: displayError
|
||||
})
|
||||
monaco.editor.setModelMarkers(editor.getModel()!, `@vue/compiler-dom`, [])
|
||||
console.log(`AST: `, ast)
|
||||
lastSuccessfulCode = code + `\n\n// Check the console for the AST`
|
||||
lastSuccessfulMap = new self._deps['source-map'].SourceMapConsumer(
|
||||
map
|
||||
) as SourceMapConsumer
|
||||
lastSuccessfulMap.computeColumnSpans()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return `/* See console for error */`
|
||||
}
|
||||
return lastSuccessfulCode
|
||||
}
|
||||
|
||||
function displayError(err: CompilerError) {
|
||||
const loc = err.loc
|
||||
if (loc) {
|
||||
monaco.editor.setModelMarkers(editor.getModel()!, `@vue/compiler-dom`, [
|
||||
{
|
||||
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)
|
||||
}
|
||||
])
|
||||
}
|
||||
throw err
|
||||
}
|
||||
|
||||
function reCompile() {
|
||||
const src = editor.getValue()
|
||||
window.location.hash = encodeURIComponent(src)
|
||||
const res = compileCode(src)
|
||||
if (res) {
|
||||
output.setValue(res)
|
||||
}
|
||||
}
|
||||
|
||||
const sharedOptions = {
|
||||
const sharedEditorOptions = {
|
||||
theme: 'vs-dark',
|
||||
fontSize: 14,
|
||||
wordWrap: 'on',
|
||||
scrollBeyondLastLine: false,
|
||||
renderWhitespace: 'selection',
|
||||
contextmenu: false,
|
||||
minimap: {
|
||||
enabled: false
|
||||
}
|
||||
@@ -43,41 +80,134 @@ self.init = () => {
|
||||
{
|
||||
value: persistedContent,
|
||||
language: 'html',
|
||||
...sharedOptions
|
||||
...sharedEditorOptions
|
||||
}
|
||||
)
|
||||
|
||||
const model = editor.getModel()!
|
||||
|
||||
model.updateOptions({
|
||||
editor.getModel()!.updateOptions({
|
||||
tabSize: 2
|
||||
})
|
||||
|
||||
model.onDidChangeContent(() => {
|
||||
const src = editor.getValue()
|
||||
window.location.hash = encodeURIComponent(src)
|
||||
const res = compileCode(src)
|
||||
if (res) {
|
||||
output.setValue(res)
|
||||
}
|
||||
})
|
||||
|
||||
const output = monaco.editor.create(
|
||||
document.getElementById('output') as HTMLElement,
|
||||
{
|
||||
value: compileCode(persistedContent),
|
||||
value: '',
|
||||
language: 'javascript',
|
||||
readOnly: true,
|
||||
...sharedOptions
|
||||
...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: 'template.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()
|
||||
watch(reCompile)
|
||||
}
|
||||
|
||||
function debounce<T extends Function>(fn: T, delay: number = 300): T {
|
||||
let prevTimer: NodeJS.Timeout | null = null
|
||||
return ((...args: any[]) => {
|
||||
if (prevTimer) {
|
||||
clearTimeout(prevTimer)
|
||||
}
|
||||
prevTimer = setTimeout(() => {
|
||||
fn(...args)
|
||||
prevTimer = null
|
||||
}, delay)
|
||||
}) as any
|
||||
}
|
||||
|
||||
72
packages/template-explorer/src/options.ts
Normal file
72
packages/template-explorer/src/options.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { h, reactive, createApp } from '@vue/runtime-dom'
|
||||
import { CompilerOptions } from '@vue/compiler-dom'
|
||||
|
||||
export const compilerOptions: CompilerOptions = reactive({
|
||||
mode: 'module',
|
||||
prefixIdentifiers: false,
|
||||
hoistStatic: false
|
||||
})
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () => [
|
||||
h('h1', `Vue 3 Template Explorer`),
|
||||
h('div', { id: 'options' }, [
|
||||
// mode selection
|
||||
h('span', { class: 'options-group' }, [
|
||||
h('span', { class: 'label' }, 'Mode:'),
|
||||
h('input', {
|
||||
type: 'radio',
|
||||
id: 'mode-module',
|
||||
name: 'mode',
|
||||
checked: compilerOptions.mode === 'module',
|
||||
onChange() {
|
||||
compilerOptions.mode = 'module'
|
||||
}
|
||||
}),
|
||||
h('label', { for: 'mode-module' }, 'module'),
|
||||
h('input', {
|
||||
type: 'radio',
|
||||
id: 'mode-function',
|
||||
name: 'mode',
|
||||
checked: compilerOptions.mode === 'function',
|
||||
onChange() {
|
||||
compilerOptions.mode = 'function'
|
||||
}
|
||||
}),
|
||||
h('label', { for: 'mode-function' }, 'function')
|
||||
]),
|
||||
|
||||
// toggle prefixIdentifiers
|
||||
h('input', {
|
||||
type: 'checkbox',
|
||||
id: 'prefix',
|
||||
disabled: compilerOptions.mode === 'module',
|
||||
checked:
|
||||
compilerOptions.prefixIdentifiers ||
|
||||
compilerOptions.mode === 'module',
|
||||
onChange(e: any) {
|
||||
compilerOptions.prefixIdentifiers =
|
||||
e.target.checked || compilerOptions.mode === 'module'
|
||||
}
|
||||
}),
|
||||
h('label', { for: 'prefix' }, 'prefixIdentifiers'),
|
||||
|
||||
// toggle hoistStatic
|
||||
h('input', {
|
||||
type: 'checkbox',
|
||||
id: 'hoist',
|
||||
checked: compilerOptions.hoistStatic,
|
||||
onChange(e: any) {
|
||||
compilerOptions.hoistStatic = e.target.checked
|
||||
}
|
||||
}),
|
||||
h('label', { for: 'hoist' }, 'hoistStatic')
|
||||
])
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export function initOptions() {
|
||||
createApp().mount(App, document.getElementById('header') as HTMLElement)
|
||||
}
|
||||
Reference in New Issue
Block a user