├── .gitignore
├── examples
├── create-react-app
│ ├── .gitignore
│ ├── custom.config.js
│ ├── package.json
│ ├── public
│ │ ├── favicon.ico
│ │ ├── index.html
│ │ ├── logo192.png
│ │ ├── logo512.png
│ │ ├── manifest.json
│ │ └── robots.txt
│ └── src
│ │ ├── App.js
│ │ ├── dist.css
│ │ └── index.js
└── nextjs
│ ├── .babelrc.js
│ ├── .eslintrc.json
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── pages
│ ├── _app.js
│ ├── about.js
│ └── index.js
│ └── public
│ ├── dist.css
│ ├── favicon.ico
│ └── vercel.svg
├── package.json
├── packages
└── css-out-js
│ ├── babel.js
│ ├── index.js
│ ├── logo.png
│ ├── package.json
│ └── readme.md
├── readme.md
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 | .pnpm-debug.log*
9 |
10 | # Diagnostic reports (https://nodejs.org/api/report.html)
11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 | *.lcov
25 |
26 | # nyc test coverage
27 | .nyc_output
28 |
29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30 | .grunt
31 |
32 | # Bower dependency directory (https://bower.io/)
33 | bower_components
34 |
35 | # node-waf configuration
36 | .lock-wscript
37 |
38 | # Compiled binary addons (https://nodejs.org/api/addons.html)
39 | build/Release
40 |
41 | # Dependency directories
42 | node_modules/
43 | jspm_packages/
44 |
45 | # Snowpack dependency directory (https://snowpack.dev/)
46 | web_modules/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional npm cache directory
52 | .npm
53 |
54 | # Optional eslint cache
55 | .eslintcache
56 |
57 | # Optional stylelint cache
58 | .stylelintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variable files
76 | .env
77 | .env.development.local
78 | .env.test.local
79 | .env.production.local
80 | .env.local
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # vuepress v2.x temp and cache directory
104 | .temp
105 | .cache
106 |
107 | # Docusaurus cache and generated files
108 | .docusaurus
109 |
110 | # Serverless directories
111 | .serverless/
112 |
113 | # FuseBox cache
114 | .fusebox/
115 |
116 | # DynamoDB Local files
117 | .dynamodb/
118 |
119 | # TernJS port file
120 | .tern-port
121 |
122 | # Stores VSCode versions used for testing VSCode extensions
123 | .vscode-test
124 |
125 | # yarn v2
126 | .yarn/cache
127 | .yarn/unplugged
128 | .yarn/build-state.yml
129 | .yarn/install-state.gz
130 | .pnp.*
131 |
132 |
133 | packages/system-css/out.css# Logs
134 | logs
135 | *.log
136 | npm-debug.log*
137 | yarn-debug.log*
138 | yarn-error.log*
139 | lerna-debug.log*
140 | .pnpm-debug.log*
141 |
142 | # Diagnostic reports (https://nodejs.org/api/report.html)
143 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
144 |
145 | # Runtime data
146 | pids
147 | *.pid
148 | *.seed
149 | *.pid.lock
150 |
151 | # Directory for instrumented libs generated by jscoverage/JSCover
152 | lib-cov
153 |
154 | # Coverage directory used by tools like istanbul
155 | coverage
156 | *.lcov
157 |
158 | # nyc test coverage
159 | .nyc_output
160 |
161 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
162 | .grunt
163 |
164 | # Bower dependency directory (https://bower.io/)
165 | bower_components
166 |
167 | # node-waf configuration
168 | .lock-wscript
169 |
170 | # Compiled binary addons (https://nodejs.org/api/addons.html)
171 | build/Release
172 |
173 | # Dependency directories
174 | node_modules/
175 | jspm_packages/
176 |
177 | # Snowpack dependency directory (https://snowpack.dev/)
178 | web_modules/
179 |
180 | # TypeScript cache
181 | *.tsbuildinfo
182 |
183 | # Optional npm cache directory
184 | .npm
185 |
186 | # Optional eslint cache
187 | .eslintcache
188 |
189 | # Optional stylelint cache
190 | .stylelintcache
191 |
192 | # Microbundle cache
193 | .rpt2_cache/
194 | .rts2_cache_cjs/
195 | .rts2_cache_es/
196 | .rts2_cache_umd/
197 |
198 | # Optional REPL history
199 | .node_repl_history
200 |
201 | # Output of 'npm pack'
202 | *.tgz
203 |
204 | # Yarn Integrity file
205 | .yarn-integrity
206 |
207 | # dotenv environment variable files
208 | .env
209 | .env.development.local
210 | .env.test.local
211 | .env.production.local
212 | .env.local
213 |
214 | # parcel-bundler cache (https://parceljs.org/)
215 | .cache
216 | .parcel-cache
217 |
218 | # Next.js build output
219 | .next
220 | out
221 |
222 | # Nuxt.js build / generate output
223 | .nuxt
224 | dist
225 |
226 | # Gatsby files
227 | .cache/
228 | # Comment in the public line in if your project uses Gatsby and not Next.js
229 | # https://nextjs.org/blog/next-9-1#public-directory-support
230 | # public
231 |
232 | # vuepress build output
233 | .vuepress/dist
234 |
235 | # vuepress v2.x temp and cache directory
236 | .temp
237 | .cache
238 |
239 | # Docusaurus cache and generated files
240 | .docusaurus
241 |
242 | # Serverless directories
243 | .serverless/
244 |
245 | # FuseBox cache
246 | .fusebox/
247 |
248 | # DynamoDB Local files
249 | .dynamodb/
250 |
251 | # TernJS port file
252 | .tern-port
253 |
254 | # Stores VSCode versions used for testing VSCode extensions
255 | .vscode-test
256 |
257 | # yarn v2
258 | .yarn/cache
259 | .yarn/unplugged
260 | .yarn/build-state.yml
261 | .yarn/install-state.gz
262 | .pnp.*
263 | # Logs
264 | logs
265 | *.log
266 | npm-debug.log*
267 | yarn-debug.log*
268 | yarn-error.log*
269 | lerna-debug.log*
270 | .pnpm-debug.log*
271 |
272 | # Diagnostic reports (https://nodejs.org/api/report.html)
273 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
274 |
275 | # Runtime data
276 | pids
277 | *.pid
278 | *.seed
279 | *.pid.lock
280 |
281 | # Directory for instrumented libs generated by jscoverage/JSCover
282 | lib-cov
283 |
284 | # Coverage directory used by tools like istanbul
285 | coverage
286 | *.lcov
287 |
288 | # nyc test coverage
289 | .nyc_output
290 |
291 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
292 | .grunt
293 |
294 | # Bower dependency directory (https://bower.io/)
295 | bower_components
296 |
297 | # node-waf configuration
298 | .lock-wscript
299 |
300 | # Compiled binary addons (https://nodejs.org/api/addons.html)
301 | build/Release
302 |
303 | # Dependency directories
304 | node_modules/
305 | jspm_packages/
306 |
307 | # Snowpack dependency directory (https://snowpack.dev/)
308 | web_modules/
309 |
310 | # TypeScript cache
311 | *.tsbuildinfo
312 |
313 | # Optional npm cache directory
314 | .npm
315 |
316 | # Optional eslint cache
317 | .eslintcache
318 |
319 | # Optional stylelint cache
320 | .stylelintcache
321 |
322 | # Microbundle cache
323 | .rpt2_cache/
324 | .rts2_cache_cjs/
325 | .rts2_cache_es/
326 | .rts2_cache_umd/
327 |
328 | # Optional REPL history
329 | .node_repl_history
330 |
331 | # Output of 'npm pack'
332 | *.tgz
333 |
334 | # Yarn Integrity file
335 | .yarn-integrity
336 |
337 | # dotenv environment variable files
338 | .env
339 | .env.development.local
340 | .env.test.local
341 | .env.production.local
342 | .env.local
343 |
344 | # parcel-bundler cache (https://parceljs.org/)
345 | .cache
346 | .parcel-cache
347 |
348 | # Next.js build output
349 | .next
350 | out
351 |
352 | # Nuxt.js build / generate output
353 | .nuxt
354 | dist
355 |
356 | # Gatsby files
357 | .cache/
358 | # Comment in the public line in if your project uses Gatsby and not Next.js
359 | # https://nextjs.org/blog/next-9-1#public-directory-support
360 | # public
361 |
362 | # vuepress build output
363 | .vuepress/dist
364 |
365 | # vuepress v2.x temp and cache directory
366 | .temp
367 | .cache
368 |
369 | # Docusaurus cache and generated files
370 | .docusaurus
371 |
372 | # Serverless directories
373 | .serverless/
374 |
375 | # FuseBox cache
376 | .fusebox/
377 |
378 | # DynamoDB Local files
379 | .dynamodb/
380 |
381 | # TernJS port file
382 | .tern-port
383 |
384 | # Stores VSCode versions used for testing VSCode extensions
385 | .vscode-test
386 |
387 | # yarn v2
388 | .yarn/cache
389 | .yarn/unplugged
390 | .yarn/build-state.yml
391 | .yarn/install-state.gz
392 | .pnp.*
393 |
--------------------------------------------------------------------------------
/examples/create-react-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/examples/create-react-app/custom.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | babel: {
3 | plugins: [['css-out-js/babel', { path: 'src/dist.css' }]]
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/examples/create-react-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-create-react-app",
3 | "version": "0.0.0",
4 | "private": true,
5 | "cracoConfig": "custom.config.js",
6 | "dependencies": {
7 | "@craco/craco": "^6.4.3",
8 | "css-out-js": "0.0.1",
9 | "react": "^18.0.0",
10 | "react-dom": "^18.0.0",
11 | "react-scripts": "5.0.0"
12 | },
13 | "scripts": {
14 | "start": "craco start",
15 | "build": "craco build"
16 | },
17 | "eslintConfig": {
18 | "extends": [
19 | "react-app",
20 | "react-app/jest"
21 | ]
22 | },
23 | "browserslist": {
24 | "production": [
25 | ">0.2%",
26 | "not dead",
27 | "not op_mini all"
28 | ],
29 | "development": [
30 | "last 1 chrome version",
31 | "last 1 firefox version",
32 | "last 1 safari version"
33 | ]
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/examples/create-react-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siddharthkp/css-out-js/3d43e1c84fdc784008e9442e6e0e02391afa4b7a/examples/create-react-app/public/favicon.ico
--------------------------------------------------------------------------------
/examples/create-react-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | React App
13 |
14 |
15 | You need to enable JavaScript to run this app.
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/create-react-app/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siddharthkp/css-out-js/3d43e1c84fdc784008e9442e6e0e02391afa4b7a/examples/create-react-app/public/logo192.png
--------------------------------------------------------------------------------
/examples/create-react-app/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siddharthkp/css-out-js/3d43e1c84fdc784008e9442e6e0e02391afa4b7a/examples/create-react-app/public/logo512.png
--------------------------------------------------------------------------------
/examples/create-react-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/examples/create-react-app/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/examples/create-react-app/src/App.js:
--------------------------------------------------------------------------------
1 | import { css } from 'css-out-js';
2 | import './dist.css';
3 |
4 | const Title = () => {
5 | /**
6 | * css takes styles and returns a className
7 | *
8 | * if you use the babel plugin
9 | * 1. it extracts the styles into a css file and
10 | * 2. replaces the function call with className string
11 | * 3. removes the runtime import for system-css
12 | *
13 | */
14 | return Hello! ;
15 | };
16 |
17 | const LearnMore = (props) => {
18 | // if there are dynamic styles
19 | const className = css({ color: props.color });
20 |
21 | return (
22 |
23 | Learn React
24 |
25 | );
26 | };
27 |
28 | const FONT_FAMILY = 'sans-serif';
29 |
30 | function Home() {
31 | const className = css({
32 | color: 'tomato',
33 | fontFamily: FONT_FAMILY
34 | });
35 |
36 | return (
37 |
38 |
39 |
40 | Edit src/App.js
and save to reload.
41 |
42 |
43 |
44 | );
45 | }
46 |
47 | export default Home;
48 |
--------------------------------------------------------------------------------
/examples/create-react-app/src/dist.css:
--------------------------------------------------------------------------------
1 | /*
2 | This file is generated by css-out-js ✨
3 | Don't edit this file directly :)
4 | */
5 |
6 | .Title-1bdbzov {
7 | font-size: 2em
8 | }
9 | .LearnMore-1anp2xb {
10 | color: var(--props-color-1trdzir)
11 | }
12 | .Home-2b9glr {
13 | color: tomato; font-family: var(--font_family-1o4m8p2)
14 | }
--------------------------------------------------------------------------------
/examples/create-react-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 |
4 | import App from './App';
5 |
6 | const root = createRoot(document.getElementById('root'));
7 |
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/examples/nextjs/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['next/babel'],
3 | plugins: [['css-out-js/babel', { path: 'public/dist.css' }]]
4 | };
5 |
--------------------------------------------------------------------------------
/examples/nextjs/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/examples/nextjs/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 | .pnpm-debug.log*
27 |
28 | # local env files
29 | .env*.local
30 |
31 | # vercel
32 | .vercel
33 |
--------------------------------------------------------------------------------
/examples/nextjs/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | ```
12 |
13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
14 |
15 | You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
16 |
17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
18 |
19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
20 |
21 | ## Learn More
22 |
23 | To learn more about Next.js, take a look at the following resources:
24 |
25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
27 |
28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
29 |
30 | ## Deploy on Vercel
31 |
32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
33 |
34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
35 |
--------------------------------------------------------------------------------
/examples/nextjs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example-next",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "next": "12.1.4",
12 | "react": "18.0.0",
13 | "react-dom": "18.0.0",
14 | "css-out-js": "0.0.1"
15 | },
16 | "devDependencies": {
17 | "eslint": "8.12.0",
18 | "eslint-config-next": "12.1.4"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/examples/nextjs/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../public/dist.css';
2 |
3 | function MyApp({ Component, pageProps }) {
4 | return ;
5 | }
6 |
7 | export default MyApp;
8 |
--------------------------------------------------------------------------------
/examples/nextjs/pages/about.js:
--------------------------------------------------------------------------------
1 | function About() {
2 | return (
3 |
4 | About
5 | no thanks, i'm shy
6 |
7 | );
8 | }
9 |
10 | export default About;
11 |
--------------------------------------------------------------------------------
/examples/nextjs/pages/index.js:
--------------------------------------------------------------------------------
1 | import { css } from 'css-out-js';
2 |
3 | const Title = () => {
4 | /**
5 | * css takes styles and returns a className
6 | *
7 | * if you use the babel plugin
8 | * 1. it extracts the styles into a css file and
9 | * 2. replaces the function call with className string
10 | * 3. removes the runtime import for css-out-js
11 | *
12 | */
13 | return Hello! ;
14 | };
15 |
16 | const LearnMore = (props) => {
17 | // if there are dynamic styles
18 | const className = css({ color: props.color });
19 |
20 | return (
21 |
22 | Learn React
23 |
24 | );
25 | };
26 |
27 | const FONT_FAMILY = 'sans-serif';
28 |
29 | function Home() {
30 | const className = css({
31 | color: 'tomato',
32 | fontFamily: FONT_FAMILY
33 | });
34 |
35 | return (
36 |
37 |
38 |
39 | Edit src/App.js
and save to reload.
40 |
41 |
42 |
43 | );
44 | }
45 |
46 | export default Home;
47 |
--------------------------------------------------------------------------------
/examples/nextjs/public/dist.css:
--------------------------------------------------------------------------------
1 | /*
2 | This file is generated by css-out-js ✨
3 | Don't edit this file directly :)
4 | */
5 |
6 | .Title-1bdbzov {
7 | font-size: 2em
8 | }
9 | .LearnMore-qyvrpz {
10 | color: var(--props-color-1i957tr)
11 | }
12 | .Home-1khwc8n {
13 | color: tomato; font-family: var(--font_family-mgwka)
14 | }
--------------------------------------------------------------------------------
/examples/nextjs/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siddharthkp/css-out-js/3d43e1c84fdc784008e9442e6e0e02391afa4b7a/examples/nextjs/public/favicon.ico
--------------------------------------------------------------------------------
/examples/nextjs/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "workspace",
3 | "private": "true",
4 | "workspaces": [
5 | "packages/*",
6 | "examples/*"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/packages/css-out-js/babel.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const nodePath = require('path');
3 | const { types } = require('@babel/core');
4 | const { declare } = require('@babel/helper-plugin-utils');
5 | const { default: hash } = require('@emotion/hash');
6 | const { string: toString } = require('to-style');
7 |
8 | const prefix = 'css';
9 | let ensuredOutFileExists = false;
10 |
11 | /** ignore, dev mode debugger */
12 | const log = (message = '') => {
13 | fs.appendFileSync(nodePath.join(__dirname, 'debug.log'), JSON.stringify(message, null, 2) + '\n', 'utf8');
14 | };
15 |
16 | const cssMap = new Map();
17 |
18 | const appendCSS = ({ outFilePath, className, stringifiedStyles }) => {
19 | // already inserted
20 | if (cssMap.get(className) === stringifiedStyles) return;
21 |
22 | cssMap.set(className, stringifiedStyles);
23 |
24 | let contents = `/*
25 | This file is generated by css-out-js ✨
26 | Don't edit this file directly :)
27 | */
28 | `;
29 | cssMap.forEach((value, key) => {
30 | contents += `
31 | .${key} {
32 | ${value}
33 | }`;
34 | });
35 |
36 | // flush
37 | fs.writeFileSync(outFilePath, contents);
38 | };
39 |
40 | module.exports = declare((api) => {
41 | api.assertVersion(7);
42 |
43 | const visitor = {
44 | Program: {
45 | enter(path, state) {
46 | const outFilePath = state.opts.path
47 | ? nodePath.join(state.cwd, state.opts.path)
48 | : nodePath.join(__dirname, 'out.css'); // should this be state.cwd and not __dirname?
49 |
50 | state.file.set('outFilePath', outFilePath);
51 | if (state.opts.addImport) state.file.set('moduleSpecifier', outFilePath);
52 |
53 | // reset outfile
54 | if (!ensuredOutFileExists) {
55 | fs.writeFileSync(outFilePath, '');
56 | ensuredOutFileExists = true;
57 | }
58 | }
59 | },
60 | CallExpression(path, state) {
61 | const { callee, arguments: args } = path.node;
62 |
63 | if (callee.name !== 'css') return;
64 |
65 | const [arg] = args;
66 |
67 | // if a className string passed, replace expression with className
68 | if (arg.type === 'Identifier') path.replaceWith(arg);
69 |
70 | if (arg.type !== 'ObjectExpression') return;
71 |
72 | const styles = {};
73 |
74 | arg.properties.forEach((property) => {
75 | if (['NumericLiteral', 'StringLiteral'].includes(property.value.type)) {
76 | styles[property.key.name] = property.value.value;
77 | } else if (types.isExpression(property.value) || types.isIdentifier(property.value)) {
78 | const rawCode = state.file.code.slice(property.value.start, property.value.end);
79 |
80 | // we wan't an unique custom variable that is
81 | const uniq = hash(state.filename + rawCode);
82 | const cssVarName = '--' + rawCode.replace(/\./g, '-').toLowerCase() + '-' + uniq; // example: props-color-ioan3s
83 |
84 | styles[property.key.name] = `var(${cssVarName})`;
85 |
86 | if (path.container.type === 'JSXExpressionContainer') {
87 | setCSSVarInStyle(path, cssVarName, property);
88 | } else {
89 | injectRuntimeExpressionToSetCSSVar(path, cssVarName, property);
90 | }
91 | }
92 | });
93 |
94 | const stringifiedStyles = toString(styles);
95 |
96 | /**
97 | * Add component name to the className for better naming
98 | *
99 | * TODO: for runtime enabled, pass componentName as the second parameter
100 | * in case there is a runtime so that the output matches
101 | */
102 | let componentName;
103 |
104 | const functionParent = path.getFunctionParent();
105 | if (types.isArrowFunctionExpression(functionParent)) componentName = functionParent.parent.id.name;
106 | else if (types.isFunctionDeclaration(functionParent)) componentName = functionParent.node.id.name;
107 |
108 | const className = (componentName || prefix) + '-' + hash(stringifiedStyles);
109 |
110 | /* Write css into outFile */
111 | const outFilePath = state.file.get('outFilePath');
112 | appendCSS({ outFilePath, className, stringifiedStyles });
113 |
114 | /* Replace the function call with the className string */
115 | path.replaceWith(types.stringLiteral(className));
116 | },
117 | ImportDeclaration(path, state) {
118 | if (state.opts.keepRuntime) return;
119 |
120 | if (path.node.source.value === 'css-out-js') {
121 | const moduleSpecifier = state.file.get('moduleSpecifier');
122 | if (moduleSpecifier) {
123 | path.insertAfter(types.importDeclaration([], types.stringLiteral(moduleSpecifier)));
124 | }
125 | path.remove();
126 | }
127 | }
128 | };
129 |
130 | return { name: 'babel-plugin-css-out-js', visitor };
131 | });
132 |
133 | const setCSSVarInStyle = (path, cssVar, property) => {
134 | const jsxOpeningElement = path.findParent((path) => path.isJSXOpeningElement());
135 | const styleAttr = jsxOpeningElement.node.attributes.find((attr) => attr.name.name === 'style');
136 |
137 | const keyVal = types.objectProperty(types.stringLiteral(cssVar), property.value);
138 |
139 | if (styleAttr) {
140 | styleAttr.value.expression.properties.push(keyVal);
141 | } else {
142 | jsxOpeningElement.node.attributes.push(
143 | types.jsxAttribute(types.jsxIdentifier('style'), types.jsxExpressionContainer(types.objectExpression([keyVal])))
144 | );
145 | }
146 | };
147 |
148 | const injectRuntimeExpressionToSetCSSVar = (path, cssVar, property) => {
149 | // TODO: test if (typeof document !== 'undefined')
150 | const test = types.binaryExpression(
151 | '!==',
152 | types.unaryExpression('typeof', types.identifier('document')),
153 | types.stringLiteral('undefined')
154 | );
155 |
156 | // consonent: document.documentElement.style.setProperty('--custom', value);
157 | const consonent = types.expressionStatement(
158 | types.callExpression(
159 | types.memberExpression(
160 | types.memberExpression(
161 | types.memberExpression(types.identifier('document'), types.identifier('documentElement')),
162 | types.identifier('style')
163 | ),
164 | types.identifier('setProperty')
165 | ),
166 | [types.stringLiteral(cssVar), property.value]
167 | )
168 | );
169 |
170 | path.parentPath.parentPath.insertAfter(types.ifStatement(test, consonent));
171 | };
172 |
--------------------------------------------------------------------------------
/packages/css-out-js/index.js:
--------------------------------------------------------------------------------
1 | export const css = () => {};
2 |
--------------------------------------------------------------------------------
/packages/css-out-js/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siddharthkp/css-out-js/3d43e1c84fdc784008e9442e6e0e02391afa4b7a/packages/css-out-js/logo.png
--------------------------------------------------------------------------------
/packages/css-out-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-out-js",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {},
7 | "keywords": [],
8 | "author": "siddharthkp",
9 | "license": "MIT",
10 | "dependencies": {
11 | "@emotion/hash": "^0.8.0",
12 | "to-style": "^1.3.3"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/css-out-js/readme.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Author styles collocated in JS, pull them out into static CSS on build. No runtime dependency.
5 |
6 |
7 |
8 |
9 |
10 | ### the big idea:
11 |
12 | I like to **author styles collocated with component code**, that's how I find them maintainable.
13 |
14 | But before shipping them to users, I'd like to pull styles out of components and ship them in **static CSS files**. No runtime dependency.
15 |
16 | 
17 |
18 | prior art: This idea is not entirely new, there have been other similar interpretations of this idea before like [callstack/linaria](https://github.com/callstack/linaria) and [atlassian-labs/compiled](https://github.com/atlassian-labs/compiled)
19 |
20 |
21 |
22 |
23 |
24 | **input**
25 |
26 | `App.js`
27 |
28 | ```js
29 | import { css } from 'css-out-js';
30 |
31 | const Title = (props) => {
32 | return Hello! ;
33 | };
34 | ```
35 |
36 | **output**
37 |
38 | The plugin extracts the styles into a css file
39 | and replaces the function call with generated className string.
40 | If there are any dynamic styles, it creates a css variable for them.
41 | Finally, it removes the import for `css-out-js`, no runtime.
42 |
43 | `dist.css`
44 |
45 | ```diff
46 | +.Title-1bdbzov {
47 | + font-size: 2em;
48 | +}
49 | ```
50 |
51 | `App.js`
52 |
53 | ```diff
54 | - import { css } from 'css-out-js';
55 |
56 | const Title = (props) => {
57 | - return Hello! ;
58 | + return Hello! ;
59 | };
60 | ```
61 |
62 |
63 |
64 | works with dynamic styles as well:
65 |
66 | **input**
67 |
68 | `App.js`
69 |
70 | ```js
71 | import { css } from 'css-out-js';
72 |
73 | const Home = (props) => {
74 | const className = css({ color: props.color });
75 |
76 | return (
77 |
78 | Home sweet home
79 |
80 | );
81 | };
82 | ```
83 |
84 | **output**
85 |
86 | Dynamic styles based on props are made possible by setting a css variable.
87 |
88 | `dist.css`
89 |
90 | ```diff
91 | +.Home-2b9glr {
92 | + color: var(--props-color-1trdzir);
93 | +}
94 | ```
95 |
96 | `App.js`
97 |
98 | ```diff
99 | - import { css } from 'css-out-js';
100 |
101 | const Home = (props) => {
102 | - const className = css({ color: props.color });
103 | + const className = "Home-2b9glr"
104 | + document.documentElement.style.setProperty('--props-color-1trdzir', props.color),
105 |
106 | return (
107 |
108 | Home sweet home
109 |
110 | );
111 | }
112 | ```
113 |
114 | ### setup
115 |
116 | 1. install
117 |
118 | ```
119 | npm install css-out-js --save-dev
120 |
121 | # or
122 |
123 | yarn add css-out-js --dev
124 | ```
125 |
126 | 2. Add plugin to babelrc, you can specify the location of your generated css file (hint: see [examples](https://github.com/siddharthkp/css-out-js/blob/main/examples) directory)
127 |
128 | ```js
129 | // .babelrc
130 | module.exports = {
131 | plugins: [['css-out-js/babel', { path: 'public/dist.css' }]]
132 | };
133 | ```
134 |
135 | 3. add css import
136 |
137 | this may differ based on the framework of your choice. (hint: see [examples](https://github.com/siddharthkp/css-out-js/blob/main/examples) directory)
138 |
139 | ```js
140 | // App.js
141 | import './dist.css';
142 | ```
143 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | packages/css-out-js/readme.md
--------------------------------------------------------------------------------