Skip to content

Commit f30d62b

Browse files
committed
Merge branch 'main' into v5
2 parents e7912c9 + 992e422 commit f30d62b

36 files changed

Lines changed: 1671 additions & 83 deletions

.github/workflows/publish-npm.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ on:
1515
description: 'Node.js version to use when publishing.'
1616
required: false
1717
type: string
18-
default: '20'
18+
default: '24'
1919
registry-url:
2020
description: 'Registry URL used for npm publish.'
2121
required: false

.github/workflows/release-production.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
- name: 🟢 Configure Node for Publish
5555
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
5656
with:
57-
node-version: '20'
57+
node-version: '24'
5858
registry-url: 'https://registry.npmjs.org'
5959

6060
- name: 🔄 Ensure Latest npm

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
1+
# 🐓 [4.44.0](https://github.com/stenciljs/core/compare/v4.43.5...v4.44.0) (2026-08-10)
2+
3+
4+
### Bug Fixes
5+
6+
* **compiler:** escape backslashes before other escape sequences in styles ([#6773](https://github.com/stenciljs/core/issues/6773)) ([348f226](https://github.com/stenciljs/core/commit/348f226f6f1502c5e3b93eb0a337a09ae04b06e4))
7+
* **compiler:** handle constructor overloads ([#6766](https://github.com/stenciljs/core/issues/6766)) ([1dca769](https://github.com/stenciljs/core/commit/1dca7692a6b279ef65156285e90fd9ea4852116f)), closes [#6712](https://github.com/stenciljs/core/issues/6712)
8+
* **compiler:** preserve bundler directive comments in es5 output ([#6779](https://github.com/stenciljs/core/issues/6779)) ([f82296d](https://github.com/stenciljs/core/commit/f82296d387ff78f588debd3bdd196b6f89ac76dd))
9+
* **compiler:** resolve inherited type references against the declaring file ([#6782](https://github.com/stenciljs/core/issues/6782)) ([267d88c](https://github.com/stenciljs/core/commit/267d88caf2345be90faaecef91f88d4cd439adec))
10+
* **compiler:** resolveVar follows import chain ([#6801](https://github.com/stenciljs/core/issues/6801)) ([85235c3](https://github.com/stenciljs/core/commit/85235c3a3e4d095004a6e44a4c72106cbb138b60))
11+
* **compiler:** too many required props / "union type too complex" error ([#6804](https://github.com/stenciljs/core/issues/6804)) ([74722db](https://github.com/stenciljs/core/commit/74722db38c86dbf52405d1ec2e544683631425f8))
12+
* **docs:** stop `toLowerCase()` shadow part names ([#6813](https://github.com/stenciljs/core/issues/6813)) ([77f4510](https://github.com/stenciljs/core/commit/77f45103adddc4518fce5dbb5185991a44db786f))
13+
* **runtime:** ensure non-shadow DOM getters return null instead of undefined ([#6792](https://github.com/stenciljs/core/issues/6792)) ([6b63cb3](https://github.com/stenciljs/core/commit/6b63cb32ad1373f96671b69199280eda1489aa8f))
14+
* **runtime:** guard against render reentrancy under `taskQueue: 'immediate'` ([#6817](https://github.com/stenciljs/core/issues/6817)) ([b9b5b55](https://github.com/stenciljs/core/commit/b9b5b55db716fa3f57a43a25a2a87d0b11318049))
15+
* **runtime:** memory leak on document.hidden ([#6811](https://github.com/stenciljs/core/issues/6811)) ([cda3b04](https://github.com/stenciljs/core/commit/cda3b0481fc26ffce9113009cca9a9883efe9040))
16+
* **runtime:** retry lazy component load after a failed dynamic import ([#6772](https://github.com/stenciljs/core/issues/6772)) ([1be6b13](https://github.com/stenciljs/core/commit/1be6b13231a940217ba0968b5c531b4bbaff19ef))
17+
* **runtime:** stop forwarded named slot being added to nested default slot ([#6815](https://github.com/stenciljs/core/issues/6815)) ([b493d61](https://github.com/stenciljs/core/commit/b493d61a960deeb656f940248759f19a3094de6b))
18+
* **runtime:** VDOM should not process non-vnode objects ([#6812](https://github.com/stenciljs/core/issues/6812)) ([ffc5212](https://github.com/stenciljs/core/commit/ffc52124017581c77034b97e7ec5dcf3cf7f050b))
19+
20+
21+
### Features
22+
23+
* **config:** allow suppressing reserved event name warnings ([#6748](https://github.com/stenciljs/core/issues/6748)) ([ca85879](https://github.com/stenciljs/core/commit/ca858791094349a3d5ccfd9afbdb71612973c2cc))
24+
* **docs:** better default part / slot handling ([#6819](https://github.com/stenciljs/core/issues/6819)) ([76a3d74](https://github.com/stenciljs/core/commit/76a3d74934cba0006bc9001aaf3d04e9def981db))
25+
* **jsx:** add `closedby` and `focusgroup` HTML attributes ([#6797](https://github.com/stenciljs/core/issues/6797)) ([449d7ec](https://github.com/stenciljs/core/commit/449d7ec956ecbffb963ba6ce1fe45f4830e8e7c3))
26+
* **jsx:** add fetchpriority attribute to img, link, script, iframe ([#6803](https://github.com/stenciljs/core/issues/6803)) ([1a5bcc8](https://github.com/stenciljs/core/commit/1a5bcc8083d696be998bc732d2e4a65326553a30)), closes [#6802](https://github.com/stenciljs/core/issues/6802)
27+
* **jsx:** add global `translate` attribute to HTMLAttributes ([#6808](https://github.com/stenciljs/core/issues/6808)) ([1626295](https://github.com/stenciljs/core/commit/1626295f3d40dcdab116aa56e9599a1c818a3036))
28+
29+
30+
131
## 🍐 [4.43.5](https://github.com/stenciljs/core/compare/v4.43.4...v4.43.5) (2026-05-28)
232

333

cspell-wordlist.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,4 +183,5 @@ dotless
183183
queryparams
184184
extensionless
185185
rgba
186-
unhashed
186+
unhashed
187+
reentrancy
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import type * as d from '@stencil/core';
3+
4+
/**
5+
* Test loadModule()'s cache-busting behavior when a module fails to load and is retried.
6+
*/
7+
describe('loadModule cache-busting on retry', () => {
8+
const bundleId = 'cmp-retry-cache-bust';
9+
const cmpMeta = {
10+
$tagName$: 'cmp-retry-cache-bust',
11+
$lazyBundleId$: bundleId,
12+
} as d.ComponentRuntimeMeta;
13+
const hostRef = { $hostElement$: {} } as d.HostRef;
14+
15+
// client-load-module.ts caches failed-attempt counts at module scope, so each test needs
16+
// a fresh module instance to start from a clean retry count.
17+
beforeEach(() => {
18+
vi.resetModules();
19+
});
20+
21+
const attemptedSpecifier = async (
22+
loadModule: typeof import('../client-load-module').loadModule,
23+
hmrVersionId?: string,
24+
) => {
25+
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
26+
await loadModule(cmpMeta, hostRef, hmrVersionId);
27+
const [error] = errorSpy.mock.calls.at(-1)!;
28+
errorSpy.mockRestore();
29+
return (error as Error).message;
30+
};
31+
32+
it('retries a failed import with an incrementing ?s-retry=N param', async () => {
33+
const { loadModule } = await import('../client-load-module');
34+
35+
expect(await attemptedSpecifier(loadModule)).toContain(`${bundleId}.entry.js'`);
36+
expect(await attemptedSpecifier(loadModule)).toContain(`${bundleId}.entry.js?s-retry=1`);
37+
expect(await attemptedSpecifier(loadModule)).toContain(`${bundleId}.entry.js?s-retry=2`);
38+
});
39+
40+
it('joins the retry and HMR params with "&", retry first', async () => {
41+
const { loadModule } = await import('../client-load-module');
42+
const { BUILD } = await import('virtual:app-data');
43+
BUILD.hotModuleReplacement = true;
44+
45+
expect(await attemptedSpecifier(loadModule, 'abc123')).toContain(
46+
`${bundleId}.entry.js?s-hmr=abc123`,
47+
);
48+
expect(await attemptedSpecifier(loadModule, 'abc123')).toContain(
49+
`${bundleId}.entry.js?s-retry=1&s-hmr=abc123`,
50+
);
51+
52+
BUILD.hotModuleReplacement = false;
53+
});
54+
});

packages/core/src/client/client-load-module.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ export const cmpModules = /*@__PURE__*/ new Map<
88
{ [exportName: string]: d.ComponentConstructor }
99
>();
1010

11+
/**
12+
* Tracks how many times a dynamic `import()` for a given lazy bundle has failed.
13+
* Used to cache bust the retry attempt in connected-callback.ts
14+
*/
15+
const failedLoadAttempts = /*@__PURE__*/ new Map<string, number>();
16+
1117
/**
1218
* We need to separate out this prefix so that Esbuild doesn't try to resolve
1319
* the below, but instead retains a dynamic `import()` statement in the
@@ -54,13 +60,26 @@ export const loadModule = (
5460
if (module) {
5561
return module[exportName];
5662
}
63+
const retryCount = failedLoadAttempts.get(bundleId) ?? 0;
64+
const cacheBustParams = [
65+
retryCount > 0 ? `s-retry=${retryCount}` : '',
66+
BUILD.hotModuleReplacement && hmrVersionId ? `s-hmr=${hmrVersionId}` : '',
67+
]
68+
.filter(Boolean)
69+
.join('&');
5770
/*!__STENCIL_STATIC_IMPORT_SWITCH__*/
58-
const entryFile = `${bundleId}.entry.js${BUILD.hotModuleReplacement && hmrVersionId ? '?s-hmr=' + hmrVersionId : ''}`;
71+
const entryFile = `${bundleId}.entry.js${cacheBustParams ? '?' + cacheBustParams : ''}`;
5972
const onLoad = (importedModule: any) => {
60-
if (!BUILD.hotModuleReplacement) cmpModules.set(bundleId, importedModule);
73+
if (!BUILD.hotModuleReplacement) {
74+
failedLoadAttempts.delete(bundleId);
75+
cmpModules.set(bundleId, importedModule);
76+
}
6177
return importedModule[exportName];
6278
};
63-
const onError = (e: Error) => consoleError(e, hostRef.$hostElement$);
79+
const onError = (e: Error) => {
80+
failedLoadAttempts.set(bundleId, retryCount + 1);
81+
consoleError(e, hostRef.$hostElement$);
82+
};
6483
if (lazyLoadBasePath) {
6584
return import(
6685
/* @vite-ignore */

packages/core/src/compiler/build/compiler-ctx.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ export const getModuleLegacy = (compilerCtx: d.CompilerCtx, sourceFilePath: stri
129129
htmlAttrNames: [],
130130
htmlTagNames: [],
131131
htmlParts: [],
132+
htmlSlots: [],
132133
isCollectionDependency: false,
133134
isLegacy: false,
134135
localImports: [],

packages/core/src/compiler/docs/_test_/generate-doc-data.spec.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import type * as d from '@stencil/core';
1010
import { DEFAULT_STYLE_MODE, getComponentsFromModules } from '../../../utils';
1111
import { stubComponentCompilerMeta } from '../../types/_tests_/ComponentCompilerMeta.stub';
1212
import { AUTO_GENERATE_COMMENT } from '../constants';
13-
import { generateDocData, getDocsStyles } from '../generate-doc-data';
13+
import { generateDocData, getDocsSlots, getDocsStyles } from '../generate-doc-data';
1414

1515
describe('generate-doc-data', () => {
1616
describe('getDocsComponents', () => {
@@ -204,6 +204,39 @@ auto-generated content
204204
});
205205
});
206206

207+
describe('getDocsSlots', () => {
208+
it('returns an empty array when there are no JSDoc @slot tags or scraped slots', () => {
209+
const actual = getDocsSlots([], []);
210+
211+
expect(actual).toEqual([]);
212+
});
213+
214+
it('includes slots scraped from the vdom that have no JSDoc', () => {
215+
const actual = getDocsSlots(['', 'header'], []);
216+
217+
expect(actual).toEqual([
218+
{ name: '', docs: 'The default slot' },
219+
{ name: 'header', docs: '' },
220+
]);
221+
});
222+
223+
it('includes slots documented only via the `@slot` JSDoc tag', () => {
224+
const tags: d.JsonDocsTag[] = [{ name: 'slot', text: 'footer - the footer slot' }];
225+
226+
const actual = getDocsSlots([], tags);
227+
228+
expect(actual).toEqual([{ name: 'footer', docs: 'the footer slot' }]);
229+
});
230+
231+
it('prefers the JSDoc description over the scraped entry for the same slot name', () => {
232+
const tags: d.JsonDocsTag[] = [{ name: 'slot', text: 'header - the header slot' }];
233+
234+
const actual = getDocsSlots(['header'], tags);
235+
236+
expect(actual).toEqual([{ name: 'header', docs: 'the header slot' }]);
237+
});
238+
});
239+
207240
describe('getDocsStyles', () => {
208241
it('returns an empty array if no styleDocs exist on the compiler metadata', () => {
209242
const compilerMeta = stubComponentCompilerMeta();

packages/core/src/compiler/docs/generate-doc-data.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ const getDocsComponents = async (
117117
methods: getDocsMethods(cmp.methods),
118118
events: getDocsEvents(cmp.events),
119119
styles: getDocsStyles(cmp),
120-
slots: getDocsSlots(cmp.docs.tags),
120+
slots: getDocsSlots(cmp.htmlSlots, cmp.docs.tags),
121121
parts: getDocsParts(cmp.htmlParts, cmp.docs.tags),
122122
customStates: getDocsCustomStates(cmp),
123123
listeners: getDocsListeners(cmp.listeners),
@@ -377,9 +377,13 @@ const getDocsDeprecationText = (tags: d.JsonDocsTag[]): string | undefined => {
377377
return undefined;
378378
};
379379

380-
const getDocsSlots = (tags: d.JsonDocsTag[]): d.JsonDocsSlot[] => {
380+
const DEFAULT_SLOT_DOCS = 'The default slot';
381+
382+
export const getDocsSlots = (vdom: string[], tags: d.JsonDocsTag[]): d.JsonDocsSlot[] => {
383+
const docsSlots = getNameText('slot', tags).map(([name, docs]) => ({ name, docs }));
384+
const vdomSlots = vdom.map((name) => ({ name, docs: name === '' ? DEFAULT_SLOT_DOCS : '' }));
381385
return sortBy(
382-
getNameText('slot', tags).map(([name, docs]) => ({ name, docs })),
386+
unique([...docsSlots, ...vdomSlots], (s) => s.name),
383387
(a) => a.name,
384388
);
385389
};

0 commit comments

Comments
 (0)