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 = {}