Repository: styled-components/babel-plugin-styled-components
Pull request #70 (2017-06-27 21:48:12)

Fix newlines being mangled inside values


Patch:
diff --git a/src/css/preprocessUtils.js b/src/css/preprocessUtils.js
index 1674580..4c13791 100644
--- a/src/css/preprocessUtils.js
+++ b/src/css/preprocessUtils.js
@@ -10,19 +10,6 @@ import {
   fixGlobalPlaceholders,
 } from './placeholderUtils'
 
-// Reset stylis middleware
-stylis.use(null)
-
-// Set stylis options (matching SC options)
-stylis.set({
-  global: false,
-  cascade: true,
-  keyframe: false,
-  prefix: true,
-  compress: false,
-  semicolon: true,
-})
-
 // Assembles CSS partials and replaces interpolations with placeholders
 export const assembleAndInterleavePlaceholders = cssArr => {
   let css = cssArr[0]
diff --git a/src/minify/index.js b/src/minify/index.js
index f9d1360..6e6b69b 100644
--- a/src/minify/index.js
+++ b/src/minify/index.js
@@ -2,6 +2,7 @@ import { makePlaceholder, splitByPlaceholders } from '../css/placeholderUtils'
 
 const makeMultilineCommentRegex = newlinePattern => new RegExp('\\/\\*(.|' + newlinePattern + ')*?\\*\\/', 'g')
 const lineCommentStart = /\/\//g
+const symbolRegex = /(\s*[;:{},]\s*)/g
 
 // Counts occurences of substr inside str
 const countOccurences = (str, substr) => str.split(substr).length - 1
@@ -37,6 +38,25 @@ export const stripLineComment = line => (
   ))
 )
 
+export const compressSymbols = code => code
+  .split(symbolRegex)
+  .reduce((str, fragment, index) => {
+    // Even-indices are non-symbol fragments
+    if (index % 2 === 0) {
+      return str + fragment
+    }
+
+    // Only manipulate symbols outside of strings
+    if (
+      countOccurences(str, '\'') % 2 === 0 &&
+      countOccurences(str, '\"') % 2 === 0
+    ) {
+      return str + fragment.trim()
+    }
+
+    return str + fragment
+  }, '')
+
 // Detects lines that are exclusively line comments
 const isLineComment = line => line.trim().startsWith('//')
 
@@ -46,14 +66,14 @@ const minify = linebreakPattern => {
   const multilineCommentRegex = makeMultilineCommentRegex(linebreakPattern)
 
   return code => {
-    const lines = code
+    const newCode = code
       .replace(multilineCommentRegex, '\n') // Remove multiline comments
       .split(linebreakRegex) // Split at newlines
+      .filter(line => line.length > 0 && !isLineComment(line)) // Removes lines containing only line comments
       .map(stripLineComment) // Remove line comments inside text
+      .join(' ') // Rejoin all lines
 
-    return lines
-      .filter(line => !isLineComment(line)) // Removes lines containing only line comments
-      .join('')
+    return compressSymbols(newCode)
   }
 }
 


Test patch:
diff --git a/test/fixtures/01-add-display-names/after.js b/test/fixtures/01-add-display-names/after.js
index b0fb92a..8e8a26a 100644
--- a/test/fixtures/01-add-display-names/after.js
+++ b/test/fixtures/01-add-display-names/after.js
@@ -1,6 +1,6 @@
 const Test = styled.div.withConfig({
   displayName: 'Test'
-})`width: 100%;`;
+})`width:100%;`;
 const Test2 = styled('div').withConfig({
   displayName: 'Test2'
 })``;
