Skip to content

Commit 15f3b26

Browse files
fix(hydrate): support style modes in hydrate modules (#5953)
* fix(hydrate): support style modes in hydrate modules * prettier * properly resolve app-data module * support setting multiple modes * remove magic string * fix linting
1 parent 5cddfd9 commit 15f3b26

File tree

8 files changed

+88
-4
lines changed

8 files changed

+88
-4
lines changed

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ import { RollupOptions } from 'rollup';
55
import { rollup, type RollupBuild } from 'rollup';
66

77
import {
8+
STENCIL_APP_DATA_ID,
89
STENCIL_HYDRATE_FACTORY_ID,
910
STENCIL_INTERNAL_HYDRATE_ID,
1011
STENCIL_MOCK_DOC_ID,
1112
} from '../../bundle/entry-alias-ids';
1213
import { bundleHydrateFactory } from './bundle-hydrate-factory';
13-
import { HYDRATE_FACTORY_INTRO, HYDRATE_FACTORY_OUTRO } from './hydrate-factory-closure';
14+
import {
15+
HYDRATE_FACTORY_INTRO,
16+
HYDRATE_FACTORY_OUTRO,
17+
MODE_RESOLUTION_CHAIN_DECLARATION,
18+
} from './hydrate-factory-closure';
1419
import { updateToHydrateComponents } from './update-to-hydrate-components';
1520
import { writeHydrateOutputs } from './write-hydrate-outputs';
1621

@@ -50,6 +55,7 @@ export const generateHydrateApp = async (
5055
const packageDir = join(config.sys.getCompilerExecutingPath(), '..', '..');
5156
const input = join(packageDir, 'internal', 'hydrate', 'runner.js');
5257
const mockDoc = join(packageDir, 'mock-doc', 'index.js');
58+
const appData = join(packageDir, 'internal', 'app-data', 'index.js');
5359

5460
const rollupOptions: RollupOptions = {
5561
...config.rollupConfig.inputOptions,
@@ -67,6 +73,9 @@ export const generateHydrateApp = async (
6773
if (id === STENCIL_MOCK_DOC_ID) {
6874
return mockDoc;
6975
}
76+
if (id === STENCIL_APP_DATA_ID) {
77+
return appData;
78+
}
7079
return null;
7180
},
7281
load(id) {
@@ -75,6 +84,14 @@ export const generateHydrateApp = async (
7584
}
7685
return null;
7786
},
87+
transform(code) {
88+
/**
89+
* Remove the modeResolutionChain variable from the generated code.
90+
* This variable is redefined in `HYDRATE_FACTORY_INTRO` to ensure we can
91+
* use it within the hydrate and global runtime.
92+
*/
93+
return code.replace(`var ${MODE_RESOLUTION_CHAIN_DECLARATION}`, '');
94+
},
7895
},
7996
],
8097
treeshake: false,

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

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
export const HYDRATE_APP_CLOSURE_START = `/*hydrateAppClosure start*/`;
22

3+
export const MODE_RESOLUTION_CHAIN_DECLARATION = `modeResolutionChain = [];`;
4+
5+
/**
6+
* This is the entry point for the hydrate factory.
7+
*
8+
* __Note:__ the `modeResolutionChain` will be uncommented in the
9+
* `src/compiler/output-targets/dist-hydrate-script/write-hydrate-outputs.ts` file. This enables us to use
10+
* one module resolution chain across hydrate and core runtime.
11+
*/
312
export const HYDRATE_FACTORY_INTRO = `
13+
// const ${MODE_RESOLUTION_CHAIN_DECLARATION}
14+
415
export function hydrateFactory($stencilWindow, $stencilHydrateOpts, $stencilHydrateResults, $stencilAfterHydrate, $stencilHydrateResolve) {
516
var globalThis = $stencilWindow;
617
var self = $stencilWindow;

src/compiler/output-targets/dist-hydrate-script/write-hydrate-outputs.ts

+10
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { basename } from 'path';
33
import type { RollupOutput } from 'rollup';
44

55
import type * as d from '../../../declarations';
6+
import { MODE_RESOLUTION_CHAIN_DECLARATION } from './hydrate-factory-closure';
67
import { relocateHydrateContextConst } from './relocate-hydrate-context';
78

89
export const writeHydrateOutputs = (
@@ -58,6 +59,15 @@ const writeHydrateOutput = async (
5859
rollupOutput.output.map(async (output) => {
5960
if (output.type === 'chunk') {
6061
output.code = relocateHydrateContextConst(config, compilerCtx, output.code);
62+
63+
/**
64+
* Enable the line where we define `modeResolutionChain` for the hydrate module.
65+
*/
66+
output.code = output.code.replace(
67+
`// const ${MODE_RESOLUTION_CHAIN_DECLARATION}`,
68+
`const ${MODE_RESOLUTION_CHAIN_DECLARATION}`,
69+
);
70+
6171
const filePath = join(hydrateAppDirPath, output.fileName);
6272
await compilerCtx.fs.writeFile(filePath, output.code, { immediateWrite: true });
6373
}

