Skip to content

Commit

Permalink
fix(hydrate): respect HydratedFlag configuration in hydrate script
Browse files Browse the repository at this point in the history
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
  • Loading branch information
alicewriteswrongs committed May 15, 2024
1 parent 1d52b95 commit b9567f4
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 14 deletions.
1 change: 1 addition & 0 deletions src/compiler/app-core/app-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export const updateBuildConditionals = (config: ValidatedConfig, b: BuildConditi
if (config.hydratedFlag) {
b.hydratedAttribute = config.hydratedFlag.selector === 'attribute';
b.hydratedClass = config.hydratedFlag.selector === 'class';
b.hydratedSelectorName = config.hydratedFlag.name;
} else {
b.hydratedAttribute = false;
b.hydratedClass = false;
Expand Down
18 changes: 11 additions & 7 deletions src/compiler/bundle/app-data-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import { APP_DATA_CONDITIONAL, STENCIL_APP_DATA_ID, STENCIL_APP_GLOBALS_ID } fro
* @param config the Stencil configuration for a particular project
* @param compilerCtx the current compiler context
* @param buildCtx the current build context
* @param build the set build conditionals for the build
* @param buildConditionals the set build conditionals for the build
* @param platform the platform that is being built
* @returns a Rollup plugin which carries out the necessary work
*/
export const appDataPlugin = (
config: d.ValidatedConfig,
compilerCtx: d.CompilerCtx,
buildCtx: d.BuildCtx,
build: d.BuildConditionals,
buildConditionals: d.BuildConditionals,
platform: 'client' | 'hydrate' | 'worker',
): Plugin => {
if (!platform) {
Expand Down Expand Up @@ -68,7 +68,7 @@ export const appDataPlugin = (
// build custom app-data based off of component metadata
const s = new MagicString(``);
appendNamespace(config, s);
appendBuildConditionals(config, build, s);
appendBuildConditionals(config, buildConditionals, s);
appendEnv(config, s);
return s.toString();
}
Expand Down Expand Up @@ -200,13 +200,17 @@ const appendGlobalScripts = (globalScripts: GlobalScript[], s: MagicString) => {
* **This function mutates the provided {@link MagicString} argument**
*
* @param config the configuration associated with the Stencil project
* @param build the build conditionals to serialize into a JS object
* @param buildConditionals the build conditionals to serialize into a JS object
* @param s a `MagicString` to append the generated constant onto
*/
const appendBuildConditionals = (config: d.ValidatedConfig, build: d.BuildConditionals, s: MagicString): void => {
const buildData = Object.keys(build)
export const appendBuildConditionals = (
config: d.ValidatedConfig,
buildConditionals: d.BuildConditionals,
s: MagicString,
): void => {
const buildData = Object.keys(buildConditionals)
.sort()
.map((key) => key + ': ' + ((build as any)[key] ? 'true' : 'false'))
.map((key) => key + ': ' + JSON.stringify((buildConditionals as any)[key]))
.join(', ');

s.append(`export const BUILD = /* ${config.fsNamespace} */ { ${buildData} };\n`);
Expand Down
46 changes: 46 additions & 0 deletions src/compiler/bundle/test/app-data-plugin.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import * as d from '@stencil/core/declarations';
import { mockValidatedConfig } from '@stencil/core/testing';
import MagicString from 'magic-string';

import { appendBuildConditionals } from '../app-data-plugin';

function setup() {
const config = mockValidatedConfig();
const magicString = new MagicString('');
return { config, magicString };
}

describe('app data plugin', () => {
it('should include the fsNamespace in the appended BUILD constant', () => {
const { config, magicString } = setup();
appendBuildConditionals(config, {}, magicString);
expect(magicString.toString().includes(`export const BUILD = /* ${config.fsNamespace} */`)).toBe(true);
});

it.each([true, false])('should include hydratedAttribute when %p', (hydratedAttribute) => {
const conditionals: d.BuildConditionals = {
hydratedAttribute,
};
const { config, magicString } = setup();
appendBuildConditionals(config, conditionals, magicString);
expect(magicString.toString().includes(`hydratedAttribute: ${String(hydratedAttribute)}`)).toBe(true);
});

it.each([true, false])('should include hydratedClass when %p', (hydratedClass) => {
const conditionals: d.BuildConditionals = {
hydratedClass,
};
const { config, magicString } = setup();
appendBuildConditionals(config, conditionals, magicString);
expect(magicString.toString().includes(`hydratedClass: ${String(hydratedClass)}`)).toBe(true);
});

it('should append hydratedSelectorName', () => {
const conditionals: d.BuildConditionals = {
hydratedSelectorName: 'boop',
};
const { config, magicString } = setup();
appendBuildConditionals(config, conditionals, magicString);
expect(magicString.toString().includes('hydratedSelectorName: "boop"')).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import { rewriteAliasedSourceFileImportPaths } from '../../transformers/rewrite-
import { updateStencilCoreImports } from '../../transformers/update-stencil-core-import';
import { getHydrateBuildConditionals } from './hydrate-build-conditionals';

/**
* Marshall some Rollup options for the hydrate factory and then pass it to our
* {@link bundleOutput} helper
*
* @param config a validated Stencil configuration
* @param compilerCtx the current compiler context
* @param buildCtx the current build context
* @param appFactoryEntryCode an entry code for the app factory
* @returns a promise wrapping a rollup build object
*/
export const bundleHydrateFactory = async (
config: d.ValidatedConfig,
compilerCtx: d.CompilerCtx,
Expand All @@ -21,7 +31,7 @@ export const bundleHydrateFactory = async (
const bundleOpts: BundleOptions = {
id: 'hydrate',
platform: 'hydrate',
conditionals: getHydrateBuildConditionals(buildCtx.components),
conditionals: getHydrateBuildConditionals(config, buildCtx.components),
customBeforeTransformers: getCustomBeforeTransformers(config, compilerCtx),
inlineDynamicImports: true,
inputs: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ import { HYDRATE_FACTORY_INTRO, HYDRATE_FACTORY_OUTRO } from './hydrate-factory-
import { updateToHydrateComponents } from './update-to-hydrate-components';
import { writeHydrateOutputs } from './write-hydrate-outputs';

/**
* Generate and build the hydrate app and then write it to disk
*
* @param config a validated Stencil configuration
* @param compilerCtx the current compiler context
* @param buildCtx the current build context
* @param outputTargets the output targets for the current build
*/
export const generateHydrateApp = async (
config: d.ValidatedConfig,
compilerCtx: d.CompilerCtx,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import type * as d from '../../../declarations';
import { getBuildFeatures } from '../../app-core/app-data';
import { getBuildFeatures, updateBuildConditionals } from '../../app-core/app-data';

export const getHydrateBuildConditionals = (cmps: d.ComponentCompilerMeta[]) => {
/**
* Get the `BUILD` conditionals for the hydrate build based on the current
* project
*
* @param config a validated Stencil configuration
* @param cmps component metadata
* @returns a populated build conditional object
*/
export const getHydrateBuildConditionals = (config: d.ValidatedConfig, cmps: d.ComponentCompilerMeta[]) => {
const build = getBuildFeatures(cmps) as d.BuildConditionals;
// we need to make sure that things like the hydratedClass and flag are
// set for the hydrate build
updateBuildConditionals(config, build);

build.slotRelocation = true;
build.lazyLoad = true;
Expand Down Expand Up @@ -32,8 +43,6 @@ export const getHydrateBuildConditionals = (cmps: d.ComponentCompilerMeta[]) =>
build.cssAnnotations = true;
// TODO(STENCIL-854): Remove code related to legacy shadowDomShim field
build.shadowDomShim = true;
build.hydratedAttribute = false;
build.hydratedClass = true;
// TODO(STENCIL-1305): remove this option
build.scriptDataOpts = false;
build.attachStyles = true;
Expand Down
50 changes: 50 additions & 0 deletions src/compiler/output-targets/test/build-conditionals.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { mockConfig, mockLoadConfigInit } from '@stencil/core/testing';
import type * as d from '../../../declarations';
import { validateConfig } from '../../config/validate-config';
import { getCustomElementsBuildConditionals } from '../dist-custom-elements/custom-elements-build-conditionals';
import { getHydrateBuildConditionals } from '../dist-hydrate-script/hydrate-build-conditionals';
import { getLazyBuildConditionals } from '../dist-lazy/lazy-build-conditionals';

describe('build-conditionals', () => {
Expand Down Expand Up @@ -69,6 +70,15 @@ describe('build-conditionals', () => {
const bc = getCustomElementsBuildConditionals(config, cmps);
expect(bc.hydrateClientSide).toBe(true);
});

it('hydratedSelectorName', () => {
userConfig.hydratedFlag = {
name: 'boooop',
};
const { config } = validateConfig(userConfig, mockLoadConfigInit());
const bc = getCustomElementsBuildConditionals(config, cmps);
expect(bc.hydratedSelectorName).toBe('boooop');
});
});

describe('getLazyBuildConditionals', () => {
Expand Down Expand Up @@ -144,5 +154,45 @@ describe('build-conditionals', () => {
const bc = getLazyBuildConditionals(config, cmps);
expect(bc.hydrateClientSide).toBe(true);
});

it('hydratedSelectorName', () => {
userConfig.hydratedFlag = {
name: 'boooop',
};
const { config } = validateConfig(userConfig, mockLoadConfigInit());
const bc = getLazyBuildConditionals(config, cmps);
expect(bc.hydratedSelectorName).toBe('boooop');
});
});

describe('getHydrateBuildConditionals', () => {
it('hydratedSelectorName', () => {
userConfig.hydratedFlag = {
name: 'boooop',
};
const { config } = validateConfig(userConfig, mockLoadConfigInit());
const bc = getHydrateBuildConditionals(config, cmps);
expect(bc.hydratedSelectorName).toBe('boooop');
});

it('should allow setting to use a class for hydration', () => {
userConfig.hydratedFlag = {
selector: 'class',
};
const { config } = validateConfig(userConfig, mockLoadConfigInit());
const bc = getHydrateBuildConditionals(config, cmps);
expect(bc.hydratedClass).toBe(true);
expect(bc.hydratedAttribute).toBe(false);
});

it('should allow setting to use an attr for hydration', () => {
userConfig.hydratedFlag = {
selector: 'attribute',
};
const { config } = validateConfig(userConfig, mockLoadConfigInit());
const bc = getHydrateBuildConditionals(config, cmps);
expect(bc.hydratedClass).toBe(false);
expect(bc.hydratedAttribute).toBe(true);
});
});
});
1 change: 1 addition & 0 deletions src/declarations/stencil-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export interface BuildConditionals extends Partial<BuildFeatures> {
cloneNodeFix?: boolean;
hydratedAttribute?: boolean;
hydratedClass?: boolean;
hydratedSelectorName?: string;
initializeNextTick?: boolean;
// TODO(STENCIL-1305): remove this option
scriptDataOpts?: boolean;
Expand Down
10 changes: 8 additions & 2 deletions src/runtime/update-component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,11 +456,17 @@ const emitLifecycleEvent = (elm: EventTarget, lifecycleName: string) => {
}
};

/**
* Set the hydrated flag on a DOM element
*
* @param elm a reference to a DOM element
* @returns undefined
*/
const addHydratedFlag = (elm: Element) =>
BUILD.hydratedClass
? elm.classList.add('hydrated')
? elm.classList.add(BUILD.hydratedSelectorName ?? 'hydrated')
: BUILD.hydratedAttribute
? elm.setAttribute('hydrated', '')
? elm.setAttribute(BUILD.hydratedSelectorName ?? 'hydrated', '')
: undefined;

const serverSideConnected = (elm: any) => {
Expand Down

0 comments on commit b9567f4

Please sign in to comment.