diff --git a/test/fixtures/02-add-identifier/after.js b/test/fixtures/02-add-identifier/after.js
index 5096950..3750c09 100644
--- a/test/fixtures/02-add-identifier/after.js
+++ b/test/fixtures/02-add-identifier/after.js
@@ -1,6 +1,6 @@
 const Test = styled.div.withConfig({
   componentId: "f15zqz-0"
-})`width: 100%;`;
+})`width:100%;`;
 const Test2 = true ? styled.div.withConfig({
   componentId: "f15zqz-1"
 })`` : styled.div.withConfig({
diff --git a/test/fixtures/03-add-identifier-and-display-name/after.js b/test/fixtures/03-add-identifier-and-display-name/after.js
index b60370a..90a8f7f 100644
--- a/test/fixtures/03-add-identifier-and-display-name/after.js
+++ b/test/fixtures/03-add-identifier-and-display-name/after.js
@@ -1,7 +1,7 @@
 const Test = styled.div.withConfig({
   displayName: "Test",
   componentId: "s1j54k93-0"
-})`width: 100%;`;
+})`width:100%;`;
 const Test2 = true ? styled.div.withConfig({
   displayName: "Test2",
   componentId: "s1j54k93-1"
diff --git a/test/fixtures/04-track-the-imported-variable/after.js b/test/fixtures/04-track-the-imported-variable/after.js
index c18be45..9b0f4f8 100644
--- a/test/fixtures/04-track-the-imported-variable/after.js
+++ b/test/fixtures/04-track-the-imported-variable/after.js
@@ -3,7 +3,7 @@ import s from "styled-components";
 const Test = s.div.withConfig({
   displayName: "Test",
   componentId: "syqrfc-0"
-})`width: 100%;`;
+})`width:100%;`;
 const Test2 = true ? s.div.withConfig({
   displayName: "Test2",
   componentId: "syqrfc-1"
diff --git a/test/fixtures/05-use-file-name/after.js b/test/fixtures/05-use-file-name/after.js
index 21b7b28..3437baa 100644
--- a/test/fixtures/05-use-file-name/after.js
+++ b/test/fixtures/05-use-file-name/after.js
@@ -3,7 +3,7 @@ import styled from "styled-components";
 const Test = styled.div.withConfig({
   displayName: "before__Test",
   componentId: "gl3p53-0"
-})`color: red;`;
+})`color:red;`;
 styled.div.withConfig({
   displayName: "before",
   componentId: "gl3p53-1"
diff --git a/test/fixtures/06-work-with-hoisted-default-as-import/after.js b/test/fixtures/06-work-with-hoisted-default-as-import/after.js
index 22cbf69..0d28270 100644
--- a/test/fixtures/06-work-with-hoisted-default-as-import/after.js
+++ b/test/fixtures/06-work-with-hoisted-default-as-import/after.js
@@ -1,5 +1,5 @@
 const Test = s.div.withConfig({
   displayName: 'before__Test',
   componentId: 's2wuo7o-0'
-})`width: 100%;`;
+})`width:100%;`;
 import { default as s, css } from 'styled-components';
diff --git a/test/fixtures/07-not-use-private-api-if-not-required/after.js b/test/fixtures/07-not-use-private-api-if-not-required/after.js
index 9fa94bb..08a20d6 100644
--- a/test/fixtures/07-not-use-private-api-if-not-required/after.js
+++ b/test/fixtures/07-not-use-private-api-if-not-required/after.js
@@ -1,3 +1,3 @@
 import styled from 'styled-components';
 
-const Test = styled.div`width: 100%;`;
+const Test = styled.div`width:100%;`;
diff --git a/test/fixtures/08-minify-css-to-use-with-transpilation/after.js b/test/fixtures/08-minify-css-to-use-with-transpilation/after.js
index d3bc61b..33208c6 100644
--- a/test/fixtures/08-minify-css-to-use-with-transpilation/after.js
+++ b/test/fixtures/08-minify-css-to-use-with-transpilation/after.js
@@ -1,10 +1,10 @@
 'use strict';
 
-var _templateObject = _taggedTemplateLiteral(['width: 100%;'], ['width: 100%;']),
-    _templateObject2 = _taggedTemplateLiteral(['content: "  ', '  ";'], ['content: "  ', '  ";']),
-    _templateObject3 = _taggedTemplateLiteral(['content: "  ', '  ";color: red;'], ['content: "  ', '  ";color: red;']),
-    _templateObject4 = _taggedTemplateLiteral(['color: red;'], ['color: red;']),
-    _templateObject5 = _taggedTemplateLiteral(['&:hover {color: blue;}'], ['&:hover {color: blue;}']);
+var _templateObject = _taggedTemplateLiteral(['width:100%;'], ['width:100%;']),
+    _templateObject2 = _taggedTemplateLiteral(['content:"  ', '  ";'], ['content:"  ', '  ";']),
+    _templateObject3 = _taggedTemplateLiteral(['content:"  ', '  ";color:red;'], ['content:"  ', '  ";color:red;']),
+    _templateObject4 = _taggedTemplateLiteral(['color:red;'], ['color:red;']),
+    _templateObject5 = _taggedTemplateLiteral(['&:hover{color:blue;}'], ['&:hover{color:blue;}']);
 
 var _styledComponents = require('styled-components');
 
