@lightningjs/blits 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ # v0.6.6
4
+
5
+ _24 jan 2024_
6
+
7
+ - Added temporary fix for renderer issue [#123](https://github.com/lightning-js/renderer/issues/123)
8
+ - Fixed issue with setting mount, pivot and scale values to zero (0) in object literal with x and y key
9
+ - Added automatic mapping of ref string to each item in a for-loop
10
+
11
+ # v0.6.5
12
+
13
+ _16 jan 2024_
14
+
15
+ - Added `path` to boilerplate vite.config.js for deployments in a sub folder
16
+ - Improved error handling in the template parser, with more contextual error messages
17
+
3
18
  # v0.6.4
4
19
 
5
20
  _15 jan 2024_
@@ -6,6 +6,6 @@
6
6
  </head>
7
7
  <body>
8
8
  <div id="app"></div>
9
- <script type="module" src="/src/index.js"></script>
9
+ <script type="module" src="src/index.js"></script>
10
10
  </body>
11
11
  </html>
@@ -31,6 +31,6 @@
31
31
  "vite": "^4.0.4"
32
32
  },
33
33
  "dependencies": {
34
- "@lightningjs/blits": "^0.6.4"
34
+ "@lightningjs/blits": "^0.6.6"
35
35
  }
36
36
  }
@@ -11,8 +11,22 @@ Blits.Launch(App, 'app', {
11
11
  debugLevel: 1,
12
12
  fontLoader: fontLoader,
13
13
  fonts: [
14
- {family: 'lato', type: 'msdf', png: '/fonts/Lato-Regular.msdf.png', json: '/fonts/Lato-Regular.msdf.json'},
15
- {family: 'raleway', type: 'msdf', png: '/fonts/Raleway-ExtraBold.msdf.png', json: '/fonts/Raleway-ExtraBold.msdf.json'},
16
- {family: 'opensans', type: 'web', file: '/fonts/OpenSans-Medium.ttf'}
14
+ {
15
+ family: 'lato',
16
+ type: 'msdf',
17
+ png: 'fonts/Lato-Regular.msdf.png',
18
+ json: 'fonts/Lato-Regular.msdf.json',
19
+ },
20
+ {
21
+ family: 'raleway',
22
+ type: 'msdf',
23
+ png: 'fonts/Raleway-ExtraBold.msdf.png',
24
+ json: 'fonts/Raleway-ExtraBold.msdf.json',
25
+ },
26
+ {
27
+ family: 'opensans',
28
+ type: 'web',
29
+ file: 'fonts/OpenSans-Medium.ttf',
30
+ },
17
31
  ],
18
32
  })
@@ -3,6 +3,7 @@ import blitsVitePlugins from '@lightningjs/blits/vite'
3
3
 
