xbintsc 0.5.45 → 0.5.52
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/AGENTS.md +1 -1
- package/README.md +5 -4
- package/README.zh-CN.md +4 -3
- package/dist/src/cli/extensions.d.ts +24 -0
- package/dist/src/cli/extensions.js +62 -0
- package/dist/src/cli/extensions.js.map +1 -0
- package/dist/src/cli/help.d.ts +1 -1
- package/dist/src/cli/help.js +3 -1
- package/dist/src/cli/help.js.map +1 -1
- package/dist/src/cli/hints.js +8 -3
- package/dist/src/cli/hints.js.map +1 -1
- package/dist/src/cli/main.js +21 -28
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/diagnostics/diagnostic.d.ts +3 -1
- package/dist/src/diagnostics/diagnostic.js +2 -0
- package/dist/src/diagnostics/diagnostic.js.map +1 -1
- package/dist/src/driver/build-key.d.ts +2 -0
- package/dist/src/driver/build-key.js +1 -0
- package/dist/src/driver/build-key.js.map +1 -1
- package/dist/src/driver/compiler.js +61 -8
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/config.d.ts +4 -0
- package/dist/src/driver/config.js +2 -0
- package/dist/src/driver/config.js.map +1 -1
- package/dist/src/driver/link-features.d.ts +37 -0
- package/dist/src/driver/link-features.js +123 -0
- package/dist/src/driver/link-features.js.map +1 -0
- package/dist/src/driver/options.d.ts +10 -0
- package/dist/src/driver/runtime-objects.d.ts +5 -0
- package/dist/src/driver/runtime-objects.js +20 -1
- package/dist/src/driver/runtime-objects.js.map +1 -1
- package/dist/src/driver/usage.d.ts +23 -0
- package/dist/src/driver/usage.js +353 -0
- package/dist/src/driver/usage.js.map +1 -0
- package/dist/src/driver/version.d.ts +1 -1
- package/dist/src/driver/version.js +1 -1
- package/dist/src/extensions/catalog.js +1 -2
- package/dist/src/extensions/catalog.js.map +1 -1
- package/dist/src/extensions/gui/features.d.ts +34 -0
- package/dist/src/extensions/gui/features.js +165 -0
- package/dist/src/extensions/gui/features.js.map +1 -0
- package/dist/src/extensions/gui/index.js +10 -4
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/link-feature.d.ts +70 -0
- package/dist/src/extensions/link-feature.js +18 -0
- package/dist/src/extensions/link-feature.js.map +1 -0
- package/dist/src/extensions/registry.d.ts +24 -4
- package/dist/src/extensions/registry.js +26 -3
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +0 -1
- package/dist/src/index.js.map +1 -1
- package/dist/tests/cli/hints.test.js +5 -7
- package/dist/tests/cli/hints.test.js.map +1 -1
- package/dist/tests/driver/link-features.test.js +105 -0
- package/dist/tests/driver/link-features.test.js.map +1 -0
- package/dist/tests/driver/usage.test.d.ts +1 -0
- package/dist/tests/driver/usage.test.js +88 -0
- package/dist/tests/driver/usage.test.js.map +1 -0
- package/dist/tests/e2e/gui-audio.test.js +4 -4
- package/dist/tests/e2e/gui-audio.test.js.map +1 -1
- package/dist/tests/e2e/gui-css-coverage.test.d.ts +9 -0
- package/dist/tests/e2e/gui-css-coverage.test.js +333 -0
- package/dist/tests/e2e/gui-css-coverage.test.js.map +1 -0
- package/dist/tests/e2e/gui-css-features.test.d.ts +6 -0
- package/dist/tests/e2e/gui-css-features.test.js +270 -0
- package/dist/tests/e2e/gui-css-features.test.js.map +1 -0
- package/dist/tests/e2e/gui-effects.test.js +92 -11
- package/dist/tests/e2e/gui-effects.test.js.map +1 -1
- package/dist/tests/e2e/gui-feature-linking.test.d.ts +9 -0
- package/dist/tests/e2e/gui-feature-linking.test.js +68 -0
- package/dist/tests/e2e/gui-feature-linking.test.js.map +1 -0
- package/dist/tests/e2e/gui-font-face.test.d.ts +10 -0
- package/dist/tests/e2e/gui-font-face.test.js +142 -0
- package/dist/tests/e2e/gui-font-face.test.js.map +1 -0
- package/dist/tests/e2e/gui-fonts.test.d.ts +10 -0
- package/dist/tests/e2e/gui-fonts.test.js +150 -0
- package/dist/tests/e2e/gui-fonts.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +10 -1
- package/dist/tests/e2e/gui-helpers.js +6 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -1
- package/dist/tests/e2e/gui-layout.test.js +7 -3
- package/dist/tests/e2e/gui-layout.test.js.map +1 -1
- package/dist/tests/e2e/gui-media-disabled.test.d.ts +5 -4
- package/dist/tests/e2e/gui-media-disabled.test.js +7 -6
- package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -1
- package/dist/tests/e2e/gui-media.test.d.ts +5 -4
- package/dist/tests/e2e/gui-media.test.js +9 -9
- package/dist/tests/e2e/gui-media.test.js.map +1 -1
- package/dist/tests/e2e/gui-paint.test.js +5 -2
- package/dist/tests/e2e/gui-paint.test.js.map +1 -1
- package/dist/tests/e2e/gui-shaping.test.d.ts +5 -0
- package/dist/tests/e2e/gui-shaping.test.js +47 -0
- package/dist/tests/e2e/gui-shaping.test.js.map +1 -0
- package/dist/tests/e2e/gui-track-sizing.test.d.ts +6 -0
- package/dist/tests/e2e/gui-track-sizing.test.js +173 -0
- package/dist/tests/e2e/gui-track-sizing.test.js.map +1 -0
- package/dist/tests/e2e/gui-transform.test.js +3 -2
- package/dist/tests/e2e/gui-transform.test.js.map +1 -1
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.d.ts +18 -0
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js +120 -0
- package/dist/tests/e2e/gui-webgl-pipeline-cache.test.js.map +1 -0
- package/dist/tests/extensions/gui-features.test.d.ts +1 -0
- package/dist/tests/extensions/gui-features.test.js +175 -0
- package/dist/tests/extensions/gui-features.test.js.map +1 -0
- package/doc/ai/cli.md +5 -1
- package/doc/ai/extensions.md +56 -26
- package/doc/ai/troubleshooting.md +10 -7
- package/doc/ai/zh-CN/cli.md +5 -1
- package/doc/ai/zh-CN/extensions.md +48 -22
- package/doc/ai/zh-CN/troubleshooting.md +6 -5
- package/doc/gui/animation.md +39 -9
- package/doc/gui/api.md +2 -1
- package/doc/gui/build.md +137 -19
- package/doc/gui/canvas.md +1 -1
- package/doc/gui/dom.md +20 -4
- package/doc/gui/known-issues.md +63 -26
- package/doc/gui/layout.md +31 -8
- package/doc/gui/media.md +13 -7
- package/doc/gui/rendering.md +114 -39
- package/doc/gui/runtime.md +5 -0
- package/doc/gui/webgl.md +24 -6
- package/doc/gui.md +110 -47
- package/doc/implemented/cli.md +2 -0
- package/doc/implemented/extensions.md +9 -3
- package/doc/npm-package.md +4 -4
- package/doc/xbintsc.config.schema.json +10 -0
- package/doc/zh-CN/gui/animation.md +33 -10
- package/doc/zh-CN/gui/api.md +2 -1
- package/doc/zh-CN/gui/build.md +110 -16
- package/doc/zh-CN/gui/canvas.md +1 -1
- package/doc/zh-CN/gui/dom.md +16 -3
- package/doc/zh-CN/gui/known-issues.md +45 -15
- package/doc/zh-CN/gui/layout.md +23 -6
- package/doc/zh-CN/gui/media.md +11 -6
- package/doc/zh-CN/gui/rendering.md +84 -26
- package/doc/zh-CN/gui/runtime.md +3 -0
- package/doc/zh-CN/gui/webgl.md +3 -3
- package/doc/zh-CN/gui.md +92 -41
- package/doc/zh-CN/implemented/cli.md +2 -0
- package/doc/zh-CN/implemented/extensions.md +9 -3
- package/doc/zh-CN/npm-package.md +4 -3
- package/package.json +7 -7
- package/runtime/ext_gui/animation_values.cpp +425 -0
- package/runtime/ext_gui/animation_values.h +64 -0
- package/runtime/ext_gui/browser.cpp +22 -15
- package/runtime/ext_gui/browser_loop.cpp +13 -1
- package/runtime/ext_gui/canvas.h +1 -1
- package/runtime/ext_gui/canvas_raster.cpp +1 -1
- package/runtime/ext_gui/canvas_text.cpp +1 -1
- package/runtime/ext_gui/css.cpp +172 -21
- package/runtime/ext_gui/css.h +75 -4
- package/runtime/ext_gui/css_match.cpp +4 -0
- package/runtime/ext_gui/css_sheet.cpp +218 -2
- package/runtime/ext_gui/document.cpp +42 -192
- package/runtime/ext_gui/document.h +25 -12
- package/runtime/ext_gui/document_animation.cpp +428 -0
- package/runtime/ext_gui/dom_api_canvas.cpp +15 -8
- package/runtime/ext_gui/dom_api_canvas_extra.cpp +44 -20
- package/runtime/ext_gui/dom_api_canvas_image.cpp +3 -1
- package/runtime/ext_gui/dom_api_node_read.cpp +10 -0
- package/runtime/ext_gui/dom_api_offscreen.cpp +3 -1
- package/runtime/ext_gui/dom_api_style.cpp +7 -0
- package/runtime/ext_gui/dom_api_webgl_internal.h +4 -0
- package/runtime/ext_gui/dom_api_webgl_source.cpp +5 -2
- package/runtime/ext_gui/effect_values.cpp +245 -21
- package/runtime/ext_gui/gpu_bridge.c +28 -0
- package/runtime/ext_gui/gpu_feature.cpp +30 -0
- package/runtime/ext_gui/gpu_shader.cpp +22 -0
- package/runtime/ext_gui/gpu_shader_api.h +50 -0
- package/runtime/ext_gui/gradient_values.cpp +24 -0
- package/runtime/ext_gui/grid_values.cpp +23 -0
- package/runtime/ext_gui/gui.cpp +6 -37
- package/runtime/ext_gui/gui_canvas.cpp +14 -6
- package/runtime/ext_gui/gui_cursor.cpp +50 -0
- package/runtime/ext_gui/gui_engine.h +1 -18
- package/runtime/ext_gui/gui_events.cpp +19 -6
- package/runtime/ext_gui/gui_internal.h +3 -0
- package/runtime/ext_gui/gui_webgl.cpp +25 -10
- package/runtime/ext_gui/image_api.cpp +18 -0
- package/runtime/ext_gui/image_api.h +43 -0
- package/runtime/ext_gui/image_bitmap_browser.cpp +20 -11
- package/runtime/ext_gui/image_bridge.c +22 -0
- package/runtime/ext_gui/image_feature.cpp +107 -0
- package/runtime/ext_gui/inline_svg.cpp +95 -8
- package/runtime/ext_gui/inline_svg.h +5 -1
- package/runtime/ext_gui/layout.cpp +15 -9
- package/runtime/ext_gui/layout.h +8 -2
- package/runtime/ext_gui/layout_flex.cpp +134 -29
- package/runtime/ext_gui/layout_flow.cpp +58 -7
- package/runtime/ext_gui/layout_flow_internal.h +39 -4
- package/runtime/ext_gui/layout_grid.cpp +31 -0
- package/runtime/ext_gui/layout_inline.cpp +26 -2
- package/runtime/ext_gui/layout_vertical.cpp +196 -0
- package/runtime/ext_gui/media.cpp +4 -2
- package/runtime/ext_gui/media.h +3 -3
- package/runtime/ext_gui/media_api.h +7 -6
- package/runtime/ext_gui/paint.cpp +67 -9
- package/runtime/ext_gui/paint.h +61 -8
- package/runtime/ext_gui/paint_backdrop.cpp +211 -0
- package/runtime/ext_gui/paint_background.cpp +175 -97
- package/runtime/ext_gui/paint_effects.cpp +12 -6
- package/runtime/ext_gui/paint_internal.h +27 -0
- package/runtime/ext_gui/paint_outline.cpp +44 -0
- package/runtime/ext_gui/paint_shadow.cpp +368 -0
- package/runtime/ext_gui/renderer.cpp +15 -7
- package/runtime/ext_gui/renderer.h +11 -2
- package/runtime/ext_gui/renderer_backdrop.cpp +374 -0
- package/runtime/ext_gui/renderer_bitmap.cpp +82 -0
- package/runtime/ext_gui/renderer_gradient.cpp +51 -17
- package/runtime/ext_gui/renderer_image.cpp +19 -7
- package/runtime/ext_gui/renderer_internal.h +21 -0
- package/runtime/ext_gui/renderer_layer.cpp +37 -6
- package/runtime/ext_gui/renderer_text.cpp +2 -1
- package/runtime/ext_gui/stb_truetype.h +5077 -0
- package/runtime/ext_gui/style.cpp +62 -8
- package/runtime/ext_gui/style.h +111 -4
- package/runtime/ext_gui/style_cascade.cpp +83 -0
- package/runtime/ext_gui/style_internal.h +13 -1
- package/runtime/ext_gui/style_length.cpp +92 -0
- package/runtime/ext_gui/style_print.cpp +83 -2
- package/runtime/ext_gui/style_svg.cpp +40 -0
- package/runtime/ext_gui/style_text.cpp +302 -0
- package/runtime/ext_gui/style_ui.cpp +150 -0
- package/runtime/ext_gui/style_values.cpp +76 -62
- package/runtime/ext_gui/style_values_internal.h +23 -0
- package/runtime/ext_gui/text.cpp +27 -499
- package/runtime/ext_gui/text.h +18 -9
- package/runtime/ext_gui/text_api.h +45 -0
- package/runtime/ext_gui/text_bridge.c +23 -0
- package/runtime/ext_gui/text_cache.cpp +12 -3
- package/runtime/ext_gui/text_font_db.cpp +427 -0
- package/runtime/ext_gui/text_font_paths.cpp +250 -0
- package/runtime/ext_gui/text_font_paths.h +39 -0
- package/runtime/ext_gui/text_font_sfnt.cpp +396 -0
- package/runtime/ext_gui/text_font_sfnt.h +44 -0
- package/runtime/ext_gui/text_fonts.cpp +348 -0
- package/runtime/ext_gui/text_fonts.h +78 -0
- package/runtime/ext_gui/text_fonts_internal.h +77 -0
- package/runtime/ext_gui/text_glyph_cache.cpp +2 -2
- package/runtime/ext_gui/text_hb.cpp +438 -0
- package/runtime/ext_gui/text_stb.cpp +369 -0
- package/runtime/ext_gui/transform.cpp +107 -9
- package/runtime/ext_gui/transform.h +5 -0
- package/runtime/ext_gui/webgl_api.cpp +19 -0
- package/runtime/ext_gui/webgl_api.h +53 -0
- package/runtime/ext_gui/webgl_bridge.c +25 -0
- package/runtime/ext_gui/webgl_draw_setup.cpp +42 -1
- package/runtime/ext_gui/webgl_feature.cpp +82 -0
- package/runtime/ext_gui/webgl_feature.h +28 -0
- package/runtime/ext_gui/webgl_msl.cpp +41 -0
- package/runtime/ext_gui/webgl_msl.h +6 -0
- package/runtime/ext_gui/webgl_shader.cpp +40 -32
- package/runtime/ext_gui/window.cpp +7 -7
- package/runtime/ext_media/decode_audio.cpp +1 -1
- package/runtime/ext_media/media.cpp +1 -1
- package/runtime/ext_media/media_bridge.c +7 -7
- package/scripts/build-gui.ts +97 -98
- package/scripts/check-file-length.ts +1 -0
- package/scripts/gui-build-options.ts +122 -0
- package/src/cli/extensions.ts +72 -0
- package/src/cli/help.ts +3 -1
- package/src/cli/hints.ts +9 -3
- package/src/cli/main.ts +21 -27
- package/src/diagnostics/diagnostic.ts +2 -0
- package/src/driver/build-key.ts +3 -0
- package/src/driver/compiler.ts +63 -8
- package/src/driver/config.ts +6 -0
- package/src/driver/link-features.ts +163 -0
- package/src/driver/options.ts +10 -0
- package/src/driver/runtime-objects.ts +26 -1
- package/src/driver/usage.ts +376 -0
- package/src/driver/version.ts +1 -1
- package/src/extensions/catalog.ts +1 -2
- package/src/extensions/gui/features.ts +166 -0
- package/src/extensions/gui/index.ts +10 -4
- package/src/extensions/link-feature.ts +72 -0
- package/src/extensions/registry.ts +45 -4
- package/src/index.ts +3 -2
- package/dist/src/extensions/media/index.d.ts +0 -28
- package/dist/src/extensions/media/index.js +0 -59
- package/dist/src/extensions/media/index.js.map +0 -1
- package/dist/tests/extensions/media.test.js +0 -63
- package/dist/tests/extensions/media.test.js.map +0 -1
- package/src/extensions/media/index.ts +0 -65
- /package/dist/tests/{extensions/media.test.d.ts → driver/link-features.test.d.ts} +0 -0
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static usage analysis for optional link features.
|
|
3
|
+
*
|
|
4
|
+
* The bundled AST is the last place where the compiler can see *all* of a
|
|
5
|
+
* program: imported modules and `.html` assets are already merged into one
|
|
6
|
+
* `SourceFile`, and asset loaders have inlined external stylesheets. This module
|
|
7
|
+
* extracts the facts a {@link LinkFeature} needs to decide whether it may be
|
|
8
|
+
* used, without pretending to be a full data-flow analysis:
|
|
9
|
+
*
|
|
10
|
+
* - every string literal / template chunk (HTML tags, CSS property values),
|
|
11
|
+
* - identifiers and property names (`Audio`, `decodeAudioData`, …),
|
|
12
|
+
* - every `getContext(...)` kind that can be resolved statically,
|
|
13
|
+
* - whether HTML/CSS can be built from data the compiler cannot see.
|
|
14
|
+
*
|
|
15
|
+
* Constants are resolved through a small, name-based table (`const` only,
|
|
16
|
+
* invalidated by shadowing or reassignment). Anything that cannot be resolved is
|
|
17
|
+
* treated as dynamic, which makes features conservative: a false positive costs
|
|
18
|
+
* size, a false negative could disable a capability at run time.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
SyntaxKind,
|
|
23
|
+
type BinaryExpression,
|
|
24
|
+
type CallExpression,
|
|
25
|
+
type Expression,
|
|
26
|
+
type Identifier,
|
|
27
|
+
type NoSubstitutionTemplateLiteral,
|
|
28
|
+
type Parameter,
|
|
29
|
+
type PrefixUnaryExpression,
|
|
30
|
+
type PrivateIdentifier,
|
|
31
|
+
type PropertyAccessExpression,
|
|
32
|
+
type SourceFileNode,
|
|
33
|
+
type StringLiteral,
|
|
34
|
+
type TemplateLiteral,
|
|
35
|
+
type VariableStatement,
|
|
36
|
+
type ElementAccessExpression,
|
|
37
|
+
type AssignmentExpression,
|
|
38
|
+
type PostfixUnaryExpression,
|
|
39
|
+
type NumericLiteral,
|
|
40
|
+
type BigIntLiteral,
|
|
41
|
+
type BooleanLiteral,
|
|
42
|
+
type Node,
|
|
43
|
+
} from "../ast/nodes.js";
|
|
44
|
+
import { walk } from "../ast/visitor.js";
|
|
45
|
+
import type { ProgramUsage } from "../extensions/link-feature.js";
|
|
46
|
+
|
|
47
|
+
/** Operators that make a `BinaryExpression`-shaped node an assignment. */
|
|
48
|
+
const ASSIGNMENT_OPERATORS = new Set<string>([
|
|
49
|
+
"=",
|
|
50
|
+
"+=",
|
|
51
|
+
"-=",
|
|
52
|
+
"*=",
|
|
53
|
+
"/=",
|
|
54
|
+
"%=",
|
|
55
|
+
"**=",
|
|
56
|
+
"<<=",
|
|
57
|
+
">>=",
|
|
58
|
+
">>>=",
|
|
59
|
+
"&=",
|
|
60
|
+
"|=",
|
|
61
|
+
"^=",
|
|
62
|
+
"&&=",
|
|
63
|
+
"||=",
|
|
64
|
+
"??=",
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
/** Property names whose assigned value is parsed as markup or CSS. */
|
|
68
|
+
const MARKUP_PROPERTIES = new Set(["innerHTML", "outerHTML", "cssText"]);
|
|
69
|
+
|
|
70
|
+
/** A call site with a statically known method/function name. */
|
|
71
|
+
interface CallSite {
|
|
72
|
+
readonly name: string;
|
|
73
|
+
readonly arguments: readonly Expression[];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** An assignment whose left side is a statically known property name. */
|
|
77
|
+
interface PropertyAssignmentSite {
|
|
78
|
+
readonly name: string;
|
|
79
|
+
readonly right: Expression;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A wrapper expression (`as`, `satisfies`, `!`, parentheses) around a value. */
|
|
83
|
+
interface WrappedExpression {
|
|
84
|
+
readonly expression: Expression;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The property/method name of `a.b` or `a["b"]`, or the identifier of a bare
|
|
89
|
+
* call (`loadHTML(...)`). Returns undefined for a computed access.
|
|
90
|
+
*/
|
|
91
|
+
function memberName(expression: Expression): string | undefined {
|
|
92
|
+
if (expression.kind === SyntaxKind.PropertyAccessExpression) {
|
|
93
|
+
return (expression as PropertyAccessExpression).name.text;
|
|
94
|
+
}
|
|
95
|
+
if (expression.kind === SyntaxKind.ElementAccessExpression) {
|
|
96
|
+
const argument = (expression as ElementAccessExpression).argumentExpression;
|
|
97
|
+
if (argument.kind === SyntaxKind.StringLiteral) return (argument as StringLiteral).value;
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
if (expression.kind === SyntaxKind.Identifier) return (expression as Identifier).text;
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The assignment-shaped view of a `BinaryExpression`, when it is one. */
|
|
105
|
+
function asAssignment(node: Node): AssignmentExpression | undefined {
|
|
106
|
+
if (node.kind !== SyntaxKind.BinaryExpression) return undefined;
|
|
107
|
+
const operator = (node as BinaryExpression).operator;
|
|
108
|
+
return ASSIGNMENT_OPERATORS.has(String(operator)) ? (node as unknown as AssignmentExpression) : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** True for literals that stringify to text which can never contain markup. */
|
|
112
|
+
function isPrimitiveLiteral(expression: Expression): boolean {
|
|
113
|
+
switch (expression.kind) {
|
|
114
|
+
case SyntaxKind.NumericLiteral:
|
|
115
|
+
case SyntaxKind.BigIntLiteral:
|
|
116
|
+
case SyntaxKind.TrueKeyword:
|
|
117
|
+
case SyntaxKind.FalseKeyword:
|
|
118
|
+
case SyntaxKind.NullKeyword:
|
|
119
|
+
case SyntaxKind.UndefinedKeyword:
|
|
120
|
+
return true;
|
|
121
|
+
default:
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Convert a primitive literal to the text it contributes to a template. */
|
|
127
|
+
function primitiveText(expression: Expression): string | undefined {
|
|
128
|
+
switch (expression.kind) {
|
|
129
|
+
case SyntaxKind.NumericLiteral:
|
|
130
|
+
return String((expression as NumericLiteral).value);
|
|
131
|
+
case SyntaxKind.BigIntLiteral:
|
|
132
|
+
return (expression as BigIntLiteral).value.toString();
|
|
133
|
+
case SyntaxKind.TrueKeyword:
|
|
134
|
+
case SyntaxKind.FalseKeyword:
|
|
135
|
+
return (expression as BooleanLiteral).value ? "true" : "false";
|
|
136
|
+
case SyntaxKind.NullKeyword:
|
|
137
|
+
return "null";
|
|
138
|
+
case SyntaxKind.UndefinedKeyword:
|
|
139
|
+
return "undefined";
|
|
140
|
+
default:
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Collect the usage facts of one bundled program. */
|
|
146
|
+
export function collectUsage(sourceFile: SourceFileNode): ProgramUsage {
|
|
147
|
+
const strings = new Set<string>();
|
|
148
|
+
const identifiers = new Set<string>();
|
|
149
|
+
const constants = new Map<string, Expression>();
|
|
150
|
+
const ambiguous = new Set<string>();
|
|
151
|
+
const assigned = new Set<string>();
|
|
152
|
+
const calls: CallSite[] = [];
|
|
153
|
+
const assignments: PropertyAssignmentSite[] = [];
|
|
154
|
+
let createsStyleElement = false;
|
|
155
|
+
let unresolvedTextContent = false;
|
|
156
|
+
|
|
157
|
+
const forget = (name: string): void => {
|
|
158
|
+
if (constants.delete(name)) ambiguous.add(name);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
walk(sourceFile, (node) => {
|
|
162
|
+
switch (node.kind) {
|
|
163
|
+
case SyntaxKind.StringLiteral:
|
|
164
|
+
strings.add((node as StringLiteral).value);
|
|
165
|
+
break;
|
|
166
|
+
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
167
|
+
strings.add((node as NoSubstitutionTemplateLiteral).value);
|
|
168
|
+
break;
|
|
169
|
+
case SyntaxKind.TemplateLiteral: {
|
|
170
|
+
const template = node as TemplateLiteral;
|
|
171
|
+
strings.add(template.head);
|
|
172
|
+
for (const span of template.spans) strings.add(span.literal);
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
case SyntaxKind.Identifier:
|
|
176
|
+
identifiers.add((node as Identifier).text);
|
|
177
|
+
break;
|
|
178
|
+
case SyntaxKind.PrivateIdentifier:
|
|
179
|
+
identifiers.add((node as PrivateIdentifier).text);
|
|
180
|
+
break;
|
|
181
|
+
case SyntaxKind.VariableStatement: {
|
|
182
|
+
const statement = node as VariableStatement;
|
|
183
|
+
if (statement.declarationList.declarationKind !== "const") break;
|
|
184
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
185
|
+
if (declaration.name.kind !== SyntaxKind.Identifier || declaration.initializer === undefined) continue;
|
|
186
|
+
const name = declaration.name.text;
|
|
187
|
+
/* A second declaration of the same name makes the initializer
|
|
188
|
+
* ambiguous: shadowing is not tracked, so refuse to resolve it. */
|
|
189
|
+
if (constants.has(name) || ambiguous.has(name)) {
|
|
190
|
+
forget(name);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
constants.set(name, declaration.initializer);
|
|
194
|
+
}
|
|
195
|
+
break;
|
|
196
|
+
}
|
|
197
|
+
case SyntaxKind.Parameter: {
|
|
198
|
+
/* A parameter shadows any const of the same name; resolution only looks
|
|
199
|
+
* at the name, so mark it ambiguous. */
|
|
200
|
+
const parameter = node as Parameter;
|
|
201
|
+
if (parameter.name.kind === SyntaxKind.Identifier) ambiguous.add(parameter.name.text);
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case SyntaxKind.PrefixUnaryExpression: {
|
|
205
|
+
const unary = node as PrefixUnaryExpression;
|
|
206
|
+
if (unary.operator === "++" || unary.operator === "--") {
|
|
207
|
+
if (unary.operand.kind === SyntaxKind.Identifier) assigned.add(unary.operand.text);
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
case SyntaxKind.PostfixUnaryExpression: {
|
|
212
|
+
const unary = node as PostfixUnaryExpression;
|
|
213
|
+
if (unary.operand.kind === SyntaxKind.Identifier) assigned.add(unary.operand.text);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case SyntaxKind.BinaryExpression: {
|
|
217
|
+
const assignment = asAssignment(node);
|
|
218
|
+
if (assignment === undefined) break;
|
|
219
|
+
if (assignment.left.kind === SyntaxKind.Identifier) assigned.add(assignment.left.text);
|
|
220
|
+
const name = memberName(assignment.left);
|
|
221
|
+
if (name !== undefined) assignments.push({ name, right: assignment.right });
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case SyntaxKind.CallExpression: {
|
|
225
|
+
const call = node as CallExpression;
|
|
226
|
+
const name = memberName(call.expression);
|
|
227
|
+
if (name !== undefined) calls.push({ name, arguments: call.arguments });
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
default:
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const resolve = (expression: Expression, stack: Set<string> = new Set()): string | undefined => {
|
|
236
|
+
const primitive = primitiveText(expression);
|
|
237
|
+
if (primitive !== undefined) return primitive;
|
|
238
|
+
switch (expression.kind) {
|
|
239
|
+
case SyntaxKind.StringLiteral:
|
|
240
|
+
return (expression as StringLiteral).value;
|
|
241
|
+
case SyntaxKind.NoSubstitutionTemplateLiteral:
|
|
242
|
+
return (expression as NoSubstitutionTemplateLiteral).value;
|
|
243
|
+
case SyntaxKind.TemplateLiteral: {
|
|
244
|
+
const template = expression as TemplateLiteral;
|
|
245
|
+
let text = template.head;
|
|
246
|
+
for (const span of template.spans) {
|
|
247
|
+
const value = resolve(span.expression, stack);
|
|
248
|
+
if (value === undefined) {
|
|
249
|
+
if (!isPrimitiveLiteral(span.expression)) return undefined;
|
|
250
|
+
} else {
|
|
251
|
+
text += value;
|
|
252
|
+
}
|
|
253
|
+
text += span.literal;
|
|
254
|
+
}
|
|
255
|
+
return text;
|
|
256
|
+
}
|
|
257
|
+
case SyntaxKind.Identifier: {
|
|
258
|
+
const name = (expression as Identifier).text;
|
|
259
|
+
if (assigned.has(name) || ambiguous.has(name)) return undefined;
|
|
260
|
+
const initializer = constants.get(name);
|
|
261
|
+
if (initializer === undefined || stack.has(name)) return undefined;
|
|
262
|
+
stack.add(name);
|
|
263
|
+
const value = resolve(initializer, stack);
|
|
264
|
+
stack.delete(name);
|
|
265
|
+
return value;
|
|
266
|
+
}
|
|
267
|
+
case SyntaxKind.ParenthesizedExpression:
|
|
268
|
+
case SyntaxKind.AsExpression:
|
|
269
|
+
case SyntaxKind.SatisfiesExpression:
|
|
270
|
+
case SyntaxKind.NonNullExpression: {
|
|
271
|
+
return resolve((expression as unknown as WrappedExpression).expression, stack);
|
|
272
|
+
}
|
|
273
|
+
case SyntaxKind.BinaryExpression: {
|
|
274
|
+
const binary = expression as BinaryExpression;
|
|
275
|
+
if (ASSIGNMENT_OPERATORS.has(String(binary.operator)) || binary.operator !== "+") return undefined;
|
|
276
|
+
const left = resolve(binary.left, stack);
|
|
277
|
+
const right = resolve(binary.right, stack);
|
|
278
|
+
return left === undefined || right === undefined ? undefined : left + right;
|
|
279
|
+
}
|
|
280
|
+
default:
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
let dynamicMarkup = false;
|
|
286
|
+
const getContextKinds: (string | undefined)[] = [];
|
|
287
|
+
|
|
288
|
+
const sink = (expression: Expression | undefined): void => {
|
|
289
|
+
if (expression === undefined) {
|
|
290
|
+
dynamicMarkup = true;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
const text = resolve(expression);
|
|
294
|
+
if (text === undefined) dynamicMarkup = true;
|
|
295
|
+
else strings.add(text);
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const sinkAll = (expressions: readonly Expression[]): void => {
|
|
299
|
+
for (const expression of expressions) {
|
|
300
|
+
const text = resolve(expression);
|
|
301
|
+
if (text === undefined) dynamicMarkup = true;
|
|
302
|
+
else strings.add(text);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
for (const site of calls) {
|
|
307
|
+
switch (site.name) {
|
|
308
|
+
case "getContext": {
|
|
309
|
+
const kind = site.arguments[0] === undefined ? undefined : resolve(site.arguments[0]);
|
|
310
|
+
getContextKinds.push(kind === undefined ? undefined : kind.toLowerCase());
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
case "createElement": {
|
|
314
|
+
const tag = site.arguments[0] === undefined ? undefined : resolve(site.arguments[0]);
|
|
315
|
+
if (tag === undefined) dynamicMarkup = true;
|
|
316
|
+
else if (tag.toLowerCase() === "style") createsStyleElement = true;
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case "createElementNS": {
|
|
320
|
+
const tag = site.arguments[1] === undefined ? undefined : resolve(site.arguments[1]);
|
|
321
|
+
if (tag === undefined) dynamicMarkup = true;
|
|
322
|
+
else if (tag.toLowerCase() === "style") createsStyleElement = true;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case "loadHTML":
|
|
326
|
+
case "parseFromString":
|
|
327
|
+
case "write":
|
|
328
|
+
case "writeln":
|
|
329
|
+
sink(site.arguments[0]);
|
|
330
|
+
break;
|
|
331
|
+
case "insertAdjacentHTML":
|
|
332
|
+
sink(site.arguments[1]);
|
|
333
|
+
break;
|
|
334
|
+
case "setAttribute": {
|
|
335
|
+
const attribute = site.arguments[0] === undefined ? undefined : resolve(site.arguments[0]);
|
|
336
|
+
if (attribute === undefined) dynamicMarkup = true;
|
|
337
|
+
else if (attribute.toLowerCase() === "style") sink(site.arguments[1]);
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
case "setProperty": {
|
|
341
|
+
/* The property *name* decides whether a CSS feature is used; the value
|
|
342
|
+
* cannot introduce a new property. A computed name is therefore the
|
|
343
|
+
* only reason to be conservative here. */
|
|
344
|
+
if (site.arguments[0] === undefined || resolve(site.arguments[0]) === undefined) dynamicMarkup = true;
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
case "insertRule":
|
|
348
|
+
case "addRule":
|
|
349
|
+
case "replaceSync":
|
|
350
|
+
sinkAll(site.arguments);
|
|
351
|
+
break;
|
|
352
|
+
default:
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
for (const assignment of assignments) {
|
|
358
|
+
if (MARKUP_PROPERTIES.has(assignment.name)) {
|
|
359
|
+
sink(assignment.right);
|
|
360
|
+
} else if (assignment.name === "textContent" || assignment.name === "innerText") {
|
|
361
|
+
if (resolve(assignment.right) === undefined) unresolvedTextContent = true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* A `<style>` element created at run time is usually filled through
|
|
366
|
+
* `textContent`; if that value is not a literal, CSS (and therefore
|
|
367
|
+
* `mix-blend-mode`) may come from data the compiler cannot see. */
|
|
368
|
+
if (createsStyleElement && unresolvedTextContent) dynamicMarkup = true;
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
strings: [...strings],
|
|
372
|
+
identifiers,
|
|
373
|
+
getContext: getContextKinds,
|
|
374
|
+
dynamicMarkup,
|
|
375
|
+
};
|
|
376
|
+
}
|
package/src/driver/version.ts
CHANGED
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
import type { Extension } from "./registry.js";
|
|
14
14
|
import { nodeExtension } from "./node/index.js";
|
|
15
15
|
import { guiExtension } from "./gui/index.js";
|
|
16
|
-
import { mediaExtension } from "./media/index.js";
|
|
17
16
|
|
|
18
17
|
/** Every extension bundled with xbintsc, in registration order. */
|
|
19
18
|
export function bundledExtensions(): readonly Extension[] {
|
|
20
|
-
return [nodeExtension, guiExtension
|
|
19
|
+
return [nodeExtension, guiExtension];
|
|
21
20
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional link features of the `gui` extension.
|
|
3
|
+
*
|
|
4
|
+
* The GUI engine itself is always linked with `--ext gui`; these are the two
|
|
5
|
+
* heavy halves a program may never touch:
|
|
6
|
+
*
|
|
7
|
+
* - `media` — the FFmpeg decoder backend (`runtime/ext_media/`, `media.a`).
|
|
8
|
+
* The DOM media surface lives in `gui.a`; only decoding is optional, and an
|
|
9
|
+
* element without the backend still exists and fires `error`.
|
|
10
|
+
* - `gpu` — glslang (+ SPIRV-Cross on macOS), the GLSL→SPIR-V translator used
|
|
11
|
+
* by WebGL *and* by the `mix-blend-mode` / `backdrop-filter` offscreen
|
|
12
|
+
* compositor. The engine's own shaders are precompiled, so a DOM page
|
|
13
|
+
* never needs it.
|
|
14
|
+
*
|
|
15
|
+
* Detection is static: the compiler sees imported `.html`/`.css` assets and all
|
|
16
|
+
* bundled modules, so it can recognise `<video>`/`<audio>`, `getContext("webgl")`
|
|
17
|
+
* and `mix-blend-mode`/`backdrop-filter`. When markup can be built at run time
|
|
18
|
+
* the usage collector sets `dynamicMarkup`, which conservatively enables both
|
|
19
|
+
* features; users who know better can shrink the binary with `--no-feature`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { join } from "node:path";
|
|
23
|
+
import type { LinkFeature, ProgramUsage } from "../link-feature.js";
|
|
24
|
+
import { findRuntimeDir, platformSlug } from "../../driver/paths.js";
|
|
25
|
+
import { findRuntimeLibrary, runtimeLibDir } from "../../driver/runtime-lib.js";
|
|
26
|
+
|
|
27
|
+
/** `<video …>` / `<audio …>` in an HTML string or asset. */
|
|
28
|
+
const MEDIA_TAG = /<(?:video|audio)\b/i;
|
|
29
|
+
/** A literal tag name handed to `createElement("video")`. */
|
|
30
|
+
const MEDIA_TAG_NAME = /^(?:video|audio)$/i;
|
|
31
|
+
/** Anything that needs the GLSL translator. */
|
|
32
|
+
const GPU_TOKEN = /webgl|mix-blend-mode|mixBlendMode|backdrop-filter|backdropFilter/i;
|
|
33
|
+
|
|
34
|
+
/** True when the program may create or drive a media element. */
|
|
35
|
+
export function hasMediaUsage(usage: ProgramUsage): boolean {
|
|
36
|
+
if (usage.dynamicMarkup) return true;
|
|
37
|
+
for (const name of ["Audio", "HTMLMediaElement", "HTMLVideoElement", "HTMLAudioElement", "decodeAudioData"]) {
|
|
38
|
+
if (usage.identifiers.has(name)) return true;
|
|
39
|
+
}
|
|
40
|
+
return usage.strings.some((value) => MEDIA_TAG.test(value) || MEDIA_TAG_NAME.test(value));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** True when the program may compile GLSL at run time (WebGL or blending). */
|
|
44
|
+
export function hasGpuUsage(usage: ProgramUsage): boolean {
|
|
45
|
+
if (usage.dynamicMarkup) return true;
|
|
46
|
+
if (usage.identifiers.has("mixBlendMode") || usage.identifiers.has("backdropFilter")) return true;
|
|
47
|
+
if (usage.getContext.some((kind) => kind === undefined || /webgl/i.test(kind))) return true;
|
|
48
|
+
return usage.strings.some((value) => GPU_TOKEN.test(value));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** True when the program may ask for a WebGL context (not just blending). */
|
|
52
|
+
export function hasWebglUsage(usage: ProgramUsage): boolean {
|
|
53
|
+
if (usage.dynamicMarkup) return true;
|
|
54
|
+
if (usage.getContext.some((kind) => kind === undefined || /webgl/i.test(kind))) return true;
|
|
55
|
+
return usage.strings.some((value) => /webgl/i.test(value));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** True when the program may draw a bitmap or SVG. */
|
|
59
|
+
export function hasImageUsage(usage: ProgramUsage): boolean {
|
|
60
|
+
if (usage.dynamicMarkup) return true;
|
|
61
|
+
for (const name of ["Image", "ImageBitmap", "createImageBitmap", "drawImage"]) {
|
|
62
|
+
if (usage.identifiers.has(name)) return true;
|
|
63
|
+
}
|
|
64
|
+
return usage.strings.some((value) =>
|
|
65
|
+
/<img\b|<svg\b|background-image|url\(|\.svg\b|drawImage|createImageBitmap/i.test(value),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** True when the program may need full OpenType shaping. Non-Latin text needs it
|
|
70
|
+
* for shaping, and an author `@font-face` often points at a CFF/OTF font that
|
|
71
|
+
* the default stb_truetype backend cannot outline. */
|
|
72
|
+
export function hasShapingUsage(usage: ProgramUsage): boolean {
|
|
73
|
+
if (usage.dynamicMarkup) return true;
|
|
74
|
+
return usage.strings.some((value) => {
|
|
75
|
+
if (/@font-face\b/i.test(value)) return true;
|
|
76
|
+
for (const character of value) {
|
|
77
|
+
if ((character.codePointAt(0) ?? 0) > 0x7f) return true;
|
|
78
|
+
}
|
|
79
|
+
return /\\u\{[0-9a-fA-F]+\}|\\u[0-9a-fA-F]{4}|&#(?:x[0-9a-fA-F]+|\d+);/.test(value);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Absolute path to the per-platform prebuilt media archive. */
|
|
84
|
+
function mediaArchive(): string {
|
|
85
|
+
return join(runtimeLibDir(), process.platform === "win32" ? "media.lib" : "media.a");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export const guiLinkFeatures: readonly LinkFeature[] = [
|
|
89
|
+
{
|
|
90
|
+
name: "media",
|
|
91
|
+
description: "FFmpeg decoding for <video>/<audio>",
|
|
92
|
+
/**
|
|
93
|
+
* The registration bridge is a tiny C file compiled and linked only when
|
|
94
|
+
* the feature is active. Its constructor installs the decoder table into
|
|
95
|
+
* the engine and references the backend entry points, which is what makes
|
|
96
|
+
* the linker extract `media.a` — an archive whose only entry point is a
|
|
97
|
+
* constructor would otherwise never be pulled in.
|
|
98
|
+
*/
|
|
99
|
+
runtimeSources: () => [join(findRuntimeDir(), "ext_media", "media_bridge.c")],
|
|
100
|
+
nativeObjects: () => {
|
|
101
|
+
const archive = findRuntimeLibrary("media");
|
|
102
|
+
if (archive === undefined) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`media native library not found at ${mediaArchive()} (media.a or media.lib).\n` +
|
|
105
|
+
`The optional @xbintsc/${platformSlug()} package ships it: reinstall xbintsc ` +
|
|
106
|
+
`without --omit=optional, or build it with \`npm run media\` (see doc/gui/build.md).`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
return [archive];
|
|
110
|
+
},
|
|
111
|
+
/** `media.a` is C++ and FFmpeg itself needs libm; the GUI extension already
|
|
112
|
+
* contributes the C++ runtime and the OS frameworks. On Windows the FFmpeg
|
|
113
|
+
* objects reference the Win32 crypto/COM services (`bcrypt`, `ole32`) and
|
|
114
|
+
* `user32`. */
|
|
115
|
+
linkerFlags: () => (process.platform === "win32" ? ["-lbcrypt", "-lole32", "-luser32"] : ["-lm"]),
|
|
116
|
+
used: hasMediaUsage,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "gpu",
|
|
120
|
+
description: "GLSL translation (WebGL, mix-blend-mode and backdrop-filter) via glslang",
|
|
121
|
+
/**
|
|
122
|
+
* `gpu_bridge.c` is linked only when the feature is active. It calls into
|
|
123
|
+
* `gpu_feature.cpp` inside `gui.a`, which installs the shader-translation
|
|
124
|
+
* table and references `compile_glsl`/`cross_compile_msl`; those references
|
|
125
|
+
* are what pull the glslang members out of `gui.a`. Without the bridge the
|
|
126
|
+
* engine's shader paths see a null table and fail gracefully (blend falls
|
|
127
|
+
* back, WebGL reports a compile log).
|
|
128
|
+
*/
|
|
129
|
+
runtimeSources: () => [join(findRuntimeDir(), "ext_gui", "gpu_bridge.c")],
|
|
130
|
+
used: hasGpuUsage,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "images",
|
|
134
|
+
description: "stb_image/NanoSVG decoding for <img>, CSS backgrounds and inline <svg>",
|
|
135
|
+
/**
|
|
136
|
+
* `image_bridge.c` installs `XtImageApi` and references the decoders; a page
|
|
137
|
+
* with no `<img>`/SVG/`background-image`/`drawImage` links neither
|
|
138
|
+
* stb_image nor NanoSVG.
|
|
139
|
+
*/
|
|
140
|
+
runtimeSources: () => [join(findRuntimeDir(), "ext_gui", "image_bridge.c")],
|
|
141
|
+
used: hasImageUsage,
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: "shaping",
|
|
145
|
+
description: "HarfBuzz/FreeType shaping for complex scripts and CFF/OTF fonts",
|
|
146
|
+
/**
|
|
147
|
+
* `text_bridge.c` installs `TextApi` and references `text_hb.cpp`, pulling
|
|
148
|
+
* HarfBuzz and FreeType out of `gui.a`. A Latin-only page keeps the faster
|
|
149
|
+
* stb_truetype path and links neither library.
|
|
150
|
+
*/
|
|
151
|
+
runtimeSources: () => [join(findRuntimeDir(), "ext_gui", "text_bridge.c")],
|
|
152
|
+
used: hasShapingUsage,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "webgl",
|
|
156
|
+
description: "WebGL context and DOM API (requires gpu)",
|
|
157
|
+
requires: ["gpu"],
|
|
158
|
+
/**
|
|
159
|
+
* `webgl_bridge.c` installs `XtWebglApi` and references the WebGL context
|
|
160
|
+
* and DOM bindings, pulling them out of `gui.a`. A page that never asks for
|
|
161
|
+
* WebGL therefore links none of `webgl_*`/`dom_api_webgl*`.
|
|
162
|
+
*/
|
|
163
|
+
runtimeSources: () => [join(findRuntimeDir(), "ext_gui", "webgl_bridge.c")],
|
|
164
|
+
used: hasWebglUsage,
|
|
165
|
+
},
|
|
166
|
+
];
|
|
@@ -27,6 +27,7 @@ import { platformSlug } from "../../driver/paths.js";
|
|
|
27
27
|
import { findRuntimeLibrary, runtimeLibDir } from "../../driver/runtime-lib.js";
|
|
28
28
|
import { loadHtmlAsset } from "./html.js";
|
|
29
29
|
import { loadScriptAsset } from "./three.js";
|
|
30
|
+
import { guiLinkFeatures } from "./features.js";
|
|
30
31
|
|
|
31
32
|
/** Top-level `gui` module bindings. */
|
|
32
33
|
const guiModule: ExtensionModule = {
|
|
@@ -141,9 +142,8 @@ export const guiExtension: Extension = {
|
|
|
141
142
|
},
|
|
142
143
|
/**
|
|
143
144
|
* System libraries the archive itself depends on. The vendored third-party
|
|
144
|
-
* libraries (GPU backend,
|
|
145
|
-
* by its build script, so only OS frameworks remain here.
|
|
146
|
-
* the exact set once the GPU backend is chosen (see doc/gui.md).
|
|
145
|
+
* libraries (GPU backend, stb_truetype, stb_image/NanoSVG, ...) are linked
|
|
146
|
+
* *into* `gui.a` by its build script, so only OS frameworks remain here.
|
|
147
147
|
*/
|
|
148
148
|
linkerFlags: () => {
|
|
149
149
|
if (process.platform === "darwin") {
|
|
@@ -162,7 +162,7 @@ export const guiExtension: Extension = {
|
|
|
162
162
|
"-framework", "GameController",
|
|
163
163
|
"-framework", "Metal",
|
|
164
164
|
"-framework", "QuartzCore",
|
|
165
|
-
/*
|
|
165
|
+
/* Cocoa/system text and graphics services used by SDL. */
|
|
166
166
|
"-framework", "CoreText",
|
|
167
167
|
"-framework", "CoreGraphics",
|
|
168
168
|
"-framework", "CoreFoundation",
|
|
@@ -216,4 +216,10 @@ export const guiExtension: Extension = {
|
|
|
216
216
|
globalValues: () => browserGlobals,
|
|
217
217
|
/** Rewrite imported `.html` assets and patch three.js's shader unroller. */
|
|
218
218
|
assetLoaders: () => ({ ".html": loadHtmlAsset, ".js": loadScriptAsset, ".mjs": loadScriptAsset }),
|
|
219
|
+
/**
|
|
220
|
+
* FFmpeg and glslang are optional halves of the engine, selected from the
|
|
221
|
+
* program (see `features.ts`). A program that only renders HTML/CSS never
|
|
222
|
+
* links either.
|
|
223
|
+
*/
|
|
224
|
+
linkFeatures: () => guiLinkFeatures,
|
|
219
225
|
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional link features: runtime capabilities an extension can contribute
|
|
3
|
+
* without making every program pay for them.
|
|
4
|
+
*
|
|
5
|
+
* A feature is *not* an extension: the extension is the compile-time unit
|
|
6
|
+
* (`--ext gui`), while a feature is a heavy optional half of it (`FFmpeg`,
|
|
7
|
+
* `glslang`). The driver decides which features a program may use from a static
|
|
8
|
+
* scan of the bundled AST (see `src/driver/usage.ts`) and then links the
|
|
9
|
+
* feature's registration object and archives only for those.
|
|
10
|
+
*
|
|
11
|
+
* The registration object is the "strong reference": it is a C translation unit
|
|
12
|
+
* linked directly only when the feature is active. Its constructor installs the
|
|
13
|
+
* feature's dispatch table into the core, and its references force the linker to
|
|
14
|
+
* extract the archive members that implement the feature. This is the same
|
|
15
|
+
* mechanism the former `media` extension used for `media_bridge.c`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Facts about a bundled program that features use to decide whether they are needed. */
|
|
19
|
+
export interface ProgramUsage {
|
|
20
|
+
/**
|
|
21
|
+
* Every string literal value in the bundle, including the HTML/CSS of
|
|
22
|
+
* imported `.html` assets and the static chunks of template literals.
|
|
23
|
+
*/
|
|
24
|
+
readonly strings: readonly string[];
|
|
25
|
+
/** Identifiers and property names referenced anywhere in the bundle. */
|
|
26
|
+
readonly identifiers: ReadonlySet<string>;
|
|
27
|
+
/**
|
|
28
|
+
* One entry per `getContext(...)` call: the context kind when the argument
|
|
29
|
+
* could be resolved to a literal, or `undefined` when it is decided at run
|
|
30
|
+
* time (which must be treated conservatively as "might be WebGL").
|
|
31
|
+
*/
|
|
32
|
+
readonly getContext: readonly (string | undefined)[];
|
|
33
|
+
/**
|
|
34
|
+
* True when the program can build HTML/CSS from data the compiler cannot
|
|
35
|
+
* see (`innerHTML = fetched`, `loadHTML(buildPage())`, …). Features must
|
|
36
|
+
* assume they may be used when this is set.
|
|
37
|
+
*/
|
|
38
|
+
readonly dynamicMarkup: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* One optional capability an extension can contribute.
|
|
43
|
+
*
|
|
44
|
+
* `used()` is only consulted when the providing extension is enabled. When it
|
|
45
|
+
* returns true (or the user forces the feature) the driver compiles
|
|
46
|
+
* `runtimeSources()` and links `nativeObjects()`, which makes the implementations
|
|
47
|
+
* reachable. Nothing else about the feature exists at compile time.
|
|
48
|
+
*/
|
|
49
|
+
export interface LinkFeature {
|
|
50
|
+
/** Stable name used by `--feature` / `--no-feature`, e.g. `media`. */
|
|
51
|
+
readonly name: string;
|
|
52
|
+
/** One-line description for diagnostics and `--verbose`. */
|
|
53
|
+
readonly description: string;
|
|
54
|
+
/**
|
|
55
|
+
* C sources compiled and linked only when the feature is active. At least one
|
|
56
|
+
* of them must reference the feature's implementation so the linker extracts
|
|
57
|
+
* it from its archive.
|
|
58
|
+
*/
|
|
59
|
+
runtimeSources?(): readonly string[];
|
|
60
|
+
/**
|
|
61
|
+
* Pre-built objects/archives linked only when the feature is active. Throwing
|
|
62
|
+
* here means "the feature is unavailable on this host"; the driver turns that
|
|
63
|
+
* into a warning for auto-detected features and an error for forced ones.
|
|
64
|
+
*/
|
|
65
|
+
nativeObjects?(): readonly string[];
|
|
66
|
+
/** Extra linker flags contributed only when the feature is active. */
|
|
67
|
+
linkerFlags?(): readonly string[];
|
|
68
|
+
/** Other link features this one needs (activated with it). */
|
|
69
|
+
readonly requires?: readonly string[];
|
|
70
|
+
/** Whether the program may use this feature. */
|
|
71
|
+
used(usage: ProgramUsage): boolean;
|
|
72
|
+
}
|