@lightningjs/blits
Advanced tools
Comparing version 0.6.4 to 0.6.5
@@ -34,4 +34,4 @@ { | ||
"dependencies": { | ||
"@lightningjs/blits": "^0.6.4" | ||
"@lightningjs/blits": "^0.6.5" | ||
} | ||
} |
@@ -14,6 +14,20 @@ import Blits from '@lightningjs/blits' | ||
fonts: [ | ||
{family: 'lato', type: 'msdf', png: '/fonts/Lato-Regular.msdf.png', json: '/fonts/Lato-Regular.msdf.json'}, | ||
{family: 'raleway', type: 'msdf', png: '/fonts/Raleway-ExtraBold.msdf.png', json: '/fonts/Raleway-ExtraBold.msdf.json'}, | ||
{family: 'opensans', type: 'web', file: '/fonts/OpenSans-Medium.ttf'} | ||
{ | ||
family: 'lato', | ||
type: 'msdf', | ||
png: 'fonts/Lato-Regular.msdf.png', | ||
json: 'fonts/Lato-Regular.msdf.json', | ||
}, | ||
{ | ||
family: 'raleway', | ||
type: 'msdf', | ||
png: 'fonts/Raleway-ExtraBold.msdf.png', | ||
json: 'fonts/Raleway-ExtraBold.msdf.json', | ||
}, | ||
{ | ||
family: 'opensans', | ||
type: 'web', | ||
file: 'fonts/OpenSans-Medium.ttf', | ||
}, | ||
], | ||
}) |
@@ -6,2 +6,3 @@ import { defineConfig } from 'vite' | ||
return { | ||
base: '/', // Set to your base path if you are deploying to a subdirectory (example: /myApp/) | ||
plugins: [...blitsVitePlugins], | ||
@@ -8,0 +9,0 @@ resolve: { |
# Changelog | ||
# v0.6.5 | ||
_16 jan 2024_ | ||
- Added `path` to boilerplate vite.config.js for deployments in a sub folder | ||
- Improved error handling in the template parser, with more contextual error messages | ||
# v0.6.4 | ||
@@ -4,0 +11,0 @@ |
{ | ||
"name": "@lightningjs/blits", | ||
"version": "0.6.4", | ||
"version": "0.6.5", | ||
"description": "Blits: The Lightning 3 App Development Framework", | ||
@@ -5,0 +5,0 @@ "bin": "bin/index.cjs", |
@@ -50,7 +50,7 @@ /* | ||
const Component = (name = required('name'), config = required('config')) => { | ||
const setupComponent = (lifecycle) => { | ||
const setupComponent = (lifecycle, parentComponent) => { | ||
// code generation | ||
if (!config.code) { | ||
Log.debug(`Generating code for ${name} component`) | ||
config.code = codegenerator.call(config, parser(config.template)) | ||
config.code = codegenerator.call(config, parser(config.template, name, parentComponent)) | ||
} | ||
@@ -119,3 +119,3 @@ | ||
if (!component.setup) { | ||
setupComponent(this.lifecycle) | ||
setupComponent(this.lifecycle, parentComponent) | ||
} | ||
@@ -122,0 +122,0 @@ |
@@ -269,3 +269,2 @@ /* | ||
this.node[p] = v | ||
console.log('set it', this.node[p], v) | ||
} | ||
@@ -272,0 +271,0 @@ } |
export default { | ||
currentView: Symbol('currentView'), | ||
cursorTagStart: Symbol('cursorTagStart'), | ||
computedKeys: Symbol('computedKeys'), | ||
@@ -4,0 +5,0 @@ destroy: Symbol('destroy'), |
@@ -20,12 +20,5 @@ /* | ||
class TemplateParseError extends Error { | ||
constructor(message, name, context) { | ||
super(`TemplateParseError: ${message}`) | ||
this.name = name | ||
this.context = context | ||
} | ||
} | ||
export default (template = '') => { | ||
export default (template = '', componentName, parentComponent, filePath = null) => { | ||
let cursor = 0 | ||
let prevCursor = 0 | ||
let tags = [] | ||
@@ -49,9 +42,6 @@ let currentTag = null | ||
} catch (error) { | ||
if (error instanceof TemplateParseError) { | ||
console.error(`${error.message} | ${error.name}`) | ||
} else { | ||
console.error(error) | ||
if (error.name == 'TemplateParseError' || error.name == 'TemplateStructureError') { | ||
error.message = `${error.message}\n${error.context}` | ||
} | ||
// return errors gracefully | ||
return null | ||
throw error | ||
} | ||
@@ -68,5 +58,5 @@ } | ||
// utils | ||
const clean = (template) => { | ||
const clean = (templateText) => { | ||
// remove all unnecessary new lines and comments | ||
return template | ||
return templateText | ||
.replace(/<!--.*?-->/gms, '') // remove comments | ||
@@ -81,3 +71,6 @@ .replace(/\r?\n\s*\r\n/gm, ' ') // remove empty lines | ||
const match = template.slice(cursor).match(regex) | ||
if (match) cursor += match[0].length | ||
if (match) { | ||
prevCursor = cursor | ||
cursor += match[0].length | ||
} | ||
return match | ||
@@ -90,3 +83,8 @@ } | ||
if (match) { | ||
tags.push({ type: null, [symbols.type]: 'opening', [symbols.level]: currentLevel }) | ||
tags.push({ | ||
type: null, | ||
[symbols.type]: 'opening', | ||
[symbols.level]: currentLevel, | ||
[symbols.cursorTagStart]: prevCursor, | ||
}) | ||
currentLevel++ | ||
@@ -103,3 +101,7 @@ parseLoop(parseEmptyTagStart) | ||
currentLevel-- | ||
tags.push({ type: null, [symbols.type]: 'closing', [symbols.level]: currentLevel }) | ||
tags.push({ | ||
type: null, | ||
[symbols.type]: 'closing', | ||
[symbols.level]: currentLevel, | ||
}) | ||
parseLoop(parseEmptyTagStart) | ||
@@ -114,7 +116,13 @@ } else { | ||
if (match) { | ||
currentTag = { | ||
type: match[1], | ||
[symbols.level]: currentLevel, | ||
[symbols.cursorTagStart]: prevCursor, | ||
} | ||
if (match[0].startsWith('</')) { | ||
currentLevel-- | ||
currentTag = { type: match[1], [symbols.type]: 'closing', [symbols.level]: currentLevel } | ||
currentTag[symbols.type] = 'closing' | ||
currentTag[symbols.level] = currentLevel | ||
} else { | ||
currentTag = { type: match[1], [symbols.type]: 'opening', [symbols.level]: currentLevel } | ||
currentTag[symbols.type] = 'opening' | ||
currentLevel++ | ||
@@ -124,3 +132,3 @@ } | ||
} else { | ||
throw new TemplateParseError('InvalidTag', template.slice(cursor)) | ||
throw TemplateParseError('InvalidTag') | ||
} | ||
@@ -134,4 +142,3 @@ } | ||
if (currentTag[symbols.type] === 'closing') { | ||
// 10 is arbitrary, just to show some context by moving the cursor back a bit | ||
throw new TemplateParseError('InvalidClosingTag', template.slice(cursor - 10)) | ||
throw TemplateParseError('InvalidClosingTag') | ||
} | ||
@@ -159,5 +166,10 @@ currentTag[symbols.type] = 'self-closing' | ||
//fixme: closing tags cannot have attributes | ||
const parseAttributes = () => { | ||
const attrNameMatch = moveCursorOnMatch(attrNameRegex) | ||
if (attrNameMatch) { | ||
if (currentTag[symbols.type] === 'closing') { | ||
throw TemplateParseError('AttributesInClosingTag') | ||
} | ||
const delimiter = attrNameMatch[2] | ||
@@ -173,6 +185,6 @@ const attrValueRegex = new RegExp(`^(.*?)${delimiter}\\s*`) | ||
} else { | ||
throw new TemplateParseError('MissingOrInvalidAttributeValue', template.slice(cursor)) | ||
throw TemplateParseError('MissingOrInvalidAttributeValue') | ||
} | ||
} else { | ||
throw new TemplateParseError('InvalidAttribute', template.slice(cursor)) | ||
throw TemplateParseError('InvalidAttribute') | ||
} | ||
@@ -191,6 +203,4 @@ } | ||
// formating and validation | ||
/* | ||
validation rules: | ||
Formatting & validation rules: | ||
#1: Every opening tag must have a corresponding closing tag at the same level. If a closing tag is encountered without | ||
@@ -215,3 +225,3 @@ a preceding opening tag at the same level, or if an opening tag is not followed by a corresponding closing tag at | ||
if (rootElementDefined) { | ||
throw new TemplateParseError('MultipleTopLevelTags', formatErrorContext(element)) | ||
throw TemplateStructureError('MultipleTopLevelTags', element) | ||
} | ||
@@ -226,2 +236,3 @@ rootElementDefined = true | ||
[symbols.type]: element[symbols.type], | ||
[symbols.cursorTagStart]: element[symbols.cursorTagStart], | ||
type: element.type, | ||
@@ -240,3 +251,3 @@ parent: currentParent, // helps getting the previous parent when closing tag is encountered | ||
if (isStackEmpty || isLevelMismatch || isTagMismatch) { | ||
throw new TemplateParseError('MismatchedClosingTag', formatErrorContext(element)) | ||
throw TemplateStructureError('MismatchedClosingTag', element) | ||
} | ||
@@ -253,2 +264,3 @@ | ||
delete newItem[symbols.level] | ||
delete newItem[symbols.cursorTagStart] | ||
@@ -270,20 +282,77 @@ // if it is an opening tag, add children[] to it and update current parent | ||
if (stack.length > 0) { | ||
const unclosedTags = stack | ||
.map((item) => { | ||
return formatErrorContext(item) | ||
}) | ||
.join(', ') | ||
throw new TemplateParseError('UnclosedTags', unclosedTags) | ||
throw TemplateStructureError('UnclosedTags', stack) | ||
} | ||
function formatErrorContext(element) { | ||
return `${element.type || 'empty-tag'}[${element[symbols.type]}] at level ${ | ||
element[symbols.level] | ||
}` | ||
return output | ||
} | ||
// error reporting | ||
const contextPaddingBefore = 10 // number of characters to show before the error location | ||
const contextPaddingAfter = 50 // number of characters to show after the error location | ||
const TemplateParseError = (message, context) => { | ||
const location = getErrorLocation() | ||
message = `${message} in ${location}` | ||
const error = new Error(message) | ||
error.name = 'TemplateParseError' | ||
// generate context if the error is related to parsing | ||
if (!context) { | ||
const start = Math.max(0, prevCursor - contextPaddingBefore) | ||
const end = Math.min(template.length, cursor + contextPaddingAfter) | ||
const contextText = template.slice(start, end) | ||
// add ^ caret to show where the error is | ||
const caretPosition = cursor - start | ||
error.context = insertContextCaret(caretPosition, contextText) | ||
} else { | ||
error.context = context | ||
} | ||
return error | ||
} | ||
return output | ||
const TemplateStructureError = (message, context) => { | ||
const location = getErrorLocation() | ||
message = `${message} in ${location}` | ||
const error = new Error(message) | ||
error.name = 'TemplateStructureError' | ||
// check if context is an array | ||
if (Array.isArray(context)) { | ||
error.context = context.map((tag) => generateContext(tag)).join('\n') | ||
} else { | ||
error.context = generateContext(context) | ||
} | ||
function generateContext(element) { | ||
const start = Math.max(0, element[symbols.cursorTagStart] - contextPaddingBefore) | ||
const contextText = template.slice(start, start + contextPaddingAfter) | ||
// add ^ caret to show where the error is | ||
return insertContextCaret(contextPaddingBefore, contextText) | ||
} | ||
return error | ||
} | ||
const insertContextCaret = (position, contextText) => { | ||
const caret = ' '.repeat(position) + '^' | ||
return `\n${contextText}\n${caret}\n` | ||
} | ||
const getErrorLocation = () => { | ||
if (parentComponent) { | ||
let hierarchy = componentName || '' | ||
let currentParent = parentComponent | ||
while (currentParent) { | ||
hierarchy = `${currentParent.type}/${hierarchy}` | ||
currentParent = currentParent.parent | ||
} | ||
return hierarchy | ||
} | ||
return filePath ? filePath : 'Blits.Application' | ||
} | ||
return parse() | ||
} |
@@ -597,39 +597,2 @@ /* | ||
// test('Parse template with a nameless tag but with arguments', (assert) => { | ||
// const template = ` | ||
// <x="100" y="200"> | ||
// <x="50" y="20"> | ||
// <Component w="100" h="20" /> | ||
// </> | ||
// </>` | ||
// const expected = { | ||
// children: [ | ||
// { | ||
// type: null, | ||
// x: '100', | ||
// y: '200', | ||
// children: [ | ||
// { | ||
// type: null, | ||
// x: '50', | ||
// y: '20', | ||
// children: [ | ||
// { | ||
// type: 'Component', | ||
// w: '100', | ||
// h: '20', | ||
// }, | ||
// ], | ||
// }, | ||
// ], | ||
// }, | ||
// ], | ||
// } | ||
// const actual = parser(template) | ||
// assert.deepEqual(actual, expected, 'Parser should return object representation of template') | ||
// assert.end() | ||
// }) | ||
test('Parse template with a transition argument (single value)', (assert) => { | ||
@@ -978,4 +941,12 @@ const template = ` | ||
const actual = parser(template) | ||
assert.equal(actual, null, 'Parser should throw an error') | ||
try { | ||
parser(template) | ||
assert.fail('Parser should throw TemplateStructureError:MultipleTopLevelTags') | ||
} catch (error) { | ||
assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError') | ||
assert.ok( | ||
error.message.startsWith('MultipleTopLevelTags'), | ||
'Parser should throw TemplateStructureError:MultipleTopLevelTags' | ||
) | ||
} | ||
@@ -992,4 +963,12 @@ assert.end() | ||
const actual = parser(template) | ||
assert.equal(actual, null, 'Parser should throw an error') | ||
try { | ||
parser(template) | ||
assert.fail('Parser should throw TemplateStructureError:MismatchedClosingTag') | ||
} catch (error) { | ||
assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError') | ||
assert.ok( | ||
error.message.startsWith('MismatchedClosingTag'), | ||
'Parser should throw TemplateStructureError:MismatchedClosingTag' | ||
) | ||
} | ||
assert.end() | ||
@@ -1010,4 +989,12 @@ }) | ||
const actual = parser(template) | ||
assert.equal(actual, null, 'Parser should throw an error') | ||
try { | ||
parser(template) | ||
assert.fail('Parser should throw TemplateStructureError:MismatchedClosingTag') | ||
} catch (error) { | ||
assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError') | ||
assert.ok( | ||
error.message.startsWith('MismatchedClosingTag'), | ||
'Parser should throw TemplateStructureError:MismatchedClosingTag' | ||
) | ||
} | ||
assert.end() | ||
@@ -1024,4 +1011,12 @@ }) | ||
const actual = parser(template) | ||
assert.equal(actual, null, 'Parser should throw an error') | ||
try { | ||
parser(template) | ||
assert.fail('Parser should throw TemplateParseError:InvalidClosingTag') | ||
} catch (error) { | ||
assert.equal(error.name, 'TemplateParseError', 'Parser should throw TemplateParseError') | ||
assert.ok( | ||
error.message.startsWith('InvalidClosingTag'), | ||
'Parser should throw TemplateParseError:InvalidClosingTag' | ||
) | ||
} | ||
@@ -1037,4 +1032,13 @@ assert.end() | ||
const actual = parser(template) | ||
assert.equal(actual, null, 'Parser should throw an error') | ||
try { | ||
parser(template) | ||
assert.fail('Parser should throw TemplateStructureError:MultipleTopLevelTags') | ||
} catch (error) { | ||
assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError') | ||
assert.ok( | ||
error.message.startsWith('MultipleTopLevelTags'), | ||
'Parser should throw TemplateStructureError:MultipleTopLevelTags' | ||
) | ||
} | ||
assert.end() | ||
@@ -1051,5 +1055,36 @@ }) | ||
const actual = parser(template) | ||
assert.equal(actual, null, 'Parser should throw an error') | ||
try { | ||
parser(template) | ||
assert.fail('Parser should throw TemplateStructureError:MismatchedClosingTag') | ||
} catch (error) { | ||
assert.equal(error.name, 'TemplateStructureError', 'Parser should throw TemplateStructureError') | ||
assert.ok( | ||
error.message.startsWith('MismatchedClosingTag'), | ||
'Parser should throw TemplateStructureError:MismatchedClosingTag' | ||
) | ||
} | ||
assert.end() | ||
}) | ||
test('Parse template with a closing tag that has attributes and parsing should fail', (assert) => { | ||
const template = ` | ||
<Component> | ||
<Text>Lorem ipsum dolor sit amet</Text> | ||
<Element></Element x="200"> | ||
</Component> | ||
` | ||
try { | ||
parser(template) | ||
assert.fail('Parser should throw TemplateParseError:AttributesInClosingTag') | ||
} catch (error) { | ||
assert.equal(error.name, 'TemplateParseError', 'Parser should throw TemplateParseError') | ||
assert.ok( | ||
error.message.startsWith('AttributesInClosingTag'), | ||
'Parser should throw TemplateParseError:AttributesInClosingTag' | ||
) | ||
} | ||
assert.end() | ||
}) |
import parser from '../src/lib/templateparser/parser.js' | ||
import generator from '../src/lib/codegenerator/generator.js' | ||
import path from 'path' | ||
@@ -11,3 +12,3 @@ export default function () { | ||
}, | ||
transform(source) { | ||
transform(source, filePath) { | ||
if (config.blits && config.blits.precompile === false) return source | ||
@@ -31,3 +32,8 @@ if (source.indexOf('Blits.Component(') > -1 || source.indexOf('Blits.Application(') > -1) { | ||
// Parse the template | ||
const parsed = parser(templateContentResult[1]) | ||
let resourceName = 'Blits.Application' | ||
if (source.indexOf('Blits.Component(') > -1) { | ||
resourceName = source.match(/Blits\.Component\(['"](.*)['"]\s*,/)[1] | ||
} | ||
const componentPath = path.relative(process.cwd(), filePath) | ||
const parsed = parser(templateContentResult[1], resourceName, null, componentPath) | ||
@@ -34,0 +40,0 @@ // Generate the code |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
932843
22652