Skip to content

Commit b9567f4

Browse files
fix(hydrate): respect HydratedFlag configuration in hydrate script
This changes how BUILD conditionals get set for the Hydrate script to ensure that configuration set by users using `hydratedFlag` is respected. fixes #3606 STENCIL-609
1 parent 1d52b95 commit b9567f4

File tree

9 files changed

+149
-14
lines changed

9 files changed

+149
-14
lines changed

src/compiler/app-core/app-data.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export const updateBuildConditionals = (config: ValidatedConfig, b: BuildConditi
187187
if (config.hydratedFlag) {
188188
b.hydratedAttribute = config.hydratedFlag.selector === 'attribute';
189189
b.hydratedClass = config.hydratedFlag.selector === 'class';
190+
b.hydratedSelectorName = config.hydratedFlag.name;
190191
} else {
191192
b.hydratedAttribute = false;
192193
b.hydratedClass = false;

src/compiler/bundle/app-data-plugin.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import { APP_DATA_CONDITIONAL, STENCIL_APP_DATA_ID, STENCIL_APP_GLOBALS_ID } fro
1414
* @param config the Stencil configuration for a particular project
1515
* @param compilerCtx the current compiler context
1616
* @param buildCtx the current build context
17-
* @param build the set build conditionals for the build
17+
* @param buildConditionals the set build conditionals for the build
1818
* @param platform the platform that is being built
1919
* @returns a Rollup plugin which carries out the necessary work
2020
*/
2121
export const appDataPlugin = (
2222
config: d.ValidatedConfig,
2323
compilerCtx: d.CompilerCtx,
2424
buildCtx: d.BuildCtx,
25-
build: d.BuildConditionals,
25+
buildConditionals: d.BuildConditionals,
2626
platform: 'client' | 'hydrate' | 'worker',
2727
): Plugin => {
2828
if (!platform) {
@@ -68,7 +68,7 @@ export const appDataPlugin = (
6868
// build custom app-data based off of component metadata
6969
const s = new MagicString(``);
7070
appendNamespace(config, s);
71-
appendBuildConditionals(config, build, s);
71+
appendBuildConditionals(config, buildConditionals, s);
7272
appendEnv(config, s);
7373
return s.toString();
7474
}
@@ -200,13 +200,17 @@ const appendGlobalScripts = (globalScripts: GlobalScript[], s: MagicString) => {
200200
* **This function mutates the provided {@link MagicString} argument**
201201
*
202202
* @param config the configuration associated with the Stencil project
203-
* @param build the build conditionals to serialize into a JS object
203+
* @param buildConditionals the build conditionals to serialize into a JS object
204204
* @param s a `MagicString` to append the generated constant onto
205205
*/
206-
const appendBuildConditionals = (config: d.ValidatedConfig, build: d.BuildConditionals, s: MagicString): void => {
207-
const buildData = Object.keys(build)
206+
export const appendBuildConditionals = (
207+
config: d.ValidatedConfig,
208+
buildConditionals: d.BuildConditionals,
209+
s: MagicString,
210+
): void => {
211+
const buildData = Object.keys(buildConditionals)
208212
.sort()
209-
.map((key) => key + ': ' + ((build as any)[key] ? 'true' : 'false'))
213+
.map((key) => key + ': ' + JSON.stringify((buildConditionals as any)[key]))
210214
.join(', ');
211215

212216
s.append(`export const BUILD = /* ${config.fsNamespace} */ { ${buildData} };\n`);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as d from '@stencil/core/declarations';
2+
import { mockValidatedConfig } from '@stencil/core/testing';
3+
import MagicString from 'magic-string';
4+
5+
import { appendBuildConditionals } from '../app-data-plugin';
6+
7+
function setup() {
8+
const config = mockValidatedConfig();
9+
const magicString = new MagicString('');
10+
return { config, magicString };
11+
}
12+
13+
describe('app data plugin', () => {
14+
it('should include the fsNamespace in the appended BUILD constant', () => {
15+
const { config, magicString } = setup();
16+
appendBuildConditionals(config, {}, magicString);
17+
expect(magicString.toString().includes(`export const BUILD = /* ${config.fsNamespace} */`)).toBe(true);
18+
});
19+
20+
it.each([true, false])('should include hydratedAttribute when %p', (hydratedAttribute) => {
21+
const conditionals: d.BuildConditionals = {
22+
hydratedAttribute,
23+
};
24+
const { config, magicString } = setup();
25+
appendBuildConditionals(config, conditionals, magicString);
26+
expect(magicString.toString().includes(`hydratedAttribute: ${String(hydratedAttribute)}`)).toBe(true);
27+
});
28+
29+
it.each([true, false])('should include hydratedClass when %p', (hydratedClass) => {
30+
const conditionals: d.BuildConditionals = {
31+
hydratedClass,
32+
};
33+
const { config, magicString } = setup();
34+
appendBuildConditionals(config, conditionals, magicString);
35+
expect(magicString.toString().includes(`hydratedClass: ${String(hydratedClass)}`)).toBe(true);
36+
});
37+
38+
it('should append hydratedSelectorName', () => {
39+
const conditionals: d.BuildConditionals = {
40+
hydratedSelectorName: 'boop',
41+
};
42+
const { config, magicString } = setup();
43+
appendBuildConditionals(config, conditionals, magicString);
44+
expect(magicString.toString().includes('hydratedSelectorName: "boop"')).toBe(true);
45+
});
46+
});

src/compiler/output-targets/dist-hydrate-script/bundle-hydrate-factory.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ import { rewriteAliasedSourceFileImportPaths } from '../../transformers/rewrite-
1111
import { updateStencilCoreImports } from '../../transformers/update-stencil-core-import';
1212
import { getHydrateBuildConditionals } from './hydrate-build-conditionals';
1313

14+
/**
15+
* Marshall some Rollup options for the hydrate factory and then pass it to our
16+
* {@link bundleOutput} helper
17+
*
18+
* @param config a validated Stencil configuration
19+
* @param compilerCtx the current compiler context
20+
* @param buildCtx the current build context
21+
* @param appFactoryEntryCode an entry code for the app factory
22+
* @returns a promise wrapping a rollup build object
23+
*/
1424
export const bundleHydrateFactory = async (
1525
config: d.ValidatedConfig,
1626
compilerCtx: d.CompilerCtx,
@@ -21,7 +31,7 @@ export const bundleHydrateFactory = async (
2131
const bundleOpts: BundleOptions = {
2232
id: 'hydrate',
2333
platform: 'hydrate',
24-
conditionals: getHydrateBuildConditionals(buildCtx.components),
34+
conditionals: getHydrateBuildConditionals(config, buildCtx.components),
2535
customBeforeTransformers: getCustomBeforeTransformers(config, compilerCtx),
2636
inlineDynamicImports: true,
2737
inputs: {

src/compiler/output-targets/dist-hydrate-script/generate-hydrate-app.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ import { HYDRATE_FACTORY_INTRO, HYDRATE_FACTORY_OUTRO } from './hydrate-factory-
1414
import { updateToHydrateComponents } from './update-to-hydrate-components';
1515
import { writeHydrateOutputs } from './write-hydrate-outputs';
1616

17+
/**
18+
* Generate and build the hydrate app and then write it to disk
19+
*
20+
* @param config a validated Stencil configuration
21+
* @param compilerCtx the current compiler context
22+
* @param buildCtx the current build context
23+
* @param outputTargets the output targets for the current build
24+
*/
1725
export const generateHydrateApp = async (
1826
config: d.ValidatedConfig,
1927
compilerCtx: d.CompilerCtx,

src/compiler/output-targets/dist-hydrate-script/hydrate-build-conditionals.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
import type * as d from '../../../declarations';
2-
import { getBuildFeatures } from '../../app-core/app-data';
2+
import { getBuildFeatures, updateBuildConditionals } from '../../app-core/app-data';
33

4-
export const getHydrateBuildConditionals = (cmps: d.ComponentCompilerMeta[]) => {
4+
/**
5+
* Get the `BUILD` conditionals for the hydrate build based on the current
6+
* project
7+
*
8+
* @param config a validated Stencil configuration
9+
* @param cmps component metadata
10+
* @returns a populated build conditional object
11+
*/
12+
export const getHydrateBuildConditionals = (config: d.ValidatedConfig, cmps: d.ComponentCompilerMeta[]) => {
513
const build = getBuildFeatures(cmps) as d.BuildConditionals;
14+
// we need to make sure that things like the hydratedClass and flag are
15+
// set for the hydrate build
16+
updateBuildConditionals(config, build);
617

718
build.slotRelocation = true;
819
build.lazyLoad = true;
@@ -32,8 +43,6 @@ export const getHydrateBuildConditionals = (cmps: d.ComponentCompilerMeta[]) =>
3243
build.cssAnnotations = true;
3344
// TODO(STENCIL-854): Remove code related to legacy shadowDomShim field
3445
build.shadowDomShim = true;
35-
build.hydratedAttribute = false;
36-
build.hydratedClass = true;
3746
// TODO(STENCIL-1305): remove this option
3847
build.scriptDataOpts = false;
3948
build.attachStyles = true;

src/compiler/output-targets/test/build-conditionals.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { mockConfig, mockLoadConfigInit } from '@stencil/core/testing';
33
import type * as d from '../../../declarations';
44
import { validateConfig } from '../../config/validate-config';
55
import { getCustomElementsBuildConditionals } from '../dist-custom-elements/custom-elements-build-conditionals';
6+
import { getHydrateBuildConditionals } from '../dist-hydrate-script/hydrate-build-conditionals';
67
import { getLazyBuildConditionals } from '../dist-lazy/lazy-build-conditionals';
78

89
describe('build-conditionals', () => {
@@ -69,6 +70,15 @@ describe('build-conditionals', () => {
6970
const bc = getCustomElementsBuildConditionals(config, cmps);
7071
expect(bc.hydrateClientSide).toBe(true);
7172
});
73+
74+
it('hydratedSelectorName', () => {
75+
userConfig.hydratedFlag = {
76+
name: 'boooop',
77+
};
78+
const { config } = validateConfig(userConfig, mockLoadConfigInit());
79+
const bc = getCustomElementsBuildConditionals(config, cmps);
80+
expect(bc.hydratedSelectorName).toBe('boooop');
81+
});
7282
});
7383

7484
describe('getLazyBuildConditionals', () => {
@@ -144,5 +154,45 @@ describe('build-conditionals', () => {
144154
const bc = getLazyBuildConditionals(config, cmps);
145155
expect(bc.hydrateClientSide).toBe(true);
146156
});
157+
158+
it('hydratedSelectorName', () => {
159+
userConfig.hydratedFlag = {
160+
name: 'boooop',
161+
};
162+
const { config } = validateConfig(userConfig, mockLoadConfigInit());
163+
const bc = getLazyBuildConditionals(config, cmps);
164+
expect(bc.hydratedSelectorName).toBe('boooop');
165+
});
166+
});
167+
168+
describe('getHydrateBuildConditionals', () => {
169+
it('hydratedSelectorName', () => {
170+
userConfig.hydratedFlag = {
171+
name: 'boooop',
172+
};
173+
const { config } = validateConfig(userConfig, mockLoadConfigInit());
174+
const bc = getHydrateBuildConditionals(config, cmps);
175+
expect(bc.hydratedSelectorName).toBe('boooop');
176+
});
177+
178+
it('should allow setting to use a class for hydration', () => {
179+
userConfig.hydratedFlag = {
180+
selector: 'class',
181+
};
182+
const { config } = validateConfig(userConfig, mockLoadConfigInit());
183+
const bc = getHydrateBuildConditionals(config, cmps);
184+
expect(bc.hydratedClass).toBe(true);
185+
expect(bc.hydratedAttribute).toBe(false);
186+
});
187+
188+
it('should allow setting to use an attr for hydration', () => {
189+
userConfig.hydratedFlag = {
190+
selector: 'attribute',
191+
};
192+
const { config } = validateConfig(userConfig, mockLoadConfigInit());
193+
const bc = getHydrateBuildConditionals(config, cmps);
194+
expect(bc.hydratedClass).toBe(false);
195+
expect(bc.hydratedAttribute).toBe(true);
196+
});
147197
});
148198
});

src/declarations/stencil-private.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export interface BuildConditionals extends Partial<BuildFeatures> {
180180
cloneNodeFix?: boolean;
181181
hydratedAttribute?: boolean;
182182
hydratedClass?: boolean;
183+
hydratedSelectorName?: string;
183184
initializeNextTick?: boolean;
184185
// TODO(STENCIL-1305): remove this option
185186
scriptDataOpts?: boolean;

src/runtime/update-component.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,11 +456,17 @@ const emitLifecycleEvent = (elm: EventTarget, lifecycleName: string) => {
456456
}
457457
};
458458

459+
/**
460+
* Set the hydrated flag on a DOM element
461+
*
462+
* @param elm a reference to a DOM element
463+
* @returns undefined
464+
*/
459465
const addHydratedFlag = (elm: Element) =>
460466
BUILD.hydratedClass
461-
? elm.classList.add('hydrated')
467+
? elm.classList.add(BUILD.hydratedSelectorName ?? 'hydrated')
462468
: BUILD.hydratedAttribute
463-
? elm.setAttribute('hydrated', '')
469+
? elm.setAttribute(BUILD.hydratedSelectorName ?? 'hydrated', '')
464470
: undefined;
465471

466472
const serverSideConnected = (elm: any) => {

0 commit comments

Comments
 (0)