From f96d3f2294c4b218296d85c579faa1114ca890c5 Mon Sep 17 00:00:00 2001 From: Chris Date: Fri, 29 May 2020 15:29:28 -0400 Subject: [PATCH 1/3] Adding collectRules to expose currently inserted styelsheet rules --- src/constructors/collectRules.js | 7 +++++++ src/index.js | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/constructors/collectRules.js diff --git a/src/constructors/collectRules.js b/src/constructors/collectRules.js new file mode 100644 index 0000000..58518b5 --- /dev/null +++ b/src/constructors/collectRules.js @@ -0,0 +1,7 @@ +import StyleSheet from '../models/StyleSheet' + +const collectRules = () => { + return StyleSheet.rules().filter(rule => rule.cssText.length > 0) +} + +export default collectRules diff --git a/src/index.js b/src/index.js index 8dc4e0c..ee413be 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ 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' @@ -14,4 +15,4 @@ const styled = _styled( export default styled -export { css, injectGlobal, keyframes, ThemeProvider } +export { css, collectRules, injectGlobal, keyframes, ThemeProvider } From 01752765d551263004bbd6c519e2db1889c8f09a Mon Sep 17 00:00:00 2001 From: Pedro Del Moral Lopez Date: Tue, 20 Jul 2021 00:32:16 -0400 Subject: [PATCH 2/3] Server Side Rendering patch for FOUC implemented, double CSS injection fixed. --- package-lock.json | 110 ++++++++++++++++++++++++++++- package.json | 1 + src/index.js | 4 +- src/mixins/ssr.js | 14 ++++ src/models/StyleSheet.js | 16 ++++- src/models/test/StyleSheet.test.js | 36 ++++++++++ src/test/ssr.test.js | 23 ++++++ src/vendor/glamor/sheet.js | 20 +++++- vue-styled-components/package.json | 90 +++++++++++++++++++++++ 9 files changed, 308 insertions(+), 6 deletions(-) create mode 100644 src/mixins/ssr.js create mode 100644 src/test/ssr.test.js create mode 100644 vue-styled-components/package.json diff --git a/package-lock.json b/package-lock.json index 70814c4..e9c2124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vue-styled-components", - "version": "1.5.0", + "version": "1.5.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1402,6 +1402,41 @@ "any-observable": "^0.3.0" } }, + "@sinonjs/commons": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", + "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "@sinonjs/samsam": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.0.2.tgz", + "integrity": "sha512-jxPRPp9n93ci7b8hMfJOFDPRLFYadN6FSpeROFTR4UNF4i5b+EK6m4QXPO46BDhFgRy1JuS87zAnFOzCUwMJcQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", @@ -6010,6 +6045,12 @@ "verror": "1.10.0" } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -7399,6 +7440,36 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "dev": true }, + "nise": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.0.tgz", + "integrity": "sha512-W5WlHu+wvo3PaKLsJJkgPup2LrsXCcm7AWwyNZkUnn5rwPkuPBi3Iwk5SQtN0mv+K65k7nKKjwNQ30wg3wLAQQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } + }, "node-cleanup": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", @@ -9017,6 +9088,43 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "sinon": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.1.tgz", + "integrity": "sha512-ZSSmlkSyhUWbkF01Z9tEbxZLF/5tRC9eojCdFh33gtQaP7ITQVaMWQHGuFM7Cuf/KEfihuh1tTl3/ABju3AQMg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "dependencies": { + "diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", diff --git a/package.json b/package.json index 27e8438..8e30e1d 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/index.js b/src/index.js index ee413be..73ff080 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,8 @@ 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)) @@ -15,4 +17,4 @@ const styled = _styled( export default styled -export { css, collectRules, injectGlobal, keyframes, ThemeProvider } +export { css, collectRules, injectGlobal, StyleSheet, ServerSideRenderMixin, keyframes, ThemeProvider } diff --git a/src/mixins/ssr.js b/src/mixins/ssr.js new file mode 100644 index 0000000..6c954ba --- /dev/null +++ b/src/mixins/ssr.js @@ -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 {} + } +} diff --git a/src/models/StyleSheet.js b/src/models/StyleSheet.js index 68f6373..e38c7f8 100644 --- a/src/models/StyleSheet.js +++ b/src/models/StyleSheet.js @@ -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() @@ -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 */ diff --git a/src/models/test/StyleSheet.test.js b/src/models/test/StyleSheet.test.js index 0540ef7..a699a1d 100644 --- a/src/models/test/StyleSheet.test.js +++ b/src/models/test/StyleSheet.test.js @@ -1,6 +1,7 @@ import styleSheet from '../StyleSheet' import { resetStyled } from '../../test/utils' import expect from 'expect' +import sinon from 'sinon' describe('stylesheet', () => { beforeEach(() => { @@ -9,6 +10,7 @@ describe('stylesheet', () => { describe('inject', () => { beforeEach(() => { + styleSheet.serverRendered = false styleSheet.inject() }) it('should inject the global sheet', () => { @@ -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() diff --git a/src/test/ssr.test.js b/src/test/ssr.test.js new file mode 100644 index 0000000..cf06eb9 --- /dev/null +++ b/src/test/ssr.test.js @@ -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; + // }) +}) \ No newline at end of file diff --git a/src/vendor/glamor/sheet.js b/src/vendor/glamor/sheet.js index 0286d73..650c069 100644 --- a/src/vendor/glamor/sheet.js +++ b/src/vendor/glamor/sheet.js @@ -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 @@ -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) { diff --git a/vue-styled-components/package.json b/vue-styled-components/package.json new file mode 100644 index 0000000..27e8438 --- /dev/null +++ b/vue-styled-components/package.json @@ -0,0 +1,90 @@ +{ + "name": "vue-styled-components", + "version": "1.5.1", + "description": "Visual primitives for the component age. A simple port of styled-components 💅 for Vue", + "main": "lib/index.js", + "module": "dist/vue-styled-components.es.js", + "author": "Lorenzo Girardi", + "license": "MIT", + "bugs": { + "url": "https://github.com/styled-components/vue-styled-components/issues" + }, + "scripts": { + "build": "npm run build:lib && npm run build:dist", + "prebuild:lib": "rm -rf lib/*", + "build:lib": "babel --out-dir lib src", + "prebuild:umd": "rm -rf dist/*", + "prebuild:dist": "rm -rf dist/*", + "build:dist": "rollup -c && rollup -c --environment PRODUCTION", + "build:watch": "npm run build:lib -- --watch", + "test": "mocha \"./src/**/*.test.js\" --require @babel/register --require ./mocha-bootstrap --timeout 5000", + "test:watch": "npm run test -- --watch", + "lint": "eslint src", + "prepublish": "npm run build", + "lint-staged": "lint-staged", + "dev": "node example/devServer.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/styled-components/vue-styled-components.git" + }, + "keywords": [ + "vue", + "css", + "css-in-js" + ], + "dependencies": { + "glamor": "^2.20.40", + "inline-style-prefixer": "^6.0.0", + "lodash.isplainobject": "^4.0.6", + "lodash.zipobject": "^4.1.3", + "stylis": "^3.5.4" + }, + "devDependencies": { + "@babel/cli": "^7.8.4", + "@babel/core": "^7.9.0", + "@babel/plugin-external-helpers": "^7.8.3", + "@babel/plugin-proposal-class-properties": "^7.8.3", + "@babel/plugin-proposal-object-rest-spread": "^7.9.5", + "@babel/preset-env": "^7.9.5", + "@babel/register": "^7.9.0", + "babel-eslint": "^10.1.0", + "babel-loader": "^8.1.0", + "babel-plugin-add-module-exports": "^1.0.2", + "chai": "^4.2.0", + "chokidar": "^3.3.1", + "danger": "^10.1.1", + "eslint": "^6.8.0", + "eslint-config-vue": "^2.0.2", + "eslint-plugin-html": "^6.0.2", + "eslint-plugin-vue": "^6.2.2", + "expect": "^25.4.0", + "express": "^4.17.1", + "jsdom": "^16.2.2", + "jsdom-global": "^3.0.2", + "lint-staged": "^10.1.7", + "lodash": "^4.17.15", + "mocha": "^7.1.1", + "node-watch": "^0.6.3", + "pre-commit": "^1.2.2", + "rollup": "^2.7.1", + "rollup-plugin-babel": "^4.4.0", + "rollup-plugin-commonjs": "^10.1.0", + "rollup-plugin-inject": "^3.0.2", + "rollup-plugin-json": "^4.0.0", + "rollup-plugin-node-builtins": "^2.1.2", + "rollup-plugin-node-resolve": "^5.2.0", + "rollup-plugin-replace": "^2.2.0", + "rollup-plugin-terser": "^5.3.0", + "rollup-plugin-visualizer": "^4.0.4", + "rollup-plugin-vue2": "^0.8.1", + "vue": "^2.6.11" + }, + "lint-staged": { + "*.js": [ + "eslint --fix", + "git add" + ] + }, + "pre-commit": "lint-staged" +} From 42e475af61cca956ef191b2bd1a6021a22a1a1e8 Mon Sep 17 00:00:00 2001 From: Pedro Del Moral Lopez Date: Tue, 20 Jul 2021 00:43:12 -0400 Subject: [PATCH 3/3] Removed old vue styled components folder. --- vue-styled-components/package.json | 90 ------------------------------ 1 file changed, 90 deletions(-) delete mode 100644 vue-styled-components/package.json diff --git a/vue-styled-components/package.json b/vue-styled-components/package.json deleted file mode 100644 index 27e8438..0000000 --- a/vue-styled-components/package.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "name": "vue-styled-components", - "version": "1.5.1", - "description": "Visual primitives for the component age. A simple port of styled-components 💅 for Vue", - "main": "lib/index.js", - "module": "dist/vue-styled-components.es.js", - "author": "Lorenzo Girardi", - "license": "MIT", - "bugs": { - "url": "https://github.com/styled-components/vue-styled-components/issues" - }, - "scripts": { - "build": "npm run build:lib && npm run build:dist", - "prebuild:lib": "rm -rf lib/*", - "build:lib": "babel --out-dir lib src", - "prebuild:umd": "rm -rf dist/*", - "prebuild:dist": "rm -rf dist/*", - "build:dist": "rollup -c && rollup -c --environment PRODUCTION", - "build:watch": "npm run build:lib -- --watch", - "test": "mocha \"./src/**/*.test.js\" --require @babel/register --require ./mocha-bootstrap --timeout 5000", - "test:watch": "npm run test -- --watch", - "lint": "eslint src", - "prepublish": "npm run build", - "lint-staged": "lint-staged", - "dev": "node example/devServer.js" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/styled-components/vue-styled-components.git" - }, - "keywords": [ - "vue", - "css", - "css-in-js" - ], - "dependencies": { - "glamor": "^2.20.40", - "inline-style-prefixer": "^6.0.0", - "lodash.isplainobject": "^4.0.6", - "lodash.zipobject": "^4.1.3", - "stylis": "^3.5.4" - }, - "devDependencies": { - "@babel/cli": "^7.8.4", - "@babel/core": "^7.9.0", - "@babel/plugin-external-helpers": "^7.8.3", - "@babel/plugin-proposal-class-properties": "^7.8.3", - "@babel/plugin-proposal-object-rest-spread": "^7.9.5", - "@babel/preset-env": "^7.9.5", - "@babel/register": "^7.9.0", - "babel-eslint": "^10.1.0", - "babel-loader": "^8.1.0", - "babel-plugin-add-module-exports": "^1.0.2", - "chai": "^4.2.0", - "chokidar": "^3.3.1", - "danger": "^10.1.1", - "eslint": "^6.8.0", - "eslint-config-vue": "^2.0.2", - "eslint-plugin-html": "^6.0.2", - "eslint-plugin-vue": "^6.2.2", - "expect": "^25.4.0", - "express": "^4.17.1", - "jsdom": "^16.2.2", - "jsdom-global": "^3.0.2", - "lint-staged": "^10.1.7", - "lodash": "^4.17.15", - "mocha": "^7.1.1", - "node-watch": "^0.6.3", - "pre-commit": "^1.2.2", - "rollup": "^2.7.1", - "rollup-plugin-babel": "^4.4.0", - "rollup-plugin-commonjs": "^10.1.0", - "rollup-plugin-inject": "^3.0.2", - "rollup-plugin-json": "^4.0.0", - "rollup-plugin-node-builtins": "^2.1.2", - "rollup-plugin-node-resolve": "^5.2.0", - "rollup-plugin-replace": "^2.2.0", - "rollup-plugin-terser": "^5.3.0", - "rollup-plugin-visualizer": "^4.0.4", - "rollup-plugin-vue2": "^0.8.1", - "vue": "^2.6.11" - }, - "lint-staged": { - "*.js": [ - "eslint --fix", - "git add" - ] - }, - "pre-commit": "lint-staged" -}