Skip to content

Commit d7fbbf5

Browse files
authored
Code Analysis Upgrades and Fixes
- **cleaned up some comments** - **fix(render): skip child traversal when include runs; content takes precedence** - **test(render): add traversal tests and fixtures for include vs content semantics** - **chore(templates): remove redundant case-insensitive flag in template id sanitization** - **docs: document content vs include precedence, traversal behavior, and escaping** - **build: update dist/faintly.js** - **docs(agents): use build:strict in checklist; add size-check scripts and CI uses build:strict** - **chore(dev): update dev tooling and apply security overrides\n\n- @web/test-runner -> 0.20.2\n- @babel/eslint-parser -> 7.28.5\n- eslint-plugin-import -> 2.32.0\n- esbuild -> 0.25.11\n- overrides: koa@2.16.3, @babel/helpers@^7.28.4\n- refresh lockfile\n\nAll tests pass; build stays under size limit.,** - **chore: deps updates** - **build: use .mjs scripts with watch and size-check; lint: include .mjs and node env for scripts; size: add core+total gzip caps** - **chore(release): 1.0.1** - **chore: update ESLint configuration to require .js extensions globally and allow .mjs extensions in build scripts; enhance build script to conditionally handle security file**
1 parent 89e6602 commit d7fbbf5

File tree

17 files changed

+811
-499
lines changed

17 files changed

+811
-499
lines changed

.eslintrc.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,19 @@ module.exports = {
1111
requireConfigFile: false,
1212
},
1313
rules: {
14-
'import/extensions': ['error', { js: 'always' }], // require js file extensions in imports
14+
// Globally require .js file extensions in imports
15+
'import/extensions': ['error', { js: 'always' }],
1516
'linebreak-style': ['error', 'unix'], // enforce unix linebreaks
1617
'no-param-reassign': [2, { props: false }], // allow modifying properties of param
1718
},
19+
overrides: [
20+
{
21+
files: ['scripts/**/*.mjs', 'scripts/**/*.js'],
22+
env: { node: true },
23+
rules: {
24+
// Allow .mjs extensions in build scripts only
25+
'import/extensions': ['error', { js: 'always', mjs: 'always' }],
26+
},
27+
},
28+
],
1829
};

.github/workflows/main.yaml

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,7 @@ jobs:
2222
- run: npm ci
2323
- run: npm run lint
2424
- run: npm run test
25-
- run: npm run build
26-
- name: Check gzipped dist size
27-
run: |
28-
gz_size=$(gzip -c dist/faintly.js | wc -c)
29-
echo "Gzipped size: ${gz_size} bytes"
30-
if [ "$gz_size" -gt 5120 ]; then
31-
echo "Error: dist/faintly.js gzipped size ${gz_size} exceeds 5KB (5120 bytes)"
32-
exit 1
33-
fi
25+
- run: npm run build:strict
3426
- name: Commit dist if changed
3527
run: |
3628
git config user.name "github-actions[bot]"

AGENTS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ Authoritative guide for AI/code agents contributing to this repository.
1717
- **Lint (auto-fix)**: `npm run lint:fix`
1818
- **Unit tests + coverage**: `npm test`
1919
- **Performance tests**: `npm run test:perf`
20-
- **Build bundle**: `npm run build` → outputs `dist/faintly.js`
21-
- **Build (watch)**: `npm run build:watch`
20+
- **Build bundle**: `npm run build` → outputs `dist/faintly.js` and prints gzipped size (warns if over limit)
21+
- **Build (strict)**: `npm run build:strict` → fails if gzipped size exceeds 5120 bytes
2222
- **Clean**: `npm run clean`
2323

2424
### Tests and coverage
@@ -49,7 +49,7 @@ Authoritative guide for AI/code agents contributing to this repository.
4949

5050
### CI behavior (GitHub Actions)
5151
- Workflow: `.github/workflows/main.yaml` runs on pull requests (open/sync/reopen).
52-
- Steps: checkout → Node 20 → `npm ci``npm run lint``npm test``npm run build` → gzip size check (<= 5120 bytes).
52+
- Steps: checkout → Node 20 → `npm ci``npm run lint``npm test``npm run build:strict`.
5353
- The workflow will attempt to commit updated `dist/` artifacts back to the PR branch if they changed.
5454

