Skip to content

Commit 8f92b11

Browse files
fix(runtime): place scoped component styles after preconnect links but before custom styles (#5938)
* fix(runtime): place scoped component styles after preconnect links but before custom styles * prettier * make styles appear for custom styles if there are no preconnect links * prettier * prettier
1 parent e9acd8c commit 8f92b11

File tree

3 files changed

+72
-9
lines changed

3 files changed

+72
-9
lines changed

src/runtime/styles.ts

+19-2
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,27 @@ export const addStyle = (styleContainerNode: any, cmpMeta: d.ComponentRuntimeMet
9292
}
9393

9494
/**
95-
* attach styles at the end of the head tag if we render shadow components
95+
* attach styles at the end of the head tag if we render scoped components
9696
*/
9797
if (!(cmpMeta.$flags$ & CMP_FLAGS.shadowDomEncapsulation)) {
98-
styleContainerNode.append(styleElm);
98+
if (styleContainerNode.nodeName === 'HEAD') {
99+
/**
100+
* if the page contains preconnect links, we want to insert the styles
101+
* after the last preconnect link to ensure the styles are preloaded
102+
*/
103+
const preconnectLinks = styleContainerNode.querySelectorAll('link[rel=preconnect]');
104+
const referenceNode =
105+
preconnectLinks.length > 0
106+
? preconnectLinks[preconnectLinks.length - 1].nextSibling
107+
: document.querySelector('style');
108+
styleContainerNode.insertBefore(styleElm, referenceNode);
109+
} else if ('host' in styleContainerNode) {
110+
/**
111+
* if a scoped component is used within a shadow root, we want to insert the styles
112+
* at the beginning of the shadow root node
113+
*/
114+
styleContainerNode.prepend(styleElm, null);
115+
}
99116
}
100117

101118
/**

test/end-to-end/src/miscellaneous/renderToString.e2e.ts

+52-6
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,16 @@ describe('renderToString', () => {
4949
);
5050
});
5151

52-
it('puts style last in the head tag', async () => {
52+
it('puts style after preconnect links in the head tag', async () => {
5353
const { html } = await renderToString(
5454
`<html>
5555
<head>
5656
<link rel="preconnect" href="https://some-url.com" />
57+
<style>
58+
.myComponent {
59+
display: none;
60+
}
61+
</style>
5762
</head>
5863
5964
<body>
@@ -72,11 +77,52 @@ describe('renderToString', () => {
7277
{ fullDocument: true, serializeShadowRoot: false },
7378
);
7479

80+
/**
81+
* expect the scoped component styles to be injected after the preconnect link
82+
*/
7583
expect(html).toContain(
76-
'<link rel="preconnect" href="https://some-url.com"> <style sty-id="sc-scoped-car-list">.sc-scoped-car-list-h{',
84+
'<link rel="preconnect" href="https://some-url.com"><style sty-id="sc-scoped-car-list">.sc-scoped-car-list-h',
7785
);
78-
expect(html).toContain(
79-
'.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style></head> <body> <div class="__next"> <main> <scoped-car-list cars',
86+
/**
87+
* expect the custom style tag to be last in the head tag
88+
*/
89+
expect(html.replaceAll(/\n[ ]*/g, '')).toContain(
90+
`.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style> <style>.myComponent {display: none;}</style> </head> <body>`,
91+
);
92+
});
93+
94+
it('puts styles before any custom styles', async () => {
95+
const { html } = await renderToString(
96+
`<html>
97+
<head>
98+
<style>
99+
.myComponent {
100+
display: none;
101+
}
102+
</style>
103+
</head>
104+
105+
<body>
106+
<div class="__next">
107+
<main>
108+
<scoped-car-list cars=${JSON.stringify([vento, beetle])}></scoped-car-list>
109+
</main>
110+
</div>
111+
112+
<script type="module">
113+
import { defineCustomElements } from "./static/loader/index.js";
114+
defineCustomElements().catch(console.error);
115+
</script>
116+
</body>
117+
</html>`,
118+
{ fullDocument: true, serializeShadowRoot: false },
119+
);
120+
121+
/**
122+
* expect the scoped component styles to be injected before custom styles
123+
*/
124+
expect(html.replaceAll(/\n[ ]*/g, '')).toContain(
125+
'.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><style class="vjs-styles-defaults">.video-js {width: 300px;height: 150px;}.vjs-fluid {padding-top: 56.25%}</style> <style>.myComponent {display: none;}</style> </head>',
80126
);
81127
});
82128

@@ -109,8 +155,8 @@ describe('renderToString', () => {
109155
/**
110156
* renders hydration styles and custom link tag within the head tag
111157
*/
112-
expect(html).toContain(
113-
'<link rel="stylesheet" href="whatever.css"> <style sty-id="sc-scoped-car-list">.sc-scoped-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}ul.sc-scoped-car-list{display:block;margin:0;padding:0}li.sc-scoped-car-list{list-style:none;margin:0;padding:20px}.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style></head> <body> <div class="__next"> <main> <scoped-car-list cars=',
158+
expect(html.replaceAll(/\n[ ]*/g, '')).toContain(
159+
'<head><meta charset="utf-8"><style sty-id="sc-scoped-car-list">.sc-scoped-car-list-h{display:block;margin:10px;padding:10px;border:1px solid blue}ul.sc-scoped-car-list{display:block;margin:0;padding:0}li.sc-scoped-car-list{list-style:none;margin:0;padding:20px}.selected.sc-scoped-car-list{font-weight:bold;background:rgb(255, 255, 210)}</style><style class="vjs-styles-defaults">.video-js {width: 300px;height: 150px;}.vjs-fluid {padding-top: 56.25%}</style> <link rel="stylesheet" href="whatever.css"> </head>',
114160
);
115161
});
116162
});

test/end-to-end/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"jsxFragmentFactory": "Fragment",
1313
"lib": [
1414
"dom",
15-
"es2017"
15+
"es2021"
1616
],
1717
"module": "esnext",
1818
"moduleResolution": "node",

0 commit comments

Comments
 (0)