Skip to content

Commit

Permalink
feat(compiler): allow disabling the injected hydration stylesheet (#2989
Browse files Browse the repository at this point in the history
)

provide a new `stencil.config.ts` option called `invisiblePrehydration`

Ionic has an opinion to visually hide components while they are not hydrated by automatically injecting a stylesheet into the head of a document containing a css ruleset setting visibility: hidden on each of compiled components, only until they have a hydrated class.

this option defaults to true, in order to allow for backwards compatibility with previous versions of Stencil 2.X. 

when set to `false`, consumers  may opt-out of that Ionic opinion, so that they can use their own fallback styles, and more closely resemble the HTML Spec around Shadow DOM, Slots, and Dehydrated Web Components.

STENCIL-23: Stencil.js: Allow turning off hydrated styles
  • Loading branch information
splitinfinities committed Aug 27, 2021
1 parent ac49d7c commit a3d2928
Show file tree
Hide file tree
Showing 28 changed files with 254 additions and 5 deletions.
1 change: 1 addition & 0 deletions src/app-data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export const BUILD: BuildConditionals = {
scriptDataOpts: false,
shadowDomShim: false,
slotChildNodesFix: false,
invisiblePrehydration: true,
propBoolean: true,
propNumber: true,
propString: true,
Expand Down
1 change: 1 addition & 0 deletions src/client/polyfills/css-shim/load-link-styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export function addGlobalLink(doc: Document, globalScopes: CSSScope[], linkElm:
if (hasRelativeUrls(text)) {
text = fixRelativeUrls(text, url);
}

const styleEl = doc.createElement('style');
styleEl.setAttribute('data-styles', '');
styleEl.textContent = text;
Expand Down
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 @@ -155,6 +155,7 @@ export const updateBuildConditionals = (config: Config, b: BuildConditionals) =>
b.scriptDataOpts = config.extras.scriptDataOpts;
b.shadowDomShim = config.extras.shadowDomShim;
b.attachStyles = true;
b.invisiblePrehydration = typeof config.invisiblePrehydration === 'undefined' ? true : config.invisiblePrehydration;
if (b.shadowDomShim) {
b.slotRelocation = b.slot;
}
Expand Down
1 change: 1 addition & 0 deletions src/compiler/prerender/prerender-optimize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export const inlineExternalStyleSheets = async (sys: d.CompilerSystem, appDir: s
const optimizeResults = await optimizeCss({
input: styles,
});

styles = optimizeResults.output;

// insert inline <style>
Expand Down
1 change: 1 addition & 0 deletions src/declarations/stencil-private.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ export interface BuildConditionals extends Partial<BuildFeatures> {
isTesting?: boolean;
isDev?: boolean;
devTools?: boolean;
invisiblePrehydration?: boolean;
hydrateServerSide?: boolean;
hydrateClientSide?: boolean;
lifecycleDOMEvents?: boolean;
Expand Down
11 changes: 11 additions & 0 deletions src/declarations/stencil-public-compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,17 @@ export interface StencilConfig {
*/
hydratedFlag?: HydratedFlag;

/**
* Ionic perfers to hide all components prior to hydration with a style tag appended
* to the head of the document containing some `visibility: hidden;` css rules.
*
* Disabling this will remove the style tag that sets `visibility: hidden;` on all
* unhydrated web components. This more closely follows the HTML spec, and allows
* you to set your own fallback content.
*
*/
invisiblePrehydration?: boolean;

/**
* Sets the task queue used by stencil's runtime. The task queue schedules DOM read and writes
* across the frames to efficiently render and reduce layout thrashing. By default,
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/bootstrap-lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ export const bootstrapLazy = (lazyBundles: d.LazyBundlesRuntimeData, options: d.
})
);

if (BUILD.hydratedClass || BUILD.hydratedAttribute) {
if (BUILD.invisiblePrehydration && (BUILD.hydratedClass || BUILD.hydratedAttribute)) {
visibilityStyle.innerHTML = cmpTags + HYDRATED_CSS;
visibilityStyle.setAttribute('data-styles', '');
head.insertBefore(visibilityStyle, metaCharset ? metaCharset.nextSibling : head.firstChild);
Expand Down
1 change: 1 addition & 0 deletions src/testing/reset-build-conditionals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export function resetBuildConditionals(b: d.BuildConditionals) {
b.style = false;
b.hydratedAttribute = false;
b.hydratedClass = true;
b.invisiblePrehydration = true;
b.appendChildSlotFix = false;
b.cloneNodeFix = false;
b.dynamicImportShim = false;
Expand Down
6 changes: 5 additions & 1 deletion test/karma/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
"compile.test-app": "node ../../node_modules/typescript/lib/tsc -p tsconfig.json",
"build.prerender": "node ../../bin/stencil build --config test-prerender/stencil.config-prerender.ts --prerender --debug && node test-prerender/no-script-build.js",
"build.sibling": "node ../../bin/stencil build --config test-sibling/stencil.config.ts --debug",
"build.invisible-prehydration": "npm run build.invisible-prehydration.default && npm run build.invisible-prehydration.true && npm run build.invisible-prehydration.false",
"build.invisible-prehydration.default": "node ../../bin/stencil build --config test-invisible-prehydration/stencil.config.ts --debug",
"build.invisible-prehydration.true": "node ../../bin/stencil build --config test-invisible-prehydration/stencil.invisiblePrehydrationTrue.config.ts --debug",
"build.invisible-prehydration.false": "node ../../bin/stencil build --config test-invisible-prehydration/stencil.invisiblePrehydrationFalse.config.ts --debug",
"karma": "karma start karma.config.js",
"karma.chrome": "karma start karma.config.js --browsers=Chrome --single-run=false",
"karma.firefox": "karma start karma.config.js --browsers=Firefox --single-run=false",
"karma.prod": "npm run build.sibling && npm run build.app && npm run karma.webpack && npm run build.prerender && npm run karma",
"karma.prod": "npm run build.sibling && npm run build.invisible-prehydration && npm run build.app && npm run karma.webpack && npm run build.prerender && npm run karma",
"karma.ie": "karma start karma.config.js --browsers=IE --single-run=false",
"karma.edge": "karma start karma.config.js --browsers=Edge --single-run=false",
"karma.webpack": "webpack-cli --config test-app/esm-webpack/webpack.config.js",
Expand Down
1 change: 1 addition & 0 deletions test/karma/stencil.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const config: Config = {
outputTargets: [
{
type: 'www',
empty: false,
copy: [{ src: '**/*.html' }, { src: '**/*.css' }, { src: 'noscript.js' }],
},
{
Expand Down
6 changes: 6 additions & 0 deletions test/karma/test-app/invisible-prehydration-default/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<meta charset="utf8">
<script src="/build/testinvisibledefaultprehydration.esm.js" type="module"></script>
<script src="/build/testinvisibledefaultprehydration.js" nomodule></script>

<prehydrated-styles></prehydrated-styles>
17 changes: 17 additions & 0 deletions test/karma/test-app/invisible-prehydration-default/karma.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { setupDomTests } from '../util';

describe('invisible-prehydration-default', () => {
const { setupDom, tearDownDom, tearDownStylesScripts } = setupDomTests(document);

afterEach(tearDownDom);

it('the style element will not be placed in the head', async () => {
tearDownStylesScripts();
await setupDom('/invisible-prehydration-default/index.html', 1000);

// Is the component hydrated?
expect(document.querySelector('prehydrated-styles').innerHTML).toEqual('<div>prehydrated-styles</div>');

expect(document.head.querySelectorAll('style[data-styles]').length).toEqual(1);
});
});
6 changes: 6 additions & 0 deletions test/karma/test-app/invisible-prehydration-false/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<meta charset="utf8">
<script src="/build/testinvisiblefalseprehydration.esm.js" type="module"></script>
<script src="/build/testinvisiblefalseprehydration.js" nomodule></script>

<prehydrated-styles></prehydrated-styles>
17 changes: 17 additions & 0 deletions test/karma/test-app/invisible-prehydration-false/karma.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { setupDomTests } from '../util';

describe('invisible-prehydration-false', () => {
const { setupDom, tearDownDom, tearDownStylesScripts } = setupDomTests(document);

afterEach(tearDownDom);

it('the style element will not be placed in the head', async () => {
tearDownStylesScripts();
await setupDom('/invisible-prehydration-false/index.html', 1000);

// Is the component hydrated?
expect(document.querySelector('prehydrated-styles').innerHTML).toEqual('<div>prehydrated-styles</div>');

expect(document.head.querySelectorAll('style[data-styles]').length).toEqual(0);
});
});
6 changes: 6 additions & 0 deletions test/karma/test-app/invisible-prehydration-true/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!DOCTYPE html>
<meta charset="utf8">
<script src="/build/testinvisibletrueprehydration.esm.js" type="module"></script>
<script src="/build/testinvisibletrueprehydration.js" nomodule></script>

<prehydrated-styles></prehydrated-styles>
17 changes: 17 additions & 0 deletions test/karma/test-app/invisible-prehydration-true/karma.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { setupDomTests } from '../util';

describe('invisible-prehydration-true', () => {
const { setupDom, tearDownDom, tearDownStylesScripts } = setupDomTests(document);

afterEach(tearDownDom);

it('the style element will not be placed in the head', async () => {
tearDownStylesScripts();
await setupDom('/invisible-prehydration-true/index.html', 1000);

// Is the component hydrated?
expect(document.querySelector('prehydrated-styles').innerHTML).toEqual('<div>prehydrated-styles</div>');

expect(document.head.querySelectorAll('style[data-styles]').length).toEqual(1);
});
});
22 changes: 21 additions & 1 deletion test/karma/test-app/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,26 @@ export function setupDomTests(document: Document) {
testBed!.innerHTML = '';
}

/**
* Run this after each test that needs it's resources flushed
*/
function tearDownStylesScripts() {
document.head.querySelectorAll('style[data-styles]').forEach((e) => e.remove());

[
'/build/testinvisibleprehydration.esm.js',
'/build/testinvisibleprehydration.js',
'/build/testprehydratedtruestyles.esm.js',
'/build/testprehydratedtruestyles.js',
'/build/testprehydratedfalsestyles.esm.js',
'/build/testprehydratedfalsestyles.js',
'/build/testapp.esm.js',
'/build/testapp.js',
].forEach((src) => {
document.querySelectorAll(`script[src="${src}"]`).forEach((e) => e.remove());
});
}

/**
* Create web component for executing tests against
*/
Expand Down Expand Up @@ -169,7 +189,7 @@ export function setupDomTests(document: Document) {
});
}

return { setupDom, tearDownDom };
return { setupDom, tearDownDom, tearDownStylesScripts };
}

/**
Expand Down
1 change: 1 addition & 0 deletions test/karma/test-invisible-prehydration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/www
4 changes: 4 additions & 0 deletions test/karma/test-invisible-prehydration/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions test/karma/test-invisible-prehydration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "test-invisible-prehydration",
"main": "dist/index.cjs.js",
"module": "dist/index.js",
"collection": "dist/collection/collection-manifest.json",
"types": "dist/types/components.d.ts"
}
37 changes: 37 additions & 0 deletions test/karma/test-invisible-prehydration/src/components.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable */
/* tslint:disable */
/**
* This is an autogenerated file created by the Stencil compiler.
* It contains typing information for all components that exist in this project.
*/
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
export namespace Components {
interface PrehydratedStyles {
}
}
declare global {
interface HTMLPrehydratedStylesElement extends Components.PrehydratedStyles, HTMLStencilElement {
}
var HTMLPrehydratedStylesElement: {
prototype: HTMLPrehydratedStylesElement;
new (): HTMLPrehydratedStylesElement;
};
interface HTMLElementTagNameMap {
"prehydrated-styles": HTMLPrehydratedStylesElement;
}
}
declare namespace LocalJSX {
interface PrehydratedStyles {
}
interface IntrinsicElements {
"prehydrated-styles": PrehydratedStyles;
}
}
export { LocalJSX as JSX };
declare module "@stencil/core" {
export namespace JSX {
interface IntrinsicElements {
"prehydrated-styles": LocalJSX.PrehydratedStyles & JSXBase.HTMLAttributes<HTMLPrehydratedStylesElement>;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component, h } from '@stencil/core';

@Component({
tag: 'prehydrated-styles',
})
export class PrehydratedStyles {
render() {
return <div>prehydrated-styles</div>;
}
}
15 changes: 15 additions & 0 deletions test/karma/test-invisible-prehydration/stencil.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Config } from '../../../dist/declarations';

export const config: Config = {
namespace: 'TestInvisibleDefaultPrehydration',
tsconfig: 'tsconfig.json',
outputTargets: [
{
type: 'www',
dir: '../www',
empty: false,
indexHtml: 'prehydrated-styles.html',
serviceWorker: null,
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Config } from '../../../dist/declarations';

export const config: Config = {
namespace: 'TestInvisibleFalsePrehydration',
tsconfig: 'tsconfig.json',
invisiblePrehydration: false,
outputTargets: [
{
type: 'www',
dir: '../www',
empty: false,
indexHtml: 'prehydrated-styles.html',
serviceWorker: null,
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Config } from '../../../dist/declarations';

export const config: Config = {
namespace: 'TestInvisibleTruePrehydration',
tsconfig: 'tsconfig.json',
invisiblePrehydration: true,
outputTargets: [
{
type: 'www',
dir: '../www',
empty: false,
indexHtml: 'prehydrated-styles.html',
serviceWorker: null,
},
],
};
32 changes: 32 additions & 0 deletions test/karma/test-invisible-prehydration/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"compilerOptions": {
"alwaysStrict": true,
"allowSyntheticDefaultImports": true,
"allowUnreachableCode": true,
"declaration": false,
"resolveJsonModule": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"jsx": "react",
"jsxFactory": "h",
"lib": [
"dom",
"es2017"
],
"module": "esnext",
"moduleResolution": "node",
"noImplicitAny": false,
"noImplicitReturns": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"pretty": true,
"target": "es2017",
"baseUrl": ".",
"paths": {
"@stencil/core": ["../../../internal"],
"@stencil/core/internal": ["../../../internal"],
"@stencil/core/testing": ["../../../testing"]
}
},
"include": ["src"]
}
2 changes: 1 addition & 1 deletion test/karma/tsconfig-stencil.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"test-app/**/*.spec.ts",
"test-app/**/*.tsx",
"test-app/util.ts",
"test-app/global.ts",
"test-app/global.ts"
],
"exclude": [
"test-prerender",
Expand Down
2 changes: 1 addition & 1 deletion test/karma/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"paths": {
"@stencil/core": ["../../internal"],
"@stencil/core/internal": ["../../internal"],
"@stencil/core/testing": ["../../testing"]
"@stencil/core/testing": ["../../testing"],
}
},
"include": [
Expand Down

0 comments on commit a3d2928

Please sign in to comment.