├── .eslintignore ├── .gitattributes ├── .gitignore ├── .npmignore ├── .nvmrc ├── .prettierignore ├── .travis.yml ├── CODEOWNERS ├── LICENSE ├── README.md ├── package.json ├── src ├── index.js ├── tests │ ├── __snapshots__ │ │ ├── cssNamespace.integration.test.js.snap │ │ └── cssNamespace.test.js.snap │ ├── cssNamespace.integration.test.js │ ├── cssNamespace.test.js │ └── fixtures │ │ ├── complex_styled_component.js │ │ ├── compost_styled_component.js │ │ ├── double_ampersand.js │ │ ├── double_self_refs_styled_component.js │ │ ├── extended_styled_component.js │ │ ├── helpers.js │ │ ├── integration │ │ ├── consecutive_expressions.js │ │ ├── double_ampersand.js │ │ ├── sibling_selector.js │ │ └── simple.js │ │ ├── interpolated_selector.js │ │ ├── keyframes_styled_component.js │ │ ├── nested_helper_styled_component.js │ │ ├── not_styled_component.js │ │ ├── reordered-template-expressions.js │ │ ├── simple_styled_component.js │ │ ├── styled_component_with_comment_and_js.js │ │ ├── styled_component_with_comment_and_value_in_js.js │ │ ├── styled_component_with_media_query.js │ │ ├── styled_component_with_media_query_interpolation.js │ │ ├── styled_component_without_trailing_semicolon.js │ │ ├── styled_components_ends_with_expression.js │ │ └── styled_components_only_expression_in_block.js └── visitors │ └── cssNamespace.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | output.js 2 | lib/* 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # From https://github.com/Danimoth/gitattributes/blob/master/Web.gitattributes 2 | 3 | # Handle line endings automatically for files detected as text 4 | # and leave all files detected as binary untouched. 5 | * text=auto 6 | 7 | # 8 | # The above will handle all files NOT found below 9 | # 10 | 11 | # 12 | ## These files are text and should be normalized (Convert crlf => lf) 13 | # 14 | 15 | # source code 16 | *.php text 17 | *.css text 18 | *.sass text 19 | *.scss text 20 | *.less text 21 | *.styl text 22 | *.js text eol=lf 23 | *.coffee text 24 | *.json text 25 | *.htm text 26 | *.html text 27 | *.xml text 28 | *.svg text 29 | *.txt text 30 | *.ini text 31 | *.inc text 32 | *.pl text 33 | *.rb text 34 | *.py text 35 | *.scm text 36 | *.sql text 37 | *.sh text 38 | *.bat text 39 | 40 | # templates 41 | *.ejs text 42 | *.hbt text 43 | *.jade text 44 | *.haml text 45 | *.hbs text 46 | *.dot text 47 | *.tmpl text 48 | *.phtml text 49 | 50 | # server config 51 | .htaccess text 52 | .nginx.conf text 53 | 54 | # git config 55 | .gitattributes text 56 | .gitignore text 57 | .gitconfig text 58 | 59 | # code analysis config 60 | .jshintrc text 61 | .jscsrc text 62 | .jshintignore text 63 | .csslintrc text 64 | 65 | # misc config 66 | *.yaml text 67 | *.yml text 68 | .editorconfig text 69 | 70 | # build config 71 | *.npmignore text 72 | *.bowerrc text 73 | 74 | # Heroku 75 | Procfile text 76 | .slugignore text 77 | 78 | # Documentation 79 | *.md text 80 | LICENSE text 81 | AUTHORS text 82 | 83 | 84 | # 85 | ## These files are binary and should be left untouched 86 | # 87 | 88 | # (binary is a macro for -text -diff) 89 | *.png binary 90 | *.jpg binary 91 | *.jpeg binary 92 | *.gif binary 93 | *.ico binary 94 | *.mov binary 95 | *.mp4 binary 96 | *.mp3 binary 97 | *.flv binary 98 | *.fla binary 99 | *.swf binary 100 | *.gz binary 101 | *.zip binary 102 | *.7z binary 103 | *.ttf binary 104 | *.eot binary 105 | *.woff binary 106 | *.pyc binary 107 | *.pdf binary 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.iml 3 | *.idea 4 | 5 | build 6 | lib 7 | node_modules 8 | .env 9 | 10 | lerna-debug.log 11 | npm-debug.* 12 | yarn-error.log 13 | 14 | .vscode 15 | **/wdio.conf.js 16 | 17 | certs 18 | 19 | log 20 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.idea 3 | .vscode 4 | 5 | lerna-debug.log 6 | yarn-error.log 7 | 8 | 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | output.js 2 | package.json 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - node_modules 5 | before_deploy: npm run prepare 6 | deploy: 7 | provider: npm 8 | email: quickbase-oss@quickbase.com 9 | api_key: 10 | secure: O49BwbG01js7onm7tEl5skC/dIjEh5vngtX5X5fVeWJ7qc+bs/Y1XPaIAWKuUcXlEATLptFQl8TN/H0SQ6gstg2/t8TxPpZhEA/whMM9xTC3gnSBAvv9WgPIuJ4jtE1Rbq6nz+APFGtkETGd9GaDJeS9x9ZEZz47TuKMQxAzah8cmkY2sC76OQii0OOoX3onTOSQwZ67NrfcZQklB6f5+hWYngKJtjXWPT9lkWbOgPPHNrDxcGnH9Cw3UFcodY9o1ecVN0DECekEaMDQIt1mKWGzvZWQj1ssNDvMdhRNkeh5Zk8oipbyZHInGVJLXVC1NsftEWEqL2OeCN/a1Iuk1VxM+63hmC1TG1W6npEYloqHsVQO1lRHoD5XbH/IfYKrXUIOCrQvprGn9ouM8TaEQvG15Z679ieX7a3c7zZi+hbFodftXGi+heBtVpHyJdLfetmE4K614thI4CkNPi+DFhwhQK6HH4U6kitG3hpFu0jvSVCOVSlHgYg1xx4ZzaJ9FHbkQHESe8kaSL4odyOsuDbpvgKPMWNgXbjYYqXwryLiCCPgtRflPri6Ibx0RAQBZQRKmpbPWow7Knor4QpEmzId14fasVNlqf4XfDPmfOnw63Btgkb32EKLYOSBi982C4Gk/mXV17CW1ilowmXELNqdH/UIm1ENgKdOgbY2OJc= 11 | skip_cleanup: true 12 | on: 13 | tags: true 14 | repo: QuickBase/babel-plugin-styled-components-css-namespace 15 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://github.com/blog/2392-introducing-code-owners, https://help.github.com/articles/about-codeowners/. 2 | 3 | @harrismd @nvanselow 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Quick Base 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @quickbaseoss/babel-plugin-styled-components-css-namespace 2 | 3 | [![Travis CI status and link to builds](https://api.travis-ci.org/QuickBase/babel-plugin-styled-components-css-namespace.svg?branch=master)](https://travis-ci.org/github/QuickBase/babel-plugin-styled-components-css-namespace) 4 | 5 | ## Getting Started 6 | 7 | 1. Add the plugin with `yarn add @quickbaseoss/babel-plugin-styled-components-css-namespace` or `npm install @quickbaseoss/babel-plugin-styled-components-css-namespace` 8 | 1. Include the plugin in your babel configuration. 9 | 10 | ```json 11 | "babel": { 12 | "plugins": [ 13 | "@quickbaseoss/babel-plugin-styled-components-css-namespace" 14 | ] 15 | } 16 | ``` 17 | 18 | If you are also using [babel-plugin-styled-components](https://github.com/styled-components/babel-plugin-styled-components), you must place `styled-components-css-namespace` **before** `babel-plugin-styled-components`. 19 | 20 | ```json 21 | "babel": { 22 | "plugins": [ 23 | "@quickbaseoss/babel-plugin-styled-components-css-namespace", 24 | "babel-plugin-styled-components" 25 | ] 26 | } 27 | ``` 28 | 29 | ## Options 30 | 31 | ### Default 32 | 33 | Without adding options, this plugin will duplicate the class name generated by `styled-components` as suggested in [this issue](https://github.com/styled-components/styled-components/issues/613). 34 | 35 | ```css 36 | /* output */ 37 | .c0.c0 { 38 | background-color: blue; 39 | } 40 | ``` 41 | 42 | ### Increasing Specificity 43 | 44 | A common scenario when integrating styled-components into existing projects is fighting against extremely specific legacy CSS selectors such as `#someId .grossly .nested section input {/* styles */}`. 45 | 46 | To increase the specificity that this plugin adds, you can leverage the [recommended approach from the styled-components docs](https://www.styled-components.com/docs/faqs#how-can-i-override-styles-with-higher-specificity). Add the appropriate number of `&` selectors equal to the desired selector duplication as the `cssNamespace` option (the default behavior is x2 `{"cssNamespace": "&&"}`). 47 | 48 | ```json 49 | { 50 | "plugins": [ 51 | [ 52 | "@quickbaseoss/babel-plugin-styled-components-css-namespace", 53 | {"cssNamespace": "&&&"} 54 | ], 55 | "babel-plugin-styled-components" 56 | ] 57 | } 58 | ``` 59 | 60 | ```css 61 | /* output */ 62 | .c0.c0.c0 { 63 | background-color: blue; 64 | } 65 | ``` 66 | 67 | ### Custom Namespace 68 | 69 | You can provide a `cssNamespace` to use instead of duplicating the class name. Remember to include a DOM element with that class that wraps the styled-component. The `cssNamespace` takes the form of the selector you want to use to wrap all of your styles with. 70 | 71 | ```json 72 | "babel": { 73 | "plugins": [ 74 | ["@quickbaseoss/babel-plugin-styled-components-css-namespace", {"cssNamespace": ".specific .moreSpecific .reallySpecific"}], 75 | "styled-components" 76 | ] 77 | } 78 | ``` 79 | 80 | ```css 81 | /* output */ 82 | .specific .moreSpecific .reallySpecific .c0 { 83 | background-color: blue; 84 | } 85 | ``` 86 | 87 | _where .c0 is the class added by styled-components to the element_ 88 | 89 | ## Notes 90 | 91 | ### Media Query String Interpolation 92 | 93 | While an uncommon use-case, it can often be useful to interpolate media query at-rules in your css 94 | template string. Compared to the [method for creating media queries from the 95 | styled-component docs](https://www.styled-components.com/docs/advanced#media-templates), this 96 | method reduces the overhead of multiple calls of `css` while still allowing queries to be 97 | constructed without requiring nested template literals. 98 | 99 | ```javascript 100 | const mediaQuery = '@media only screen and (min-width: 426px)' 101 | 102 | const StyledComponent = styled.div` 103 | background-color: red; 104 | ${mediaQuery} { 105 | background-color: blue; 106 | } 107 | ` 108 | ``` 109 | 110 | Unfortunately, this syntax is identical to the syntax used to refer to other components and this 111 | plugin cannot distinguish between the two and will produce broken CSS rules. Since referring to 112 | other components is more common, the below method of formatting `@media` inline can be 113 | used as a workaround. 114 | 115 | ```javascript 116 | const mediaQuery = 'only screen and (min-width: 426px)'; 117 | 118 | const StyledComponent = styled.div` 119 | background-color: red; 120 | @media ${mediaQuery} { 121 | background-color: blue; 122 | } 123 | `; 124 | ``` 125 | 126 | ## Upgrade to version 1.0.0 127 | 128 | Note that `rawCssNamespace` was dropped in favor of the single `cssNamespace` option. Additionally, support for an array of selectors was dropped as well. Update any references to `rawCssNamespace` with `cssNamespace`. 129 | 130 | If you were already using `cssNamespace`, update your configuration to use a css selector rather than an array of classes. E.g., `cssNamespace: 'moreSpecific'` should be `cssNamespace: '.moreSpecific'` and `cssNamespace: ['specific', 'verySpecific']` should be `cssNamespace: '.specific .verySpecific'`. 131 | 132 | ## The Problem 133 | 134 | [styled-components](https://github.com/QuickBase/styled-components) is an awesome library for css-in-js and feels like a natural combination of React and CSS. It is easy to use and produces css instead of inline styles. 135 | 136 | However, if you are trying to gradually introduce [styled-components](https://github.com/QuickBase/styled-components) into a legacy website that might not have the best CSS, the legacy styles may bleed into your styled-components because they have more specificity than the single class styled-components. 137 | 138 | ## The Solution 139 | 140 | This plugin will automatically add additional css namespaces or duplicated classes to the selectors produced by styled components effectively creating a wall between the legacy css code and your new shiny styled components. 141 | 142 | ![monty-python-castle](https://media.giphy.com/media/12TIvbgMTrGhhu/giphy.gif) 143 | 144 | ## Styling frameworks 145 | 146 | This plugin was built for [Styled Components](https://www.styled-components.com/); however, since initially creating it, we at Quick Base have switched to [Emotion](https://emotion.sh/). It works as an alternative to the [stylis extra scope plugin](https://github.com/Andarist/stylis-plugin-extra-scope) which requires creating your own instance of stylis. 147 | 148 | ## Developing 149 | 150 | 1. Clone the repo with `git clone https://github.com/QuickBase/babel-plugin-styled-components-css-namespace.git` 151 | 1. `yarn install` (prefer `yarn` although `npm` should work as well) 152 | 1. `yarn test` to run the tests 153 | 154 | ## Publishing 155 | 156 | When we are ready to release a new version, one of the admins needs to run the following commands to publish the new version to npm. 157 | We probably need to invest in a better deploy and semver management system. Interested? See [this issue](https://github.com/QuickBase/babel-plugin-styled-components-css-namespace/issues/9). 158 | 159 | - If needed, open a new PR to update the version in the [package.json](https://github.com/QuickBase/babel-plugin-styled-components-css-namespace/blob/master/package.json) 160 | - Copy the commit hash from the [commit log](https://github.com/QuickBase/babel-plugin-styled-components-css-namespace/commits/master) 161 | - Run `git tag -a {version} {commit_hash}`. For example: `git tag -a 0.0.9 abf3123` 162 | - In the editor, add a message about the changes in this version and save 163 | - Push the tag to GitHub with `git push --follow-tags` 164 | - Travis CI will build and publish the new version to npm 165 | 166 | ## Acknowledgements 167 | 168 | Open source software is a group effort. This version of the plugin was heavily inspired by [a fork](https://github.com/TrevorBurnham/babel-plugin-namespace-styled-components) of the original plugin from @TrevorBurnham. 169 | 170 | We also would like to thank some of our contributors who helped solve some tough issues with the previous iteration of this plugin: 171 | 172 | - @daniloster 173 | - @dan-kez 174 | - @prevostc 175 | - @danielhusar 176 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@quickbaseoss/babel-plugin-styled-components-css-namespace", 3 | "version": "1.0.1", 4 | "description": "A babel plugin to add css namespaces to all styled components.", 5 | "keywords": [ 6 | "styled-components", 7 | "css-in-js", 8 | "babel-plugin", 9 | "css-namespaces", 10 | "css-specificity" 11 | ], 12 | "publishConfig": { 13 | "access": "public" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/QuickBase/babel-plugin-styled-components-css-namespace" 18 | }, 19 | "bugs": { 20 | "url": "https://github.com/QuickBase/babel-plugin-styled-components-css-namespace/issues" 21 | }, 22 | "engines": { 23 | "node": ">=8.0.0" 24 | }, 25 | "main": "lib/index.js", 26 | "files": [ 27 | "lib" 28 | ], 29 | "author": "Quick Base", 30 | "contributors": [ 31 | "Gen Edwards ", 32 | "Michael Harris ", 33 | "Nick Vanselow ", 34 | "Danilo Castro ", 35 | "Zach Hardesty (https://zachhardesty.com)" 36 | ], 37 | "license": "MIT", 38 | "private": false, 39 | "scripts": { 40 | "clean": "rimraf lib", 41 | "build": "babel src -d lib --ignore src/tests,src/vendor", 42 | "test": "jest", 43 | "test:watch": "yarn run test -- --watch", 44 | "test:snap": "yarn run test -- --updateSnapshot", 45 | "prepare": "yarn run clean && yarn run build", 46 | "precommit": "lint-staged", 47 | "lint": "yarn run lint:js", 48 | "lint:js": "yarn run lint:eslint -- . ", 49 | "lint:eslint": "eslint --ignore-path .eslintignore" 50 | }, 51 | "husky": { 52 | "hooks": { 53 | "pre-commit": "lint-staged" 54 | } 55 | }, 56 | "lint-staged": { 57 | "*.js": [ 58 | "prettier --single-quote --write", 59 | "yarn run lint:eslint", 60 | "git add" 61 | ] 62 | }, 63 | "babel": { 64 | "presets": [ 65 | [ 66 | "@babel/preset-env", 67 | { 68 | "targets": { 69 | "node": 11 70 | } 71 | } 72 | ], 73 | "@babel/preset-react" 74 | ] 75 | }, 76 | "eslintConfig": { 77 | "parser": "babel-eslint", 78 | "extends": [ 79 | "prettier", 80 | "plugin:react/recommended" 81 | ], 82 | "env": { 83 | "browser": false, 84 | "node": true, 85 | "jest": true, 86 | "es6": true 87 | }, 88 | "plugins": [ 89 | "prettier", 90 | "react" 91 | ], 92 | "parserOptions": { 93 | "ecmaVersion": 6, 94 | "sourceType": "module" 95 | }, 96 | "rules": { 97 | "prettier/prettier": [ 98 | "error", 99 | { 100 | "singleQuote": true 101 | } 102 | ], 103 | "arrow-body-style": [ 104 | 2, 105 | "as-needed" 106 | ], 107 | "comma-dangle": [ 108 | 2, 109 | "never" 110 | ], 111 | "indent": [ 112 | 0, 113 | 2, 114 | { 115 | "SwitchCase": 1 116 | } 117 | ], 118 | "max-len": 0, 119 | "newline-per-chained-call": 0, 120 | "no-confusing-arrow": 0, 121 | "no-console": 1, 122 | "no-use-before-define": 0, 123 | "prefer-template": 2, 124 | "class-methods-use-this": 0, 125 | "require-yield": 0, 126 | "quotes": [ 127 | 2, 128 | "single", 129 | { 130 | "avoidEscape": true, 131 | "allowTemplateLiterals": true 132 | } 133 | ], 134 | "no-unused-vars": 2 135 | } 136 | }, 137 | "jest": { 138 | "collectCoverageFrom": [ 139 | "src/**/*.js" 140 | ], 141 | "testMatch": [ 142 | "/src/**/*.(spec|test).js" 143 | ], 144 | "transform": { 145 | "^.+\\.js?$": "babel-jest" 146 | } 147 | }, 148 | "dependencies": { 149 | "@babel/types": "^7.5.0", 150 | "babel-plugin-styled-components": "1.10.x", 151 | "postcss": "7.0.x", 152 | "postcss-nested": "4.1.x", 153 | "postcss-parent-selector": "1.0.x", 154 | "postcss-safe-parser": "4.0.x" 155 | }, 156 | "devDependencies": { 157 | "@babel/cli": "7.2.x", 158 | "@babel/core": "7.2.x", 159 | "@babel/preset-env": "7.3.x", 160 | "@babel/preset-react": "7.0.x", 161 | "babel-eslint": "10.0.x", 162 | "babel-jest": "24.1.x", 163 | "babel-plugin-tester": "6.2.x", 164 | "eslint": "5.13.x", 165 | "eslint-config-prettier": "4.3.x", 166 | "eslint-plugin-prettier": "3.1.x", 167 | "eslint-plugin-react": "7.12.x", 168 | "husky": "1.3.x", 169 | "jest": "24.8.x", 170 | "jest-styled-components": "6.3.x", 171 | "lint-staged": "8.1.x", 172 | "prettier": "1.17.x", 173 | "react": "16.8.x", 174 | "react-dom": "16.8.x", 175 | "react-test-renderer": "16.8.x", 176 | "rimraf": "2.6.x", 177 | "styled-components": "4.2.x" 178 | } 179 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import cssNamespace from './visitors/cssNamespace'; 2 | 3 | export default function() { 4 | return { 5 | visitor: { 6 | TaggedTemplateExpression(path, state) { 7 | cssNamespace(path, state); 8 | } 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/tests/__snapshots__/cssNamespace.integration.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`styled-components output for a style block with && 1`] = ` 4 | #different-wrapper .c0.c0 { 5 | border: 1px solid fuchsia; 6 | } 7 | 8 | 11 | `; 12 | 13 | exports[`styled-components output for a style block with a sibling selector 1`] = ` 14 | #different-wrapper .c1 + .c0 { 15 | background-color: 'yellow'; 16 | } 17 | 18 |
21 | `; 22 | 23 | exports[`styled-components output for a style block with interpolated selectors 1`] = ` 24 | #different-wrapper .c0 { 25 | position: relative; 26 | } 27 | 28 | #different-wrapper .c0 .c1 { 29 | -webkit-transform: scale(90%); 30 | -ms-transform: scale(90%); 31 | transform: scale(90%); 32 | } 33 | 34 | #different-wrapper .c2 + .c0, 35 | #different-wrapper .c0 + .sc-bZQynM { 36 | margin-right: 12px; 37 | } 38 | 39 |
42 | `; 43 | 44 | exports[`styled-components output for a style block with no selectors 1`] = ` 45 | #different-wrapper .c0 { 46 | background-color: 'yellow'; 47 | } 48 | 49 |
52 | `; 53 | 54 | exports[`styled-components output for two consecutive template expressions 1`] = ` 55 | #different-wrapper .c0 { 56 | background-color: blue; 57 | font-weight: bold; 58 | } 59 | 60 |
63 | `; 64 | -------------------------------------------------------------------------------- /src/tests/__snapshots__/cssNamespace.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`styled-components-css-namespace adds namespace to complex styled-component: adds namespace to complex styled-component 1`] = ` 4 | " 5 | const styled = { div() {} }; 6 | 7 | const MyStyledComponent = styled.div\` 8 | background-color: \${props => (props.isDark ? 'red' : 'yellow')}; 9 | color: \${props => (props.isDark ? 'white' : 'navy')}; 10 | font-size: 30px; 11 | padding: 20px; 12 | 13 | & { 14 | border: 1px solid black; 15 | } 16 | \`; 17 | 18 | export default MyStyledComponent; 19 | 20 | ↓ ↓ ↓ ↓ ↓ ↓ 21 | 22 | const styled = { 23 | div() {} 24 | 25 | }; 26 | const MyStyledComponent = styled.div\` 27 | && { 28 | background-color: \${props => props.isDark ? 'red' : 'yellow'}; 29 | color: \${props => props.isDark ? 'white' : 'navy'}; 30 | font-size: 30px; 31 | padding: 20px; 32 | } 33 | 34 | && { 35 | border: 1px solid black; 36 | } 37 | \`; 38 | export default MyStyledComponent; 39 | " 40 | `; 41 | 42 | exports[`styled-components-css-namespace adds namespace to extended styled-components: adds namespace to extended styled-components 1`] = ` 43 | " 44 | const styled = () => {}; 45 | styled.div = () => {}; 46 | 47 | const styledDiv = styled.div\` 48 | width: 100px; 49 | \`; 50 | 51 | const MyStyledComponent = styled(styledDiv)\` 52 | background-color: 'yellow'; 53 | \`; 54 | 55 | export default MyStyledComponent; 56 | 57 | ↓ ↓ ↓ ↓ ↓ ↓ 58 | 59 | const styled = () => {}; 60 | 61 | styled.div = () => {}; 62 | 63 | const styledDiv = styled.div\` 64 | body .specific .rule & { 65 | width: 100px; 66 | } 67 | \`; 68 | const MyStyledComponent = styled(styledDiv)\` 69 | body .specific .rule & { 70 | background-color: 'yellow'; 71 | } 72 | \`; 73 | export default MyStyledComponent; 74 | " 75 | `; 76 | 77 | exports[`styled-components-css-namespace adds namespace to simple styled-component: adds namespace to simple styled-component 1`] = ` 78 | " 79 | const styled = { div() {} }; 80 | 81 | const MyStyledComponent = styled.div\` 82 | background-color: 'yellow'; 83 | \`; 84 | 85 | export default MyStyledComponent; 86 | 87 | ↓ ↓ ↓ ↓ ↓ ↓ 88 | 89 | const styled = { 90 | div() {} 91 | 92 | }; 93 | const MyStyledComponent = styled.div\` 94 | && { 95 | background-color: 'yellow'; 96 | } 97 | \`; 98 | export default MyStyledComponent; 99 | " 100 | `; 101 | 102 | exports[`styled-components-css-namespace does not add extra selectors to child helper styles: does not add extra selectors to child helper styles 1`] = ` 103 | " 104 | const styled = { div() {} }; 105 | const css = { css() {} }; 106 | 107 | const styledCSS = css\` 108 | width: 100px; 109 | \`; 110 | 111 | const MyStyledComponent = styled.div\` 112 | background-color: 'yellow'; 113 | \${styledCSS}; 114 | \`; 115 | 116 | export default MyStyledComponent; 117 | 118 | ↓ ↓ ↓ ↓ ↓ ↓ 119 | 120 | const styled = { 121 | div() {} 122 | 123 | }; 124 | const css = { 125 | css() {} 126 | 127 | }; 128 | const styledCSS = css\` 129 | width: 100px; 130 | \`; 131 | const MyStyledComponent = styled.div\` 132 | .specific & { 133 | background-color: 'yellow'; 134 | \${styledCSS}; 135 | } 136 | \`; 137 | export default MyStyledComponent; 138 | " 139 | `; 140 | 141 | exports[`styled-components-css-namespace does not add namespace to keyframes as part of the simple raw wrapper: does not add namespace to keyframes as part of the simple raw wrapper 1`] = ` 142 | " 143 | const styled = { div() {} }; 144 | 145 | const mymove = styled.keyframes\` 146 | 0% { 147 | color: transparent; 148 | } 149 | 100% { 150 | color: radboats; 151 | } 152 | \`; 153 | 154 | const MyStyledComponent = styled.div\` 155 | animation: \${mymove} 5s infinite; 156 | \`; 157 | 158 | export default MyStyledComponent; 159 | 160 | ↓ ↓ ↓ ↓ ↓ ↓ 161 | 162 | const styled = { 163 | div() {} 164 | 165 | }; 166 | const mymove = styled.keyframes\` 167 | 0% { 168 | color: transparent; 169 | } 170 | 100% { 171 | color: radboats; 172 | } 173 | \`; 174 | const MyStyledComponent = styled.div\` 175 | body .specific .rule & { 176 | animation: \${mymove} 5s infinite; 177 | } 178 | \`; 179 | export default MyStyledComponent; 180 | " 181 | `; 182 | 183 | exports[`styled-components-css-namespace does not add namespace to keyframes: does not add namespace to keyframes 1`] = ` 184 | " 185 | const styled = { div() {} }; 186 | 187 | const mymove = styled.keyframes\` 188 | 0% { 189 | color: transparent; 190 | } 191 | 100% { 192 | color: radboats; 193 | } 194 | \`; 195 | 196 | const MyStyledComponent = styled.div\` 197 | animation: \${mymove} 5s infinite; 198 | \`; 199 | 200 | export default MyStyledComponent; 201 | 202 | ↓ ↓ ↓ ↓ ↓ ↓ 203 | 204 | const styled = { 205 | div() {} 206 | 207 | }; 208 | const mymove = styled.keyframes\` 209 | 0% { 210 | color: transparent; 211 | } 212 | 100% { 213 | color: radboats; 214 | } 215 | \`; 216 | const MyStyledComponent = styled.div\` 217 | .specific & { 218 | animation: \${mymove} 5s infinite; 219 | } 220 | \`; 221 | export default MyStyledComponent; 222 | " 223 | `; 224 | 225 | exports[`styled-components-css-namespace does not namespace style blocks in helpers: does not namespace style blocks in helpers 1`] = ` 226 | " 227 | import { 228 | createGlobalStyle, 229 | css, 230 | injectGlobal, 231 | keyframes 232 | } from 'styled-components'; 233 | 234 | export const getBodyColorStyle = color => createGlobalStyle\` 235 | body { 236 | color: \${color}; 237 | } 238 | \`; 239 | 240 | export const injectBodyStyles = styles => { 241 | injectGlobal\` 242 | body { 243 | \${styles}; 244 | } 245 | \`; 246 | }; 247 | 248 | export const Rotate = keyframes\` 249 | from { 250 | transform: rotate(0deg); 251 | } 252 | to { 253 | transform: rotate(360deg); 254 | } 255 | \`; 256 | 257 | export const Clearfix = css\` 258 | &:after { 259 | display: table; 260 | content: ''; 261 | clear: 'both'; 262 | } 263 | \`; 264 | 265 | ↓ ↓ ↓ ↓ ↓ ↓ 266 | 267 | import { createGlobalStyle, css, injectGlobal, keyframes } from 'styled-components'; 268 | export const getBodyColorStyle = color => createGlobalStyle\` 269 | body { 270 | color: \${color}; 271 | } 272 | \`; 273 | export const injectBodyStyles = styles => { 274 | injectGlobal\` 275 | body { 276 | \${styles}; 277 | } 278 | \`; 279 | }; 280 | export const Rotate = keyframes\` 281 | from { 282 | transform: rotate(0deg); 283 | } 284 | to { 285 | transform: rotate(360deg); 286 | } 287 | \`; 288 | export const Clearfix = css\` 289 | &:after { 290 | display: table; 291 | content: ''; 292 | clear: 'both'; 293 | } 294 | \`; 295 | " 296 | `; 297 | 298 | exports[`styled-components-css-namespace handles case where styled component ends with expression: handles case where styled component ends with expression 1`] = ` 299 | " 300 | const styled = { div() {} }; 301 | 302 | const MyStyledComponent = styled.div\` 303 | background-color: \${props => (props.isDark ? 'red' : 'yellow')}; 304 | color: \${props => (props.isDark ? 'white' : 'navy')}; 305 | font-size: 30px; 306 | padding: 20px; 307 | 308 | & { 309 | border: 1px solid black; 310 | } 311 | 312 | \${props => (props.isDark ? 'color: red' : 'color: white')}; 313 | \`; 314 | 315 | export default MyStyledComponent; 316 | 317 | ↓ ↓ ↓ ↓ ↓ ↓ 318 | 319 | const styled = { 320 | div() {} 321 | 322 | }; 323 | const MyStyledComponent = styled.div\` 324 | #different-wrapper & { 325 | background-color: \${props => props.isDark ? 'red' : 'yellow'}; 326 | color: \${props => props.isDark ? 'white' : 'navy'}; 327 | font-size: 30px; 328 | padding: 20px; 329 | 330 | \${props => props.isDark ? 'color: red' : 'color: white'}; 331 | } 332 | 333 | #different-wrapper & { 334 | border: 1px solid black; 335 | } 336 | \`; 337 | export default MyStyledComponent; 338 | " 339 | `; 340 | 341 | exports[`styled-components-css-namespace handles case where styled component has a media query: handles case where styled component has a media query 1`] = ` 342 | " 343 | const styled = { div() {} }; 344 | 345 | const MyStyledComponent = styled.div\` 346 | background-color: red; 347 | @media only screen and (min-width: 426px) { 348 | background-color: blue; 349 | } 350 | \`; 351 | 352 | export default MyStyledComponent; 353 | 354 | ↓ ↓ ↓ ↓ ↓ ↓ 355 | 356 | const styled = { 357 | div() {} 358 | 359 | }; 360 | const MyStyledComponent = styled.div\` 361 | && { 362 | background-color: red; 363 | } 364 | @media only screen and (min-width: 426px) { 365 | && { 366 | background-color: blue 367 | } 368 | } 369 | \`; 370 | export default MyStyledComponent; 371 | " 372 | `; 373 | 374 | exports[`styled-components-css-namespace handles case where styled component has only an expression in a css block: handles case where styled component has only an expression in a css block 1`] = ` 375 | " 376 | const styled = { div() {} }; 377 | 378 | const MyStyledComponent = styled.div\` 379 | background-color: \${props => (props.isDark ? 'red' : 'yellow')}; 380 | color: \${props => (props.isDark ? 'white' : 'navy')}; 381 | font-size: 30px; 382 | padding: 20px; 383 | 384 | &:hover { 385 | \${props => (props.isDark ? 'color: red' : 'color: white')}; 386 | } 387 | \`; 388 | 389 | export default MyStyledComponent; 390 | 391 | ↓ ↓ ↓ ↓ ↓ ↓ 392 | 393 | const styled = { 394 | div() {} 395 | 396 | }; 397 | const MyStyledComponent = styled.div\` 398 | #different-wrapper & { 399 | background-color: \${props => props.isDark ? 'red' : 'yellow'}; 400 | color: \${props => props.isDark ? 'white' : 'navy'}; 401 | font-size: 30px; 402 | padding: 20px; 403 | } 404 | 405 | #different-wrapper &:hover { 406 | \${props => props.isDark ? 'color: red' : 'color: white'}; 407 | } 408 | \`; 409 | export default MyStyledComponent; 410 | " 411 | `; 412 | 413 | exports[`styled-components-css-namespace handles case where the semicolon is provided in js: handles case where the semicolon is provided in js 1`] = ` 414 | " 415 | const styled = { div() {} }; 416 | 417 | // prettier-ignore 418 | const MyStyledComponent = styled.div\` 419 | color: \${() => 'color: blue;'} 420 | \`; 421 | 422 | export default MyStyledComponent; 423 | 424 | ↓ ↓ ↓ ↓ ↓ ↓ 425 | 426 | const styled = { 427 | div() {} 428 | 429 | }; // prettier-ignore 430 | 431 | const MyStyledComponent = styled.div\` 432 | && { 433 | color: \${() => 'color: blue;'} 434 | } 435 | \`; 436 | export default MyStyledComponent; 437 | " 438 | `; 439 | 440 | exports[`styled-components-css-namespace handles case where there is a comment followed by a value provided by js: handles case where there is a comment followed by a value provided by js 1`] = ` 441 | " 442 | const styled = { div() {} }; 443 | 444 | const MyStyledComponent = styled.div\` 445 | /* comment */ 446 | color: \${() => 'blue'}; 447 | \`; 448 | 449 | export default MyStyledComponent; 450 | 451 | ↓ ↓ ↓ ↓ ↓ ↓ 452 | 453 | const styled = { 454 | div() {} 455 | 456 | }; 457 | const MyStyledComponent = styled.div\` 458 | && { 459 | /* comment */ 460 | color: \${() => 'blue'}; 461 | } 462 | \`; 463 | export default MyStyledComponent; 464 | " 465 | `; 466 | 467 | exports[`styled-components-css-namespace handles case where there is a comment followed by js: handles case where there is a comment followed by js 1`] = ` 468 | " 469 | const styled = { div() {} }; 470 | 471 | const MyStyledComponent = styled.div\` 472 | /* comment */ 473 | \${() => 'color: blue;'}; 474 | \`; 475 | 476 | export default MyStyledComponent; 477 | 478 | ↓ ↓ ↓ ↓ ↓ ↓ 479 | 480 | const styled = { 481 | div() {} 482 | 483 | }; 484 | const MyStyledComponent = styled.div\` 485 | && { 486 | /* comment */ 487 | \${() => 'color: blue;'}; 488 | } 489 | \`; 490 | export default MyStyledComponent; 491 | " 492 | `; 493 | 494 | exports[`styled-components-css-namespace handles re-ordering of template expressions: handles re-ordering of template expressions 1`] = ` 495 | " 496 | import styled from 'styled-components'; 497 | 498 | /* 499 | The PostCSS flattening pulls the &:hover and &:active out of the 500 | main style block, placing it _after_ the main block. Therefore 501 | the template expressions in those pseudo-class style blocks are 502 | not in the same relative position in the final processed CSS 503 | as they were in the original. The bug-fix tracks the _order_ of 504 | the expressions, so that when they're inserted back into the 505 | template each expression winds up in the correct position. 506 | */ 507 | 508 | export default styled.div\` 509 | background: \${props => props.background}; 510 | border: 1px solid \${props => props.borderColor}; 511 | width: 100%; 512 | 513 | \${props => props.styles}; 514 | 515 | &:hover { 516 | border-color: \${props => props.hoverBorder}; 517 | } 518 | 519 | &:active { 520 | border-color: \${props => props.activeBorder}; 521 | \${props.activeStyles}; 522 | } 523 | 524 | \${props => props.moreStyles}; 525 | \`; 526 | 527 | ↓ ↓ ↓ ↓ ↓ ↓ 528 | 529 | import styled from 'styled-components'; 530 | /* 531 | The PostCSS flattening pulls the &:hover and &:active out of the 532 | main style block, placing it _after_ the main block. Therefore 533 | the template expressions in those pseudo-class style blocks are 534 | not in the same relative position in the final processed CSS 535 | as they were in the original. The bug-fix tracks the _order_ of 536 | the expressions, so that when they're inserted back into the 537 | template each expression winds up in the correct position. 538 | */ 539 | 540 | export default styled.div\` 541 | .class-wrapper .other-wrapper & { 542 | background: \${props => props.background}; 543 | border: 1px solid \${props => props.borderColor}; 544 | width: 100%; 545 | 546 | \${props => props.styles}; 547 | 548 | \${props => props.moreStyles}; 549 | } 550 | 551 | .class-wrapper .other-wrapper &:hover { 552 | border-color: \${props => props.hoverBorder}; 553 | } 554 | 555 | .class-wrapper .other-wrapper &:active { 556 | border-color: \${props => props.activeBorder}; 557 | \${props.activeStyles}; 558 | } 559 | \`; 560 | " 561 | `; 562 | 563 | exports[`styled-components-css-namespace namespaces a style block with &&: namespaces a style block with && 1`] = ` 564 | " 565 | const styled = { input() {} }; 566 | 567 | export default styled.input\` 568 | && { 569 | border: \${props => props.borderWidth} solid \${props => props.borderColor}; 570 | } 571 | \`; 572 | 573 | ↓ ↓ ↓ ↓ ↓ ↓ 574 | 575 | const styled = { 576 | input() {} 577 | 578 | }; 579 | export default styled.input\` 580 | #different-wrapper && { 581 | border: \${props => props.borderWidth} solid \${props => props.borderColor}; 582 | } 583 | \`; 584 | " 585 | `; 586 | 587 | exports[`styled-components-css-namespace namespaces a style block with interpolated selectors: namespaces a style block with interpolated selectors 1`] = ` 588 | " 589 | import styled from 'styled-components'; 590 | 591 | const Child = styled.span; 592 | 593 | export default styled.div\` 594 | position: relative; 595 | \${Child} { 596 | \${props => props.childStyles}; 597 | } 598 | \${Child} + &, & + \${Child} { 599 | margin-right: \${props => props.spaceBetween}; 600 | } 601 | \`; 602 | 603 | ↓ ↓ ↓ ↓ ↓ ↓ 604 | 605 | import styled from 'styled-components'; 606 | const Child = styled.span; 607 | export default styled.div\` 608 | #different-wrapper & { 609 | position: relative; 610 | } 611 | #different-wrapper & \${Child} { 612 | \${props => props.childStyles}; 613 | } 614 | #different-wrapper \${Child} + &, #different-wrapper & + \${Child} { 615 | margin-right: \${props => props.spaceBetween}; 616 | } 617 | \`; 618 | " 619 | `; 620 | 621 | exports[`styled-components-css-namespace uses a namespace specified in the options as simple wrapper raw wrapper: uses a namespace specified in the options as simple wrapper raw wrapper 1`] = ` 622 | " 623 | const styled = { div() {} }; 624 | 625 | const MyStyledComponent = styled.div\` 626 | background-color: \${props => (props.isDark ? 'red' : 'yellow')}; 627 | color: \${props => (props.isDark ? 'white' : 'navy')}; 628 | font-size: 30px; 629 | padding: 20px; 630 | 631 | & { 632 | border: 1px solid black; 633 | } 634 | \`; 635 | 636 | export default MyStyledComponent; 637 | 638 | ↓ ↓ ↓ ↓ ↓ ↓ 639 | 640 | const styled = { 641 | div() {} 642 | 643 | }; 644 | const MyStyledComponent = styled.div\` 645 | body .specific .rule & { 646 | background-color: \${props => props.isDark ? 'red' : 'yellow'}; 647 | color: \${props => props.isDark ? 'white' : 'navy'}; 648 | font-size: 30px; 649 | padding: 20px; 650 | } 651 | 652 | body .specific .rule & { 653 | border: 1px solid black; 654 | } 655 | \`; 656 | export default MyStyledComponent; 657 | " 658 | `; 659 | 660 | exports[`styled-components-css-namespace uses a namespace specified in the options: uses a namespace specified in the options 1`] = ` 661 | " 662 | const styled = { div() {} }; 663 | 664 | const MyStyledComponent = styled.div\` 665 | background-color: \${props => (props.isDark ? 'red' : 'yellow')}; 666 | color: \${props => (props.isDark ? 'white' : 'navy')}; 667 | font-size: 30px; 668 | padding: 20px; 669 | 670 | & { 671 | border: 1px solid black; 672 | } 673 | \`; 674 | 675 | export default MyStyledComponent; 676 | 677 | ↓ ↓ ↓ ↓ ↓ ↓ 678 | 679 | const styled = { 680 | div() {} 681 | 682 | }; 683 | const MyStyledComponent = styled.div\` 684 | .moreSpecific & { 685 | background-color: \${props => props.isDark ? 'red' : 'yellow'}; 686 | color: \${props => props.isDark ? 'white' : 'navy'}; 687 | font-size: 30px; 688 | padding: 20px; 689 | } 690 | 691 | .moreSpecific & { 692 | border: 1px solid black; 693 | } 694 | \`; 695 | export default MyStyledComponent; 696 | " 697 | `; 698 | 699 | exports[`styled-components-css-namespace uses an array of namespaces specified in the options: uses an array of namespaces specified in the options 1`] = ` 700 | " 701 | const styled = { div() {} }; 702 | 703 | const MyStyledComponent = styled.div\` 704 | background-color: \${props => (props.isDark ? 'red' : 'yellow')}; 705 | color: \${props => (props.isDark ? 'white' : 'navy')}; 706 | font-size: 30px; 707 | padding: 20px; 708 | 709 | & { 710 | border: 1px solid black; 711 | } 712 | \`; 713 | 714 | export default MyStyledComponent; 715 | 716 | ↓ ↓ ↓ ↓ ↓ ↓ 717 | 718 | const styled = { 719 | div() {} 720 | 721 | }; 722 | const MyStyledComponent = styled.div\` 723 | .specific .verySpecific .extraSpecific & { 724 | background-color: \${props => props.isDark ? 'red' : 'yellow'}; 725 | color: \${props => props.isDark ? 'white' : 'navy'}; 726 | font-size: 30px; 727 | padding: 20px; 728 | } 729 | 730 | .specific .verySpecific .extraSpecific & { 731 | border: 1px solid black; 732 | } 733 | \`; 734 | export default MyStyledComponent; 735 | " 736 | `; 737 | -------------------------------------------------------------------------------- /src/tests/cssNamespace.integration.test.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import React from 'react'; 3 | import renderer from 'react-test-renderer'; 4 | import { transformFileSync } from '@babel/core'; 5 | import 'jest-styled-components'; 6 | 7 | const evalFixture = (filename, cssNamespace = '#different-wrapper') => { 8 | const { code } = transformFileSync(filename, { 9 | plugins: [[path.join(__dirname, '../index.js'), { cssNamespace }]] 10 | }); 11 | 12 | if (code == null) throw new Error(`Fixture not found: ${filename}`); 13 | 14 | return eval(code); 15 | }; 16 | 17 | describe('styled-components output', () => { 18 | test('for a style block with no selectors', () => { 19 | const Simple = evalFixture( 20 | path.join(__dirname, 'fixtures/integration/simple.js') 21 | ); 22 | expect( 23 | renderer.create().toJSON() 24 | ).toMatchSnapshot(); 25 | }); 26 | 27 | test('for a style block with &&', () => { 28 | const Input = evalFixture( 29 | path.join(__dirname, 'fixtures/integration/double_ampersand.js') 30 | ); 31 | expect( 32 | renderer 33 | .create() 34 | .toJSON() 35 | ).toMatchSnapshot(); 36 | }); 37 | 38 | test('for a style block with a sibling selector', () => { 39 | const Button = evalFixture( 40 | path.join(__dirname, 'fixtures/integration/sibling_selector.js') 41 | ); 42 | expect( 43 | renderer.create(