├── .eslintignore ├── .gitattributes ├── .npmrc ├── src ├── __tests__ │ ├── __fixtures__ │ │ ├── .babelrc │ │ ├── import.js │ │ ├── require.js │ │ ├── wrapped-component.js │ │ ├── styles.js │ │ ├── glam-not-orous.js │ │ ├── references.js │ │ ├── imported-styles.js │ │ ├── member-expression-reference.js │ │ ├── arrow-ternary.js │ │ ├── .eslintrc │ │ └── untouched.js │ ├── __snapshots__ │ │ ├── babel-plugin.js.snap │ │ └── index.js.snap │ ├── babel-plugin.js │ └── index.js ├── index.js ├── get-literalizers.js └── babel-plugin.js ├── .gitignore ├── other ├── EXAMPLES.md ├── ROADMAP.md └── CODE_OF_CONDUCT.md ├── .babelrc ├── CHANGELOG.md ├── .travis.yml ├── .all-contributorsrc ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── LICENSE ├── package-scripts.js ├── package.json ├── CONTRIBUTING.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.js text eol=lf 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=http://registry.npmjs.org/ 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "babelrc": false 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | dist 4 | .opt-in 5 | .opt-out 6 | 7 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/import.js: -------------------------------------------------------------------------------- 1 | import fred from 'glamorous' 2 | fred.div({fontSize: 10}) 3 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/require.js: -------------------------------------------------------------------------------- 1 | const ethel = require('glamorous') 2 | ethel.span({fontSize: 25}) 3 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/wrapped-component.js: -------------------------------------------------------------------------------- 1 | import g from 'glamorous' 2 | 3 | g('div')({margin: 2}) 4 | -------------------------------------------------------------------------------- /other/EXAMPLES.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | There aren't any examples yet! Want to add one? See `CONTRIBUTING.md` 4 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/styles.js: -------------------------------------------------------------------------------- 1 | export const colors = { 2 | primary: 'red', 3 | secondary: 'blue', 4 | } 5 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/glam-not-orous.js: -------------------------------------------------------------------------------- 1 | import glamNotOrous from 'glam-not-orous' 2 | glamNotOrous.div({blah: 'blah'}) 3 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/references.js: -------------------------------------------------------------------------------- 1 | import glamorous from 'glamorous' 2 | 3 | const styles = {color: 'palevioletred'} 4 | 5 | glamorous.div(styles) 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { 5 | "node": 4.5 6 | } 7 | }], 8 | "stage-2" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/imported-styles.js: -------------------------------------------------------------------------------- 1 | import glamorous from 'glamorous' 2 | import {colors as col} from './styles' 3 | 4 | glamorous.p({color: col.primary}) 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | The changelog is automatically updated using [semantic-release](https://github.com/semantic-release/semantic-release). 4 | You can see it on the [releases page](../../releases). 5 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/member-expression-reference.js: -------------------------------------------------------------------------------- 1 | import glamorous from 'glamorous' 2 | 3 | const colors = { 4 | primary: 'red', 5 | secondary: 'blue', 6 | } 7 | 8 | glamorous.p({color: colors.primary}) 9 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/arrow-ternary.js: -------------------------------------------------------------------------------- 1 | import glamorous from 'glamorous' 2 | 3 | glamorous.div( 4 | ({big}) => 5 | (big 6 | ? { 7 | fontSize: 20, 8 | } 9 | : { 10 | fontSize: 12, 11 | }) 12 | ) 13 | -------------------------------------------------------------------------------- /other/ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Project Roadmap 2 | 3 | This is where we'll define a few things about the library's goals. 4 | 5 | We haven't filled this out yet though. Care to help? See `CONTRIBUTING.md` 6 | 7 | ## Want to do 8 | 9 | ## Might do 10 | 11 | ## Wont do 12 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | "kentcdodds/possible-errors", 5 | "kentcdodds/es6/possible-errors", 6 | "kentcdodds/prettier" 7 | ], 8 | "rules": { 9 | "semi": [2, "never"] // for ever and ever 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/babel-plugin.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`converts static objects to css class names 1`] = ` 4 | "import glamorous from 'glamorous' 5 | glamorous.div( 6 | \\"css-1i7gko5\\", 7 | \\"css-oh2yv\\", 8 | )" 9 | `; 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '6' 10 | script: 11 | - yarn start validate 12 | after_success: 13 | - yarn start report-coverage 14 | - yarn start release 15 | branches: 16 | only: 17 | - master 18 | -------------------------------------------------------------------------------- /src/__tests__/__fixtures__/untouched.js: -------------------------------------------------------------------------------- 1 | import fakeGlamorous from 'something-else' 2 | import realGlamorous from 'glamorous' 3 | const requiredGlamorous = require('glamorous') 4 | 5 | fakeGlamorous.div({fontSize: 20}) 6 | 7 | realGlamorous.div() 8 | 9 | realGlamorous.div 10 | 11 | realGlamorous.span(globalThing) 12 | 13 | requiredGlamorous('h2') 14 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "css-in-js-precompiler", 3 | "projectOwner": "kentcdodds", 4 | "files": [ 5 | "README.md" 6 | ], 7 | "imageSize": 100, 8 | "commit": false, 9 | "contributors": [ 10 | { 11 | "login": "kentcdodds", 12 | "name": "Kent C. Dodds", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3", 14 | "profile": "https://kentcdodds.com", 15 | "contributions": [ 16 | "code", 17 | "doc", 18 | "infra", 19 | "test" 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/__tests__/babel-plugin.js: -------------------------------------------------------------------------------- 1 | import stripIndent from 'strip-indent' 2 | import * as recast from 'recast' 3 | import * as babel from 'babel-core' 4 | import plugin from '../babel-plugin' 5 | 6 | test('converts static objects to css class names', () => { 7 | const source = ` 8 | import glamorous from 'glamorous' 9 | glamorous.div( 10 | { 11 | fontSize: 20, 12 | fontWeight: 'normal', 13 | }, 14 | { 15 | margin: 20, 16 | ':hover': { 17 | margin: 10, 18 | }, 19 | }, 20 | ) 21 | ` 22 | const code = transpile(source) 23 | expect(code).toMatchSnapshot() 24 | }) 25 | 26 | function transpile(source) { 27 | const {code} = babel.transform(stripIndent(source).trim(), { 28 | parserOpts: {parser: recast.parse}, 29 | generatorOpts: {generator: recast.print, lineTerminator: '\n'}, 30 | babelrc: false, 31 | plugins: [plugin], 32 | }) 33 | return code 34 | } 35 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | **What**: 19 | 20 | 21 | **Why**: 22 | 23 | 24 | **How**: 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2016 Kent C. Dodds 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 13 | 14 | - `css-in-js-precompiler` version: 15 | - `node` version: 16 | - `npm` (or `yarn`) version: 17 | 18 | Relevant code or config 19 | 20 | ```javascript 21 | 22 | ``` 23 | 24 | What you did: 25 | 26 | 27 | 28 | What happened: 29 | 30 | 31 | 32 | Reproduction repository: 33 | 34 | 38 | 39 | Problem description: 40 | 41 | 42 | 43 | Suggested solution: 44 | -------------------------------------------------------------------------------- /package-scripts.js: -------------------------------------------------------------------------------- 1 | const npsUtils = require('nps-utils') 2 | 3 | const series = npsUtils.series 4 | const concurrent = npsUtils.concurrent 5 | const rimraf = npsUtils.rimraf 6 | 7 | module.exports = { 8 | scripts: { 9 | contributors: { 10 | add: { 11 | description: 'When new people contribute to the project, run this', 12 | script: 'all-contributors add', 13 | }, 14 | generate: { 15 | description: 'Update the badge and contributors table', 16 | script: 'all-contributors generate', 17 | }, 18 | }, 19 | commit: { 20 | description: 'This uses commitizen to help us generate well formatted commit messages', 21 | script: 'git-cz', 22 | }, 23 | test: { 24 | default: 'jest --coverage', 25 | watch: 'jest --watch', 26 | }, 27 | build: { 28 | description: 'delete the dist directory and run babel to build the files', 29 | script: series( 30 | rimraf('dist'), 31 | 'babel --copy-files --out-dir dist --ignore __tests__ src' 32 | ), 33 | }, 34 | lint: { 35 | description: 'lint the entire project', 36 | script: 'eslint .', 37 | }, 38 | reportCoverage: { 39 | description: 'Report coverage stats to codecov. This should be run after the `test` script', 40 | script: 'codecov', 41 | }, 42 | release: { 43 | description: 'We automate releases with semantic-release. This should only be run on travis', 44 | script: 'echo "we are not auto-releasing quite yet..."', 45 | // script: series('semantic-release pre', 'npm publish', 'semantic-release post'), 46 | }, 47 | validate: { 48 | description: 'This runs several scripts to make sure things look good before committing or on clean install', 49 | script: concurrent.nps('lint', 'build', 'test'), 50 | }, 51 | }, 52 | options: { 53 | silent: false, 54 | }, 55 | } 56 | 57 | // this is not transpiled 58 | /* 59 | eslint 60 | max-len: 0, 61 | comma-dangle: [ 62 | 2, 63 | { 64 | arrays: 'always-multiline', 65 | objects: 'always-multiline', 66 | functions: 'never' 67 | } 68 | ] 69 | */ 70 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import * as babel from 'babel-core' 3 | import * as glamor from 'glamor' 4 | import {renderStatic} from 'glamor/server' 5 | import plugin from './babel-plugin' 6 | 7 | const defaultBabelOptions = { 8 | babelrc: false, 9 | sourceMaps: true, 10 | plugins: [], 11 | parserOpts: { 12 | plugins: [ 13 | // include all the things because why not? 14 | // 'estree', // except this one because why...? 15 | 'jsx', 16 | 'flow', 17 | 'classConstructorCall', 18 | 'doExpressions', 19 | 'trailingFunctionCommas', 20 | 'objectRestSpread', 21 | 'decorators', 22 | 'classProperties', 23 | 'exportExtensions', 24 | 'exponentiationOperator', 25 | 'asyncGenerators', 26 | 'functionBind', 27 | 'functionSent', 28 | 'dynamicImport', 29 | 'asyncFunctions', 30 | ], 31 | }, 32 | } 33 | 34 | module.exports = precompile 35 | 36 | function precompile({sources = []}) { 37 | let transformed = [] 38 | const {css, ids} = renderStatic(() => { 39 | transformed = sources 40 | .map(({filename, code = fs.readFileSync(filename, 'utf8'), ...rest}) => ({ 41 | filename, 42 | code, 43 | ...rest, 44 | })) 45 | .filter(({code}) => hasGlamorous(code)) 46 | .map(({filename, code, babelOptions = {}}) => { 47 | if (!hasGlamorous(code)) { 48 | return { 49 | source: code, 50 | code, 51 | filename, 52 | } 53 | } 54 | const babelOptionsToUse = { 55 | filename, 56 | ...defaultBabelOptions, 57 | ...babelOptions, 58 | } 59 | babelOptionsToUse.plugins.unshift(plugin) 60 | return { 61 | source: code, 62 | filename, 63 | ...babel.transform(code, babelOptionsToUse), 64 | } 65 | }) 66 | return '
fake html to make glamor happy
' 67 | }) 68 | glamor.flush() // make sure multiple runs don't mess things up 69 | return {transformed, css, ids} 70 | } 71 | 72 | // TODO: when we support more than just glamorous, we'll want to 73 | // expand this or even remove it entirely, but this ahead-of-time 74 | // filtering is really handy for performance. 75 | function hasGlamorous(code) { 76 | return code.indexOf('glamorous') !== -1 77 | } 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-in-js-precompiler", 3 | "version": "0.0.0-semantically-released", 4 | "description": "Precompiles CSS-in-JS objects to CSS strings", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "nps", 8 | "test": "nps test", 9 | "commitmsg": "opt --in commit-msg --exec \"validate-commit-msg\"", 10 | "precommit": "lint-staged && opt --in pre-commit --exec \"npm start validate\"" 11 | }, 12 | "files": [ 13 | "dist" 14 | ], 15 | "keywords": [], 16 | "author": "Kent C. Dodds (http://kentcdodds.com/)", 17 | "license": "MIT", 18 | "dependencies": { 19 | "babel-core": "^6.24.1", 20 | "glamor": "^2.20.24" 21 | }, 22 | "devDependencies": { 23 | "all-contributors-cli": "^4.0.1", 24 | "babel-cli": "^6.23.0", 25 | "babel-jest": "^19.0.0", 26 | "babel-preset-env": "^1.2.0", 27 | "babel-preset-stage-2": "^6.22.0", 28 | "babel-register": "^6.23.0", 29 | "codecov": "^2.1.0", 30 | "commitizen": "^2.9.6", 31 | "css": "^2.2.1", 32 | "cz-conventional-changelog": "^2.0.0", 33 | "eslint": "^3.17.0", 34 | "eslint-config-kentcdodds": "^12.0.0", 35 | "husky": "^0.13.2", 36 | "jest-cli": "^19.0.2", 37 | "lint-staged": "^3.3.1", 38 | "nps": "^5.0.3", 39 | "nps-utils": "^1.1.2", 40 | "opt-cli": "^1.5.1", 41 | "prettier-eslint-cli": "^3.1.2", 42 | "recast": "^0.12.3", 43 | "semantic-release": "^6.3.6", 44 | "strip-indent": "^2.0.0", 45 | "validate-commit-msg": "^2.11.1" 46 | }, 47 | "eslintConfig": { 48 | "extends": [ 49 | "kentcdodds", 50 | "kentcdodds/jest", 51 | "kentcdodds/prettier" 52 | ] 53 | }, 54 | "lint-staged": { 55 | "*.js": [ 56 | "prettier-eslint --write", 57 | "git add" 58 | ] 59 | }, 60 | "jest": { 61 | "testEnvironment": "node", 62 | "testPathIgnorePatterns": [ 63 | "/node_modules/", 64 | "/__fixtures__/" 65 | ], 66 | "transformIgnorePatterns": [ 67 | "/node_modules/", 68 | "/__fixtures__/" 69 | ], 70 | "coverageThreshold": { 71 | "global": { 72 | "branches": 80, 73 | "functions": 80, 74 | "lines": 80, 75 | "statements": 80 76 | } 77 | } 78 | }, 79 | "config": { 80 | "commitizen": { 81 | "path": "node_modules/cz-conventional-changelog" 82 | } 83 | }, 84 | "repository": { 85 | "type": "git", 86 | "url": "https://github.com/kentcdodds/css-in-js-precompiler.git" 87 | }, 88 | "bugs": { 89 | "url": "https://github.com/kentcdodds/css-in-js-precompiler/issues" 90 | }, 91 | "homepage": "https://github.com/kentcdodds/css-in-js-precompiler#readme" 92 | } 93 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for being willing to contribute! 4 | 5 | **Working on your first Pull Request?** You can learn how from this *free* series 6 | [How to Contribute to an Open Source Project on GitHub][egghead] 7 | 8 | ## Project setup 9 | 10 | 1. Fork and clone the repo 11 | 2. `$ npm install` to install dependencies 12 | 3. `$ npm start validate` to validate you've got it working 13 | 4. Create a branch for your PR 14 | 15 | This project uses [`nps`][nps] and you can run `npm start` to see what scripts are available. 16 | 17 | ## Add yourself as a contributor 18 | 19 | This project follows the [all contributors][all-contributors] specification. To add yourself to the table of 20 | contributors on the README.md, please use the automated script as part of your PR: 21 | 22 | ```console 23 | npm start "addContributor " 24 | ``` 25 | 26 | Follow the prompt. If you've already added yourself to the list and are making a new type of contribution, you can run 27 | it again and select the added contribution type. 28 | 29 | ## Committing and Pushing changes 30 | 31 | This project uses [`semantic-release`][semantic-release] to do automatic releases and generate a changelog based on the 32 | commit history. So we follow [a convention][convention] for commit messages. Please follow this convention for your 33 | commit messages. 34 | 35 | You can use `commitizen` to help you to follow [the convention][convention] 36 | 37 | Once you are ready to commit the changes, please use the below commands 38 | 39 | 1. `git add ` 40 | 2. `$ npm start commit` 41 | 42 | ... and follow the instruction of the interactive prompt. 43 | 44 | ### opt into git hooks 45 | 46 | There are git hooks set up with this project that are automatically installed when you install dependencies. They're 47 | really handy, but are turned off by default (so as to not hinder new contributors). You can opt into these by creating 48 | a file called `.opt-in` at the root of the project and putting this inside: 49 | 50 | ``` 51 | commit-msg 52 | pre-commit 53 | ``` 54 | 55 | ## Help needed 56 | 57 | Please checkout the [ROADMAP.md][ROADMAP] and raise an issue to discuss 58 | any of the items in the want to do or might do list. 59 | 60 | Also, please watch the repo and respond to questions/bug reports/feature requests! Thanks! 61 | 62 | [egghead]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 63 | [semantic-release]: https://npmjs.com/package/semantic-release 64 | [convention]: https://github.com/conventional-changelog/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md 65 | [all-contributors]: https://github.com/kentcdodds/all-contributors 66 | [ROADMAP]: ./other/ROADMAP.md 67 | [nps]: https://npmjs.com/package/nps 68 | -------------------------------------------------------------------------------- /other/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at kent+coc@doddsfamily.us. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/index.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`1. follows imports 1`] = ` 4 | " 5 | import fred from 'glamorous' 6 | fred.div({fontSize: 10}) 7 | 8 | 👇 9 | 10 | import fred from 'glamorous' 11 | fred.div(\\"css-1ea0ufb\\") 12 | 13 | .css-1ea0ufb, 14 | [data-css-1ea0ufb] { 15 | font-size: 10px; 16 | } 17 | " 18 | `; 19 | 20 | exports[`2. follows requires 1`] = ` 21 | " 22 | const ethel = require('glamorous') 23 | ethel.span({fontSize: 25}) 24 | 25 | 👇 26 | 27 | const ethel = require('glamorous') 28 | ethel.span(\\"css-1rbjb59\\") 29 | 30 | .css-1rbjb59, 31 | [data-css-1rbjb59] { 32 | font-size: 25px; 33 | } 34 | " 35 | `; 36 | 37 | exports[`3. supports statics in arrow functions 1`] = ` 38 | " 39 | import glamorous from 'glamorous' 40 | 41 | glamorous.div( 42 | ({big}) => 43 | (big 44 | ? { 45 | fontSize: 20, 46 | } 47 | : { 48 | fontSize: 12, 49 | }) 50 | ) 51 | 52 | 👇 53 | 54 | import glamorous from 'glamorous' 55 | 56 | glamorous.div( 57 | ({big}) => 58 | (big 59 | ? \\"css-vfrxuj\\" 60 | : \\"css-1dmiui7\\") 61 | ) 62 | 63 | .css-vfrxuj, 64 | [data-css-vfrxuj] { 65 | font-size: 20px; 66 | } 67 | 68 | .css-1dmiui7, 69 | [data-css-1dmiui7] { 70 | font-size: 12px; 71 | } 72 | " 73 | `; 74 | 75 | exports[`4. follows references 1`] = ` 76 | " 77 | import glamorous from 'glamorous' 78 | 79 | const styles = {color: 'palevioletred'} 80 | 81 | glamorous.div(styles) 82 | 83 | 👇 84 | 85 | import glamorous from 'glamorous' 86 | 87 | const styles = \\"css-zij22n\\" 88 | 89 | glamorous.div(styles) 90 | 91 | .css-zij22n, 92 | [data-css-zij22n] { 93 | color: palevioletred; 94 | } 95 | " 96 | `; 97 | 98 | exports[`5. when creating custom glamorous components 1`] = ` 99 | " 100 | import g from 'glamorous' 101 | 102 | g('div')({margin: 2}) 103 | 104 | 👇 105 | 106 | import g from 'glamorous' 107 | 108 | g('div')(\\"css-18570p2\\") 109 | 110 | .css-18570p2, 111 | [data-css-18570p2] { 112 | margin: 2px; 113 | } 114 | " 115 | `; 116 | 117 | exports[`6. styles using member expressions 1`] = ` 118 | " 119 | import glamorous from 'glamorous' 120 | 121 | const colors = { 122 | primary: 'red', 123 | secondary: 'blue', 124 | } 125 | 126 | glamorous.p({color: colors.primary}) 127 | 128 | 👇 129 | 130 | import glamorous from 'glamorous' 131 | 132 | const colors = { 133 | primary: 'red', 134 | secondary: 'blue', 135 | } 136 | 137 | glamorous.p(\\"css-1ezp9xe\\") 138 | 139 | .css-1ezp9xe, 140 | [data-css-1ezp9xe] { 141 | color: red; 142 | } 143 | " 144 | `; 145 | 146 | exports[`7. styles using variables across files 1`] = ` 147 | " 148 | import glamorous from 'glamorous' 149 | import {colors as col} from './styles' 150 | 151 | glamorous.p({color: col.primary}) 152 | 153 | 👇 154 | 155 | import glamorous from 'glamorous' 156 | import {colors as col} from './styles' 157 | 158 | glamorous.p(\\"css-1ezp9xe\\") 159 | 160 | .css-1ezp9xe, 161 | [data-css-1ezp9xe] { 162 | color: red; 163 | } 164 | " 165 | `; 166 | 167 | exports[`css from 4 concatenated files 1`] = ` 168 | " 169 | justCode: 170 | import glamorous from 'glamorous';glamorous.div({margin: 0}) 171 | import glamorous from 'glamorous';glamorous.article({margin: 1}) 172 | 173 | justFiles: 174 | /src/__tests__/__fixtures__/import.js 175 | /src/__tests__/__fixtures__/require.js 176 | 177 | 👇 178 | 179 | .css-1wiofhw, 180 | [data-css-1wiofhw] { 181 | margin: 0; 182 | } 183 | 184 | .css-1jr7337, 185 | [data-css-1jr7337] { 186 | margin: 1px; 187 | } 188 | 189 | .css-1ea0ufb, 190 | [data-css-1ea0ufb] { 191 | font-size: 10px; 192 | } 193 | 194 | .css-1rbjb59, 195 | [data-css-1rbjb59] { 196 | font-size: 25px; 197 | } 198 | " 199 | `; 200 | -------------------------------------------------------------------------------- /src/get-literalizers.js: -------------------------------------------------------------------------------- 1 | import nodePath from 'path' 2 | import * as babel from 'babel-core' 3 | 4 | const {types: t} = babel 5 | 6 | export default getLiteralizers 7 | 8 | function getLiteralizers({file}) { 9 | return toLiteral 10 | 11 | function objectToLiteral(path) { 12 | const props = path.get('properties').map(propPath => { 13 | const propNodeClone = t.clone(propPath.node) 14 | const valPath = propPath.get('value') 15 | propNodeClone.shorthand = false 16 | propNodeClone.value = toLiteral(valPath) 17 | return propNodeClone 18 | }) 19 | return t.objectExpression(props) 20 | } 21 | 22 | function arrayToLiteral(path) { 23 | const els = path.get('elements').map(toLiteral) 24 | return t.arrayExpression(els) 25 | } 26 | 27 | function identifierToLiteral(path) { 28 | const binding = path.scope.getBinding(path.node.name) 29 | if (binding.path.type.startsWith('Import')) { 30 | return importToLiteral(binding.path) 31 | } 32 | const initPath = binding.path.get('init') 33 | return toLiteral(initPath) 34 | } 35 | 36 | function importToLiteral(path) { 37 | const importedFile = nodePath.resolve( 38 | nodePath.dirname(file.opts.filename), 39 | // TODO: support any extension and import style 40 | `${path.parent.source.value}.js`, 41 | ) 42 | let literalValue 43 | babel.transformFileSync(importedFile, { 44 | plugins: [ 45 | () => { 46 | let thingToLiteralize 47 | return { 48 | visitor: { 49 | // TODO: support other exports 50 | ExportNamedDeclaration(namedDeclarationPath) { 51 | thingToLiteralize = namedDeclarationPath 52 | .get('declaration.declarations')[0] 53 | .get('init') 54 | }, 55 | Program: { 56 | exit() { 57 | literalValue = toLiteral(thingToLiteralize) 58 | }, 59 | }, 60 | }, 61 | } 62 | }, 63 | ], 64 | }) 65 | return literalValue 66 | } 67 | 68 | function memberExpressionToLiteral(path) { 69 | const pathProperty = path.get('property') 70 | let literalProperty 71 | const pathPropertyValue = getPathPropertyValue() 72 | const literalObject = toLiteral(path.get('object')) 73 | if (t.isObjectExpression(literalObject)) { 74 | const literalObjectProperty = literalObject.properties.find(prop => { 75 | return prop.key.name === pathPropertyValue 76 | }) 77 | if (literalObjectProperty) { 78 | literalProperty = literalObjectProperty.value 79 | } 80 | } else if (t.isArrayExpression(literalObject)) { 81 | literalProperty = literalObject.elements[pathPropertyValue] 82 | } else { 83 | throw new Error( 84 | // eslint-disable-next-line max-len 85 | `${literalObject.type} is not yet supported in memberExpressionToLiteral`, 86 | ) 87 | } 88 | return t.clone(literalProperty || t.identifier('undefined')) 89 | 90 | function getPathPropertyValue() { 91 | if (pathProperty.isIdentifier() && path.node.computed) { 92 | return getLiteralPropertyValue(identifierToLiteral(pathProperty)) 93 | } else { 94 | return getLiteralPropertyValue(pathProperty.node) 95 | } 96 | } 97 | 98 | function getLiteralPropertyValue(node) { 99 | if (t.isLiteral(node)) { 100 | return node.value 101 | } else if (t.isIdentifier(node)) { 102 | return node.name 103 | } else { 104 | throw new Error( 105 | // eslint-disable-next-line max-len 106 | `${node.type} is not yet supported in getLiteralPropertyValue of memberExpressionToLiteral`, 107 | ) 108 | } 109 | } 110 | } 111 | 112 | function toLiteral(path) { 113 | const toLiterals = [ 114 | [t.isLiteral, p => t.clone(p.node)], 115 | [t.isMemberExpression, memberExpressionToLiteral], 116 | [t.isIdentifier, identifierToLiteral], 117 | [t.isArrayExpression, arrayToLiteral], 118 | [t.isObjectExpression, objectToLiteral], 119 | ] 120 | return toLiterals.reduce((literalVal, [test, toLiteralFn]) => { 121 | if (literalVal) { 122 | return literalVal 123 | } 124 | if (test(path)) { 125 | return toLiteralFn(path) 126 | } 127 | return null 128 | }, null) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/__tests__/index.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import * as babel from 'babel-core' 4 | import cssParser from 'css' 5 | import stripIndent from 'strip-indent' 6 | import * as recast from 'recast' 7 | 8 | const precompile = require('../') 9 | 10 | const babelOptions = { 11 | parserOpts: {parser: recast.parse}, 12 | generatorOpts: {generator: recast.print, lineTerminator: '\n'}, 13 | } 14 | 15 | const tests = [ 16 | { 17 | title: 'follows imports', 18 | fixtureName: 'import.js', 19 | }, 20 | { 21 | title: 'follows requires', 22 | fixtureName: 'require.js', 23 | }, 24 | { 25 | title: 'supports statics in arrow functions', 26 | fixtureName: 'arrow-ternary.js', 27 | }, 28 | { 29 | title: 'follows references', 30 | fixtureName: 'references.js', 31 | }, 32 | { 33 | title: 'when creating custom glamorous components', 34 | fixtureName: 'wrapped-component.js', 35 | }, 36 | { 37 | title: 'styles using member expressions', 38 | fixtureName: 'member-expression-reference.js', 39 | }, 40 | { 41 | title: 'styles using variables across files', 42 | fixtureName: 'imported-styles.js', 43 | }, 44 | ] 45 | 46 | tests.forEach(({title, fixtureName, modifier}, index) => { 47 | if (modifier) { 48 | test[modifier](title, testFn) 49 | } else { 50 | test(title, testFn) 51 | } 52 | function testFn() { 53 | const filename = fixturePath(fixtureName) 54 | const sourceCode = fs.readFileSync(filename, 'utf8') 55 | const {transformed, css} = precompile({ 56 | sources: [{filename, code: sourceCode, babelOptions}], 57 | }) 58 | 59 | const [{code}] = transformed 60 | const output = trimAndNewline( 61 | [ 62 | '\n', 63 | sourceCode.trim(), 64 | `\n\n 👇\n\n`, 65 | code.trim(), 66 | '\n\n', 67 | formatCSS(css), 68 | '\n', 69 | ].join(''), 70 | ) 71 | 72 | expect(output).toMatchSnapshot(`${index + 1}. ${title}`) 73 | } 74 | }) 75 | 76 | test('does not change code that should not be changed', () => { 77 | const sourceFile = path.join(__dirname, '__fixtures__/untouched.js') 78 | const source = fs.readFileSync(sourceFile, 'utf8') 79 | const {transformed, css} = precompile({ 80 | sources: [{filename: sourceFile, babelOptions}], 81 | }) 82 | 83 | const [{code}] = transformed 84 | expect(code.trim()).toEqual(source.trim()) 85 | expect(css).toEqual('') 86 | }) 87 | 88 | test('forwards along a bunch of stuff from babel', () => { 89 | const results = precompile({ 90 | sources: [ 91 | { 92 | code: stripIndent( 93 | ` 94 | import glamorous from 'glamorous' 95 | glamorous.div( 96 | { 97 | fontSize: 20, 98 | fontWeight: 'normal', 99 | }, 100 | { 101 | margin: 20, 102 | ':hover': { 103 | margin: 10, 104 | }, 105 | }, 106 | ) 107 | someOtherCall({fontSize: 30}) 108 | `, 109 | ).trim(), 110 | }, 111 | ], 112 | }) 113 | expect(Object.keys(results.transformed[0])).toEqual([ 114 | // just extra things that might be useful 115 | 'source', 116 | 'filename', 117 | // babel stuff 118 | 'metadata', 119 | 'options', 120 | 'ignored', 121 | 'code', 122 | 'ast', 123 | 'map', 124 | ]) 125 | expect(Object.keys(results)).toEqual(['transformed', 'css', 'ids']) 126 | }) 127 | 128 | test('can accept sources with just code or just filename', () => { 129 | const justFilename = [ 130 | {filename: fixturePath('import.js')}, 131 | {filename: fixturePath('require.js')}, 132 | ] 133 | const justCode = [ 134 | {code: `import glamorous from 'glamorous';glamorous.div({margin: 0})`}, 135 | {code: `import glamorous from 'glamorous';glamorous.article({margin: 1})`}, 136 | ] 137 | const {transformed, css} = precompile({ 138 | sources: [...justCode, ...justFilename], 139 | }) 140 | expect(transformed).toHaveLength(4) 141 | const output = trimAndNewline( 142 | [ 143 | 'justCode:\n ', 144 | justCode.map(({code}) => code).join('\n '), 145 | '\n\njustFiles:\n ', 146 | justFilename.map(({filename}) => relativizePaths(filename)).join('\n '), 147 | `\n\n 👇\n\n`, 148 | formatCSS(css), 149 | ].join(''), 150 | ) 151 | expect(output).toMatchSnapshot(`css from 4 concatenated files`) 152 | }) 153 | 154 | test('does not parse source which does not use `glamorous`', () => { 155 | const sources = [{code: 'import glamNotOrous from "glam-not-orous"'}] 156 | const sourceFiles = [fixturePath('glam-not-orous.js')] 157 | const transformSpy = jest.spyOn(babel, 'transform') 158 | precompile({sources, sourceFiles}) 159 | expect(transformSpy).not.toHaveBeenCalled() 160 | transformSpy.mockRestore() 161 | }) 162 | 163 | //////////////////// utils //////////////////// 164 | 165 | function fixturePath(name) { 166 | return path.join(__dirname, '__fixtures__', name) 167 | } 168 | 169 | function formatCSS(css) { 170 | return cssParser.stringify(cssParser.parse(css)).trim() 171 | } 172 | 173 | /* 174 | * This takes the results object and removes environment-specific 175 | * elements from the path. 176 | */ 177 | function relativizePaths(filepath) { 178 | return filepath 179 | .replace(':/', ':\\') 180 | .replace(path.resolve(__dirname, '../../'), '') 181 | .replace(/\\/g, '/') 182 | } 183 | 184 | function trimAndNewline(string) { 185 | return `\n${string.trim()}\n` 186 | } 187 | -------------------------------------------------------------------------------- /src/babel-plugin.js: -------------------------------------------------------------------------------- 1 | import * as glamor from 'glamor' 2 | import getLiteralizers from './get-literalizers' 3 | 4 | export default function(babel) { 5 | const {types: t} = babel 6 | const glamorousIdentifiers = new Set() 7 | return { 8 | name: 'glamorous-static', 9 | visitor: { 10 | ImportDeclaration(path) { 11 | const defaultSpecifierPath = path.get('specifiers')[0] 12 | if ( 13 | path.node.source.value !== 'glamorous' || 14 | !t.isImportDefaultSpecifier(defaultSpecifierPath) 15 | ) { 16 | return 17 | } 18 | const {node: {local: {name}}} = defaultSpecifierPath 19 | const {referencePaths} = path.scope.getBinding(name) 20 | referencePaths.forEach(reference => { 21 | glamorousIdentifiers.add(reference) 22 | }) 23 | }, 24 | VariableDeclarator(path) { 25 | const {node} = path 26 | if (!isRequireCall(node.init) || !t.isIdentifier(node.id)) { 27 | return 28 | } 29 | const {id: {name}} = node 30 | const binding = path.scope.getBinding(name) 31 | const {referencePaths} = binding 32 | referencePaths.forEach(reference => { 33 | glamorousIdentifiers.add(reference) 34 | }) 35 | }, 36 | Program: { 37 | exit(programPath, state) { 38 | const toLiteral = getLiteralizers(state) 39 | Array.from(glamorousIdentifiers).forEach(identifier => { 40 | const isGlamorousCall = looksLike(identifier, { 41 | parentPath: { 42 | type: type => 43 | type === 'MemberExpression' || type === 'CallExpression', 44 | parentPath: { 45 | type: 'CallExpression', 46 | }, 47 | }, 48 | }) 49 | if (!isGlamorousCall) { 50 | return 51 | } 52 | const callExpression = identifier.parentPath.parentPath 53 | const staticPaths = callExpression 54 | .get('arguments') 55 | .reduce( 56 | (paths, argPath) => paths.concat(getStaticPaths(argPath)), 57 | [], 58 | ) 59 | .filter(Boolean) 60 | staticPaths.forEach(staticPath => { 61 | staticPath.replaceWith(t.stringLiteral(glamorize(staticPath))) 62 | }) 63 | 64 | function glamorize(literalNodePath) { 65 | let obj 66 | const {code} = babel.transformFromAst( 67 | t.program([t.expressionStatement(toLiteral(literalNodePath))]), 68 | ) 69 | // eslint-disable-next-line no-eval 70 | eval(`obj = ${code}`) 71 | const className = glamor.css(obj).toString() 72 | return className 73 | } 74 | }) 75 | 76 | // babel utils 77 | function getStaticPaths(path) { 78 | const pathGetters = [ 79 | getStaticObjectPaths, 80 | getStaticsInDynamic, 81 | getStaticReferences, 82 | ] 83 | return pathGetters.reduce((pathsAlreadyGotten, pathGetter) => { 84 | if (pathsAlreadyGotten) { 85 | return pathsAlreadyGotten 86 | } 87 | const paths = pathGetter(path) 88 | if (paths.length) { 89 | return paths 90 | } 91 | return null 92 | }, null) 93 | } 94 | 95 | function getStaticObjectPaths(path) { 96 | if (path.type !== 'ObjectExpression') { 97 | return [] 98 | } 99 | return [path] 100 | } 101 | 102 | function getStaticsInDynamic(path) { 103 | const isSupportedFunction = looksLike(path, { 104 | node: { 105 | type: 'ArrowFunctionExpression', 106 | body: { 107 | type: 'ConditionalExpression', 108 | consequent: {type: 'ObjectExpression'}, 109 | alternate: {type: 'ObjectExpression'}, 110 | }, 111 | }, 112 | }) 113 | if (!isSupportedFunction) { 114 | return [] 115 | } 116 | const things = [ 117 | ...getStaticPaths(path.get('body.consequent')), 118 | ...getStaticPaths(path.get('body.alternate')), 119 | ] 120 | return things 121 | } 122 | 123 | function getStaticReferences(path) { 124 | if (path.type !== 'Identifier') { 125 | return [] 126 | } 127 | const binding = path.scope.getBinding(path.node.name) 128 | if (binding) { 129 | return getStaticObjectPaths(binding.path.get('init')) 130 | } else { 131 | return [] 132 | } 133 | } 134 | }, 135 | }, 136 | }, 137 | } 138 | } 139 | 140 | function isRequireCall(callExpression) { 141 | return looksLike(callExpression, { 142 | type: 'CallExpression', 143 | callee: { 144 | name: 'require', 145 | }, 146 | arguments: args => 147 | args.length === 1 && looksLike(args[0], {value: 'glamorous'}), 148 | }) 149 | } 150 | 151 | // generic utils 152 | function looksLike(a, b) { 153 | return ( 154 | a && 155 | b && 156 | Object.keys(b).every(bKey => { 157 | const bVal = b[bKey] 158 | const aVal = a[bKey] 159 | if (typeof bVal === 'function') { 160 | return bVal(aVal) 161 | } 162 | return isPrimitive(bVal) ? bVal === aVal : looksLike(aVal, bVal) 163 | }) 164 | ) 165 | } 166 | 167 | function isPrimitive(val) { 168 | // eslint-disable-next-line 169 | return val == null || /^[sbn]/.test(typeof val); 170 | } 171 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # css-in-js-precompiler 2 | 3 | Precompiles static CSS-in-JS objects to CSS strings 4 | 5 | **CURRENTLY A WORK IN PROGRESS** 6 | 7 | 27 | 28 | ## The problem 29 | 30 | You love the benefits of CSS-in-JS, but don't love some of the performance 31 | characteristics and trade-offs you have to make with regards to not using actual 32 | CSS files. 33 | 34 | ## This solution 35 | 36 | This module takes in your source code and gives you back the source code with 37 | the literal CSS-in-JS objects swapped for class names as well as the generated 38 | CSS. You can then use that to create a css file and deploy that. 39 | 40 | **It's still pretty early stages** 41 | 42 | ## Installation 43 | 44 | 53 | 54 | This is not yet published. But you can install it anyway with: 55 | 56 | ``` 57 | npm install kentcdodds/css-in-js-precompiler 58 | cd node_modules/css-in-js-precompiler 59 | npm install 60 | npm start build 61 | ``` 62 | 63 | And you should be able to use it now :) 64 | 65 | ## Usage 66 | 67 | This is still under development so the API and assumptions are going to change. 68 | But right now, we're naïvely assuming you're using `glamorous`. _But this 69 | solution will be modified to work with **any** CSS-in-JS library you're using_. 70 | 71 | ```javascript 72 | const precompileCSSInJS = require('css-in-js-precompiler') 73 | const options = { 74 | sources: [ 75 | { 76 | code: `import glamorous from 'glamorous';\nglamorous.div({fontSize: 23})`, 77 | filename: '/some/path.js', 78 | babelOptions: {/* optional. Shallowly merges with the default babelOptions */} 79 | }, 80 | ], 81 | } 82 | 83 | const result = precompileCSSInJS(options) 84 | result.transformed[0].code === `import glamorous from 'glamorous';\nglamorous.div("css-my79es");` 85 | result.transformed[0].map === '' 86 | result.css === '.css-my79es,[data-css-my79es]{font-size:23px;}' 87 | ``` 88 | 89 | ### options 90 | 91 | #### `sources` 92 | 93 | This is an array of `SourceObjects` which will be used to determine what source 94 | to precompile and how. Here are the available properties on these objects: 95 | 96 | #### code 97 | 98 | This is the source code to actually precompile. If this is not provided, then 99 | the code will be derived from the `filename`. 100 | 101 | ##### filename 102 | 103 | This is a string path to the filename. If the `code` is not provided, this will 104 | be used to read the file. If this is not provided, then you will be unable to 105 | handle importing dynamic values from other files. 106 | 107 | #### babelOptions 108 | 109 | This is the same thing you would pass to `babel.transform` if you were calling 110 | it yourself. Read more [here](http://babeljs.io/docs/core-packages/#options). 111 | This will be shallowly merged with the default `babelOptions`. Currently 112 | (2017-05-03) the default babelOptions are: 113 | 114 | ```javascript 115 | { 116 | babelrc: false, 117 | sourceMaps: true, 118 | plugins: [/* our custom plugin to do this extraction */], 119 | parserOpts: {plugins: [/* all of them except estree */]}, 120 | } 121 | ``` 122 | 123 | This is shallowly merged, with the exception of `plugins`. You can specify any 124 | plugins you want and we'll make sure we always include our own plugin to do 125 | the precompiling. (You're welcome). 126 | 127 | ## Inspiration 128 | 129 | I started thinking about this around [here][inspiration-link]. 130 | 131 | ## Other Solutions 132 | 133 | I'm not aware of any, if you are please [make a pull request][prs] and add it 134 | here! 135 | 136 | ## Contributors 137 | 138 | Thanks goes to these people ([emoji key][emojis]): 139 | 140 | 141 | | [
Kent C. Dodds](https://kentcdodds.com)
[💻](https://github.com/kentcdodds/css-in-js-precompiler/commits?author=kentcdodds) [📖](https://github.com/kentcdodds/css-in-js-precompiler/commits?author=kentcdodds) 🚇 [⚠️](https://github.com/kentcdodds/css-in-js-precompiler/commits?author=kentcdodds) | 142 | | :---: | 143 | 144 | 145 | This project follows the [all-contributors][all-contributors] specification. 146 | Contributions of any kind welcome! 147 | 148 | ## LICENSE 149 | 150 | MIT 151 | 152 | [npm]: https://www.npmjs.com/ 153 | [node]: https://nodejs.org 154 | [build-badge]: https://img.shields.io/travis/kentcdodds/css-in-js-precompiler.svg?style=flat-square 155 | [build]: https://travis-ci.org/kentcdodds/css-in-js-precompiler 156 | [coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/css-in-js-precompiler.svg?style=flat-square 157 | [coverage]: https://codecov.io/github/kentcdodds/css-in-js-precompiler 158 | [dependencyci-badge]: https://dependencyci.com/github/kentcdodds/css-in-js-precompiler/badge?style=flat-square 159 | [dependencyci]: https://dependencyci.com/github/kentcdodds/css-in-js-precompiler 160 | [version-badge]: https://img.shields.io/npm/v/css-in-js-precompiler.svg?style=flat-square 161 | [package]: https://www.npmjs.com/package/css-in-js-precompiler 162 | [downloads-badge]: https://img.shields.io/npm/dm/css-in-js-precompiler.svg?style=flat-square 163 | [npm-stat]: http://npm-stat.com/charts.html?package=css-in-js-precompiler&from=2016-04-01 164 | [license-badge]: https://img.shields.io/npm/l/css-in-js-precompiler.svg?style=flat-square 165 | [license]: https://github.com/kentcdodds/css-in-js-precompiler/blob/master/other/LICENSE 166 | [prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square 167 | [prs]: http://makeapullrequest.com 168 | [donate-badge]: https://img.shields.io/badge/$-support-green.svg?style=flat-square 169 | [donate]: http://kcd.im/donate 170 | [coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square 171 | [coc]: https://github.com/kentcdodds/css-in-js-precompiler/blob/master/other/CODE_OF_CONDUCT.md 172 | [roadmap-badge]: https://img.shields.io/badge/%F0%9F%93%94-roadmap-CD9523.svg?style=flat-square 173 | [roadmap]: https://github.com/kentcdodds/css-in-js-precompiler/blob/master/other/ROADMAP.md 174 | [examples-badge]: https://img.shields.io/badge/%F0%9F%92%A1-examples-8C8E93.svg?style=flat-square 175 | [examples]: https://github.com/kentcdodds/css-in-js-precompiler/blob/master/other/EXAMPLES.md 176 | [github-watch-badge]: https://img.shields.io/github/watchers/kentcdodds/css-in-js-precompiler.svg?style=social 177 | [github-watch]: https://github.com/kentcdodds/css-in-js-precompiler/watchers 178 | [github-star-badge]: https://img.shields.io/github/stars/kentcdodds/css-in-js-precompiler.svg?style=social 179 | [github-star]: https://github.com/kentcdodds/css-in-js-precompiler/stargazers 180 | [twitter]: https://twitter.com/intent/tweet?text=Check%20out%20css-in-js-precompiler!%20https://github.com/kentcdodds/css-in-js-precompiler%20%F0%9F%91%8D 181 | [twitter-badge]: https://img.shields.io/twitter/url/https/github.com/kentcdodds/css-in-js-precompiler.svg?style=social 182 | [emojis]: https://github.com/kentcdodds/all-contributors#emoji-key 183 | [all-contributors]: https://github.com/kentcdodds/all-contributors 184 | [inspiration-link]: https://github.com/paypal/glamorous/issues/43#issuecomment-294153104 185 | --------------------------------------------------------------------------------