Skip to content

Commit 516fe45

Browse files
mnajdovaAndarist
andauthored
Add insertionPoint option in EmotionCache (#2521)
* Add insertionPoint option in EmotionCache * yarn changeset * yarn lint * Add tests as per view * Updated changeset, improved tests * Update packages/sheet/src/index.js * Update packages/cache/__tests__/index.js Co-authored-by: Mateusz Burzyński <[email protected]> * Improve tests * Use @testing-library * Address comments from review * Fix some flow issues * fix selectors * small tweaks Co-authored-by: Mateusz Burzyński <[email protected]>
1 parent 01e4e0f commit 516fe45

File tree

10 files changed

+235
-8
lines changed

10 files changed

+235
-8
lines changed

.changeset/sixty-balloons-build.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
'@emotion/cache': minor
3+
'@emotion/sheet': minor
4+
---
5+
6+
Add insertionPoint option to the EmotionCache, to insert rules after the specified element.
7+
8+
```jsx
9+
const head = document.querySelector('head')
10+
11+
// <meta name="emotion-insertion-point" content="">
12+
const emotionInsertionPoint = document.createElement('meta')
13+
emotionInsertionPoint.setAttribute('name', 'emotion-insertion-point')
14+
emotionInsertionPoint.setAttribute('content', '')
15+
16+
head.appendChild(emotionInsertionPoint)
17+
18+
// the emotion sheets should be inserted right after the meta tag
19+
const cache = createCache({
20+
key: 'my-app',
21+
insertionPoint: emotionInsertionPoint
22+
})
23+
24+
function App() {
25+
return (
26+
<CacheProvider value={cache}>
27+
<Main />
28+
</CacheProvider>
29+
)
30+
}
31+
```
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,27 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`should accept insertionPoint option 1`] = `
4+
<head>
5+
6+
7+
<style
8+
id="first"
9+
/>
10+
<style
11+
data-emotion="test-insertion-point"
12+
data-s=""
13+
>
14+
15+
.test-insertion-point-83n355{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;color:blue;}
16+
</style>
17+
18+
19+
<style
20+
id="last"
21+
/>
22+
23+
24+
</head>
25+
`;
26+
327
exports[`throws correct error with invalid key 1`] = `"Emotion key must only contain lower case alphabetical characters and - but \\".\\" was passed"`;

packages/cache/__tests__/index.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,36 @@
11
// @flow
2+
/** @jsx jsx */
3+
import 'test-utils/next-env'
4+
import { safeQuerySelector } from 'test-utils'
25
import createCache from '@emotion/cache'
6+
import { jsx, CacheProvider } from '@emotion/react'
7+
import { render } from '@testing-library/react'
38

49
test('throws correct error with invalid key', () => {
510
expect(() => {
611
createCache({ key: '.' })
712
}).toThrowErrorMatchingSnapshot()
813
})
14+
15+
it('should accept insertionPoint option', () => {
16+
const head = safeQuerySelector('head')
17+
18+
head.innerHTML = `
19+
<style id="first"></style>
20+
<style id="last"></style>
21+
`
22+
23+
// the sheet should be inserted between the first and last style nodes
24+
const cache = createCache({
25+
key: 'test-insertion-point',
26+
insertionPoint: safeQuerySelector('#first')
27+
})
28+
29+
render(
30+
<CacheProvider value={cache}>
31+
<div css={{ display: 'flex', color: 'blue' }} />
32+
</CacheProvider>
33+
)
34+
35+
expect(document.head).toMatchSnapshot()
36+
})

packages/cache/src/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export type Options = {
2828
key: string,
2929
container?: HTMLElement,
3030
speedy?: boolean,
31-
prepend?: boolean
31+
prepend?: boolean,
32+
insertionPoint?: HTMLElement
3233
}
3334

3435
let getServerStylisCache = isBrowser
@@ -252,7 +253,8 @@ let createCache = (options: Options): EmotionCache => {
252253
container: ((container: any): HTMLElement),
253254
nonce: options.nonce,
254255
speedy: options.speedy,
255-
prepend: options.prepend
256+
prepend: options.prepend,
257+
insertionPoint: options.insertionPoint
256258
}),
257259
nonce: options.nonce,
258260
inserted,

packages/cache/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ export interface Options {
3636
key: string
3737
container?: HTMLElement
3838
speedy?: boolean
39+
/** @deprecate use `insertionPoint` instead */
3940
prepend?: boolean
41+
insertionPoint?: HTMLElement
4042
}
4143

4244
export default function createCache(options: Options): EmotionCache

packages/sheet/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,39 @@ This defines how rules are inserted. If it is true, rules will be inserted with
4949
5050
#### prepend
5151
52+
**Deprecated:** Please use `insertionPoint` option instead.
53+
5254
This defines where rules are inserted into the `container`. By default they are appended but this can be changed by using `prepend: true` option.
5355
56+
#### insertionPoint
57+
58+
This defines specific dom node after which the rules are inserted into the `container`. You can use a `meta` tag to specify the specific location:
59+
60+
```jsx
61+
const head = document.querySelector('head')
62+
63+
// <meta name="emotion-insertion-point" content="">
64+
const emotionInsertionPoint = document.createElement('meta')
65+
emotionInsertionPoint.setAttribute('name', 'emotion-insertion-point')
66+
emotionInsertionPoint.setAttribute('content', '')
67+
68+
head.appendChild(emotionInsertionPoint)
69+
70+
// the emotion sheets should be inserted right after the meta tag
71+
const cache = createCache({
72+
key: 'my-app',
73+
insertionPoint: emotionInsertionPoint
74+
})
75+
76+
function App() {
77+
return (
78+
<CacheProvider value={cache}>
79+
<Main />
80+
</CacheProvider>
81+
)
82+
}
83+
```
84+
5485
### Methods
5586