5555
### Repo layout
@@ -63,7 +63,7 @@ Authoritative guide for AI/code agents contributing to this repository.
6363
2. Make focused edits under `src/` and relevant tests under `test/`.
6464
3. Run `npm run lint:fix` then `npm run lint` and resolve any remaining issues.
6565
4. Run `npm test` and ensure coverage stays at 100%.
66-
5. Run `npm run build` and verify `dist/faintly.js` updates (if source changed).
66+
5. Run `npm run build:strict` and verify `dist/faintly.js` updates (if source changed).
6767
6. Ensure gzipped size of `dist/faintly.js` remains <= 5120 bytes (CI will enforce).
6868
7. Update `README.md` if you change public behavior or usage.
6969
8. Commit changes; open a PR. CI will validate and may commit updated `dist/` to the PR branch.

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ Faintly supports the following directives.
7979
* `data-fly-repeat` - Repeat an element for each item of a collection. Attribute value should be an expression that resolves to a collection of Nodes/Elements.
8080
* `data-fly-attributes` - Set attributes on the element. Attribute value should be an expression that resolves to a collection of key/value pairs.
8181
* `data-fly-content` - Replace the elements content/children. Attribute value should be an expression that resolves to a Node/Element/String, or a collection there-of.
82+
* Content has precedence over include: if both `data-fly-content` and `data-fly-include` are present on the same element, only content is executed.
8283
* `data-fly-include` - Replace the elements content/children with another template. Attribute value can be:
8384
* the name of a template: `data-fly-include="a-template-name"`
8485
* the absolute path to a template file: `data-fly-include="/blocks/some-block/some-template.html"`
@@ -89,11 +90,15 @@ Faintly supports the following directives.
8990
> Directives are evaluated in a fixed order, as listed above, regardless of the order you place them on the element.
9091
>
9192
> This means, for example, that the context item set in `data-fly-repeat` can be used in `data-fly-include` on the same element, but not in a `data-fly-test`.
93+
>
94+
> When `data-fly-include` runs, the included template is fully rendered before being inserted and the element's children are not traversed again. This prevents double-processing. Conversely, when `data-fly-content` runs, the injected nodes are traversed so that any directives/expressions inside them are processed.
9295
9396
## Expressions
9497

9598
Faintly supports a simple expression syntax for resolving data from the rendering context. It supports only object dot-notation, but will call (optionally async) functions as well. This means that if you need to do something that can't be expressed in dot-notation, then you need to define a custom function for it, and add that function to the rendering context.
9699

97-
For `data-fly-include`, HTML text, and normal attributes, wrap your expression in `${}`.
100+
For `data-fly-include`, HTML text, and normal attributes, wrap your expression in `${}`.
101+
102+
Escaping: use a leading backslash to prevent evaluation of an expression in text/attributes, e.g. `\${some.value}` will remain literal `${some.value}`.
98103

99104
In all other `data-fly-*` attributes, just set the expression directly as the attribute value, no wrapping needed.

dist/faintly.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ var dp = new DOMParser();
33
async function resolveTemplate(context) {
44
context.template = context.template || {};
55
context.template.path = context.template.path || `${context.codeBasePath}/blocks/${context.blockName}/${context.blockName}.html`;
6-
const templateId = `faintly-template-${context.template.path}#${context.template.name || ""}`.toLowerCase().replace(/[^0-9a-z]/gi, "-");
6+
const templateId = `faintly-template-${context.template.path}#${context.template.name || ""}`.toLowerCase().replace(/[^0-9a-z]/g, "-");
77
let template = document.getElementById(templateId);
88
if (!template) {
99
const resp = await fetch(context.template.path);
@@ -12,7 +12,7 @@ async function resolveTemplate(context) {
1212
const templateDom = dp.parseFromString(markup, "text/html");
1313
templateDom.querySelectorAll("template").forEach((t) => {
1414
const name = t.getAttribute("data-fly-name") || "";
15-
t.id = `faintly-template-${context.template.path}#${name}`.toLowerCase().replace(/[^0-9a-z]/gi, "-");
15+
t.id = `faintly-template-${context.template.path}#${name}`.toLowerCase().replace(/[^0-9a-z]/g, "-");
1616
document.body.append(t);
1717
});
1818
}
@@ -198,7 +198,9 @@ async function processNode(node, context) {
198198
const repeated = await processRepeat(node, context);
199199
if (repeated) return;
200200
await processAttributes(node, context);
201-
processChildren = await processContent(node, context) || await processInclude(node, context) || true;
201+
const hadContent = await processContent(node, context);
202+
const hadInclude = hadContent ? false : await processInclude(node, context);
203+
processChildren = !hadInclude;
202204
await resolveUnwrap(node, context);
203205
} else if (node.nodeType === Node.TEXT_NODE) {
204206
await processTextExpressions(node, context);

0 commit comments

Comments
 (0)