From 516fe458058c9ec8218740472b301e935801ebbc Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Wed, 3 Nov 2021 08:40:34 +0100 Subject: [PATCH] Add `insertionPoint` option in `EmotionCache` (#2521) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * Improve tests * Use @testing-library * Address comments from review * Fix some flow issues * fix selectors * small tweaks Co-authored-by: Mateusz Burzyński --- .changeset/sixty-balloons-build.md | 31 ++++++++++ .../__tests__/__snapshots__/index.js.snap | 24 ++++++++ packages/cache/__tests__/index.js | 28 +++++++++ packages/cache/src/index.js | 6 +- packages/cache/types/index.d.ts | 2 + packages/sheet/README.md | 31 ++++++++++ .../__tests__/__snapshots__/index.js.snap | 59 +++++++++++++++++++ packages/sheet/__tests__/index.js | 47 +++++++++++++-- packages/sheet/src/index.js | 13 +++- packages/sheet/types/index.d.ts | 2 + 10 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 .changeset/sixty-balloons-build.md diff --git a/.changeset/sixty-balloons-build.md b/.changeset/sixty-balloons-build.md new file mode 100644 index 000000000..5e57f918e --- /dev/null +++ b/.changeset/sixty-balloons-build.md @@ -0,0 +1,31 @@ +--- +'@emotion/cache': minor +'@emotion/sheet': minor +--- + +Add insertionPoint option to the EmotionCache, to insert rules after the specified element. + +```jsx +const head = document.querySelector('head') + +// +const emotionInsertionPoint = document.createElement('meta') +emotionInsertionPoint.setAttribute('name', 'emotion-insertion-point') +emotionInsertionPoint.setAttribute('content', '') + +head.appendChild(emotionInsertionPoint) + +// the emotion sheets should be inserted right after the meta tag +const cache = createCache({ + key: 'my-app', + insertionPoint: emotionInsertionPoint +}) + +function App() { + return ( + +
+ + ) +} +``` diff --git a/packages/cache/__tests__/__snapshots__/index.js.snap b/packages/cache/__tests__/__snapshots__/index.js.snap index d0dac4a7a..ef764af09 100644 --- a/packages/cache/__tests__/__snapshots__/index.js.snap +++ b/packages/cache/__tests__/__snapshots__/index.js.snap @@ -1,3 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`should accept insertionPoint option 1`] = ` + + + + + + + + + ` + + // the sheet should be inserted between the first and last style nodes + const cache = createCache({ + key: 'test-insertion-point', + insertionPoint: safeQuerySelector('#first') + }) + + render( + +
+ + ) + + expect(document.head).toMatchSnapshot() +}) diff --git a/packages/cache/src/index.js b/packages/cache/src/index.js index 741cce9f0..afde219a0 100644 --- a/packages/cache/src/index.js +++ b/packages/cache/src/index.js @@ -28,7 +28,8 @@ export type Options = { key: string, container?: HTMLElement, speedy?: boolean, - prepend?: boolean + prepend?: boolean, + insertionPoint?: HTMLElement } let getServerStylisCache = isBrowser @@ -252,7 +253,8 @@ let createCache = (options: Options): EmotionCache => { container: ((container: any): HTMLElement), nonce: options.nonce, speedy: options.speedy, - prepend: options.prepend + prepend: options.prepend, + insertionPoint: options.insertionPoint }), nonce: options.nonce, inserted, diff --git a/packages/cache/types/index.d.ts b/packages/cache/types/index.d.ts index 12afbd87e..032744da1 100644 --- a/packages/cache/types/index.d.ts +++ b/packages/cache/types/index.d.ts @@ -36,7 +36,9 @@ export interface Options { key: string container?: HTMLElement speedy?: boolean + /** @deprecate use `insertionPoint` instead */ prepend?: boolean + insertionPoint?: HTMLElement } export default function createCache(options: Options): EmotionCache diff --git a/packages/sheet/README.md b/packages/sheet/README.md index 79b40aafe..328e78db3 100644 --- a/packages/sheet/README.md +++ b/packages/sheet/README.md @@ -49,8 +49,39 @@ This defines how rules are inserted. If it is true, rules will be inserted with #### prepend +**Deprecated:** Please use `insertionPoint` option instead. + This defines where rules are inserted into the `container`. By default they are appended but this can be changed by using `prepend: true` option. +#### insertionPoint + +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: + +```jsx +const head = document.querySelector('head') + +// +const emotionInsertionPoint = document.createElement('meta') +emotionInsertionPoint.setAttribute('name', 'emotion-insertion-point') +emotionInsertionPoint.setAttribute('content', '') + +head.appendChild(emotionInsertionPoint) + +// the emotion sheets should be inserted right after the meta tag +const cache = createCache({ + key: 'my-app', + insertionPoint: emotionInsertionPoint +}) + +function App() { + return ( + +
+ + ) +} +``` + ### Methods #### insert diff --git a/packages/sheet/__tests__/__snapshots__/index.js.snap b/packages/sheet/__tests__/__snapshots__/index.js.snap index cc67d30ce..299b903bb 100644 --- a/packages/sheet/__tests__/__snapshots__/index.js.snap +++ b/packages/sheet/__tests__/__snapshots__/index.js.snap @@ -1,5 +1,39 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`StyleSheet should accept insertionPoint option 1`] = ` + + + + + + + + + + + + + +`; diff --git a/packages/sheet/__tests__/index.js b/packages/sheet/__tests__/index.js index da43e50e1..5b1b23c43 100644 --- a/packages/sheet/__tests__/index.js +++ b/packages/sheet/__tests__/index.js @@ -17,6 +17,11 @@ afterEach(() => { jest.clearAllMocks() }) +beforeEach(() => { + safeQuerySelector('head').innerHTML = '' + safeQuerySelector('body').innerHTML = '' +}) + describe('StyleSheet', () => { it('should be speedy by default in production', () => { process.env.NODE_ENV = 'production' @@ -98,8 +103,6 @@ describe('StyleSheet', () => { expect(sheet.tags).toHaveLength(1) expect(sheet.tags[0].parentNode).toBe(container) sheet.flush() - // $FlowFixMe - document.body.removeChild(container) }) it('should accept prepend option', () => { @@ -114,7 +117,44 @@ describe('StyleSheet', () => { expect(document.documentElement).toMatchSnapshot() sheet.flush() - head.removeChild(otherStyle) + }) + + it('should accept insertionPoint option', () => { + const head = safeQuerySelector('head') + + head.innerHTML = ` + + + ` + + // the sheet should be inserted between the first and last style nodes + const sheet = new StyleSheet({ + ...defaultOptions, + insertionPoint: safeQuerySelector('#first') + }) + sheet.insert(rule) + sheet.insert(rule2) + expect(document.documentElement).toMatchSnapshot() + + sheet.flush() + }) + + it('should work if insertionPoint is last element', () => { + const head = safeQuerySelector('head') + const lastStyle = document.createElement('style') + lastStyle.setAttribute('id', 'last') + head.appendChild(lastStyle) + + // the sheet should be inserted after the first node + const sheet = new StyleSheet({ + ...defaultOptions, + insertionPoint: lastStyle + }) + sheet.insert(rule) + sheet.insert(rule2) + expect(document.documentElement).toMatchSnapshot() + + sheet.flush() }) it('should be able to hydrate styles', () => { @@ -179,7 +219,6 @@ describe('StyleSheet', () => { expect(document.documentElement).toMatchSnapshot() sheet.flush() - head.removeChild(otherStyle) }) it('should not crash when flushing when styles are already detached', () => { diff --git a/packages/sheet/src/index.js b/packages/sheet/src/index.js index eba2c1328..cd559a73a 100644 --- a/packages/sheet/src/index.js +++ b/packages/sheet/src/index.js @@ -44,7 +44,8 @@ export type Options = { key: string, container: HTMLElement, speedy?: boolean, - prepend?: boolean + prepend?: boolean, + insertionPoint?: HTMLElement } function createStyleElement(options: { @@ -70,6 +71,7 @@ export class StyleSheet { nonce: string | void prepend: boolean | void before: Element | null + insertionPoint: HTMLElement | void constructor(options: Options) { this.isSpeedy = options.speedy === undefined @@ -82,13 +84,20 @@ export class StyleSheet { this.key = options.key this.container = options.container this.prepend = options.prepend + this.insertionPoint = options.insertionPoint this.before = null } _insertTag = (tag: HTMLStyleElement) => { let before if (this.tags.length === 0) { - before = this.prepend ? this.container.firstChild : this.before + if (this.insertionPoint) { + before = this.insertionPoint.nextSibling + } else if (this.prepend) { + before = this.container.firstChild + } else { + before = this.before + } } else { before = this.tags[this.tags.length - 1].nextSibling } diff --git a/packages/sheet/types/index.d.ts b/packages/sheet/types/index.d.ts index 2a0ad6b77..d07ebfda4 100644 --- a/packages/sheet/types/index.d.ts +++ b/packages/sheet/types/index.d.ts @@ -6,7 +6,9 @@ export interface Options { key: string container: HTMLElement speedy?: boolean + /** @deprecate use `insertionPoint` instead */ prepend?: boolean + insertionPoint?: HTMLElement } export class StyleSheet {