Skip to content

Commit

Permalink
Merge pull request #121 from lightning-js/dev
Browse files Browse the repository at this point in the history
Release 1.0.0
  • Loading branch information
michielvandergeest authored Jul 15, 2024
2 parents 51709dc + 10ad21d commit 8e144d2
Show file tree
Hide file tree
Showing 30 changed files with 2,037 additions and 1,953 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## v1.0.0

_15 july 2024_

- Added multiple optimizations that improve the performance
- Fixed mountY prop setting
- Added functionality for providing custom shaders
- Added initial support for accessing children
- Added first version of Layout component
- Improved test coverage
- Refactored transitions and fixed promise resolve issues
- Upgraded to renderer v1.0.0

## v0.10.0

_17 june 2024_
Expand Down
4 changes: 0 additions & 4 deletions boilerplate/js/default/src/renderer.js

This file was deleted.

2 changes: 1 addition & 1 deletion docs/essentials/displaying_text.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ You can use the Text-tag anywhere in your template, without the need to explicit
The Text-tag accepts the following attributes:

- `content` - the text to be displayed. Can be a hardcoded text, a dynamic value, or a reactive value
- `font` - the font family, defaults to `lato`, or the default font specified in the launch settings
- `font` - the font family, defaults to `sans-serif`, or the default font specified in the launch settings
- `size` - the font size, defaults to `32`
- `color` - the color to display for the text, defaults to `white` and can be any of the supported Blits color formats (HTML, hexadecimal or rgb(a))
- `letterspacing` - letterspacing in pixels, defaults to `0`
Expand Down
1,004 changes: 484 additions & 520 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lightningjs/blits",
"version": "0.10.0",
"version": "1.0.0",
"description": "Blits: The Lightning 3 App Development Framework",
"bin": "bin/index.js",
"exports": {
Expand Down Expand Up @@ -44,8 +44,8 @@
"tape": "^5.5.0"
},
"dependencies": {
"@lightningjs/msdf-generator": "^1.0.1",
"@lightningjs/renderer": "0.9.1",
"@lightningjs/msdf-generator": "^1.0.2",
"@lightningjs/renderer": "^1.0.0",
"@lightningjs/vite-plugin-import-chunk-url": "^0.3.0",
"execa": "^8.0.1",
"kolorist": "^1.8.0",
Expand Down
3 changes: 2 additions & 1 deletion src/component.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ declare namespace Component {
}
type PropsArray<T extends string> = T[]
type AdvancedProps = AdvancedProp[]
type MixedProps<T extends string> = (T | AdvancedProp)[];

// type ExtractPropNames<T extends Prop[]> = T extends (infer U)[]
// ? U extends string
Expand Down Expand Up @@ -394,7 +395,7 @@ declare namespace Component {
* }]
* ```
*/
props?: PropsArray<Props> | AdvancedProp[],
props?: PropsArray<Props> | AdvancedProps | MixedProps<Props>
/**
* Computed properties
*/
Expand Down
33 changes: 20 additions & 13 deletions src/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import parser from './lib/templateparser/parser.js'
import codegenerator from './lib/codegenerator/generator.js'
import { createHumanReadableId, createInternalId } from './lib/componentId.js'
import { emit } from './lib/hooks.js'
import { reactive } from './lib/reactivity/reactive.js'
import { reactive, getRaw } from './lib/reactivity/reactive.js'
import { effect } from './lib/reactivity/effect.js'
import Lifecycle from './lib/lifecycle.js'
import symbols from './lib/symbols.js'
Expand Down Expand Up @@ -51,7 +51,7 @@ const required = (name) => {
const Component = (name = required('name'), config = required('config')) => {
let base

const component = function (opts, parentEl, parentComponent) {
const component = function (opts, parentEl, parentComponent, rootComponent) {
// generate a human readable ID for the component instance (i.e. Blits::ComponentName1)
this.componentId = createHumanReadableId(name)

Expand All @@ -65,6 +65,9 @@ const Component = (name = required('name'), config = required('config')) => {
// set a reference to the parent component
this.parent = parentComponent

//
this.rootParent = rootComponent

// set a reference to the holder / parentElement
// Components are wrapped in a holder node (used to apply positioning and transforms
// such as rotation and scale to components)
Expand Down Expand Up @@ -99,12 +102,9 @@ const Component = (name = required('name'), config = required('config')) => {

// execute the render code that constructs the initial state of the component
// and store the children result (a flat map of elements and components)
this[symbols.children] = config.code.render.apply(stage, [
parentEl,
this,
config,
globalComponents,
])
this[symbols.children] =
config.code.render.apply(stage, [parentEl, this, config, globalComponents, effect, getRaw]) ||
[]

// create a reference to the wrapper element of the component (i.e. the root Element of the component)
this[symbols.wrapper] = this[symbols.children][0]
Expand Down Expand Up @@ -158,11 +158,18 @@ const Component = (name = required('name'), config = required('config')) => {

// setup (and execute) all the generated side effects based on the
// reactive bindings define in the template
config.code.effects.forEach((eff) => {
for (let i = 0; i < config.code.effects.length; i++) {
effect(() => {
eff.apply(stage, [this, this[symbols.children], config, globalComponents])
config.code.effects[i].apply(stage, [
this,
this[symbols.children],
config,
globalComponents,
rootComponent,
effect,
])
})
})
}

// setup watchers if the components has watchers specified
if (this[symbols.watchers]) {
Expand Down Expand Up @@ -194,7 +201,7 @@ const Component = (name = required('name'), config = required('config')) => {
return this
}

const factory = (options = {}, parentEl, parentComponent) => {
const factory = (options = {}, parentEl, parentComponent, rootComponent) => {
// setup the component once, using Base as the prototype
if (!base) {
Log.debug(`Setting up ${name} component`)
Expand All @@ -213,7 +220,7 @@ const Component = (name = required('name'), config = required('config')) => {
}

// create an instance of the component, using base as the prototype (which contains Base)
return component.call(Object.create(base), options, parentEl, parentComponent)
return component.call(Object.create(base), options, parentEl, parentComponent, rootComponent)
}

// store the config on the factory, in order to access the config
Expand Down
2 changes: 1 addition & 1 deletion src/component/base/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import eventListeners from '../../lib/eventListeners'
import eventListeners from '../../lib/eventListeners.js'

export default {
$emit: {
Expand Down
35 changes: 34 additions & 1 deletion src/component/base/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import symbols from '../../lib/symbols'
import symbols from '../../lib/symbols.js'

import { renderer } from '../../launch.js'

Expand All @@ -26,4 +26,37 @@ export default {
enumerable: true,
configurable: false,
},
[symbols.getChildren]: {
value() {
const parent = this.rootParent || this.parent
return (this[symbols.children] || []).concat(
(parent &&
parent[symbols.getChildren]()
.map((child) => {
if (Object.getPrototypeOf(child) === Object.prototype) {
return Object.values(child).map((c) => {
// ugly hack .. but the point is to reference the right component
c.forComponent = c.config && c.config.parent.component
return c
})
}
return child
})
.flat()
.filter((child) => {
// problem is that component of a forloop in a slot has component of root component
if (child && child.component) {
return (
(child.component && child.component.componentId === this.componentId) ||
(child.forComponent && child.forComponent.componentId === this.componentId)
)
}
})) ||
[]
)
},
writable: false,
enumerable: true,
configurable: false,
},
}
4 changes: 3 additions & 1 deletion src/component/setup/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ export default (component, props = []) => {
const value = prop.cast(
this[symbols.props] && prop.key in this[symbols.props]
? this[symbols.props][prop.key]
: prop.default || undefined
: 'default' in prop
? prop.default
: undefined
)

if (prop.required && value === undefined) {
Expand Down
70 changes: 51 additions & 19 deletions src/component/setup/props.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@

import test from 'tape'
import propsFn from './props.js'
import { initLog } from '../log.js'

import symbols from '../symbols.js'

initLog()
import { initLog } from '../../lib/log.js'
import Settings from '../../settings.js'
import symbols from '../../lib/symbols.js'

test('Type props function', (assert) => {
const expected = 'function'
Expand Down Expand Up @@ -49,7 +47,7 @@ test('Pass props as an array', (assert) => {
assert.equal(props.length, props.map(prop => component[symbols.propKeys].indexOf(prop) > -1).filter(prop => prop === true).length, 'All passed props should be stored on propKeys')

props.forEach((prop) => {
assert.true(typeof Object.getOwnPropertyDescriptor(component.prototype, prop).get === 'function', `A getter should have been created for property ${prop}`)
assert.true(typeof Object.getOwnPropertyDescriptor(component, prop).get === 'function', `A getter should have been created for property ${prop}`)
})

assert.end()
Expand All @@ -59,14 +57,14 @@ test('Pass props as an array', (assert) => {
test('Get value of props', (assert) => {
const component = new Function()

const componentInstance = new component
const componentInstance = Object.create(component)
componentInstance[symbols.props] = {
index: 1,
img: 'lorem-ipsum.jpg',
url: 'http://localhost'
}

const componentInstance2 = new component
const componentInstance2 = Object.create(component)
componentInstance2[symbols.props] = {
index: 2,
img: 'bla.jpg',
Expand Down Expand Up @@ -95,7 +93,7 @@ test('Passing props as an object', (assert) => {
assert.equal(props.length, props.map(prop => component[symbols.propKeys].indexOf(typeof prop === 'object' ? prop.key : prop) > -1).filter(prop => prop === true).length, 'All passed props should be stored on propKeys')

props.forEach((prop) => {
assert.true(typeof Object.getOwnPropertyDescriptor(component.prototype, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
assert.true(typeof Object.getOwnPropertyDescriptor(component, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
})

assert.end()
Expand All @@ -111,7 +109,7 @@ test('Passing props as an object mixed with single keys', (assert) => {
assert.equal(props.length, props.map(prop => component[symbols.propKeys].indexOf(typeof prop === 'object' ? prop.key : prop) > -1).filter(prop => prop === true).length, 'All passed props should be stored on propKeys')

props.forEach((prop) => {
assert.true(typeof Object.getOwnPropertyDescriptor(component.prototype, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
assert.true(typeof Object.getOwnPropertyDescriptor(component, typeof prop === 'object' ? prop.key : prop).get === 'function', `A getter should have been created for property ${prop}`)
})

assert.end()
Expand All @@ -120,7 +118,7 @@ test('Passing props as an object mixed with single keys', (assert) => {
test('Casting props to a type', (assert) => {
const component = new Function()

const componentInstance = new component
const componentInstance = Object.create(component)
componentInstance[symbols.props] = {
number: '1',
string: 100,
Expand All @@ -145,9 +143,9 @@ test('Casting props to a type', (assert) => {
}]
propsFn(component, props)

assert.true(typeof componentInstance.number === 'number', 'Should cast prop value to a Number')
assert.true(typeof componentInstance.string === 'string', 'Should cast prop value to a String')
assert.true(typeof componentInstance.boolean === 'boolean', 'Should cast prop value to a Boolean')
assert.equal(typeof componentInstance.number, 'number', 'Should cast prop value to a Number')
assert.equal(typeof componentInstance.string, 'string', 'Should cast prop value to a String')
assert.equal(typeof componentInstance.boolean, 'boolean', 'Should cast prop value to a Boolean')
assert.equal(componentInstance.image, 'http://localhost/my_image.jpg','Should cast according to a custom function')

assert.end()
Expand All @@ -156,7 +154,7 @@ test('Casting props to a type', (assert) => {
test('Setting default value for undefined props', (assert) => {
const component = new Function()

const componentInstance = new component
const componentInstance = Object.create(component)

const props = [{
key: 'missing',
Expand All @@ -172,7 +170,7 @@ test('Setting default value for undefined props', (assert) => {
test('Required props with default', (assert) => {
const component = new Function()

const componentInstance = new component
const componentInstance = Object.create(component)

const props = [{
key: 'missing',
Expand All @@ -187,9 +185,11 @@ test('Required props with default', (assert) => {
})

test('Required props without default', (assert) => {
initLogTest(assert)
const capture = assert.capture(console, 'warn')
const component = new Function()

const componentInstance = new component
const componentInstance = Object.create(component)

const props = [{
key: 'missing',
Expand All @@ -198,9 +198,41 @@ test('Required props without default', (assert) => {
propsFn(component, props)

assert.equal(componentInstance.missing, undefined, 'Should return undefined prop value when undefined')
// todo: should log a warning about prop being required
const logs = capture()
assert.equal(logs.length, 1)
assert.equal(logs[0].args.pop(), 'missing is required', 'Should log warning message')

assert.end()
})

test('Setting prop value directly', (assert) => {
initLogTest(assert)
const capture = assert.capture(console, 'warn')
const component = new Function()
const componentInstance = Object.create(component)
componentInstance[symbols.props] = {
property: 'foo'
}
const props = [{
key: 'property',
}]

propsFn(component, props)
componentInstance.property = 'bar'

assert.equal(componentInstance[symbols.props].property, 'bar', 'Should be possible to mutate the property')
let logs = capture()
assert.equal(logs.length, 1)
assert.equal(logs[0].args.pop(), 'Warning! Avoid mutating props directly (property)', 'Should log warning message')

assert.end()
})

// todo add test when setting a prop (should work, but should log a warning about avoiding mutating a prop)
function initLogTest(assert) {
assert.capture(Settings, 'get', (key) => {
if (key === 'debugLevel') {
return 1
}
})
initLog()
}
Loading

0 comments on commit 8e144d2

Please sign in to comment.