Skip to content

Commit 65eb2bc

Browse files
authored
feat: emit an error when template not compile (#34)
* feat: emit an error when template not compile * feat: capture errors in template compilation * feat: check template expressions * feat: compile template and emit errors
1 parent 9836404 commit 65eb2bc

File tree

8 files changed

+260
-60
lines changed

8 files changed

+260
-60
lines changed

demo/App.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
:code="codeTemplate"
2020
:layout="CustomLayout"
2121
:components="registeredComponents"
22+
@error="(e) => log('Error on first example', e)"
2223
/>
2324
</div>
2425

@@ -135,6 +136,9 @@ export default {
135136
updateCode(code) {
136137
this.separateCode = code;
137138
},
139+
log() {
140+
console.log(...arguments);
141+
},
138142
},
139143
};
140144
</script>

demo/main.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import Vue from "vue";
33
import App from "./App.vue";
44

55
Vue.config.productionTip = false;
6+
Vue.config.devtools = true;
67

78
new Vue({
8-
render: h => h(App)
9+
render: (h) => h(App),
910
}).$mount("#app");

package-lock.json

Lines changed: 28 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@
1313
"cy:verify": "cypress verify"
1414
},
1515
"dependencies": {
16+
"acorn": "^6.1.1",
1617
"core-js": "^3.6.5",
1718
"debounce": "^1.2.0",
1819
"hash-sum": "^2.0.0",
1920
"prismjs": "^1.16.0",
21+
"recast": "^0.19.1",
2022
"vue-gh-corners": "^3.0.1",
2123
"vue-github-corners": "^1.2.3",
2224
"vue-inbrowser-compiler": "^3.21.0",
23-
"vue-prism-editor": "^0.3.0"
25+
"vue-prism-editor": "^0.3.0",
26+
"vue-template-compiler": "^2.0.0"
2427
},
2528
"devDependencies": {
2629
"@vue/cli-plugin-babel": "^4.3.1",

src/Preview.vue

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,40 @@
11
<template>
2-
<div>
3-
<div style="color:red" v-if="error">{{this.error}}</div>
4-
<component v-if="!error && previewedComponent" :id="scope" :is="previewedComponent" />
5-
</div>
2+
<pre :class="$style.error" v-if="error">{{ this.error }}</pre>
3+
<component
4+
v-else-if="previewedComponent"
5+
:id="scope"
6+
:is="previewedComponent"
7+
:key="iteration"
8+
/>
69
</template>
710

811
<script>
912
import {
10-
compile,
13+
compile as compileScript,
1114
isCodeVueSfc,
1215
addScopedStyle,
1316
adaptCreateElement,
14-
concatenate
17+
concatenate,
1518
} from "vue-inbrowser-compiler";
19+
import checkTemplate, {
20+
VueLiveUndefinedVariableError,
21+
} from "./utils/checkTemplate";
1622
import evalInContext from "./utils/evalInContext";
1723
import requireAtRuntime from "./utils/requireAtRuntime";
1824
1925
export default {
2026
name: "VueLivePreviewComponent",
2127
components: {},
28+
errorCaptured(err) {
29+
this.handleError(err);
30+
},
2231
props: {
2332
/**
2433
* code rendered
2534
*/
2635
code: {
2736
type: String,
28-
required: true
37+
required: true,
2938
},
3039
/**
3140
* Hashtable of auto-registered components
@@ -34,7 +43,7 @@ export default {
3443
*/
3544
components: {
3645
type: Object,
37-
default: () => {}
46+
default: () => {},
3847
},
3948
/**
4049
* Hashtable of modules available in require and import statements
@@ -44,26 +53,27 @@ export default {
4453
*/
4554
requires: {
4655
type: Object,
47-
default: () => {}
56+
default: () => {},
4857
},
4958
jsx: {
5059
type: Boolean,
51-
default: false
60+
default: false,
5261
},
5362
/**
5463
* Outside data to the preview
5564
* @example { count: 1 }
5665
*/
5766
dataScope: {
5867
type: Object,
59-
default: () => {}
60-
}
68+
default: () => {},
69+
},
6170
},
6271
data() {
6372
return {
6473
scope: this.generateScope(),
6574
previewedComponent: undefined,
66-
error: false
75+
iteration: 0,
76+
error: false,
6777
};
6878
},
6979
created() {
@@ -72,28 +82,32 @@ export default {
7282
watch: {
7383
code(value) {
7484
this.renderComponent(value.trim());
75-
}
85+
},
7686
},
7787
methods: {
7888
/**
7989
* Generates the Scope Id attribute value. It will be added to each
8090
* tag if a style is applied to scope the style only to this example
8191
*/
8292
generateScope() {
83-
return "v-xxxxxxxx".replace(/[xy]/g, c => {
93+
return "v-xxxxxxxx".replace(/[xy]/g, (c) => {
8494
const r = (Math.random() * 16) | 0;
8595
const v = c === "x" ? r : (r & 0x3) | 0x8;
8696
return v.toString(16);
8797
});
8898
},
8999
handleError(e) {
100+
this.$emit("error", e);
101+
if (e.constructor === VueLiveUndefinedVariableError) {
102+
e.message = `Cannot parse template expression: ${e.expression}\n\n${e.message}`;
103+
}
90104
this.error = e.message;
91105
},
92106
renderComponent(code) {
93-
let data = {};
107+
let options = {};
94108
let style;
95109
try {
96-
const renderedComponent = compile(
110+
const renderedComponent = compileScript(
97111
code,
98112
this.jsx
99113
? { jsx: "__pragma__(h)", objectAssign: "__concatenate__" }
@@ -110,46 +124,63 @@ export default {
110124
// - a script setting up variables => we set up the data property of renderedComponent
111125
// - a `new Vue()` script that will return a full config object
112126
const script = renderedComponent.script;
113-
data =
127+
options =
114128
evalInContext(
115129
script,
116-
filepath => requireAtRuntime(this.requires, filepath),
130+
(filepath) => requireAtRuntime(this.requires, filepath),
117131
adaptCreateElement,
118132
concatenate
119133
) || {};
120134
121135
if (this.dataScope) {
122-
const mergeData = { ...data.data(), ...this.dataScope };
123-
data.data = () => mergeData;
136+
const mergeData = { ...options.data(), ...this.dataScope };
137+
options.data = () => mergeData;
124138
}
125139
}
126140
if (renderedComponent.template) {
127141
// if this is a pure template or if we are in hybrid vsg mode,
128142
// we need to set the template up.
129-
data.template = `<div>${renderedComponent.template}</div>`;
143+
options.template = `<div>${renderedComponent.template}</div>`;
130144
}
131145
} catch (e) {
132146
this.handleError(e);
133147
return;
134148
}
135149
136-
data.components = this.components;
150+
try {
151+
checkTemplate(options.template, options);
152+
} catch (e) {
153+
this.handleError(e);
154+
return;
155+
}
156+
157+
options.components = this.components;
137158
if (style) {
138159
// To add the scope id attribute to each item in the html
139160
// this way when we add the scoped style sheet it will be aplied
140-
data._scopeId = `data-${this.scope}`;
161+
options._scopeId = `data-${this.scope}`;
141162
addScopedStyle(style, this.scope);
142163
}
143164
144-
if (data.template || data.render) {
145-
this.previewedComponent = data;
165+
if (options.template || options.render) {
166+
this.previewedComponent = options;
167+
this.iteration = this.iteration + 1;
146168
} else {
147169
this.handleError({
148170
message:
149-
"[Vue Live] no template or render function specified, you might have an issue in your example"
171+
"[Vue Live] no template or render function specified, you might have an issue in your example",
150172
});
151173
}
152-
}
153-
}
174+
},
175+
},
154176
};
155177
</script>
178+
179+
<style module>
180+
.error {
181+
color: red;
182+
text-align: left;
183+
overflow: auto;
184+
white-space: pre-wrap;
185+
}
186+
</style>

0 commit comments

Comments
 (0)