@microsoft/fast-build 0.7.0 → 0.8.0-fast-element-v3-rc-20260615

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/README.md CHANGED
@@ -38,9 +38,10 @@ fast build [options]
38
38
  | `--entry="<path>"` | `index.html` | Entry HTML template to render |
39
39
  | `--state="<path>"` | `{}` when omitted | JSON file containing the template state. If omitted, no state file is loaded and rendering uses an empty state object. If `--state` is provided, the file must exist. |
40
40
  | `--output="<path>"` | `output.html` | Where to write the rendered HTML |
41
- | `--templates="<glob>"` | _(none)_ | Glob pattern(s) for custom element template HTML files. Separate multiple patterns with commas. A warning is printed if not provided or if no files match a pattern. |
41
+ | `--templates="<glob>"` | _(none)_ | Glob pattern(s) for custom element template HTML files. Separate multiple patterns with commas. A warning is printed if not provided in non-streaming mode or if no files match a pattern. |
42
42
  | `--attribute-name-strategy="<strategy>"` | `camelCase` | Strategy for mapping HTML attribute names to state property names on custom elements. `"camelCase"` converts dashes to camelCase (e.g. `foo-bar` → `fooBar`). `"none"` preserves dashes (e.g. `foo-bar` → `foo-bar`). See [Attribute name strategy](#attribute-name-strategy). |
43
43
  | `--config="<path>"` | `fast-build.config.json` | Path to a JSON configuration file. If omitted, `fast-build.config.json` in the current directory is used when present. CLI arguments take precedence over config values. See [Configuration file](#configuration-file). |
44
+ | `--stream[=true/false]` | `false` | Write rendered HTML chunks directly to stdout instead of writing `--output`. Valueless `--stream` is treated as `true`. |
44
45
 
45
46
  ### Example
46
47
 
@@ -81,6 +82,32 @@ Produces `output.html`:
81
82
  </html>
82
83
  ```
83
84
 
85
+ ### Streaming output
86
+
87
+ Pass `--stream`, `--stream=true`, or `--stream=false`, or set
88
+ `"stream": true` in `fast-build.config.json`, to control stdout streaming. When
89
+ streaming is enabled, the CLI writes raw rendered HTML chunks to stdout, does
90
+ not write the `--output` file, and does not print the normal `Built: ...`
91
+ message.
92
+
93
+ ```shell
94
+ fast build --entry=index.html --state=state.json --stream
95
+ ```
96
+
97
+ Streaming is simulated by the WebAssembly renderer: chunks are prepared in
98
+ memory, returned to JavaScript as a JSON array, parsed by the CLI, and then
99
+ written to stdout. `--stream` uses the same entry renderer with its stream flag
100
+ enabled and passes an empty templates map when no `--templates` glob is
101
+ provided. Chunk boundaries are safe for HTML parsing:
102
+
103
+ - Attribute bindings stay within complete opening tag chunks.
104
+ - Custom element opening chunks include the complete Declarative Shadow DOM
105
+ template.
106
+ - Streamed output uses the same renderer preprocessing and custom-element host
107
+ attribute propagation as normal output.
108
+ - Empty chunks are omitted.
109
+ - Concatenating all chunks matches the normal non-streamed render.
110
+
84
111
  ### Missing or omitted state
85
112
 
86
113
  State is optional. When `--state` is omitted and no `state` path is provided by config, `fast build` does not look for `state.json`; rendering uses an empty object (`{}`). An explicit `--state` path, or a `state` path from config, must exist or the command exits with an error.
@@ -117,7 +144,7 @@ Template files must use the following format:
117
144
  </f-template>
118
145
  ```
119
146
 
120
- If an `<f-template>` element has no `name` attribute, a warning is printed and it is ignored. Exact file paths (no wildcards) are also accepted as patterns, making it possible to register a single template file.
147
+ If an `<f-template>` element has no `name` attribute, a warning is printed and it is ignored. Exact file paths (no wildcards) are also accepted as patterns, making it possible to register a single template file. The parser follows browser tag boundaries for the `<f-template>` and inner `<template>` wrappers, including ASCII whitespace before `>` in opening and closing tags.
121
148
 
122
149
  Any `shadowroot*` attributes on `<f-template>` are forwarded to the rendered Declarative Shadow DOM `<template>`. The CLI normalizes `shadowrootmode` and legacy `shadowroot` for compatibility: when neither has a non-empty value, it emits `shadowrootmode="open" shadowroot="open"`; when exactly one has a non-empty value, that value is mirrored to the other; when both have explicit non-empty values, both are preserved as authored, even if they conflict.
123
150
 
@@ -167,8 +194,10 @@ fast build --config=configs/my-build.json
167
194
 
168
195
  **Path resolution:** File paths in the config file (`entry`, `state`, `output`, `templates`) are resolved relative to the config file's directory, not the current working directory. This ensures the config works correctly regardless of where the CLI is invoked.
169
196
 
170
- All keys are optional. Only the following keys are allowed: `entry`, `state`, `output`, `templates`, `attribute-name-strategy`. Unknown keys or non-string values produce an error. If `state` is omitted, rendering uses `{}`; if `state` is present, the referenced file must exist.
197
+ All keys are optional. Only the following keys are allowed: `entry`, `state`, `output`, `templates`, `attribute-name-strategy`, and `stream`. Unknown keys produce an error. Values must be strings except `stream`, which must be a JSON boolean (`true` or `false`). If `state` is omitted, rendering uses `{}`; if `state` is present, the referenced file must exist. CLI arguments always override config values, including `--stream=false` overriding `"stream": true`.
171
198
 
172
199
  ## Template syntax
173
200
 
174
- Template syntax follows the FAST declarative HTML format. See the [`@microsoft/fast-html` README](../fast-html/README.md) for full documentation on bindings, conditionals, repeats, and directives.
201
+ Template syntax follows the FAST declarative HTML format. See the
202
+ [`@microsoft/fast-element` declarative documentation](../fast-element/DECLARATIVE_HTML.md)
203
+ for full documentation on bindings, conditionals, repeats, and directives.
package/bin/fast.js CHANGED
@@ -7,16 +7,21 @@ const path = require("path");
7
7
 
8
8
  const WASM_MODULE = path.join(__dirname, "../wasm/microsoft_fast_build.js");
9
9
  const DEFAULT_CONFIG_FILENAME = "fast-build.config.json";
10
+ const VALUELESS_CLI_FLAGS = new Set(["stream"]);
10
11
  const ALLOWED_CONFIG_KEYS = new Set([
11
12
  "entry",
12
13
  "state",
13
14
  "output",
14
15
  "templates",
15
16
  "attribute-name-strategy",
17
+ "stream",
16
18
  ]);
17
19
 
20
+ /** @typedef {Record<string, string | boolean>} FastBuildConfig */
21
+
18
22
  /**
19
- * Parse CLI arguments of the form --key="value" or --key=value.
23
+ * Parse CLI arguments of the form --key="value", --key=value, or supported
24
+ * valueless flags like --stream.
20
25
  * @param {string[]} argv
21
26
  * @returns {Record<string, string>}
22
27
  */
@@ -26,11 +31,48 @@ function parseArgs(argv) {
26
31
  const match = arg.match(/^--([^=]+)=(.*)$/s);
27
32
  if (match) {
28
33
  args[match[1]] = match[2].replace(/^"|"$/g, "");
34
+ continue;
35
+ }
36
+
37
+ const flag = arg.match(/^--([^=]+)$/s);
38
+ if (flag && VALUELESS_CLI_FLAGS.has(flag[1])) {
39
+ args[flag[1]] = "true";
29
40
  }
30
41
  }
31
42
  return args;
32
43
  }
33
44
 
45
+ /**
46
+ * Resolve a boolean option, preferring CLI args over config values.
47
+ * CLI values accept true, false, or an empty valueless flag.
48
+ * @param {Record<string, string>} args - Parsed CLI arguments.
49
+ * @param {FastBuildConfig} config - Parsed config file values.
50
+ * @param {string} key - The option key.
51
+ * @returns {boolean}
52
+ */
53
+ function resolveBooleanOption(args, config, key) {
54
+ if (Object.prototype.hasOwnProperty.call(args, key)) {
55
+ const value = args[key].trim().toLowerCase();
56
+ if (value === "true" || value === "") {
57
+ return true;
58
+ }
59
+ if (value === "false") {
60
+ return false;
61
+ }
62
+
63
+ process.stderr.write(
64
+ `Error: Invalid --${key} value "${args[key]}". Expected "true" or "false".\n`
65
+ );
66
+ process.exit(1);
67
+ }
68
+
69
+ if (Object.prototype.hasOwnProperty.call(config, key)) {
70
+ return config[key] === true;
71
+ }
72
+
73
+ return false;
74
+ }
75
+
34
76
  /**
35
77
  * Load and validate a fast-build config file.
36
78
  *
@@ -42,7 +84,7 @@ function parseArgs(argv) {
42
84
  * file's directory so that the caller can use them directly.
43
85
  *
44
86
  * @param {string | undefined} configPath - Explicit path from --config, if any.
45
- * @returns {{ config: Record<string, string>, configDir: string | null }}
87
+ * @returns {{ config: FastBuildConfig, configDir: string | null }}
46
88
  */
47
89
  function loadConfig(configPath) {
48
90
  /** @type {string} */
@@ -92,6 +134,16 @@ function loadConfig(configPath) {
92
134
  );
93
135
  process.exit(1);
94
136
  }
137
+ if (key === "stream") {
138
+ if (typeof raw[key] !== "boolean") {
139
+ process.stderr.write(
140
+ `Error: Value for "${key}" in config file "${resolvedPath}" must be a boolean.\n`
141
+ );
142
+ process.exit(1);
143
+ }
144
+ continue;
145
+ }
146
+
95
147
  if (typeof raw[key] !== "string") {
96
148
  process.stderr.write(
97
149
  `Error: Value for "${key}" in config file "${resolvedPath}" must be a string.\n`
@@ -110,7 +162,7 @@ function loadConfig(configPath) {
110
162
  * CLI-derived paths are resolved relative to CWD (the default behaviour).
111
163
  *
112
164
  * @param {Record<string, string>} args - Parsed CLI arguments.
113
- * @param {Record<string, string>} config - Parsed config file values.
165
+ * @param {FastBuildConfig} config - Parsed config file values.
114
166
  * @param {string | null} configDir - Directory of the config file, or null.
115
167
  * @param {string} key - The option key.
116
168
  * @param {string} [defaultValue] - Fallback when neither source provides a value.
@@ -122,6 +174,9 @@ function resolveOption(args, config, configDir, key, defaultValue) {
122
174
  }
123
175
  if (Object.prototype.hasOwnProperty.call(config, key)) {
124
176
  const value = config[key];
177
+ if (typeof value !== "string") {
178
+ return defaultValue;
179
+ }
125
180
  const isFilePath = key === "entry" || key === "state" || key === "output" || key === "templates";
126
181
  if (isFilePath && configDir !== null) {
127
182
  return path.resolve(configDir, value);
@@ -288,6 +343,7 @@ async function runBuild(args) {
288
343
  Object.prototype.hasOwnProperty.call(args, "state") ||
289
344
  Object.prototype.hasOwnProperty.call(config, "state");
290
345
  const attributeNameStrategy = resolveOption(args, config, configDir, "attribute-name-strategy");
346
+ const stream = resolveBooleanOption(args, config, "stream");
291
347
 
292
348
  if (attributeNameStrategy && attributeNameStrategy !== "none" && attributeNameStrategy !== "camelCase") {
293
349
  process.stderr.write(
@@ -302,9 +358,11 @@ async function runBuild(args) {
302
358
  // Resolve template files
303
359
  const templatesMap = {};
304
360
  if (!templatesArg) {
305
- process.stderr.write(
306
- "Warning: --templates was not provided. No custom element templates will be loaded.\n"
307
- );
361
+ if (!stream) {
362
+ process.stderr.write(
363
+ "Warning: --templates was not provided. No custom element templates will be loaded.\n"
364
+ );
365
+ }
308
366
  } else {
309
367
  const patterns = templatesArg.split(",").map((p) => p.trim());
310
368
  for (const pattern of patterns) {
@@ -347,6 +405,49 @@ async function runBuild(args) {
347
405
  }
348
406
 
349
407
  // Render
408
+ if (stream) {
409
+ if (typeof wasm.render_entry_with_templates !== "function") {
410
+ process.stderr.write(
411
+ "Error: Streaming requires render_entry_with_templates to be exported by the WASM module.\n"
412
+ );
413
+ process.exit(1);
414
+ }
415
+
416
+ const renderedChunks = wasm.render_entry_with_templates(
417
+ entryContent,
418
+ JSON.stringify(templatesMap),
419
+ stateContent,
420
+ attributeNameStrategy || "",
421
+ true,
422
+ );
423
+
424
+ /** @type {unknown} */
425
+ let chunks;
426
+ try {
427
+ chunks = JSON.parse(renderedChunks);
428
+ } catch (e) {
429
+ process.stderr.write(
430
+ `Error: Streaming renderer returned invalid JSON: ${e.message}\n`
431
+ );
432
+ process.exit(1);
433
+ }
434
+
435
+ if (
436
+ !Array.isArray(chunks) ||
437
+ chunks.some((chunk) => typeof chunk !== "string")
438
+ ) {
439
+ process.stderr.write(
440
+ "Error: Streaming renderer must return a JSON array of strings.\n"
441
+ );
442
+ process.exit(1);
443
+ }
444
+
445
+ for (const chunk of chunks) {
446
+ process.stdout.write(chunk);
447
+ }
448
+ return;
449
+ }
450
+
350
451
  let rendered;
351
452
  if (Object.keys(templatesMap).length > 0) {
352
453
  rendered = wasm.render_entry_with_templates(
@@ -376,6 +477,8 @@ async function main() {
376
477
  ' --entry="index.html" Entry HTML template file (default: index.html)\n' +
377
478
  ' --state="state.json" State JSON file. If omitted, rendering uses\n' +
378
479
  ' an empty state object.\n' +
480
+ ' --stream[=true|false] Write rendered HTML chunks to stdout instead\n' +
481
+ ' of writing an output file.\n' +
379
482
  ' --attribute-name-strategy="camelCase"\n' +
380
483
  ' Strategy for mapping attribute names to property names.\n' +
381
484
  ' "camelCase" (default) or "none".\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/fast-build",
3
- "version": "0.7.0",
3
+ "version": "0.8.0-fast-element-v3-rc-20260615",
4
4
  "description": "CLI and Node.js API for server-side rendering of FAST declarative HTML templates.",
5
5
  "author": {
6
6
  "name": "Microsoft",
@@ -1,6 +1,29 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
 
4
+ /**
5
+ * Apply the FAST code-sample escape to an HTML string.
6
+ *
7
+ * Walks the input HTML and, for every `<code>` element, escapes:
8
+ *
9
+ * * curly braces (`{` → `&#123;`, `}` → `&#125;`) in text content and
10
+ * attribute values — so the FAST template parser does not interpret
11
+ * `{{ ... }}` / `{ ... }` inside code samples as bindings;
12
+ * * angle brackets of FAST directive tags (`<f-when>`, `</f-when>`,
13
+ * `<f-repeat>`, `</f-repeat>`, case-insensitive) — so those directive
14
+ * tags surface as literal text instead of being activated as real
15
+ * elements. Angle brackets of any other tag (`<button>`, custom
16
+ * elements, …) are left untouched so they keep rendering as live DOM.
17
+ *
18
+ * Non-`<code>` regions of the input are returned verbatim. The function
19
+ * is idempotent — running it on an already-escaped string is a no-op.
20
+ *
21
+ * Intended for build-time tooling that needs to inject author-written
22
+ * HTML containing `<code>` samples into a rendered page without going
23
+ * through the full `render_*` pipeline.
24
+ */
25
+ export function escape_code_samples(html: string): string;
26
+
4
27
  /**
5
28
  * Parse all `<f-template>` elements from an HTML string.
6
29
  * Returns a JSON array of template metadata objects,
@@ -28,9 +51,11 @@ export function render(entry: string, state?: string | null): string;
28
51
  * e.g. `{"my-button": "<template>...</template>"}`, or template metadata objects.
29
52
  * `attribute_name_strategy` controls attribute-to-property mapping: `"camelCase"` (default)
30
53
  * or `"none"`. Pass an empty string for the default.
31
- * Returns the rendered HTML or throws a JavaScript error.
54
+ * Pass `stream: true` to return a JSON array string of stream chunks; in
55
+ * stream mode, `templates_json` may be `{}`. Omitted or `false` stream
56
+ * preserves the rendered HTML behavior.
32
57
  */
33
- export function render_entry_with_templates(entry: string, templates_json: string, state?: string | null, attribute_name_strategy?: string | null): string;
58
+ export function render_entry_with_templates(entry: string, templates_json: string, state?: string | null, attribute_name_strategy?: string | null, stream?: boolean | null): string;
34
59
 
35
60
  /**
36
61
  * Render a FAST HTML template with custom element templates and an optional JSON state string.
@@ -1,5 +1,44 @@
1
1
  /* @ts-self-types="./microsoft_fast_build.d.ts" */
2
2
 
3
+ /**
4
+ * Apply the FAST code-sample escape to an HTML string.
5
+ *
6
+ * Walks the input HTML and, for every `<code>` element, escapes:
7
+ *
8
+ * * curly braces (`{` → `&#123;`, `}` → `&#125;`) in text content and
9
+ * attribute values — so the FAST template parser does not interpret
10
+ * `{{ ... }}` / `{ ... }` inside code samples as bindings;
11
+ * * angle brackets of FAST directive tags (`<f-when>`, `</f-when>`,
12
+ * `<f-repeat>`, `</f-repeat>`, case-insensitive) — so those directive
13
+ * tags surface as literal text instead of being activated as real
14
+ * elements. Angle brackets of any other tag (`<button>`, custom
15
+ * elements, …) are left untouched so they keep rendering as live DOM.
16
+ *
17
+ * Non-`<code>` regions of the input are returned verbatim. The function
18
+ * is idempotent — running it on an already-escaped string is a no-op.
19
+ *
20
+ * Intended for build-time tooling that needs to inject author-written
21
+ * HTML containing `<code>` samples into a rendered page without going
22
+ * through the full `render_*` pipeline.
23
+ * @param {string} html
24
+ * @returns {string}
25
+ */
26
+ function escape_code_samples(html) {
27
+ let deferred2_0;
28
+ let deferred2_1;
29
+ try {
30
+ const ptr0 = passStringToWasm0(html, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
31
+ const len0 = WASM_VECTOR_LEN;
32
+ const ret = wasm.escape_code_samples(ptr0, len0);
33
+ deferred2_0 = ret[0];
34
+ deferred2_1 = ret[1];
35
+ return getStringFromWasm0(ret[0], ret[1]);
36
+ } finally {
37
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
38
+ }
39
+ }
40
+ exports.escape_code_samples = escape_code_samples;
41
+
3
42
  /**
4
43
  * Parse all `<f-template>` elements from an HTML string.
5
44
  * Returns a JSON array of template metadata objects,
@@ -68,14 +107,17 @@ exports.render = render;
68
107
  * e.g. `{"my-button": "<template>...</template>"}`, or template metadata objects.
69
108
  * `attribute_name_strategy` controls attribute-to-property mapping: `"camelCase"` (default)
70
109
  * or `"none"`. Pass an empty string for the default.
71
- * Returns the rendered HTML or throws a JavaScript error.
110
+ * Pass `stream: true` to return a JSON array string of stream chunks; in
111
+ * stream mode, `templates_json` may be `{}`. Omitted or `false` stream
112
+ * preserves the rendered HTML behavior.
72
113
  * @param {string} entry
73
114
  * @param {string} templates_json
74
115
  * @param {string | null} [state]
75
116
  * @param {string | null} [attribute_name_strategy]
117
+ * @param {boolean | null} [stream]
76
118
  * @returns {string}
77
119
  */
78
- function render_entry_with_templates(entry, templates_json, state, attribute_name_strategy) {
120
+ function render_entry_with_templates(entry, templates_json, state, attribute_name_strategy, stream) {
79
121
  let deferred6_0;
80
122
  let deferred6_1;
81
123
  try {
@@ -87,7 +129,7 @@ function render_entry_with_templates(entry, templates_json, state, attribute_nam
87
129
  var len2 = WASM_VECTOR_LEN;
88
130
  var ptr3 = isLikeNone(attribute_name_strategy) ? 0 : passStringToWasm0(attribute_name_strategy, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
89
131
  var len3 = WASM_VECTOR_LEN;
90
- const ret = wasm.render_entry_with_templates(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3);
132
+ const ret = wasm.render_entry_with_templates(ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, isLikeNone(stream) ? 0xFFFFFF : stream ? 1 : 0);
91
133
  var ptr5 = ret[0];
92
134
  var len5 = ret[1];
93
135
  if (ret[3]) {
Binary file
@@ -1,9 +1,10 @@
1
1
  /* tslint:disable */
2
2
  /* eslint-disable */
3
3
  export const memory: WebAssembly.Memory;
4
+ export const escape_code_samples: (a: number, b: number) => [number, number];
4
5
  export const parse_f_templates: (a: number, b: number) => [number, number];
5
6
  export const render: (a: number, b: number, c: number, d: number) => [number, number, number, number];
6
- export const render_entry_with_templates: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => [number, number, number, number];
7
+ export const render_entry_with_templates: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number, i: number) => [number, number, number, number];
7
8
  export const render_with_templates: (a: number, b: number, c: number, d: number, e: number, f: number, g: number, h: number) => [number, number, number, number];
8
9
  export const __wbindgen_externrefs: WebAssembly.Table;
9
10
  export const __wbindgen_malloc: (a: number, b: number) => number;