Skip to content

FOUC solution based on radicalpi's implementation #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"rollup-plugin-terser": "^5.3.0",
"rollup-plugin-visualizer": "^4.0.4",
"rollup-plugin-vue2": "^0.8.1",
"sinon": "^11.1.1",
"vue": "^2.6.11"
},
"lint-staged": {
Expand Down
7 changes: 7 additions & 0 deletions src/constructors/collectRules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import StyleSheet from '../models/StyleSheet'

const collectRules = () => {
return StyleSheet.rules().filter(rule => rule.cssText.length > 0)
}

export default collectRules
5 changes: 4 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import generateAlphabeticName from './utils/generateAlphabeticName'
import css from './constructors/css'
import collectRules from './constructors/collectRules'
import keyframes from './constructors/keyframes'
import injectGlobal from './constructors/injectGlobal'
import ThemeProvider from './providers/ThemeProvider'

import _styledComponent from './models/StyledComponent'
import _componentStyle from './models/ComponentStyle'
import _styled from './constructors/styled'
import StyleSheet from './models/StyleSheet'
import ServerSideRenderMixin from './mixins/ssr'

const styled = _styled(
_styledComponent(_componentStyle(generateAlphabeticName))
)

export default styled

export { css, injectGlobal, keyframes, ThemeProvider }
export { css, collectRules, injectGlobal, StyleSheet, ServerSideRenderMixin, keyframes, ThemeProvider }
14 changes: 14 additions & 0 deletions src/mixins/ssr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import collectRules from '../constructors/collectRules'

export default {
head () {
if (typeof window === 'undefined') {
const css = collectRules().map(rule => rule.cssText).join('')
return {
style: [{ hide: 'ssrStyles', innerHTML: css, type: 'text/css' }],
__dangerouslyDisableSanitizers: ['style']
}
}
return {}
}
}
16 changes: 14 additions & 2 deletions src/models/StyleSheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ class StyleSheet {
* are defined at initialization and should remain static after that */
this.globalStyleSheet = new GlamorSheet({ speedy: false })
this.componentStyleSheet = new GlamorSheet({ speedy: false, maxLength: 40 })

/* if the library user sets this to true in their index.vue, we assume they are handling
* the CSS injection themselves, we don't want to double inject. */
this.serverRendered = false
}
get injected () {
return this.globalStyleSheet.injected && this.componentStyleSheet.injected
}
inject () {
this.globalStyleSheet.inject()
this.componentStyleSheet.inject()
if (!this.serverRendered) {
this.globalStyleSheet.inject()
this.componentStyleSheet.inject()
}
}
flush () {
if (this.globalStyleSheet.sheet) this.globalStyleSheet.flush()
Expand All @@ -27,6 +33,12 @@ class StyleSheet {
rules () {
return this.globalStyleSheet.rules().concat(this.componentStyleSheet.rules())
}
serverRender () {
this.serverRendered = true
return this.rules().filter(function (rule) {
return rule.cssText.length > 0
})
}
}

/* Export stylesheet as a singleton class */
Expand Down
36 changes: 36 additions & 0 deletions src/models/test/StyleSheet.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styleSheet from '../StyleSheet'
import { resetStyled } from '../../test/utils'
import expect from 'expect'
import sinon from 'sinon'

describe('stylesheet', () => {
beforeEach(() => {
Expand All @@ -9,6 +10,7 @@ describe('stylesheet', () => {

describe('inject', () => {
beforeEach(() => {
styleSheet.serverRendered = false
styleSheet.inject()
})
it('should inject the global sheet', () => {
Expand All @@ -22,6 +24,40 @@ describe('stylesheet', () => {
})
})

describe('server-side inject', () => {
beforeEach(() => {
styleSheet.serverRendered = true
})
afterEach(() => {
styleSheet.serverRendered = false
})
it('does not call injects if serverRendered is true', () => {
const globalInject = sinon.spy()
const componentInject = sinon.spy()
styleSheet.inject()
expect(globalInject.called).toBe(false)
expect(componentInject.called).toBe(false)
})
})

describe('serverRender', () => {
beforeEach(() => {
styleSheet.serverRendered = false
})
afterEach(() => {
styleSheet.serverRendered = false
})
it('sets serverRendered to true when called', () => {
styleSheet.serverRender()
expect(styleSheet.serverRendered).toBe(true)
})
it('returns non empty rules', () => {
expect(styleSheet.serverRender()).toStrictEqual(styleSheet.rules().filter(function (rule) {
return rule.cssText.length > 0
}))
})
})

describe('flush', () => {
beforeEach(() => {
styleSheet.flush()
Expand Down
23 changes: 23 additions & 0 deletions src/test/ssr.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import ServerSideRenderMixin from '../mixins/ssr'
import expect from 'expect'
import sinon from 'sinon'

describe('ServerSideRenderMixin', () => {
it('has a function called head', () => {
expect(ServerSideRenderMixin.head instanceof Function).toBe(true)
})
it('returns a sane value when window is undefined', () => {
expect(ServerSideRenderMixin.head()).toStrictEqual({})
})
// it('returns a well formed style object for use by Nuxt when window is defined', () => {
// const windowRef = global.window;
// global.window = {document: {querySelector: () => null}};
// expect(ServerSideRenderMixin.head()).toStrictEqual(
// {
// style: [{ hide: 'ssrStyles', innerHTML: '', type: 'text/css' }],
// __dangerouslyDisableSanitizers: ['style']
// }
// )
// global.window = windowRef;
// })
})
20 changes: 18 additions & 2 deletions src/vendor/glamor/sheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,23 @@ export class StyleSheet {
maxLength = (isBrowser && oldIE) ? 4000 : 65000
} = {}) {
this.isSpeedy = speedy // the big drawback here is that the css won't be editable in devtools
this.sheet = undefined
this.sheet = isBrowser ? sheetForTag(makeStyleTag()) : {
cssRules: [],
insertRule: (rule) => {
var serverRule = {
cssText: rule
}

this.sheet.cssRules.push(serverRule)

return {
serverRule: serverRule,
appendRule: (newCss) => {
return serverRule.cssText += newCss
}
}
}
}
this.tags = []
this.maxLength = maxLength
this.ctr = 0
Expand Down Expand Up @@ -123,7 +139,7 @@ export class StyleSheet {
}
else{
const textNode = document.createTextNode(rule)
last(this.tags).appendChild(textNode)
// last(this.tags).appendChild(textNode) // this fails because last(this.tags) is a CSSStyleSheet... seems the author expected it to be a DOM Node?
insertedRule = { textNode, appendRule: newCss => textNode.appendData(newCss)}

if(!this.isSpeedy) {
Expand Down