@lightningjs/blits 2.10.0 → 2.12.0
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 +17 -0
- package/index.d.ts +3 -2
- package/package.json +8 -8
- package/src/component/base/methods.js +48 -11
- package/src/components/Circle.js +1 -0
- package/src/components/FPScounter.js +1 -0
- package/src/components/RouterView.js +1 -0
- package/src/engines/L3/element.js +3 -0
- package/src/engines/L3/fontLoader.js +12 -8
- package/src/engines/L3/fontTypes.js +62 -0
- package/src/engines/L3/launch.js +17 -3
- package/src/lib/codegenerator/generator.js +6 -0
- package/src/lib/symbols.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.12.0
|
|
4
|
+
|
|
5
|
+
_8 oct 2026_
|
|
6
|
+
|
|
7
|
+
- Added support for rich text using _bb-code_-style formatting
|
|
8
|
+
- Capped default number of webworkers at 2 for faster App startup time
|
|
9
|
+
- Added selective instantiation of _only_ the relevant text renders based on the registered fonts
|
|
10
|
+
- Bumped renderer to 3.6.0 with latest rich text enhancements
|
|
11
|
+
|
|
12
|
+
## 2.11.0
|
|
13
|
+
|
|
14
|
+
_28 sep 2026_
|
|
15
|
+
|
|
16
|
+
- Improved `$select()` method by adding caching
|
|
17
|
+
- Upgraded several dependencies
|
|
18
|
+
- Bumped renderer to 3.5.1
|
|
19
|
+
|
|
3
20
|
## 2.10.0
|
|
4
21
|
|
|
5
22
|
_21 sep 2026_
|
package/index.d.ts
CHANGED
|
@@ -1224,8 +1224,9 @@ declare module '@lightningjs/blits' {
|
|
|
1224
1224
|
* Maximum number of web workers to spin up simultaneously for offloading functionality such
|
|
1225
1225
|
* as image loading to separate threads (when supported by the browser)
|
|
1226
1226
|
*
|
|
1227
|
-
* If not specified defaults to the number of logical processers available as reported by
|
|
1228
|
-
* `navigator.hardwareConcurrency
|
|
1227
|
+
* If not specified defaults to the number of logical processers available on the device, as reported by
|
|
1228
|
+
* `navigator.hardwareConcurrency`, with a maximum of 2. If `navigator.hardwareConcurrency` is
|
|
1229
|
+
* not supported the default value will be 2
|
|
1229
1230
|
*/
|
|
1230
1231
|
webWorkersLimit?: number
|
|
1231
1232
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -78,25 +78,25 @@
|
|
|
78
78
|
"@babel/plugin-syntax-import-assertions": "^7.28.6",
|
|
79
79
|
"@eslint/eslintrc": "^3.3.4",
|
|
80
80
|
"@eslint/js": "^9.39.3",
|
|
81
|
-
"c8": "^
|
|
81
|
+
"c8": "^12.0.0",
|
|
82
82
|
"eslint": "^9.39.3",
|
|
83
83
|
"eslint-config-prettier": "^10.1.8",
|
|
84
84
|
"eslint-plugin-prettier": "^5.5.4",
|
|
85
85
|
"fast-glob": "^3.3.3",
|
|
86
|
-
"global-jsdom": "^
|
|
87
|
-
"globals": "^
|
|
86
|
+
"global-jsdom": "^30.0.0",
|
|
87
|
+
"globals": "^17.12.0",
|
|
88
88
|
"husky": "^9.1.7",
|
|
89
|
-
"jsdom": "^
|
|
89
|
+
"jsdom": "^30.1.0",
|
|
90
90
|
"lint-staged": "^15.5.2",
|
|
91
91
|
"prettier": "^3.6.2",
|
|
92
|
-
"sinon": "^
|
|
92
|
+
"sinon": "^22.1.0",
|
|
93
93
|
"tap-diff": "^0.1.1",
|
|
94
94
|
"tap-filter": "^1.0.3",
|
|
95
95
|
"tape": "^5.9.0"
|
|
96
96
|
},
|
|
97
97
|
"dependencies": {
|
|
98
|
-
"@lightningjs/renderer": "3.
|
|
99
|
-
"magic-string": "^
|
|
98
|
+
"@lightningjs/renderer": "^3.6.0",
|
|
99
|
+
"magic-string": "^1.4.1"
|
|
100
100
|
},
|
|
101
101
|
"repository": {
|
|
102
102
|
"type": "git",
|
|
@@ -30,6 +30,8 @@ import { renderer } from '../../launch.js'
|
|
|
30
30
|
import { keyMap } from '../../application.js'
|
|
31
31
|
import { platform } from '../../platform.js'
|
|
32
32
|
|
|
33
|
+
const selectCache = new WeakMap()
|
|
34
|
+
|
|
33
35
|
export default {
|
|
34
36
|
$focus: {
|
|
35
37
|
/**
|
|
@@ -103,6 +105,7 @@ export default {
|
|
|
103
105
|
*/
|
|
104
106
|
value: function () {
|
|
105
107
|
this.eol = true
|
|
108
|
+
selectCache.delete(this)
|
|
106
109
|
removeKeyUpCallbacks(this)
|
|
107
110
|
this[symbols.lifecycle].state = 'destroy'
|
|
108
111
|
|
|
@@ -197,26 +200,60 @@ export default {
|
|
|
197
200
|
// early exit when component is marked as end of life
|
|
198
201
|
if (this.eol === true) return
|
|
199
202
|
|
|
200
|
-
let
|
|
201
|
-
|
|
203
|
+
let cache = selectCache.get(this)
|
|
204
|
+
if (cache !== undefined && cache.has(ref) === true) {
|
|
205
|
+
const selected = cache.get(ref)
|
|
206
|
+
if (selected !== undefined && selected.eol !== true) return selected
|
|
207
|
+
cache.delete(ref)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const child of this[symbols.children]) {
|
|
211
|
+
if (child === undefined || child === null) continue
|
|
202
212
|
if (Array.isArray(child)) {
|
|
203
|
-
child.
|
|
204
|
-
|
|
205
|
-
|
|
213
|
+
const selected = child.find((c) => c['ref'] === ref)
|
|
214
|
+
if (selected !== undefined) {
|
|
215
|
+
if (cache === undefined) {
|
|
216
|
+
cache = new Map()
|
|
217
|
+
selectCache.set(this, cache)
|
|
218
|
+
}
|
|
219
|
+
cache.set(ref, selected)
|
|
220
|
+
return selected
|
|
221
|
+
}
|
|
206
222
|
} else if (Object.getPrototypeOf(child) === Object.prototype) {
|
|
207
|
-
Object.
|
|
208
|
-
|
|
209
|
-
|
|
223
|
+
const selected = Object.values(child).find((c) => c['ref'] === ref)
|
|
224
|
+
if (selected !== undefined) {
|
|
225
|
+
if (cache === undefined) {
|
|
226
|
+
cache = new Map()
|
|
227
|
+
selectCache.set(this, cache)
|
|
228
|
+
}
|
|
229
|
+
cache.set(ref, selected)
|
|
230
|
+
return selected
|
|
231
|
+
}
|
|
210
232
|
} else {
|
|
211
|
-
if (child['ref'] === ref)
|
|
233
|
+
if (child['ref'] === ref) {
|
|
234
|
+
if (cache === undefined) {
|
|
235
|
+
cache = new Map()
|
|
236
|
+
selectCache.set(this, cache)
|
|
237
|
+
}
|
|
238
|
+
cache.set(ref, child)
|
|
239
|
+
return child
|
|
240
|
+
}
|
|
212
241
|
}
|
|
213
|
-
}
|
|
214
|
-
return
|
|
242
|
+
}
|
|
243
|
+
return null
|
|
215
244
|
},
|
|
216
245
|
writable: false,
|
|
217
246
|
enumerable: true,
|
|
218
247
|
configurable: false,
|
|
219
248
|
},
|
|
249
|
+
[symbols.invalidateSelectCache]: {
|
|
250
|
+
value: function () {
|
|
251
|
+
selectCache.delete(this)
|
|
252
|
+
},
|
|
253
|
+
writable: false,
|
|
254
|
+
enumerable: false,
|
|
255
|
+
configurable: false,
|
|
256
|
+
},
|
|
220
257
|
$trigger: {
|
|
221
258
|
/**
|
|
222
259
|
* @this {import('../../component').BlitsComponent}
|
package/src/components/Circle.js
CHANGED
|
@@ -240,6 +240,9 @@ const propsTransformer = {
|
|
|
240
240
|
set rotation(v) {
|
|
241
241
|
this.props['rotation'] = v * (Math.PI / 180)
|
|
242
242
|
},
|
|
243
|
+
set rich(v) {
|
|
244
|
+
this.props['richText'] = v
|
|
245
|
+
},
|
|
243
246
|
set w(v) {
|
|
244
247
|
const parsed = parsePercentage.call(this, v, 'w')
|
|
245
248
|
this.props['w'] = parsed
|
|
@@ -17,23 +17,27 @@
|
|
|
17
17
|
|
|
18
18
|
import Settings from '../../settings.js'
|
|
19
19
|
import { renderer } from './launch.js'
|
|
20
|
-
|
|
21
|
-
const fontTypeMapping = {
|
|
22
|
-
sdf: 'sdf',
|
|
23
|
-
msdf: 'sdf',
|
|
24
|
-
canvas: 'canvas',
|
|
25
|
-
web: 'canvas',
|
|
26
|
-
}
|
|
20
|
+
import { resolveFontEngineType } from './fontTypes.js'
|
|
27
21
|
|
|
28
22
|
export default () => {
|
|
29
23
|
const stage = renderer.stage
|
|
24
|
+
const renderMode = Settings.get('renderMode', 'webgl')
|
|
30
25
|
|
|
31
26
|
const fonts = Settings.get('fonts', [])
|
|
32
27
|
for (let i = 0; i < fonts.length; i++) {
|
|
33
28
|
const font = fonts[i]
|
|
34
|
-
const type =
|
|
29
|
+
const type = resolveFontEngineType(font.type)
|
|
35
30
|
|
|
36
31
|
if (type === 'sdf') {
|
|
32
|
+
// The SDF text renderer is not compatible with the Canvas core renderer
|
|
33
|
+
// (no `sdf` font handler is registered), so skip these fonts instead of
|
|
34
|
+
// throwing in `stage.loadFont`.
|
|
35
|
+
if (renderMode === 'canvas') {
|
|
36
|
+
console.warn(
|
|
37
|
+
`Blits: Skipping SDF font "${font.family}" because SDF fonts are not supported with renderMode "canvas".`
|
|
38
|
+
)
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
37
41
|
stage.loadFont('sdf', {
|
|
38
42
|
fontFamily: font.family,
|
|
39
43
|
atlasUrl: font.png || (font.file && font.file.replace(/\.[^.]+$/, `.${font.type}.png`)),
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2023 Comcast Cable Communications Management, LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Maps a Blits font `type` (as declared in `settings.fonts`) to the
|
|
20
|
+
* renderer font-engine type (`sdf` or `canvas`).
|
|
21
|
+
*
|
|
22
|
+
* Kept in a shared module so `launch.js` (font-engine selection) and
|
|
23
|
+
* `fontLoader.js` (font loading) always agree on the mapping.
|
|
24
|
+
*/
|
|
25
|
+
const fontTypeMapping = {
|
|
26
|
+
sdf: 'sdf',
|
|
27
|
+
msdf: 'sdf',
|
|
28
|
+
canvas: 'canvas',
|
|
29
|
+
web: 'canvas',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Resolve a Blits font type to a renderer font-engine type.
|
|
34
|
+
* Unknown (or missing) types default to `sdf`, matching fontLoader behavior.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} [type] - The font type declared in settings (web, msdf, sdf)
|
|
37
|
+
* @returns {'sdf'|'canvas'}
|
|
38
|
+
*/
|
|
39
|
+
export const resolveFontEngineType = (type) => fontTypeMapping[type] || 'sdf'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Scan the configured fonts and report which font-engine types are required.
|
|
43
|
+
*
|
|
44
|
+
* @param {Array<{ type?: string }>} [fonts] - Fonts declared in settings
|
|
45
|
+
* @returns {{ hasSdfFont: boolean, hasCanvasFont: boolean }}
|
|
46
|
+
*/
|
|
47
|
+
export const getRequiredFontEngines = (fonts = []) => {
|
|
48
|
+
let hasSdfFont = false
|
|
49
|
+
let hasCanvasFont = false
|
|
50
|
+
|
|
51
|
+
const len = Array.isArray(fonts) ? fonts.length : 0
|
|
52
|
+
for (let i = 0; i < len; i++) {
|
|
53
|
+
if (resolveFontEngineType(fonts[i] && fonts[i].type) === 'sdf') {
|
|
54
|
+
hasSdfFont = true
|
|
55
|
+
} else {
|
|
56
|
+
hasCanvasFont = true
|
|
57
|
+
}
|
|
58
|
+
if (hasSdfFont === true && hasCanvasFont === true) break
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { hasSdfFont, hasCanvasFont }
|
|
62
|
+
}
|
package/src/engines/L3/launch.js
CHANGED
|
@@ -25,6 +25,7 @@ import { SCREEN_RESOLUTIONS, RENDER_QUALITIES } from '../../constants.js'
|
|
|
25
25
|
import colors from '../../lib/colors/colors.js'
|
|
26
26
|
import fontLoader from './fontLoader.js'
|
|
27
27
|
import shaderLoader from './shaderLoader.js'
|
|
28
|
+
import { getRequiredFontEngines } from './fontTypes.js'
|
|
28
29
|
import { platform } from '../../platform.js'
|
|
29
30
|
|
|
30
31
|
/** @type {RendererMain|{}} */
|
|
@@ -37,11 +38,22 @@ const renderEngine = (settings) => {
|
|
|
37
38
|
if (renderMode === 'canvas') return CanvasCoreRenderer
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
const textRenderEngines = (settings) => {
|
|
41
|
+
export const textRenderEngines = (settings) => {
|
|
41
42
|
const renderMode = 'renderMode' in settings ? settings.renderMode : 'webgl'
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
// SDF text rendering is not compatible with the Canvas core renderer
|
|
44
45
|
if (renderMode === 'canvas') return [CanvasTextRenderer]
|
|
46
|
+
|
|
47
|
+
if (renderMode === 'webgl') {
|
|
48
|
+
// Only register the font engines actually required by the configured
|
|
49
|
+
// fonts, so unused text renderers are never initialized.
|
|
50
|
+
const { hasSdfFont, hasCanvasFont } = getRequiredFontEngines(settings.fonts)
|
|
51
|
+
if (hasSdfFont === true && hasCanvasFont === true) return [SdfTextRenderer, CanvasTextRenderer]
|
|
52
|
+
if (hasSdfFont === true) return [SdfTextRenderer]
|
|
53
|
+
// Canvas-only fonts, or no fonts declared at all (system/default fonts
|
|
54
|
+
// render via the Canvas engine, which can render any font family).
|
|
55
|
+
return [CanvasTextRenderer]
|
|
56
|
+
}
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
const textureMemorySettings = (settings) => {
|
|
@@ -93,7 +105,9 @@ export default (App, target, settings = {}) => {
|
|
|
93
105
|
SCREEN_RESOLUTIONS[screenHeight] ||
|
|
94
106
|
1,
|
|
95
107
|
numImageWorkers:
|
|
96
|
-
'webWorkersLimit' in settings
|
|
108
|
+
'webWorkersLimit' in settings
|
|
109
|
+
? settings.webWorkersLimit
|
|
110
|
+
: Math.min(hardwareConcurrency || 2, 2),
|
|
97
111
|
clearColor: (settings.canvasColor && colors.normalize(settings.canvasColor)) || 0x00000000,
|
|
98
112
|
inspector: settings.inspector === true ? Inspector : undefined,
|
|
99
113
|
boundsMargin: settings.viewportMargin || 0,
|
|
@@ -223,6 +223,9 @@ const generateElementCode = function (
|
|
|
223
223
|
|
|
224
224
|
if (key === 'key') return
|
|
225
225
|
|
|
226
|
+
// a ref on a component belongs to the component itself, not to its holder element
|
|
227
|
+
if (options.holder === true && (key === 'ref' || key === ':ref')) return
|
|
228
|
+
|
|
226
229
|
// Skip inspector-data in production builds for performance optimization
|
|
227
230
|
if (key === 'inspector-data' && !isDev) return
|
|
228
231
|
|
|
@@ -524,6 +527,8 @@ const generateForLoopCode = function (templateObject, parent) {
|
|
|
524
527
|
}
|
|
525
528
|
}
|
|
526
529
|
|
|
530
|
+
const invalidateSelectCache = 'ref' in templateObject
|
|
531
|
+
|
|
527
532
|
ctx.renderCode.push(`
|
|
528
533
|
created[${forStartCounter}] = []
|
|
529
534
|
effects[${forStartCounter}] = []
|
|
@@ -532,6 +537,7 @@ const generateForLoopCode = function (templateObject, parent) {
|
|
|
532
537
|
let to${forStartCounter}
|
|
533
538
|
|
|
534
539
|
forloops[${forStartCounter}] = (collection = [], elms, created) => {
|
|
540
|
+
${invalidateSelectCache ? "rootComponent && rootComponent[Symbol.for('invalidateSelectCache')]()" : ''}
|
|
535
541
|
const rawCollection = getRaw(collection)
|
|
536
542
|
const keys = new Set()
|
|
537
543
|
let l = rawCollection.length
|
package/src/lib/symbols.js
CHANGED
|
@@ -144,4 +144,6 @@ export default {
|
|
|
144
144
|
removeEffects: Symbol.for('removeEffects'),
|
|
145
145
|
// Symbol 'tagContent' utilized within generated code
|
|
146
146
|
tagContent: Symbol.for('tagContent'),
|
|
147
|
+
// Symbol used by generated loops to invalidate cached ref lookups
|
|
148
|
+
invalidateSelectCache: Symbol.for('invalidateSelectCache'),
|
|
147
149
|
}
|