diff --git a/test/fixtures/09-minify-css-to-use-without-transpilation/after.js b/test/fixtures/09-minify-css-to-use-without-transpilation/after.js
index b0e2585..63bdade 100644
--- a/test/fixtures/09-minify-css-to-use-without-transpilation/after.js
+++ b/test/fixtures/09-minify-css-to-use-without-transpilation/after.js
@@ -1,13 +1,13 @@
 import styled from 'styled-components';
 
-const Simple = styled.div`width: 100%;`;
+const Simple = styled.div`width:100%;`;
 
-const Interpolation = styled.div`content: "https://test.com/${props => props.endpoint}";`;
+const Interpolation = styled.div`content:"https://test.com/${props => props.endpoint}";`;
 
-const SpecialCharacters = styled.div`content: "  ${props => props.text}  ";color: red;`;
+const SpecialCharacters = styled.div`content:"  ${props => props.text}  ";color:red;`;
 
-const Comment = styled.div`width: 100%;color: red;`;
+const Comment = styled.div`width:100%;color:red;`;
 
-const Parens = styled.div`&:hover {color: blue;}color: red;`;
+const Parens = styled.div`&:hover{color:blue;}color:red;`;
 
-const UrlComments = styled.div`color: red;background: red;border: 1px solid green;`;
+const UrlComments = styled.div`color:red;background:red;border:1px solid green;`;
diff --git a/test/fixtures/10-minify-css-in-helpers/after.js b/test/fixtures/10-minify-css-in-helpers/after.js
index aec3f42..0a99a02 100644
--- a/test/fixtures/10-minify-css-in-helpers/after.js
+++ b/test/fixtures/10-minify-css-in-helpers/after.js
@@ -1,5 +1,5 @@
 import { css, keyframes } from 'styled-components';
 
-const key = keyframes`to {transform: rotate(360deg);}`;
+const key = keyframes`to{transform:rotate(360deg);}`;
 
-const color = css`color: ${theColor};`;
+const color = css`color:${theColor};`;
diff --git a/test/fixtures/11-transpile-template-literals-with-config/.babelrc b/test/fixtures/11-transpile-template-literals-with-config/.babelrc
index e15d5bf..47e5afb 100644
--- a/test/fixtures/11-transpile-template-literals-with-config/.babelrc
+++ b/test/fixtures/11-transpile-template-literals-with-config/.babelrc
@@ -7,7 +7,8 @@
       "ssr": false,
       "displayName": true,
       "transpileTemplateLiterals": true,
-      "preprocess": false
+      "preprocess": false,
+      "minify": false
     }]
   ]
 }
diff --git a/test/fixtures/11-transpile-template-literals-with-config/after.js b/test/fixtures/11-transpile-template-literals-with-config/after.js
index ddd7f5e..3bf6e6f 100644
--- a/test/fixtures/11-transpile-template-literals-with-config/after.js
+++ b/test/fixtures/11-transpile-template-literals-with-config/after.js
@@ -8,11 +8,11 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
 
 var Named = _styledComponents2.default.div.withConfig({
   displayName: 'before__Named'
-})(['width: 100%;']);
+})(['\n  width: 100%;\n']);
 
 var NamedWithInterpolation = _styledComponents2.default.div.withConfig({
   displayName: 'before__NamedWithInterpolation'
-})(['color: ', ';'], function (color) {
+})(['\n  color: ', ';\n'], function (color) {
   return props.color;
 });
 
diff --git a/test/fixtures/12-transpile-template-literals-without-config/.babelrc b/test/fixtures/12-transpile-template-literals-without-config/.babelrc
index ff1ba69..c3d015b 100644
--- a/test/fixtures/12-transpile-template-literals-without-config/.babelrc
+++ b/test/fixtures/12-transpile-template-literals-without-config/.babelrc
@@ -7,7 +7,8 @@
       "ssr": false,
       "displayName": false,
       "transpileTemplateLiterals": true,