4
4
  export default defineConfig(({ command, mode, ssrBuild }) => {
5
5
  return {
6
+ base: '/', // Set to your base path if you are deploying to a subdirectory (example: /myApp/)
6
7
  plugins: [...blitsVitePlugins],
7
8
  resolve: {
8
9
  mainFields: ['browser', 'module', 'jsnext:main', 'jsnext'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.cjs",
6
6
  "exports": {
@@ -76,9 +76,7 @@ const Application = (config) => {
76
76
  setTimeout(() => Focus.set(this))
77
77
  }
78
78
 
79
- const App = Component('App', config)
80
-
81
- return App
79
+ return Component('App', config)
82
80
  }
83
81
 
84
82
  export default Application
package/src/component.js CHANGED
@@ -47,11 +47,11 @@ const required = (name) => {
47
47
  }
48
48
 
49
49
  const Component = (name = required('name'), config = required('config')) => {
50
- const setupComponent = (lifecycle) => {
50
+ const setupComponent = (lifecycle, parentComponent) => {
51
51
  // code generation
52
52
  if (!config.code) {
53
53
  Log.debug(`Generating code for ${name} component`)
54
- config.code = codegenerator.call(config, parser(config.template))
54
+ config.code = codegenerator.call(config, parser(config.template, name, parentComponent))
55
55
  }
56
56
 
57
57
  setupBase(component)
@@ -115,7 +115,7 @@ const Component = (name = required('name'), config = required('config')) => {
115
115
  this.lifecycle = createLifecycle(this)
116
116
 
117
117
  if (!component.setup) {
118
- setupComponent(this.lifecycle)
118
+ setupComponent(this.lifecycle, parentComponent)
119
119
  }
120
120
 
121
121
  this.parent = parentComponent
package/src/element.js CHANGED
@@ -51,6 +51,8 @@ const unpackTransition = (obj) => {
51
51
  return obj
52
52
  }
53
53
 
54
+ let firstNode = false
55
+
54
56
  const Props = {
55
57
  set parent(v) {
56
58
  this._props.parent = v === 'root' ? renderer.root : v.node
@@ -126,11 +128,11 @@ const Props = {
126
128
  },
127
129
  set mount(v) {
128
130
  if (typeof v === 'object' || (isObjectString(v) && (v = parseToObject(v)))) {
129
- if (v.x) {
131
+ if ('x' in v) {
130
132
  this._props.mountX = v.x
131
133
  this._set.add('mountX')
132
134
  }
133
- if (v.y) {
135
+ if ('y' in v) {
134
136
  this._props.mountY = v.y
135
137
  this._set.add('mountY')
136
138
  }
@@ -142,11 +144,11 @@ const Props = {
142
144
  },
143
145
  set pivot(v) {
144
146
  if (typeof v === 'object' || (isObjectString(v) && (v = parseToObject(v)))) {
145
- if (v.x) {
147
+ if ('x' in v) {
146
148
  this._set.add('pivotX')
147
149
  this._props.pivotX = v.x
148
150
  }
149
- if (v.y) {
151
+ if ('y' in v) {
150
152
  this._set.add('pivotY')
151
153
  this._props.pivotY = v.y
152
154
  }
@@ -158,8 +160,8 @@ const Props = {
158
160
  },
159
161
  set scale(v) {
160
162
  if (typeof v === 'object' || (isObjectString(v) && (v = parseToObject(v)))) {
161
- if (v.x) this._props.scaleX = v.x
162
- if (v.y) this._props.scaleY = v.y
163
+ if ('x' in v) this._props.scaleX = v.x
164
+ if ('y' in v) this._props.scaleY = v.y
163
165
  } else {
164
166
  this._props.scale = v
165
167
  }
@@ -239,6 +241,13 @@ const Element = {
239
241
  this.props._set.has('src') || this.props._set.has('texture') ? 0xffffffff : 0
240
242
  }
241
243
 
244
+ // temporary workaround for renderer issue https://github.com/lightning-js/renderer/issues/123
245
+ if (!firstNode) {
246
+ this.props._props.src =
247
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII='
248
+ firstNode = true
249
+ }
250
+
242
251
  this.node = props.__textnode
243
252
  ? renderer.createTextNode(this.props._props)
244
253
  : renderer.createNode(this.props._props)
@@ -266,7 +275,6 @@ const Element = {
266
275
  return this.animate(p, v, value.transition)
267
276
  } else {
268
277
  this.node[p] = v
269
- console.log('set it', this.node[p], v)
270
278
  }
271
279
  }
272
280
  },
@@ -56,7 +56,7 @@ const generateElementCode = function (
56
56
 
57
57
  renderCode.push(`
58
58
  if(!${elm}) {
59
- ${elm} = this.element({componentId: component[Symbol.for('id')], parent: parent || 'root'}, component)
59
+ ${elm} = this.element({parent: parent || 'root'}, component)
60
60
  }
61
61
  const elementConfig${counter} = {}
62
62
  `)
@@ -221,9 +221,19 @@ const generateForLoopCode = function (templateObject, parent) {
221
221
  const scope = Object.assign(component, {
222
222
  key: Math.random(),
223
223
  ${index}: __index,
224
- ${item}: collection[__index]
225
- })
224
+ ${item}: collection[__index],
225
+ `)
226
+
227
+ if ('ref' in templateObject && templateObject.ref.indexOf('$') === -1) {
228
+ // automatically map the ref for each item in the loop based on the given ref key
229
+ ctx.renderCode.push(`
230
+ __ref: '${templateObject.ref}' + __index
226
231
  `)
232
+ templateObject.ref = '$__ref'
233
+ }
234
+ ctx.renderCode.push(`
235
+ })
236
+ `)
227
237
 
228
238
  if ('key' in templateObject) {
229
239
  ctx.renderCode.push(`
@@ -107,7 +107,7 @@ test('Generate render and effect code for a template with a single simple elemen
107
107
  function anonymous(parent,component,context) {
108
108
  const elms = []
109
109
 
110
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
110
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
111
111
  const elementConfig1 = {}
112
112
 
113
113
  elementConfig1['type'] = "Component"
@@ -149,7 +149,7 @@ test('Generate code for a template with a simple element and a simple nested ele
149
149
  function anonymous(parent,component,context) {
150
150
  const elms = []
151
151
 
152
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
152
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
153
153
  const elementConfig1 = {}
154
154
 
155
155
  elementConfig1['type'] = "Component"
@@ -158,7 +158,7 @@ test('Generate code for a template with a simple element and a simple nested ele
158
158
 
159
159
  parent = elms[1]
160
160
 
161
- elms[2] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
161
+ elms[2] = this.element(parentId: parent && parent.nodeId || 'root'})
162
162
  const elementConfig2 = {}
163
163
 
164
164
  elementConfig2['type'] = "Element"
@@ -198,7 +198,7 @@ test('Generate code for a template with a single element with attributes', (asse
198
198
  function anonymous(parent,component,context) {
199
199
  const elms = []
200
200
 
201
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
201
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
202
202
  const elementConfig1 = {}
203
203
 
204
204
  elementConfig1['type'] = "Component"
@@ -246,7 +246,7 @@ test('Generate code for a template with attributes and a nested element with att
246
246
  function anonymous(parent,component,context) {
247
247
  const elms = []
248
248
 
249
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
249
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
250
250
  const elementConfig1 = {}
251
251
 
252
252
  elementConfig1['type'] = "Component"
@@ -257,7 +257,7 @@ test('Generate code for a template with attributes and a nested element with att
257
257
 
258
258
  parent = elms[1]
259
259
 
260
- elms[2] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
260
+ elms[2] = this.element(parentId: parent && parent.nodeId || 'root'})
261
261
  const elementConfig2 = {}
262
262
 
263
263
  elementConfig2['type'] = "Element"
@@ -313,7 +313,7 @@ test('Generate code for a template with attributes and 2 nested elements with at
313
313
  function anonymous(parent,component,context) {
314
314
  const elms = []
315
315
 
316
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
316
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
317
317
  const elementConfig1 = {}
318
318
 
319
319
  elementConfig1['type'] = "Component"
@@ -324,7 +324,7 @@ test('Generate code for a template with attributes and 2 nested elements with at
324
324
 
325
325
  parent = elms[1]
326
326
 
327
- elms[2] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
327
+ elms[2] = this.element(parentId: parent && parent.nodeId || 'root'})
328
328
  const elementConfig2 = {}
329
329
 
330
330
  elementConfig2['type'] = "Element"
@@ -336,7 +336,7 @@ test('Generate code for a template with attributes and 2 nested elements with at
336
336
 
337
337
  parent = elms[1]
338
338
 
339
- elms[3] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
339
+ elms[3] = this.element(parentId: parent && parent.nodeId || 'root'})
340
340
  const elementConfig3 = {}
341
341
 
342
342
  elementConfig3['type'] = "Element"
@@ -403,7 +403,7 @@ test('Generate code for a template with attributes and deep nested elements with
403
403
  function anonymous(parent,component,context) {
404
404
  const elms = []
405
405
 
406
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
406
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
407
407
  const elementConfig1 = {}
408
408
 
409
409
  elementConfig1['type'] = "Component"
@@ -414,7 +414,7 @@ test('Generate code for a template with attributes and deep nested elements with
414
414
 
415
415
  parent = elms[1]
416
416
 
417
- elms[2] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
417
+ elms[2] = this.element(parentId: parent && parent.nodeId || 'root'})
418
418
  const elementConfig2 = {}
419
419
 
420
420
  elementConfig2['type'] = "Element"
@@ -426,7 +426,7 @@ test('Generate code for a template with attributes and deep nested elements with
426
426
 
427
427
  parent = elms[1]
428
428
 
429
- elms[3] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
429
+ elms[3] = this.element(parentId: parent && parent.nodeId || 'root'})
430
430
  const elementConfig3 = {}
431
431
 
432
432
  elementConfig3['type'] = "Element"
@@ -438,7 +438,7 @@ test('Generate code for a template with attributes and deep nested elements with
438
438
 
439
439
  parent = elms[3]
440
440
 
441
- elms[4] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
441
+ elms[4] = this.element(parentId: parent && parent.nodeId || 'root'})
442
442
  const elementConfig4 = {}
443
443
 
444
444
  elementConfig4['type'] = "Element"
@@ -448,7 +448,7 @@ test('Generate code for a template with attributes and deep nested elements with
448
448
 
449
449
  parent = elms[3]
450
450
 
451
- elms[5] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
451
+ elms[5] = this.element(parentId: parent && parent.nodeId || 'root'})
452
452
  const elementConfig5 = {}
453
453
 
454
454
  elementConfig5['type'] = "Element"
@@ -492,7 +492,7 @@ test('Generate code for a template with simple dynamic attributes', (assert) =>
492
492
  function anonymous(parent,component,context) {
493
493
  const elms = []
494
494
 
495
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
495
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
496
496
  const elementConfig1 = {}
497
497
 
498
498
  elementConfig1['type'] = "Component"
@@ -559,7 +559,7 @@ test('Generate code for a template with an attribute with a dash', (assert) => {
559
559
  function anonymous(parent,component,context) {
560
560
  const elms = []
561
561
 
562
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
562
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
563
563
  const elementConfig1 = {}
564
564
 
565
565
  elementConfig1['type'] = "Component"
@@ -604,7 +604,7 @@ test('Generate code for a template with dynamic attributes with code to be evalu
604
604
  function anonymous(parent,component,context) {
605
605
  const elms = []
606
606
 
607
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
607
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
608
608
  const elementConfig1 = {}
609
609
 
610
610
  elementConfig1['type'] = "Component"
@@ -690,7 +690,7 @@ test('Generate code for a template with custom components', (assert) => {
690
690
  function anonymous(parent,component,context) {
691
691
  const elms = []
692
692
 
693
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
693
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
694
694
  const elementConfig1 = {}
695
695
 
696
696
  elementConfig1['type'] = "Component"
@@ -746,7 +746,7 @@ test('Generate code for a template with an unregistered custom component', (asse
746
746
  function anonymous(parent,component,context) {
747
747
  const elms = []
748
748
 
749
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
749
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
750
750
  const elementConfig1 = {}
751
751
 
752
752
  elementConfig1['type'] = "Component"
@@ -757,7 +757,7 @@ test('Generate code for a template with an unregistered custom component', (asse
757
757
 
758
758
  parent = elms[1]
759
759
 
760
- elms[3] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
760
+ elms[3] = this.element(parentId: parent && parent.nodeId || 'root'})
761
761
  const elementConfig3 = {}
762
762
 
763
763
  elementConfig3['type'] = "Poster2"
@@ -813,7 +813,7 @@ test('Generate code for a template with custom components with arguments', (asse
813
813
  function anonymous(parent,component,context) {
814
814
  const elms = []
815
815
 
816
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
816
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
817
817
  const elementConfig1 = {}
818
818
 
819
819
  elementConfig1['type'] = "Component"
@@ -876,7 +876,7 @@ test('Generate code for a template with custom components with reactive props',
876
876
  function anonymous(parent,component,context) {
877
877
  const elms = []
878
878
 
879
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
879
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
880
880
  const elementConfig1 = {}
881
881
 
882
882
  elementConfig1['type'] = "Component"
@@ -946,7 +946,7 @@ test('Generate code for a template with a transition attributes', (assert) => {
946
946
  function anonymous(parent,component,context) {
947
947
  const elms = []
948
948
 
949
- elms[1] = this.element({componentId: component[Symbol.for('id')], parentId: parent && parent.nodeId || 'root'})
949
+ elms[1] = this.element(parentId: parent && parent.nodeId || 'root'})
950
950
  const elementConfig1 = {}
951
951
 
952
952
  elementConfig1['type'] = "Component"
@@ -1,5 +1,6 @@
1
1
  export default {
2
2
  currentView: Symbol('currentView'),
3
+ cursorTagStart: Symbol('cursorTagStart'),
3
4
  computedKeys: Symbol('computedKeys'),
4
5
  destroy: Symbol('destroy'),
5
6
  index: Symbol('index'),
@@ -31,8 +32,6 @@ export default {
31
32
  children: Symbol.for('children'),
32
33
  // Symbol 'components' utilized within generated code
33
34
  components: Symbol.for('components'),
34
- // Symbol 'id' utilized within generated code
35
- id: Symbol.for('id'),
36
35
  // Symbol 'isSlot' utilized within generated code
37
36
  isSlot: Symbol.for('isSlot'),
38
37
  // Symbol 'props' utilized within generated code
@@ -17,16 +17,9 @@
17
17
 
18
18
  import symbols from '../symbols.js'
19
19
 
20
- class TemplateParseError extends Error {
21
- constructor(message, name, context) {
22
- super(`TemplateParseError: ${message}`)
23
- this.name = name
24
- this.context = context
25
- }
26
- }
27
-
28
- export default (template = '') => {
20
+ export default (template = '', componentName, parentComponent, filePath = null) => {
29
21
  let cursor = 0
22
+ let prevCursor = 0
30
23
  let tags = []
31
24
  let currentTag = null
32
25
  let currentLevel = 0
@@ -45,13 +38,10 @@ export default (template = '') => {
45
38
  parseLoop(parseEmptyTagStart)
46
39
  return format(tags)
47
40
  } catch (error) {
48
- if (error instanceof TemplateParseError) {
49
- console.error(`${error.message} | ${error.name}`)
50
- } else {
51
- console.error(error)
41
+ if (error.name == 'TemplateParseError' || error.name == 'TemplateStructureError') {
42
+ error.message = `${error.message}\n${error.context}`
52
43
  }
53
- // return errors gracefully
54
- return null
44
+ throw error
55
45
  }
56
46
  }
57
47
 
@@ -63,9 +53,9 @@ export default (template = '') => {
63
53
  }
64
54
 
65
55
  // utils
66
- const clean = (template) => {
56
+ const clean = (templateText) => {
67
57
  // remove all unnecessary new lines and comments
68
- return template
58
+ return templateText
69
59
  .replace(/<!--.*?-->/gms, '') // remove comments
70
60
  .replace(/\r?\n\s*\r\n/gm, ' ') // remove empty lines
71
61
  .replace(/\r?\n\s*(\S)/gm, ' $1') // remove line endings & spacing
@@ -75,7 +65,10 @@ export default (template = '') => {
75
65
 
76
66
  const moveCursorOnMatch = (regex) => {
77
67
  const match = template.slice(cursor).match(regex)
78
- if (match) cursor += match[0].length
68
+ if (match) {
69
+ prevCursor = cursor
70
+ cursor += match[0].length
71
+ }
79
72
  return match
80
73
  }
81
74
 
@@ -83,7 +76,12 @@ export default (template = '') => {
83
76
  const parseEmptyTagStart = () => {
84
77
  const match = moveCursorOnMatch(emptyTagStartRegex)
85
78
  if (match) {
86
- tags.push({ type: null, [symbols.type]: 'opening', [symbols.level]: currentLevel })
79
+ tags.push({
80
+ type: null,
81
+ [symbols.type]: 'opening',
82
+ [symbols.level]: currentLevel,
83
+ [symbols.cursorTagStart]: prevCursor,
84
+ })
87
85
  currentLevel++
88
86
  parseLoop(parseEmptyTagStart)
89
87
  } else {
@@ -95,7 +93,11 @@ export default (template = '') => {
95
93
  const match = moveCursorOnMatch(emptyTagEndRegex)
96
94
  if (match) {
97
95
  currentLevel--
98
- tags.push({ type: null, [symbols.type]: 'closing', [symbols.level]: currentLevel })
96
+ tags.push({
97
+ type: null,
98
+ [symbols.type]: 'closing',
99
+ [symbols.level]: currentLevel,
100
+ })
99
101
  parseLoop(parseEmptyTagStart)
100
102
  } else {
101
103
  parseLoop(parseTag)
@@ -105,16 +107,22 @@ export default (template = '') => {
105
107
  const parseTag = () => {
106
108
  const match = moveCursorOnMatch(tagStartRegex)
107
109
  if (match) {
110
+ currentTag = {
111
+ type: match[1],
112
+ [symbols.level]: currentLevel,
113
+ [symbols.cursorTagStart]: prevCursor,
114
+ }
108
115
  if (match[0].startsWith('</')) {
109
116
  currentLevel--
110
- currentTag = { type: match[1], [symbols.type]: 'closing', [symbols.level]: currentLevel }
117
+ currentTag[symbols.type] = 'closing'
118
+ currentTag[symbols.level] = currentLevel
111
119
  } else {
112
- currentTag = { type: match[1], [symbols.type]: 'opening', [symbols.level]: currentLevel }
120
+ currentTag[symbols.type] = 'opening'
113
121
  currentLevel++
114
122
  }
115
123
  parseLoop(parseTagEnd)
116
124
  } else {
117
- throw new TemplateParseError('InvalidTag', template.slice(cursor))
125
+ throw TemplateParseError('InvalidTag')
118
126
  }
119
127
  }
120
128
 
@@ -123,8 +131,7 @@ export default (template = '') => {
123
131
  if (match) {
124
132
  if (match[1] === '/>') {
125
133
  if (currentTag[symbols.type] === 'closing') {
126
- // 10 is arbitrary, just to show some context by moving the cursor back a bit
127
- throw new TemplateParseError('InvalidClosingTag', template.slice(cursor - 10))
134
+ throw TemplateParseError('InvalidClosingTag')
128
135
  }
129
136
  currentTag[symbols.type] = 'self-closing'
130
137
  currentLevel-- // because it was parsed as opening tag before
@@ -147,9 +154,14 @@ export default (template = '') => {
147
154
  }
148
155
  }
149
156
 
157
+ //fixme: closing tags cannot have attributes
150
158
  const parseAttributes = () => {
151
159
  const attrNameMatch = moveCursorOnMatch(attrNameRegex)
152
160
  if (attrNameMatch) {
161
+ if (currentTag[symbols.type] === 'closing') {
162
+ throw TemplateParseError('AttributesInClosingTag')
163
+ }
164
+
153
165
  const delimiter = attrNameMatch[2]
154
166
  const attrValueRegex = new RegExp(`^(.*?)${delimiter}\\s*`)
155
167
  const attrValueMatch = moveCursorOnMatch(attrValueRegex)
@@ -160,10 +172,10 @@ export default (template = '') => {
160
172
  currentTag[attr.name] = attr.value
161
173
  parseLoop(parseTagEnd)
162
174
  } else {
163
- throw new TemplateParseError('MissingOrInvalidAttributeValue', template.slice(cursor))
175
+ throw TemplateParseError('MissingOrInvalidAttributeValue')
164
176
  }
165
177
  } else {
166
- throw new TemplateParseError('InvalidAttribute', template.slice(cursor))
178
+ throw TemplateParseError('InvalidAttribute')
167
179
  }
168
180
  }
169
181
 
@@ -177,10 +189,8 @@ export default (template = '') => {
177
189
  return { name, value }
178
190
  }
179
191
 
180
- // formating and validation
181
-
182
192
  /*
183
- validation rules:
193
+ Formatting & validation rules:
184
194
  #1: Every opening tag must have a corresponding closing tag at the same level. If a closing tag is encountered without
185
195
  a preceding opening tag at the same level, or if an opening tag is not followed by a corresponding closing tag at
186
196
  the same level, an error should be thrown.
@@ -200,7 +210,7 @@ export default (template = '') => {
200
210
  // Rule #1
201
211
  if (element[symbols.level] === 0 && element[symbols.type] !== 'closing') {
202
212
  if (rootElementDefined) {
203
- throw new TemplateParseError('MultipleTopLevelTags', formatErrorContext(element))
213
+ throw TemplateStructureError('MultipleTopLevelTags', element)
204
214
  }
205
215
  rootElementDefined = true
206
216
  }
@@ -210,6 +220,7 @@ export default (template = '') => {
210
220
  stack.push({
211
221
  [symbols.level]: element[symbols.level],
212
222
  [symbols.type]: element[symbols.type],
223
+ [symbols.cursorTagStart]: element[symbols.cursorTagStart],
213
224
  type: element.type,
214
225
  parent: currentParent, // helps getting the previous parent when closing tag is encountered
215
226
  })
@@ -223,7 +234,7 @@ export default (template = '') => {
223
234
  }
224
235
 
225
236
  if (isStackEmpty || isLevelMismatch || isTagMismatch) {
226
- throw new TemplateParseError('MismatchedClosingTag', formatErrorContext(element))
237
+ throw TemplateStructureError('MismatchedClosingTag', element)
227
238
  }
228
239
 
229
240
  // when we remove the closing element from the stack, we should set
@@ -235,6 +246,7 @@ export default (template = '') => {
235
246
  const newItem = { ...element }
236
247
  delete newItem[symbols.type]
237
248
  delete newItem[symbols.level]
249
+ delete newItem[symbols.cursorTagStart]
238
250
 
239
251
  // if it is an opening tag, add children[] to it and update current parent
240
252
  if (element[symbols.type] === 'opening') {
@@ -251,21 +263,78 @@ export default (template = '') => {
251
263
 
252
264
  // Check if all tags are closed (so stack should be empty)[Rule #1]
253
265
  if (stack.length > 0) {
254
- const unclosedTags = stack
255
- .map((item) => {
256
- return formatErrorContext(item)
257
- })
258
- .join(', ')
259
- throw new TemplateParseError('UnclosedTags', unclosedTags)
266
+ throw TemplateStructureError('UnclosedTags', stack)
267
+ }
268
+
269
+ return output
270
+ }
271
+
272
+ // error reporting
273
+ const contextPaddingBefore = 10 // number of characters to show before the error location
274
+ const contextPaddingAfter = 50 // number of characters to show after the error location
275
+
276
+ const TemplateParseError = (message, context) => {
277
+ const location = getErrorLocation()
278
+ message = `${message} in ${location}`
279
+
280
+ const error = new Error(message)
281
+ error.name = 'TemplateParseError'
282
+
283
+ // generate context if the error is related to parsing
284
+ if (!context) {
285
+ const start = Math.max(0, prevCursor - contextPaddingBefore)
286
+ const end = Math.min(template.length, cursor + contextPaddingAfter)
287
+ const contextText = template.slice(start, end)
288
+
289
+ // add ^ caret to show where the error is
290
+ const caretPosition = cursor - start
291
+ error.context = insertContextCaret(caretPosition, contextText)
292
+ } else {
293
+ error.context = context
260
294
  }
295
+ return error
296
+ }
261
297
 
262
- function formatErrorContext(element) {
263
- return `${element.type || 'empty-tag'}[${element[symbols.type]}] at level ${
264
- element[symbols.level]
265
- }`
298
+ const TemplateStructureError = (message, context) => {
299
+ const location = getErrorLocation()
300
+ message = `${message} in ${location}`
301
+
302
+ const error = new Error(message)
303
+ error.name = 'TemplateStructureError'
304
+
305
+ // check if context is an array
306
+ if (Array.isArray(context)) {
307
+ error.context = context.map((tag) => generateContext(tag)).join('\n')
308
+ } else {
309
+ error.context = generateContext(context)
266
310
  }
267
311
 
268
- return output
312
+ function generateContext(element) {
313
+ const start = Math.max(0, element[symbols.cursorTagStart] - contextPaddingBefore)
314
+ const contextText = template.slice(start, start + contextPaddingAfter)
315
+ // add ^ caret to show where the error is
316
+ return insertContextCaret(contextPaddingBefore, contextText)
317
+ }
318
+ return error
319
+ }
320
+
321
+ const insertContextCaret = (position, contextText) => {
322
+ const caret = ' '.repeat(position) + '^'
323
+ return `\n${contextText}\n${caret}\n`
324
+ }
325
+
326
+ const getErrorLocation = () => {
327
+ if (parentComponent) {
328
+ let hierarchy = componentName || ''
329
+ let currentParent = parentComponent
330
+
331
+ while (currentParent) {
332
+ hierarchy = `${currentParent.type}/${hierarchy}`
333
+ currentParent = currentParent.parent
334
+ }
335
+ return hierarchy
336
+ }
337
+ return filePath ? filePath : 'Blits.Application'
269
338
  }
270
339
 
271
340
  return parse()
@@ -594,43 +594,6 @@ test('Parse template with a nameless tag', (assert) => {
594
594
  assert.end()
595
595
  })
596
596
 
597
- // test('Parse template with a nameless tag but with arguments', (assert) => {
598
- // const template = `
599
- // <x="100" y="200">
600
- // <x="50" y="20">
601
- // <Component w="100" h="20" />
602
- // </>
603
- // </>`
604
-
605
- // const expected = {
606
- // children: [
607
- // {
608
- // type: null,
609
- // x: '100',
610
- // y: '200',
611
- // children: [
612
- // {
613
- // type: null,
614
- // x: '50',
615
- // y: '20',
616
- // children: [
617
- // {
618
- // type: 'Component',
619
- // w: '100',
620
- // h: '20',
621
- // },
622
- // ],
623
- // },
624
- // ],
625
- // },
626
- // ],
627
- // }
628
- // const actual = parser(template)
629
-
630
- // assert.deepEqual(actual, expected, 'Parser should return object representation of template')
631
- // assert.end()
632
- // })
633
-
634
597
  test('Parse template with a transition argument (single value)', (assert) => {
635
598
  const template = `
636
599
  <Element x.transition="$offset" y="200">
@@ -974,8 +937,16 @@ test('Parse template with multiple top level elements and parsing should fail',
974
937
  />
975
938
  </Component>`
976
939
 
977
- const actual = parser(template)
978
- assert.equal(actual, null, 'Parser should throw an error')
940
+ try {
941
+ parser(template)
942
+ assert.fail('Parser should throw TemplateStructureError:MultipleTopLevelTags')
943
+ } catch (error) {
944
+ assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError')
945
+ assert.ok(
946
+ error.message.startsWith('MultipleTopLevelTags'),
947
+ 'Parser should throw TemplateStructureError:MultipleTopLevelTags'
948
+ )
949
+ }
979
950
 
980
951
  assert.end()
981
952
  })
@@ -987,8 +958,16 @@ test('Parse template with unclosed tag and parsing should fail', (assert) => {
987
958
  </Component>
988
959
  `
989
960
 
990
- const actual = parser(template)
991
- assert.equal(actual, null, 'Parser should throw an error')
961
+ try {
962
+ parser(template)
963
+ assert.fail('Parser should throw TemplateStructureError:MismatchedClosingTag')
964
+ } catch (error) {
965
+ assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError')
966
+ assert.ok(
967
+ error.message.startsWith('MismatchedClosingTag'),
968
+ 'Parser should throw TemplateStructureError:MismatchedClosingTag'
969
+ )
970
+ }
992
971
  assert.end()
993
972
  })
994
973
 
@@ -1004,8 +983,16 @@ test('Parse template with multiple unclosed tags and parsing should fail', (asse
1004
983
  </Component>
1005
984
  `
1006
985
 
1007
- const actual = parser(template)
1008
- assert.equal(actual, null, 'Parser should throw an error')
986
+ try {
987
+ parser(template)
988
+ assert.fail('Parser should throw TemplateStructureError:MismatchedClosingTag')
989
+ } catch (error) {
990
+ assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError')
991
+ assert.ok(
992
+ error.message.startsWith('MismatchedClosingTag'),
993
+ 'Parser should throw TemplateStructureError:MismatchedClosingTag'
994
+ )
995
+ }
1009
996
  assert.end()
1010
997
  })
1011
998
 
@@ -1017,8 +1004,16 @@ test('Parse template with an invalid closing tag and parsing should fail', (asse
1017
1004
  </Component>
1018
1005
  `
1019
1006
 
1020
- const actual = parser(template)
1021
- assert.equal(actual, null, 'Parser should throw an error')
1007
+ try {
1008
+ parser(template)
1009
+ assert.fail('Parser should throw TemplateParseError:InvalidClosingTag')
1010
+ } catch (error) {
1011
+ assert.equal(error.name, 'TemplateParseError', 'Parser should throw TemplateParseError')
1012
+ assert.ok(
1013
+ error.message.startsWith('InvalidClosingTag'),
1014
+ 'Parser should throw TemplateParseError:InvalidClosingTag'
1015
+ )
1016
+ }
1022
1017
 
1023
1018
  assert.end()
1024
1019
  })
@@ -1029,8 +1024,17 @@ test('Parse template with multiple self-closing tags at the top level and parsin
1029
1024
  <Element/>
1030
1025
  `
1031
1026
 
1032
- const actual = parser(template)
1033
- assert.equal(actual, null, 'Parser should throw an error')
1027
+ try {
1028
+ parser(template)
1029
+ assert.fail('Parser should throw TemplateStructureError:MultipleTopLevelTags')
1030
+ } catch (error) {
1031
+ assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError')
1032
+ assert.ok(
1033
+ error.message.startsWith('MultipleTopLevelTags'),
1034
+ 'Parser should throw TemplateStructureError:MultipleTopLevelTags'
1035
+ )
1036
+ }
1037
+
1034
1038
  assert.end()
1035
1039
  })
1036
1040
 
@@ -1042,7 +1046,38 @@ test('Parse template with a closing tag at the beginning and parsing should fail
1042
1046
  </Component>
1043
1047
  `
1044
1048
 
1045
- const actual = parser(template)
1046
- assert.equal(actual, null, 'Parser should throw an error')
1049
+ try {
1050
+ parser(template)
1051
+ assert.fail('Parser should throw TemplateStructureError:MismatchedClosingTag')
1052
+ } catch (error) {
1053
+ assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError')
1054
+ assert.ok(
1055
+ error.message.startsWith('MismatchedClosingTag'),
1056
+ 'Parser should throw TemplateStructureError:MismatchedClosingTag'
1057
+ )
1058
+ }
1059
+
1060
+ assert.end()
1061
+ })
1062
+
1063
+ test('Parse template with a closing tag that has attributes and parsing should fail', (assert) => {
1064
+ const template = `
1065
+ <Component>
1066
+ <Text>Lorem ipsum dolor sit amet</Text>
1067
+ <Element></Element x="200">
1068
+ </Component>
1069
+ `
1070
+
1071
+ try {
1072
+ parser(template)
1073
+ assert.fail('Parser should throw TemplateParseError:AttributesInClosingTag')
1074
+ } catch (error) {
1075
+ assert.equal(error.name, 'TemplateParseError', 'Parser should throw TemplateParseError')
1076
+ assert.ok(
1077
+ error.message.startsWith('AttributesInClosingTag'),
1078
+ 'Parser should throw TemplateParseError:AttributesInClosingTag'
1079
+ )
1080
+ }
1081
+
1047
1082
  assert.end()
1048
1083
  })
@@ -1,5 +1,6 @@
1
1
  import parser from '../src/lib/templateparser/parser.js'
2
2
  import generator from '../src/lib/codegenerator/generator.js'
3
+ import path from 'path'
3
4
 
4
5
  export default function () {
5
6
  let config
@@ -8,7 +9,7 @@ export default function () {
8
9
  configResolved(resolvedConfig) {
9
10
  config = resolvedConfig
10
11
  },
11
- transform(source) {
12
+ transform(source, filePath) {
12
13
  if (config.blits && config.blits.precompile === false) return source
13
14
  if (source.indexOf('Blits.Component(') > -1 || source.indexOf('Blits.Application(') > -1) {
14
15
  // get the start of the template key in de component configuration object
@@ -27,7 +28,12 @@ export default function () {
27
28
  templateStartIndex + templateContentResult.index + templateContentResult[0].length
28
29
 
29
30
  // Parse the template
30
- const parsed = parser(templateContentResult[1])
31
+ let resourceName = 'Blits.Application'
32
+ if (source.indexOf('Blits.Component(') > -1) {
33
+ resourceName = source.match(/Blits\.Component\(['"](.*)['"]\s*,/)[1]
34
+ }
35
+ const componentPath = path.relative(process.cwd(), filePath)
36
+ const parsed = parser(templateContentResult[1], resourceName, null, componentPath)
31
37
 
32
38
  // Generate the code
33
39
  const code = generator.call({ components: {} }, parsed)