Skip to content

Commit f2c3229

Browse files
authored
fix(compiler): restrict config extras slot fix flags (#4767)
* setup config types * update config validation logic * resolve build errors * add warning to validation logic * use initial extras values for warning message check * update config types to allow explicit values * update validation check * pr feedback * update warning message language
1 parent 1832553 commit f2c3229

3 files changed

Lines changed: 82 additions & 48 deletions

File tree

src/compiler/config/test/validate-config.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,15 @@ describe('validation', () => {
405405
});
406406

407407
it('should override slot fix config based on `experimentalSlotFixes`', () => {
408+
// This test is to verify the flags get overwritten correctly even if an
409+
// invalid config is ingested. Hence, the `any` cast
408410
userConfig.extras = {
409411
appendChildSlotFix: false,
410412
slotChildNodesFix: false,
411413
cloneNodeFix: false,
412414
scopedSlotTextContentFix: false,
413-
};
414-
userConfig.extras.experimentalSlotFixes = true;
415+
experimentalSlotFixes: true,
416+
} as any;
415417
const { config } = validateConfig(userConfig, bootstrapConfig);
416418
expect(config.extras.appendChildSlotFix).toBe(true);
417419
expect(config.extras.cloneNodeFix).toBe(true);

src/compiler/config/validate-config.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { createNodeLogger, createNodeSys } from '@sys-api-node';
22
import { buildError, isBoolean, isNumber, isString, sortBy } from '@utils';
33

4-
import { ConfigBundle, Diagnostic, LoadConfigInit, UnvalidatedConfig, ValidatedConfig } from '../../declarations';
4+
import {
5+
ConfigBundle,
6+
ConfigExtras,
7+
Diagnostic,
8+
LoadConfigInit,
9+
UnvalidatedConfig,
10+
ValidatedConfig,
11+
} from '../../declarations';
512
import { setBooleanConfig } from './config-utils';
613
import { validateOutputTargets } from './outputs';
714
import { validateDevServer } from './validate-dev-server';
@@ -104,24 +111,43 @@ export const validateConfig = (
104111
}
105112

106113
validatedConfig.extras = validatedConfig.extras || {};
107-
validatedConfig.extras.appendChildSlotFix = !!validatedConfig.extras.appendChildSlotFix;
108-
validatedConfig.extras.cloneNodeFix = !!validatedConfig.extras.cloneNodeFix;
109114
validatedConfig.extras.lifecycleDOMEvents = !!validatedConfig.extras.lifecycleDOMEvents;
110115
validatedConfig.extras.scriptDataOpts = !!validatedConfig.extras.scriptDataOpts;
111-
validatedConfig.extras.slotChildNodesFix = !!validatedConfig.extras.slotChildNodesFix;
112116
validatedConfig.extras.initializeNextTick = !!validatedConfig.extras.initializeNextTick;
113117
validatedConfig.extras.tagNameTransform = !!validatedConfig.extras.tagNameTransform;
114-
// TODO(STENCIL-914): remove `experimentalSlotFixes` when it's the default behavior
115-
validatedConfig.extras.experimentalSlotFixes = !!validatedConfig.extras.experimentalSlotFixes;
116118

117-
// if the user has set `extras.experimentalSlotFixes` then we turn on all of
118-
// the slot-related fixes automatically.
119+
// TODO(STENCIL-914): remove when `experimentalSlotFixes` is the default behavior
120+
// If the user set `experimentalSlotFixes` and any individual slot fix flags to `false`, we need to log a warning
121+
// to the user that we will "override" the individual flags
122+
if (validatedConfig.extras.experimentalSlotFixes === true) {
123+
const possibleFlags: (keyof ConfigExtras)[] = [
124+
'appendChildSlotFix',
125+
'slotChildNodesFix',
126+
'cloneNodeFix',
127+
'scopedSlotTextContentFix',
128+
];
129+
const conflictingFlags = possibleFlags.filter((flag) => validatedConfig.extras[flag] === false);
130+
if (conflictingFlags.length > 0) {
131+
const warning = buildError(diagnostics);
132+
warning.level = 'warn';
133+
warning.messageText = `If the 'experimentalSlotFixes' flag is enabled it will override any slot fix flags which are disabled. In particular, the following currently-disabled flags will be ignored: ${conflictingFlags.join(
134+
', ',
135+
)}. Please update your Stencil config accordingly.`;
136+
}
137+
}
138+
119139
// TODO(STENCIL-914): remove `experimentalSlotFixes` when it's the default behavior
120-
if (validatedConfig.extras.experimentalSlotFixes) {
140+
validatedConfig.extras.experimentalSlotFixes = !!validatedConfig.extras.experimentalSlotFixes;
141+
if (validatedConfig.extras.experimentalSlotFixes === true) {
121142
validatedConfig.extras.appendChildSlotFix = true;
122143
validatedConfig.extras.cloneNodeFix = true;
123144
validatedConfig.extras.slotChildNodesFix = true;
124145
validatedConfig.extras.scopedSlotTextContentFix = true;
146+
} else {
147+
validatedConfig.extras.appendChildSlotFix = !!validatedConfig.extras.appendChildSlotFix;
148+
validatedConfig.extras.cloneNodeFix = !!validatedConfig.extras.cloneNodeFix;
149+
validatedConfig.extras.slotChildNodesFix = !!validatedConfig.extras.slotChildNodesFix;
150+
validatedConfig.extras.scopedSlotTextContentFix = !!validatedConfig.extras.scopedSlotTextContentFix;
125151
}
126152

127153
validatedConfig.buildEs5 =

src/declarations/stencil-public-compiler.ts

Lines changed: 43 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -292,27 +292,7 @@ export interface StencilConfig {
292292
stencilCoreResolvedId?: string;
293293
}
294294

295-
export interface ConfigExtras {
296-
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
297-
/**
298-
* By default, the slot polyfill does not update `appendChild()` so that it appends
299-
* new child nodes into the correct child slot like how shadow dom works. This is an opt-in
300-
* polyfill for those who need it when using `element.appendChild(node)` and expecting the
301-
* child to be appended in the same location shadow dom would. This is not required for
302-
* IE11 or Edge 18, but can be enabled if the app is using `appendChild()`. Defaults to `false`.
303-
*/
304-
appendChildSlotFix?: boolean;
305-
306-
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
307-
/**
308-
* By default, the runtime does not polyfill `cloneNode()` when cloning a component
309-
* that uses the slot polyfill. This is an opt-in polyfill for those who need it.
310-
* This is not required for IE11 or Edge 18, but can be enabled if the app is using
311-
* `cloneNode()` and unexpected node are being cloned due to the slot polyfill
312-
* simulating shadow dom. Defaults to `false`.
313-
*/
314-
cloneNodeFix?: boolean;
315-
295+
interface ConfigExtrasBase {
316296
/**
317297
* Experimental flag. Projects that use a Stencil library built using the `dist` output target may have trouble lazily
318298
* loading components when using a bundler such as Vite or Parcel. Setting this flag to `true` will change how Stencil
@@ -343,13 +323,6 @@ export interface ConfigExtras {
343323
*/
344324
scriptDataOpts?: boolean;
345325

346-
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
347-
/**
348-
* Experimental flag to align the behavior of invoking `textContent` on a scoped component to act more like a
349-
* component that uses the shadow DOM. Defaults to `false`
350-
*/
351-
scopedSlotTextContentFix?: boolean;
352-
353326
/**
354327
* When a component is first attached to the DOM, this setting will wait a single tick before
355328
* rendering. This works around an Angular issue, where Angular attaches the elements before
@@ -358,28 +331,61 @@ export interface ConfigExtras {
358331
*/
359332
initializeNextTick?: boolean;
360333

334+
/**
335+
* Enables the tagNameTransform option of `defineCustomElements()`, so the component tagName
336+
* can be customized at runtime. Defaults to `false`.
337+
*/
338+
tagNameTransform?: boolean;
339+
}
340+
341+
// TODO(STENCIL-914): delete this interface when `experimentalSlotFixes` is the default behavior
342+
type ConfigExtrasSlotFixes<ExperimentalFixesEnabled extends boolean, IndividualFlags extends boolean> = {
343+
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
344+
/**
345+
* By default, the slot polyfill does not update `appendChild()` so that it appends
346+
* new child nodes into the correct child slot like how shadow dom works. This is an opt-in
347+
* polyfill for those who need it when using `element.appendChild(node)` and expecting the
348+
* child to be appended in the same location shadow dom would. This is not required for
349+
* IE11 or Edge 18, but can be enabled if the app is using `appendChild()`. Defaults to `false`.
350+
*/
351+
appendChildSlotFix?: IndividualFlags;
352+
353+
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
354+
/**
355+
* By default, the runtime does not polyfill `cloneNode()` when cloning a component
356+
* that uses the slot polyfill. This is an opt-in polyfill for those who need it.
357+
* This is not required for IE11 or Edge 18, but can be enabled if the app is using
358+
* `cloneNode()` and unexpected node are being cloned due to the slot polyfill
359+
* simulating shadow dom. Defaults to `false`.
360+
*/
361+
cloneNodeFix?: IndividualFlags;
362+
363+
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
364+
/**
365+
* Experimental flag to align the behavior of invoking `textContent` on a scoped component to act more like a
366+
* component that uses the shadow DOM. Defaults to `false`
367+
*/
368+
scopedSlotTextContentFix?: IndividualFlags;
369+
361370
// TODO(STENCIL-914): remove this option when `experimentalSlotFixes` is the default behavior
362371
/**
363372
* For browsers that do not support shadow dom (IE11 and Edge 18 and below), slot is polyfilled
364373
* to simulate the same behavior. However, the host element's `childNodes` and `children`
365374
* getters are not patched to only show the child nodes and elements of the default slot.
366375
* Defaults to `false`.
367376
*/
368-
slotChildNodesFix?: boolean;
369-
370-
/**
371-
* Enables the tagNameTransform option of `defineCustomElements()`, so the component tagName
372-
* can be customized at runtime. Defaults to `false`.
373-
*/
374-
tagNameTransform?: boolean;
377+
slotChildNodesFix?: IndividualFlags;
375378

376379
// TODO(STENCIL-914): remove `experimentalSlotFixes` when it's the default behavior
377380
/**
378381
* Enables all slot-related fixes such as {@link slotChildNodesFix}, and
379382
* {@link scopedSlotTextContentFix}.
380383
*/
381-
experimentalSlotFixes?: boolean;
382-
}
384+
experimentalSlotFixes?: ExperimentalFixesEnabled;
385+
};
386+
387+
export type ConfigExtras = ConfigExtrasBase &
388+
(ConfigExtrasSlotFixes<true, true> | ConfigExtrasSlotFixes<false, boolean>);
383389

384390
export interface Config extends StencilConfig {
385391
buildAppCore?: boolean;

0 commit comments

Comments
 (0)