-      "preprocess": false
+      "preprocess": false,
+      "minify": false
     }]
   ]
 }
diff --git a/test/fixtures/12-transpile-template-literals-without-config/after.js b/test/fixtures/12-transpile-template-literals-without-config/after.js
index b23b1e1..72a8ff1 100644
--- a/test/fixtures/12-transpile-template-literals-without-config/after.js
+++ b/test/fixtures/12-transpile-template-literals-without-config/after.js
@@ -6,9 +6,9 @@ var _styledComponents2 = _interopRequireDefault(_styledComponents);
 
 function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
 
-var Named = _styledComponents2.default.div(['width: 100%;']);
+var Named = _styledComponents2.default.div(['\n  width: 100%;\n']);
 
-var NamedWithInterpolation = _styledComponents2.default.div(['color: ', ';'], function (color) {
+var NamedWithInterpolation = _styledComponents2.default.div(['\n  color: ', ';\n'], function (color) {
   return props.color;
 });
 
diff --git a/test/minify/index.test.js b/test/minify/index.test.js
index 3aa223b..f8a4ee2 100644
--- a/test/minify/index.test.js
+++ b/test/minify/index.test.js
@@ -1,7 +1,8 @@
 import {
   stripLineComment,
   minifyRaw,
-  minifyCooked
+  minifyCooked,
+  compressSymbols
 } from '../../src/minify'
 
 describe('minify utils', () => {
@@ -27,7 +28,7 @@ describe('minify utils', () => {
   describe('minify(Raw|Cooked)', () => {
     it('Removes multi-line comments', () => {
       const input = 'this is a/* ignore me please */test'
-      const expected = 'this is atest' // NOTE: They're replaced with newlines, and newlines are joined
+      const expected = 'this is a test' // NOTE: They're replaced with newlines, and newlines are joined
       const actual = minifyRaw(input)
 
       expect(actual).toBe(expected)
@@ -36,7 +37,7 @@ describe('minify utils', () => {
 
     it('Joins all lines of code', () => {
       const input = 'this\nis\na/* ignore me \n please */\ntest'
-      const expected = 'thisisatest'
+      const expected = 'this is a test'
       const actual = minifyRaw(input)
 
       expect(actual).toBe(expected)
@@ -45,7 +46,7 @@ describe('minify utils', () => {
 
     it('Removes line comments filling an entire line', () => {
       const input = 'line one\n// remove this comment\nline two'
-      const expected = 'line oneline two'
+      const expected = 'line one line two'
       const actual = minifyRaw(input)
 
       expect(actual).toBe(expected)
@@ -54,7 +55,7 @@ describe('minify utils', () => {
 
     it('Removes line comments at the end of lines of code', () => {
       const input = 'valid line with // a comment\nout comments'
-      const expected = 'valid line with out comments'
+      const expected = 'valid line with  out comments'
       const actual = minifyRaw(input)
 
       expect(actual).toBe(expected)
@@ -65,13 +66,29 @@ describe('minify utils', () => {
   describe('minifyRaw', () => {
     it('works with raw escape codes', () => {
       const input = 'this\\nis\\na/* ignore me \\n please */\\ntest'
-      const expected = 'thisisatest'
+      const expected = 'this is a test'
       const actual = minifyRaw(input)
 
       expect(minifyRaw(input)).toBe(expected)
 
       // NOTE: This is just a sanity check
-      expect(minifyCooked(input)).toBe('this\\nis\\na\\ntest')
+      expect(minifyCooked(input)).toBe('this\\nis\\na \\ntest')
+    })
+  })
+
+  describe('compressSymbols', () => {
+    it('removes spaces around symbols', () => {
+      const input = ';  :  {  }  ,  ;  '
+      const expected = ';:{},;'
+
+      expect(compressSymbols(input)).toBe(expected)
+    })
+
+    it('ignores symbols inside strings', () => {
+      const input = ';   " : " \' : \' ;'
+      const expected = ';" : " \' : \';'
+
+      expect(compressSymbols(input)).toBe(expected)
     })
   })
 })