src/declarations/stencil-public-compiler.ts

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ConfigFlags } from '../cli/config-flags';
22
import type { PrerenderUrlResults, PrintLine } from '../internal';
33
import type { BuildCtx, CompilerCtx } from './stencil-private';
44
import type { JsonDocs } from './stencil-public-docs';
5+
import type { ResolutionHandler } from './stencil-public-runtime';
56

67
export * from './stencil-public-docs';
78

@@ -955,6 +956,11 @@ export interface SerializeDocumentOptions extends HydrateDocumentOptions {
955956
* @default true
956957
*/
957958
fullDocument?: boolean;
959+
/**
960+
* Style modes to render the component in.
961+
* @see https://stenciljs.com/docs/styling#style-modes
962+
*/
963+
modes?: ResolutionHandler[];
958964
}
959965

960966
export interface HydrateFactoryOptions extends SerializeDocumentOptions {

src/hydrate/runner/render.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Readable } from 'node:stream';
22

33
import { hydrateFactory } from '@hydrate-factory';
4+
import { modeResolutionChain, setMode } from '@platform';
45
import { MockWindow, serializeNodeToHtml } from '@stencil/core/mock-doc';
56
import { hasError } from '@utils';
67

@@ -133,7 +134,17 @@ async function render(win: MockWindow, opts: HydrateFactoryOptions, results: Hyd
133134
const beforeHydrateFn = typeof opts.beforeHydrate === 'function' ? opts.beforeHydrate : NOOP;
134135
try {
135136
await Promise.resolve(beforeHydrateFn(win.document));
136-
return new Promise<HydrateResults>((resolve) => hydrateFactory(win, opts, results, afterHydrate, resolve));
137+
return new Promise<HydrateResults>((resolve) => {
138+
if (Array.isArray(opts.modes)) {
139+
/**
140+
* Reset the mode resolution chain as we expect every `renderToString` call to render
141+
* the components in new environment/document.
142+
*/
143+
modeResolutionChain.length = 0;
144+
opts.modes.forEach((mode) => setMode(mode));
145+
}
146+
return hydrateFactory(win, opts, results, afterHydrate, resolve);
147+
});
137148
} catch (e) {
138149
renderCatchError(results, e);
139150
return finalizeHydrate(win, win.document, opts, results);

test/end-to-end/src/declarative-shadow-dom/test.e2e.ts

+29
Original file line numberDiff line numberDiff line change
@@ -290,4 +290,33 @@ describe('renderToString', () => {
290290
});
291291
expect(html).toBe('<cmp-with-slot custom-hydrate-flag="" s-id="1"><!--r.1-->Hello World</cmp-with-slot>');
292292
});
293+
294+
describe('modes in declarative shadow dom', () => {
295+
it('renders components in ios mode', async () => {
296+
const { html } = await renderToString('<prop-cmp first="Max" last="Mustermann"></prop-cmp>', {
297+
fullDocument: false,
298+
prettyHtml: true,
299+
modes: [() => 'ios'],
300+
});
301+
expect(html).toContain('<style>');
302+
expect(html).toContain(';color:white;');
303+
const page = await newE2EPage({ html, url: 'https://stencil.com' });
304+
const div = await page.find('>>>div');
305+
const { color } = await div.getComputedStyle();
306+
expect(color).toBe('rgb(255, 255, 255)');
307+
});
308+
309+
it('renders components in md mode', async () => {
310+
const { html } = await renderToString('<prop-cmp first="Max" last="Mustermann"></prop-cmp>', {
311+
fullDocument: false,
312+
prettyHtml: true,
313+
modes: [() => 'md'],
314+
});
315+
expect(html).toContain(';color:black;');
316+
const page = await newE2EPage({ html, url: 'https://stencil.com' });
317+
const div = await page.find('>>>div');
318+
const { color } = await div.getComputedStyle();
319+
expect(color).toBe('rgb(0, 0, 0)');
320+
});
321+
});
293322
});

test/end-to-end/src/prop-cmp/prop-cmp.md.css

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
font-size: 24px;
1313
font-weight: bold;
1414
background: #047aff;
15-
color: white;
15+
color: black;
1616
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
1717
}
1818

test/end-to-end/src/prop-cmp/prop-cmp.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, Prop, h, Host } from '@stencil/core';
1+
import { Component, h, Host, Prop } from '@stencil/core';
22
import { saveAs } from 'file-saver';
33

44
/**

0 commit comments

Comments
 (0)