workflow: improve template explorer
This commit is contained in:
parent
8d49b97cc3
commit
0873254c6c
@ -22,7 +22,8 @@ import { SourceMapGenerator, RawSourceMap } from 'source-map'
|
|||||||
import {
|
import {
|
||||||
advancePositionWithMutation,
|
advancePositionWithMutation,
|
||||||
assert,
|
assert,
|
||||||
isSimpleIdentifier
|
isSimpleIdentifier,
|
||||||
|
loadDep
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { isString, isArray } from '@vue/shared'
|
import { isString, isArray } from '@vue/shared'
|
||||||
import { TO_STRING, CREATE_VNODE, COMMENT } from './runtimeConstants'
|
import { TO_STRING, CREATE_VNODE, COMMENT } from './runtimeConstants'
|
||||||
@ -97,7 +98,7 @@ function createCodegenContext(
|
|||||||
map:
|
map:
|
||||||
__BROWSER__ || !sourceMap
|
__BROWSER__ || !sourceMap
|
||||||
? undefined
|
? undefined
|
||||||
: new (require('source-map')).SourceMapGenerator(),
|
: new (loadDep('source-map')).SourceMapGenerator(),
|
||||||
|
|
||||||
helper(name) {
|
helper(name) {
|
||||||
return prefixIdentifiers ? name : `_${name}`
|
return prefixIdentifiers ? name : `_${name}`
|
||||||
|
@ -41,7 +41,9 @@ export const transformIf = createStructuralDirectiveTransform(
|
|||||||
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
|
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
|
||||||
) {
|
) {
|
||||||
const loc = dir.exp ? dir.exp.loc : node.loc
|
const loc = dir.exp ? dir.exp.loc : node.loc
|
||||||
context.onError(createCompilerError(ErrorCodes.X_IF_NO_EXPRESSION, loc))
|
context.onError(
|
||||||
|
createCompilerError(ErrorCodes.X_IF_NO_EXPRESSION, dir.loc)
|
||||||
|
)
|
||||||
dir.exp = createSimpleExpression(`true`, false, loc)
|
dir.exp = createSimpleExpression(`true`, false, loc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ import { PropsExpression } from './transforms/transformElement'
|
|||||||
let _parse: typeof parse
|
let _parse: typeof parse
|
||||||
let _walk: typeof walk
|
let _walk: typeof walk
|
||||||
|
|
||||||
function loadDep(name: string) {
|
export function loadDep(name: string) {
|
||||||
if (typeof process !== 'undefined' && isFunction(require)) {
|
if (typeof process !== 'undefined' && isFunction(require)) {
|
||||||
return require(name)
|
return require(name)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,35 +1,21 @@
|
|||||||
|
<title>Vue Template Explorer</title>
|
||||||
<link rel="stylesheet" data-name="vs/editor/editor.main" href="../../node_modules/monaco-editor/min/vs/editor/editor.main.css">
|
<link rel="stylesheet" data-name="vs/editor/editor.main" href="../../node_modules/monaco-editor/min/vs/editor/editor.main.css">
|
||||||
<style>
|
<link rel="stylesheet" href="./style.css">
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.editor {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
#source {
|
|
||||||
left: 0;
|
|
||||||
width: 40%;
|
|
||||||
}
|
|
||||||
#output {
|
|
||||||
left: 40%;
|
|
||||||
width: 60%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
<div id="header"></div>
|
||||||
<div id="source" class="editor"></div>
|
<div id="source" class="editor"></div>
|
||||||
<div id="output" class="editor"></div>
|
<div id="output" class="editor"></div>
|
||||||
|
|
||||||
<script src="../../node_modules/acorn/dist/acorn.js"></script>
|
<script src="../../node_modules/acorn/dist/acorn.js"></script>
|
||||||
<script src="../../node_modules/estree-walker/dist/estree-walker.umd.js"></script>
|
<script src="../../node_modules/estree-walker/dist/estree-walker.umd.js"></script>
|
||||||
|
<script src="../../node_modules/source-map/dist/source-map.js"></script>
|
||||||
<script src="../../node_modules/monaco-editor/min/vs/loader.js"></script>
|
<script src="../../node_modules/monaco-editor/min/vs/loader.js"></script>
|
||||||
<script src="./dist/template-explorer.global.js"></script>
|
<script src="./dist/template-explorer.global.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window._deps = {
|
window._deps = {
|
||||||
acorn,
|
acorn,
|
||||||
'estree-walker': estreeWalker
|
'estree-walker': estreeWalker,
|
||||||
|
'source-map': sourceMap
|
||||||
}
|
}
|
||||||
|
|
||||||
require.config({
|
require.config({
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import * as m from 'monaco-editor'
|
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
|
const self = window as any
|
||||||
|
|
||||||
@ -9,30 +12,64 @@ self.init = () => {
|
|||||||
decodeURIComponent(window.location.hash.slice(1)) ||
|
decodeURIComponent(window.location.hash.slice(1)) ||
|
||||||
`<div>{{ foo + bar }}</div>`
|
`<div>{{ foo + bar }}</div>`
|
||||||
|
|
||||||
self.compilerOptions = {
|
let lastSuccessfulCode: string = `/* See console for error */`
|
||||||
mode: 'module',
|
let lastSuccessfulMap: SourceMapConsumer | undefined = undefined
|
||||||
prefixIdentifiers: true,
|
|
||||||
hoistStatic: true
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileCode(source: string): string {
|
function compileCode(source: string): string {
|
||||||
console.clear()
|
console.clear()
|
||||||
try {
|
try {
|
||||||
const { code, ast } = compile(source, self.compilerOptions)
|
const { code, ast, map } = compile(source, {
|
||||||
|
filename: 'template.vue',
|
||||||
console.log(ast)
|
...compilerOptions,
|
||||||
return code
|
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) {
|
} catch (e) {
|
||||||
console.error(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',
|
theme: 'vs-dark',
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
wordWrap: 'on',
|
wordWrap: 'on',
|
||||||
scrollBeyondLastLine: false,
|
scrollBeyondLastLine: false,
|
||||||
|
renderWhitespace: 'selection',
|
||||||
|
contextmenu: false,
|
||||||
minimap: {
|
minimap: {
|
||||||
enabled: false
|
enabled: false
|
||||||
}
|
}
|
||||||
@ -43,41 +80,134 @@ self.init = () => {
|
|||||||
{
|
{
|
||||||
value: persistedContent,
|
value: persistedContent,
|
||||||
language: 'html',
|
language: 'html',
|
||||||
...sharedOptions
|
...sharedEditorOptions
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const model = editor.getModel()!
|
editor.getModel()!.updateOptions({
|
||||||
|
|
||||||
model.updateOptions({
|
|
||||||
tabSize: 2
|
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(
|
const output = monaco.editor.create(
|
||||||
document.getElementById('output') as HTMLElement,
|
document.getElementById('output') as HTMLElement,
|
||||||
{
|
{
|
||||||
value: compileCode(persistedContent),
|
value: '',
|
||||||
language: 'javascript',
|
language: 'javascript',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
...sharedOptions
|
...sharedEditorOptions
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
output.getModel()!.updateOptions({
|
output.getModel()!.updateOptions({
|
||||||
tabSize: 2
|
tabSize: 2
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// handle resize
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
editor.layout()
|
editor.layout()
|
||||||
output.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)
|
||||||
|
}
|
69
packages/template-explorer/style.css
Normal file
69
packages/template-explorer/style.css
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 60px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #1e1e1e;
|
||||||
|
border-bottom: 1px solid #333;
|
||||||
|
padding: 0.3em 1.6em;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#options {
|
||||||
|
float: right;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-group {
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header span, #header label, #header input {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header .label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header input {
|
||||||
|
margin-left: 12px;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header label {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor {
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
bottom: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
#source {
|
||||||
|
left: 0;
|
||||||
|
width: 45%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#output {
|
||||||
|
left: 45%;
|
||||||
|
width: 55%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: rgba(46, 120, 190, 0.5);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user