diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30a1add9..fb834f19 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## v1.7.0
+
+_14 oct 2024_
+
+- Added cleanup upon component destroy of effects generated by global reactivity
+
## v1.6.0 / v1.6.1
_9 oct 2024_
diff --git a/bin/index.js b/bin/index.js
old mode 100644
new mode 100755
diff --git a/package-lock.json b/package-lock.json
index 4b23fbff..6c89d9fd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@lightningjs/blits",
- "version": "1.6.1",
+ "version": "1.7.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@lightningjs/blits",
- "version": "1.6.1",
+ "version": "1.7.0",
"license": "Apache-2.0",
"dependencies": {
"@lightningjs/msdf-generator": "^1.0.2",
diff --git a/package.json b/package.json
index 42233e59..251a4f2b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@lightningjs/blits",
- "version": "1.6.1",
+ "version": "1.7.0",
"description": "Blits: The Lightning 3 App Development Framework",
"bin": "bin/index.js",
"exports": {
diff --git a/packages/create-blits/CHANGELOG.md b/packages/create-blits/CHANGELOG.md
index 96c3f822..420e1727 100644
--- a/packages/create-blits/CHANGELOG.md
+++ b/packages/create-blits/CHANGELOG.md
@@ -1,5 +1,13 @@
# Changelog
+## v1.0.1
+
+_11 Oct 2024_
+
+- Updated boilerplate with more advanced screen
+- Added readme to boilerplate
+
+
## v1.0.0
_08 Oct 2024_
diff --git a/packages/create-blits/bin/index.js b/packages/create-blits/bin/index.js
old mode 100644
new mode 100755
diff --git a/packages/create-blits/boilerplate/common/README.md b/packages/create-blits/boilerplate/common/README.md
new file mode 100644
index 00000000..fe53485f
--- /dev/null
+++ b/packages/create-blits/boilerplate/common/README.md
@@ -0,0 +1,49 @@
+# {$appName}
+
+### {$appPackage}
+
+
+Welcome to the _{$appName}_ Lightning 3 Blits App!
+
+### Getting started
+
+Follow the steps below to get your Lightning 3 Blits App up and running in no time.
+
+#### IDE setup
+
+It is highly recommended to install the Blits [VS-code extension](https://marketplace.visualstudio.com/items?itemName=LightningJS.lightning-blits) which will give you template highlighting and improved autocompletion.
+
+#### Project setup
+
+Run the following command to install the dependencies of your App:
+
+```sh
+npm install
+```
+
+#### Build and run in development mode
+
+Run your App in development mode:
+
+```sh
+npm run dev
+```
+
+This command uses Vite to fire up a local server, with Hot Reloading support. Visit the provided link in your web browser to see the App in action.
+
+#### Build the App for production
+
+Create an optimized and minified version of your App:
+
+```sh
+npm run build
+```
+
+This will create a production version of the app in the `dist` folder.
+
+
+### Resources
+
+- [Blits documentation](https://lightningjs.io/v3-docs/blits/getting_started/intro.html) - official documentation
+- [Blits Example App](https://blits-demo.lightningjs.io/?source=true) - a great reference to learn by example
+- [Blits Components](https://lightningjs.io/blits-components.html) - off-the-shelf, basic and performant reference components
diff --git a/packages/create-blits/boilerplate/js/default/src/App.js b/packages/create-blits/boilerplate/js/default/src/App.js
index 6c0be8ca..e8ee97e0 100644
--- a/packages/create-blits/boilerplate/js/default/src/App.js
+++ b/packages/create-blits/boilerplate/js/default/src/App.js
@@ -7,6 +7,6 @@ export default Blits.Application({
- `,
+ `,
routes: [{ path: '/', component: Home }],
})
diff --git a/packages/create-blits/boilerplate/js/default/src/pages/Home.js b/packages/create-blits/boilerplate/js/default/src/pages/Home.js
index 0d454b7d..e2bcd52e 100644
--- a/packages/create-blits/boilerplate/js/default/src/pages/Home.js
+++ b/packages/create-blits/boilerplate/js/default/src/pages/Home.js
@@ -8,8 +8,66 @@ export default Blits.Component('Home', {
},
template: `
-
-
+
+
+
+
+ Hello!
+
+
+
`,
+ state() {
+ return {
+ y: 0,
+ x: -1000,
+ rotation: 0,
+ scale: 1,
+ loaderAlpha: 0,
+ textAlpha: 0.00001,
+ }
+ },
+ hooks: {
+ ready() {
+ this.loaderAlpha = 1
+ this.x = 1920 / 2
+
+ this.$setTimeout(() => {
+ this.rotation = 720
+ this.scale = 1.5
+ }, 3000)
+
+ this.$setTimeout(() => {
+ this.scale = 1
+ }, 3000 + 300)
+
+ this.$setTimeout(() => {
+ this.y = -60
+ this.loaderAlpha = 0
+ this.scale = 1
+ this.textAlpha = 1
+ }, 3800)
+ },
+ },
})
diff --git a/packages/create-blits/package-lock.json b/packages/create-blits/package-lock.json
index 58199261..4a22b780 100644
--- a/packages/create-blits/package-lock.json
+++ b/packages/create-blits/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@lightningjs/create-blits",
- "version": "1.0.0",
+ "version": "1.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@lightningjs/create-blits",
- "version": "1.0.0",
+ "version": "1.1.0",
"license": "Apache-2.0",
"dependencies": {
"execa": "^8.0.1",
diff --git a/packages/create-blits/package.json b/packages/create-blits/package.json
index 296d2c13..0a933d2e 100644
--- a/packages/create-blits/package.json
+++ b/packages/create-blits/package.json
@@ -1,6 +1,6 @@
{
"name": "@lightningjs/create-blits",
- "version": "1.0.0",
+ "version": "1.1.0",
"description": "Create a new Lightning 3 Blits App",
"bin": "bin/index.js",
"scripts": {
diff --git a/packages/create-blits/src/helpers/create.js b/packages/create-blits/src/helpers/create.js
index c4bf4265..11810ab8 100644
--- a/packages/create-blits/src/helpers/create.js
+++ b/packages/create-blits/src/helpers/create.js
@@ -24,6 +24,17 @@ export const copyLightningFixtures = (config) => {
recursive: true,
})
+ // Copy readme
+ fs.copyFileSync(
+ path.join(config.fixturesBase, 'common/README.md'),
+ path.join(targetDir, 'README.md')
+ )
+
+ // Copy IDE stuff from fixture base
+ fs.cpSync(path.join(config.fixturesBase, 'common/ide'), path.join(targetDir), {
+ recursive: true,
+ })
+
resolve(targetDir)
})
}
@@ -55,11 +66,6 @@ export const addESlint = (config) => {
path.join(config.targetDir, 'eslint.config.cjs')
)
- // Copy IDE stuff from fixture base
- fs.cpSync(path.join(config.fixturesBase, 'common/ide'), path.join(config.targetDir), {
- recursive: true,
- })
-
// Copy and merge fixture specific package.json
const origPackageJson = JSON.parse(fs.readFileSync(path.join(config.targetDir, 'package.json')))
const eslintPackageJson = JSON.parse(
@@ -77,8 +83,8 @@ export const addESlint = (config) => {
},
scripts: {
...(origPackageJson.scripts || {}),
- ...(eslintPackageJson.scripts || {})
- }
+ ...(eslintPackageJson.scripts || {}),
+ },
},
null,
2
diff --git a/src/component.js b/src/component.js
index bfe16595..d99e4dbd 100644
--- a/src/component.js
+++ b/src/component.js
@@ -161,9 +161,12 @@ const Component = (name = required('name'), config = required('config')) => {
// reactive bindings define in the template
const effects = config.code.effects
for (let i = 0; i < effects.length; i++) {
- effect(() => {
+ const eff = () => {
effects[i](this, this[symbols.children], config, globalComponents, rootComponent, effect)
- })
+ }
+ // store reference to the effect
+ this[symbols.effects].push(eff)
+ effect(eff)
}
// setup watchers if the components has watchers specified
diff --git a/src/component/base/methods.js b/src/component/base/methods.js
index bccb4029..35061bd8 100644
--- a/src/component/base/methods.js
+++ b/src/component/base/methods.js
@@ -20,6 +20,7 @@ import Focus from '../../focus.js'
import eventListeners from '../../lib/eventListeners.js'
import { trigger } from '../../lib/reactivity/effect.js'
import { Log } from '../../lib/log.js'
+import { removeGlobalEffects } from '../../lib/reactivity/effect.js'
export default {
focus: {
@@ -56,6 +57,7 @@ export default {
this.$clearIntervals()
eventListeners.removeListeners(this)
deleteChildren(this[symbols.children])
+ removeGlobalEffects(this[symbols.effects])
Log.debug(`Destroyed component ${this.componentId}`)
},
writable: false,
diff --git a/src/component/setup/index.js b/src/component/setup/index.js
index 1007a00b..113838fc 100644
--- a/src/component/setup/index.js
+++ b/src/component/setup/index.js
@@ -32,6 +32,8 @@ let counter = 0
export default function (component, config) {
component[symbols.identifier] = ++counter
+ component[symbols.effects] = []
+
// setup hooks
registerHooks(config.hooks, component[symbols.identifier])
diff --git a/src/lib/codegenerator/generator.js b/src/lib/codegenerator/generator.js
index cdb6ad8e..193f0266 100644
--- a/src/lib/codegenerator/generator.js
+++ b/src/lib/codegenerator/generator.js
@@ -392,13 +392,15 @@ const generateForLoopCode = function (templateObject, parent) {
}
// inner scope variables are part of the main forloop
- innerScopeEffects.forEach((effect) => {
+ innerScopeEffects.forEach((effect, index) => {
const key = effect.indexOf(`scope.${index}`) > -1 ? `'${interpolate(result[2], '')}'` : null
if (effect.indexOf("Symbol.for('props')") === -1) {
ctx.renderCode.push(`
- effect(() => {
+ let eff${index} = () => {
${effect}
- }, ${key})
+ }
+ effect(eff${index}, ${key})
+ component[Symbol.for('effects')].push(eff${index})
`)
} else {
// props shouldn't be wrapped in an effect, but simply passed on
diff --git a/src/lib/reactivity/effect.js b/src/lib/reactivity/effect.js
index 77115415..4f099546 100644
--- a/src/lib/reactivity/effect.js
+++ b/src/lib/reactivity/effect.js
@@ -29,8 +29,22 @@ export const resumeTracking = () => {
}
const objectMap = new WeakMap()
+const globalEffectsMap = new Map()
-export const track = (target, key) => {
+export const removeGlobalEffects = (effectsToRemove) => {
+ if (globalEffectsMap.size === 0) return
+ for (const [effect, target] of globalEffectsMap) {
+ if (effectsToRemove.indexOf(effect) === -1) continue
+ const effectsSet = objectMap.get(target)
+ if (effectsSet === undefined) continue
+ for (const set of effectsSet.values()) {
+ set.delete(effect)
+ globalEffectsMap.delete(effect)
+ }
+ }
+}
+
+export const track = (target, key, global = false) => {
if (currentEffect) {
if (paused) {
return
@@ -49,6 +63,8 @@ export const track = (target, key) => {
effectsMap.set(key, effects)
}
effects.add(currentEffect)
+
+ if (global === true) globalEffectsMap.set(currentEffect, target)
}
}
diff --git a/src/lib/reactivity/reactive.js b/src/lib/reactivity/reactive.js
index 2b04c75e..ec5d9f16 100644
--- a/src/lib/reactivity/reactive.js
+++ b/src/lib/reactivity/reactive.js
@@ -28,7 +28,7 @@ export const getRaw = (value) => {
return raw ? getRaw(raw) : value
}
-const reactiveProxy = (original, _parent = null, _key) => {
+const reactiveProxy = (original, _parent = null, _key, global) => {
// don't create a proxy when a Blits component or an Image Texture
// is assigned to a state variable
if (typeof original === 'object') {
@@ -53,7 +53,7 @@ const reactiveProxy = (original, _parent = null, _key) => {
if (Array.isArray(target)) {
if (typeof target[key] === 'object' && target[key] !== null) {
if (Array.isArray(target[key])) {
- track(target, key)
+ track(target, key, global)
}
// create a new reactive proxy
return reactiveProxy(getRaw(target[key]), target, key)
@@ -81,7 +81,7 @@ const reactiveProxy = (original, _parent = null, _key) => {
// handling objects (but not null values, which have object type in JS)
if (typeof target[key] === 'object' && target[key] !== null) {
if (Array.isArray(target[key])) {
- track(target, key)
+ track(target, key, global)
}
// create a new reactive proxy
return reactiveProxy(getRaw(target[key]), target, key)
@@ -89,7 +89,7 @@ const reactiveProxy = (original, _parent = null, _key) => {
// handling all other types
// track the key on the target
- track(target, key)
+ track(target, key, global)
// return the reflected value
return Reflect.get(target, key, receiver)
},
@@ -119,7 +119,7 @@ const reactiveProxy = (original, _parent = null, _key) => {
return proxy
}
-const reactiveDefineProperty = (target) => {
+const reactiveDefineProperty = (target, global) => {
Object.keys(target).forEach((key) => {
let internalValue = target[key]
@@ -140,7 +140,7 @@ const reactiveDefineProperty = (target) => {
enumerable: true,
configurable: true,
get() {
- track(target, key)
+ track(target, key, global)
return internalValue
},
set(newValue) {
@@ -157,8 +157,10 @@ const reactiveDefineProperty = (target) => {
return target
}
-export const reactive = (target, mode = 'Proxy') => {
- return mode === 'defineProperty' ? reactiveDefineProperty(target) : reactiveProxy(target)
+export const reactive = (target, mode = 'Proxy', global = false) => {
+ return mode === 'defineProperty'
+ ? reactiveDefineProperty(target, global)
+ : reactiveProxy(target, undefined, undefined, global)
}
export const memo = (raw) => {
diff --git a/src/lib/symbols.js b/src/lib/symbols.js
index 0b33faf0..1efe8ac8 100644
--- a/src/lib/symbols.js
+++ b/src/lib/symbols.js
@@ -46,4 +46,6 @@ export default {
componentType: Symbol.for('componentType'),
// Symbol 'isComponent' utilized within generated code
isComponent: Symbol.for('isComponent'),
+ // Symbol 'effects' utilized within generated code
+ effects: Symbol.for('effects'),
}
diff --git a/src/plugins/appstate.js b/src/plugins/appstate.js
index a4904b51..a271685c 100644
--- a/src/plugins/appstate.js
+++ b/src/plugins/appstate.js
@@ -21,6 +21,6 @@ import Settings from '../settings.js'
export default {
name: 'appState',
plugin(state = {}) {
- return reactive(state, Settings.get('reactivityMode'))
+ return reactive(state, Settings.get('reactivityMode'), true)
},
}
diff --git a/src/plugins/language.js b/src/plugins/language.js
index ea6c45e6..63e50a85 100644
--- a/src/plugins/language.js
+++ b/src/plugins/language.js
@@ -32,7 +32,8 @@ export default {
language: '',
loaded: 0,
},
- Settings.get('reactivityMode')
+ Settings.get('reactivityMode'),
+ true
)
const setLanguage = (language) => {
diff --git a/src/plugins/theme.js b/src/plugins/theme.js
index 4045446f..81c61b09 100644
--- a/src/plugins/theme.js
+++ b/src/plugins/theme.js
@@ -52,7 +52,8 @@ export default {
{
current: 'default',
},
- Settings.get('reactivityMode')
+ Settings.get('reactivityMode'),
+ true
)
let themes = {}