5687
#### insert

packages/sheet/__tests__/__snapshots__/index.js.snap

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`StyleSheet should accept insertionPoint option 1`] = `
4+
<html>
5+
<head>
6+
7+
8+
<style
9+
id="first"
10+
/>
11+
<style
12+
data-emotion=""
13+
data-s=""
14+
>
15+
16+
html { color: hotpink; }
17+
</style>
18+
<style
19+
data-emotion=""
20+
data-s=""
21+
>
22+
23+
* { box-sizing: border-box; }
24+
</style>
25+
26+
27+
<style
28+
id="last"
29+
/>
30+
31+
32+
</head>
33+
<body />
34+
</html>
35+
`;
36+
337
exports[`StyleSheet should accept prepend option 1`] = `
438
<html>
539
<head>
@@ -212,3 +246,28 @@ exports[`StyleSheet should use the container option instead of document.head to
212246
</body>
213247
</html>
214248
`;
249+
250+
exports[`StyleSheet should work if insertionPoint is last element 1`] = `
251+
<html>
252+
<head>
253+
<style
254+
id="last"
255+
/>
256+
<style
257+
data-emotion=""
258+
data-s=""
259+
>
260+
261+
html { color: hotpink; }
262+
</style>
263+
<style
264+
data-emotion=""
265+
data-s=""
266+
>
267+
268+
* { box-sizing: border-box; }
269+
</style>
270+
</head>
271+
<body />
272+
</html>
273+
`;

packages/sheet/__tests__/index.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ afterEach(() => {
1717
jest.clearAllMocks()
1818
})
1919

20+
beforeEach(() => {
21+
safeQuerySelector('head').innerHTML = ''
22+
safeQuerySelector('body').innerHTML = ''
23+
})
24+
2025
describe('StyleSheet', () => {
2126
it('should be speedy by default in production', () => {
2227
process.env.NODE_ENV = 'production'
@@ -98,8 +103,6 @@ describe('StyleSheet', () => {
98103
expect(sheet.tags).toHaveLength(1)
99104
expect(sheet.tags[0].parentNode).toBe(container)
100105
sheet.flush()
101-
// $FlowFixMe
102-
document.body.removeChild(container)
103106
})
104107

105108
it('should accept prepend option', () => {
@@ -114,7 +117,44 @@ describe('StyleSheet', () => {
114117
expect(document.documentElement).toMatchSnapshot()
115118

116119
sheet.flush()
117-
head.removeChild(otherStyle)
120+
})
121+
122+
it('should accept insertionPoint option', () => {
123+
const head = safeQuerySelector('head')
124+
125+
head.innerHTML = `
126+
<style id="first"></style>
127+
<style id="last"></style>
128+
`
129+
130+
// the sheet should be inserted between the first and last style nodes
131+
const sheet = new StyleSheet({
132+
...defaultOptions,
133+
insertionPoint: safeQuerySelector('#first')
134+
})
135+
sheet.insert(rule)
136+
sheet.insert(rule2)
137+
expect(document.documentElement).toMatchSnapshot()
138+
139+
sheet.flush()
140+
})
141+
142+
it('should work if insertionPoint is last element', () => {
143+
const head = safeQuerySelector('head')
144+
const lastStyle = document.createElement('style')
145+
lastStyle.setAttribute('id', 'last')
146+
head.appendChild(lastStyle)
147+
148+
// the sheet should be inserted after the first node
149+
const sheet = new StyleSheet({
150+
...defaultOptions,
151+
insertionPoint: lastStyle
152+
})
153+
sheet.insert(rule)
154+
sheet.insert(rule2)
155+
expect(document.documentElement).toMatchSnapshot()
156+
157+
sheet.flush()
118158
})
119159

120160
it('should be able to hydrate styles', () => {
@@ -179,7 +219,6 @@ describe('StyleSheet', () => {
179219
expect(document.documentElement).toMatchSnapshot()
180220

181221
sheet.flush()
182-
head.removeChild(otherStyle)
183222
})
184223

185224
it('should not crash when flushing when styles are already detached', () => {

packages/sheet/src/index.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ export type Options = {
4444
key: string,
4545
container: HTMLElement,
4646
speedy?: boolean,
47-
prepend?: boolean
47+
prepend?: boolean,
48+
insertionPoint?: HTMLElement
4849
}
4950

5051
function createStyleElement(options: {
@@ -70,6 +71,7 @@ export class StyleSheet {
7071
nonce: string | void
7172
prepend: boolean | void
7273
before: Element | null
74+
insertionPoint: HTMLElement | void
7375
constructor(options: Options) {
7476
this.isSpeedy =
7577
options.speedy === undefined
@@ -82,13 +84,20 @@ export class StyleSheet {
8284
this.key = options.key
8385
this.container = options.container
8486
this.prepend = options.prepend
87+
this.insertionPoint = options.insertionPoint
8588
this.before = null
8689
}
8790

8891
_insertTag = (tag: HTMLStyleElement) => {
8992
let before
9093
if (this.tags.length === 0) {
91-
before = this.prepend ? this.container.firstChild : this.before
94+
if (this.insertionPoint) {
95+
before = this.insertionPoint.nextSibling
96+
} else if (this.prepend) {
97+
before = this.container.firstChild
98+
} else {
99+
before = this.before
100+
}
92101
} else {
93102
before = this.tags[this.tags.length - 1].nextSibling
94103
}

packages/sheet/types/index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ export interface Options {
66
key: string
77
container: HTMLElement
88
speedy?: boolean
9+
/** @deprecate use `insertionPoint` instead */
910
prepend?: boolean
11+
insertionPoint?: HTMLElement
1012
}
1113

1214
export class StyleSheet {

0 commit comments

Comments
 (0)