├── .editorconfig
├── .gitignore
├── .prettierrc
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── babel.js
├── docs
└── images
│ └── lightwindcss-logo.png
├── example
├── .babelrc.js
├── .gitignore
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── pages
│ ├── _app.tsx
│ ├── api
│ │ └── hello.ts
│ └── index.tsx
├── public
│ ├── favicon.ico
│ └── vercel.svg
├── styles
│ ├── dummy.css
│ └── globals.css
└── tsconfig.json
├── package-lock.json
├── package.json
├── src
├── IDLAttribute.ts
├── ast
│ ├── analyzeStylisElement.ts
│ ├── findCssReferences.ts
│ └── parseCss.ts
├── babel
│ └── index.ts
├── cli
│ ├── analyze
│ │ ├── context.ts
│ │ ├── generate.ts
│ │ ├── generateClassnames.ts
│ │ ├── index.ts
│ │ ├── parseFile.ts
│ │ └── references.ts
│ ├── generate
│ │ └── index.ts
│ └── index.ts
├── convertCSSStringToStyle.ts
├── index.ts
├── random.ts
└── util
│ ├── Graph
│ └── index.ts
│ ├── Splitter
│ └── index.ts
│ ├── iter
│ ├── iterFlatMap.ts
│ ├── iterJoin.ts
│ └── iterMap.ts
│ └── updateMap.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_size = 2
5 | indent_style = space
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /dist
3 |
4 | lightwindcss.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.organizeImports": true
4 | },
5 | "typescript.tsdk": "node_modules/typescript/lib"
6 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 uhyo
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 |
2 |
3 | ## What?
4 |
5 | As you know, [Tailwind CSS](https://tailwindcss.com/) is great. Particularly, it has succeeded to cut off “C” of CSS from modern styling architectures. Also, there is no idea of local styles; all classes are global, which helps reducing total CSS size.
6 |
7 | However, there is one pain point; you have to remember all those Tailwind-specific classes. You still have to spare your brain space for those extra memorization drills in addition to knowledge of plain CSS which is still required to use Tailwind CSS.
8 |
9 | Then, what if you can write plain CSS directly in JSXs and still benefit from the efficiency of global styles?
10 |
11 | Here it is.
12 |
13 | ## How?
14 |
15 | LightwindCSS analyzes all source code and generate one optimized CSS file. Source code is then transformed with our Babel plugin to match the generated CSS file.
16 |
17 | For example, suppose you wrote following React code:
18 |
19 | ```tsx
20 |
27 |
35 | Hello, world!
36 |
37 |
38 | ```
39 |
40 | Then, LightwindCSS generates this CSS file:
41 |
42 | ```css
43 | .a {
44 | display: flex;
45 | justify-content: center;
46 | }
47 | .b {
48 | flex-flow: row nowrap;
49 | }
50 | .c {
51 | flex-flow: column nowrap;
52 | align-items: center;
53 | }
54 | ```
55 |
56 | and the JSX code is transformed into:
57 |
58 | ```tsx
59 |
60 | Hello, world!
61 |
62 | ```
63 |
64 | Here, one or more declarations correspond to one classname, similarly to how TailwindCSS maintains one-to-one relations between classnames and concrete styles.
65 |
66 | ## Installation
67 |
68 | ```sh
69 | npm i -D lightwindcss
70 | ```
71 |
72 | ## Usage
73 |
74 | Wrap your plain CSS string with `` css` ... ` `` to get classNames for it.
75 |
76 | ```tsx
77 | import { css } from "lightwindcss";
78 |
79 |
89 | Hello, world!
90 |
;
91 | ```
92 |
93 | During development, this works without extra configuration.
94 |
95 | ### Production Build
96 |
97 | For production, Lightwind CSS can analyze all source code and generate one optimized CSS file.
98 |
99 | First, analyze source code and generate `lightwindcss.json` by the following command:
100 |
101 | ```sh
102 | lightwindcss analyze src/
103 | ```
104 |
105 | Then, generate CSS file by:
106 |
107 | ```sh
108 | lightwindcss generate
109 | ```
110 |
111 | The `lightwindcss.json` file is also used to convert the `` css`...` `` call to optimized classnames. To this end, a babel plugin `ligutwindcss/babel` needs to be enabled in production builds:
112 |
113 | ```js
114 | // .babelrc.js
115 | module.exports = (api) => ({
116 | plugins: api.env("production")
117 | ? [
118 | [
119 | "lightwindcss/babel",
120 | {
121 | analysisFile: "./lightwindcss.json",
122 | },
123 | ],
124 | ]
125 | : [],
126 | });
127 | ```
128 |
129 | ## Examples
130 |
131 | See a Next.js example in `examples/`.
132 |
133 | ## Contributing
134 |
135 | Welcome
136 |
137 | ## License
138 |
139 | MIT
140 |
--------------------------------------------------------------------------------
/babel.js:
--------------------------------------------------------------------------------
1 | // lightwindcss/babel
2 | module.exports = require('./dist/babel/index.js').default;
--------------------------------------------------------------------------------
/docs/images/lightwindcss-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uhyo/lightwindcss/1440e91b86a5fe31a784e5adbbfa3ee0fd2bc97d/docs/images/lightwindcss-logo.png
--------------------------------------------------------------------------------
/example/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = (api) => ({
2 | "presets": ["next/babel"],
3 | "plugins": api.env("production") ? [
4 | ["lightwindcss/babel", {
5 | analysisFile: "./lightwindcss.json"
6 | }]
7 | ] : []
8 | });
--------------------------------------------------------------------------------
/example/.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 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | # lightwindcss
37 | /styles/lightwind.css
38 |
--------------------------------------------------------------------------------
/example/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/import?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 |
--------------------------------------------------------------------------------
/example/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
--------------------------------------------------------------------------------
/example/next.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | module.exports = {
4 | webpack: (config, { dev, isServer }) => {
5 | if (!dev) {
6 | return config;
7 | }
8 | // disable styles/lightwind.css in dev
9 | config.resolve.alias[
10 | path.join(__dirname, "styles/lightwind.css")
11 | ] = path.join(__dirname, "styles/dummy.css")
12 | return config
13 | },
14 | }
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "lightwindcss analyze pages/ && lightwindcss generate -o styles/lightwind.css && next build",
8 | "start": "next start"
9 | },
10 | "dependencies": {
11 | "lightwindcss": "file:..",
12 | "next": "10.0.5",
13 | "react": "17.0.1",
14 | "react-dom": "17.0.1"
15 | },
16 | "devDependencies": {
17 | "@types/node": "^14.14.22",
18 | "@types/react": "^17.0.0",
19 | "null-loader": "^4.0.1"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/example/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { AppProps } from "next/dist/next-server/lib/router/router";
2 | import "../styles/globals.css";
3 | import "../styles/lightwind.css";
4 |
5 | function MyApp({ Component, pageProps }: AppProps) {
6 | return ;
7 | }
8 |
9 | export default MyApp;
10 |
--------------------------------------------------------------------------------
/example/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | import { NextApiRequest, NextApiResponse } from "next";
4 |
5 | export default (req: NextApiRequest, res: NextApiResponse) => {
6 | res.statusCode = 200;
7 | res.json({ name: "John Doe" });
8 | };
9 |
--------------------------------------------------------------------------------
/example/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import { css } from "lightwindcss";
2 | import Head from "next/head";
3 |
4 | export default function Home() {
5 | return (
6 |
16 |
17 |
Create Next App
18 |
19 |
20 |
21 |
31 | & {
39 | color: hi;
40 | }
41 | `}
42 | >
43 | Welcome to{" "}
44 |
57 | Next.js!
58 |
59 |
60 |
61 |
68 | Get started by editing{" "}
69 |
80 | pages/index.js
81 |
82 |
83 |
84 |
133 |
134 |
135 |
166 |
167 | );
168 | }
169 |
170 | const card = css`
171 | margin: 1rem;
172 | flex-basis: 45%;
173 | padding: 1.5rem;
174 | text-align: left;
175 | color: inherit;
176 | text-decoration: none;
177 | border: 1px solid #eaeaea;
178 | border-radius: 10px;
179 | transition: color 0.15s ease, border-color 0.15s ease;
180 |
181 | &:hover,
182 | &:focus,
183 | &:active {
184 | color: #0070f3;
185 | border-color: #0070f3;
186 | }
187 | `;
188 |
189 | const cardTitle = css`
190 | margin: 0 0 1rem 0;
191 | font-size: 1.5rem;
192 | `;
193 |
194 | const cardDesc = css`
195 | margin: 0;
196 | font-size: 1.25rem;
197 | line-height: 1.5;
198 | `;
199 |
--------------------------------------------------------------------------------
/example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uhyo/lightwindcss/1440e91b86a5fe31a784e5adbbfa3ee0fd2bc97d/example/public/favicon.ico
--------------------------------------------------------------------------------
/example/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/example/styles/dummy.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/uhyo/lightwindcss/1440e91b86a5fe31a784e5adbbfa3ee0fd2bc97d/example/styles/dummy.css
--------------------------------------------------------------------------------
/example/styles/globals.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
7 | }
8 |
9 | a {
10 | color: inherit;
11 | text-decoration: none;
12 | }
13 |
14 | * {
15 | box-sizing: border-box;
16 | }
17 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "skipLibCheck": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "noEmit": true,
13 | "esModuleInterop": true,
14 | "module": "esnext",
15 | "moduleResolution": "node",
16 | "resolveJsonModule": true,
17 | "isolatedModules": true,
18 | "jsx": "preserve",
19 | "allowJs": false
20 | },
21 | "include": [
22 | "next-env.d.ts",
23 | "**/*.ts",
24 | "**/*.tsx"
25 | ],
26 | "exclude": [
27 | "node_modules"
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lightwindcss",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@babel/code-frame": {
8 | "version": "7.12.11",
9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
10 | "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
11 | "requires": {
12 | "@babel/highlight": "^7.10.4"
13 | }
14 | },
15 | "@babel/generator": {
16 | "version": "7.12.11",
17 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz",
18 | "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==",
19 | "requires": {
20 | "@babel/types": "^7.12.11",
21 | "jsesc": "^2.5.1",
22 | "source-map": "^0.5.0"
23 | }
24 | },
25 | "@babel/helper-function-name": {
26 | "version": "7.12.11",
27 | "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz",
28 | "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==",
29 | "requires": {
30 | "@babel/helper-get-function-arity": "^7.12.10",
31 | "@babel/template": "^7.12.7",
32 | "@babel/types": "^7.12.11"
33 | }
34 | },
35 | "@babel/helper-get-function-arity": {
36 | "version": "7.12.10",
37 | "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz",
38 | "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==",
39 | "requires": {
40 | "@babel/types": "^7.12.10"
41 | }
42 | },
43 | "@babel/helper-split-export-declaration": {
44 | "version": "7.12.11",
45 | "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz",
46 | "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==",
47 | "requires": {
48 | "@babel/types": "^7.12.11"
49 | }
50 | },
51 | "@babel/helper-validator-identifier": {
52 | "version": "7.12.11",
53 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
54 | "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="
55 | },
56 | "@babel/highlight": {
57 | "version": "7.10.4",
58 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
59 | "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
60 | "requires": {
61 | "@babel/helper-validator-identifier": "^7.10.4",
62 | "chalk": "^2.0.0",
63 | "js-tokens": "^4.0.0"
64 | }
65 | },
66 | "@babel/parser": {
67 | "version": "7.12.11",
68 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz",
69 | "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg=="
70 | },
71 | "@babel/template": {
72 | "version": "7.12.7",
73 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz",
74 | "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==",
75 | "requires": {
76 | "@babel/code-frame": "^7.10.4",
77 | "@babel/parser": "^7.12.7",
78 | "@babel/types": "^7.12.7"
79 | }
80 | },
81 | "@babel/traverse": {
82 | "version": "7.12.12",
83 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz",
84 | "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==",
85 | "requires": {
86 | "@babel/code-frame": "^7.12.11",
87 | "@babel/generator": "^7.12.11",
88 | "@babel/helper-function-name": "^7.12.11",
89 | "@babel/helper-split-export-declaration": "^7.12.11",
90 | "@babel/parser": "^7.12.11",
91 | "@babel/types": "^7.12.12",
92 | "debug": "^4.1.0",
93 | "globals": "^11.1.0",
94 | "lodash": "^4.17.19"
95 | }
96 | },
97 | "@babel/types": {
98 | "version": "7.12.12",
99 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz",
100 | "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==",
101 | "requires": {
102 | "@babel/helper-validator-identifier": "^7.12.11",
103 | "lodash": "^4.17.19",
104 | "to-fast-properties": "^2.0.0"
105 | }
106 | },
107 | "@nodelib/fs.scandir": {
108 | "version": "2.1.4",
109 | "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
110 | "integrity": "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA==",
111 | "requires": {
112 | "@nodelib/fs.stat": "2.0.4",
113 | "run-parallel": "^1.1.9"
114 | }
115 | },
116 | "@nodelib/fs.stat": {
117 | "version": "2.0.4",
118 | "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz",
119 | "integrity": "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q=="
120 | },
121 | "@nodelib/fs.walk": {
122 | "version": "1.2.6",
123 | "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz",
124 | "integrity": "sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow==",
125 | "requires": {
126 | "@nodelib/fs.scandir": "2.1.4",
127 | "fastq": "^1.6.0"
128 | }
129 | },
130 | "@types/babel__traverse": {
131 | "version": "7.11.0",
132 | "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz",
133 | "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==",
134 | "dev": true,
135 | "requires": {
136 | "@babel/types": "^7.3.0"
137 | }
138 | },
139 | "@types/md5": {
140 | "version": "2.2.1",
141 | "resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.2.1.tgz",
142 | "integrity": "sha512-bZB0jqBL7JETFqvRKyuDETFceFaVcLm2MBPP5LFEEL/SZuqLnyvzF37tXmMERDncC3oeEj/fOUw88ftJeMpZaw==",
143 | "dev": true,
144 | "requires": {
145 | "@types/node": "*"
146 | }
147 | },
148 | "@types/node": {
149 | "version": "14.14.22",
150 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz",
151 | "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==",
152 | "dev": true
153 | },
154 | "@types/prop-types": {
155 | "version": "15.7.3",
156 | "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.3.tgz",
157 | "integrity": "sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==",
158 | "dev": true
159 | },
160 | "@types/react": {
161 | "version": "17.0.0",
162 | "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz",
163 | "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==",
164 | "dev": true,
165 | "requires": {
166 | "@types/prop-types": "*",
167 | "csstype": "^3.0.2"
168 | }
169 | },
170 | "@types/stylis": {
171 | "version": "4.0.0",
172 | "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.0.0.tgz",
173 | "integrity": "sha512-DB1wPXVDfTTyLO9tr4wTeAptinTGd+EemFDDJTdCfsLedYXuF1mRWpJtU74Rucqx7N7HecBmMwEERbPpLt1tGA==",
174 | "dev": true
175 | },
176 | "@types/yargs": {
177 | "version": "15.0.12",
178 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz",
179 | "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==",
180 | "dev": true,
181 | "requires": {
182 | "@types/yargs-parser": "*"
183 | }
184 | },
185 | "@types/yargs-parser": {
186 | "version": "20.2.0",
187 | "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz",
188 | "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==",
189 | "dev": true
190 | },
191 | "ansi-regex": {
192 | "version": "5.0.0",
193 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
194 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
195 | },
196 | "ansi-styles": {
197 | "version": "3.2.1",
198 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
199 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
200 | "requires": {
201 | "color-convert": "^1.9.0"
202 | }
203 | },
204 | "braces": {
205 | "version": "3.0.2",
206 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
207 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
208 | "requires": {
209 | "fill-range": "^7.0.1"
210 | }
211 | },
212 | "chalk": {
213 | "version": "2.4.2",
214 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
215 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
216 | "requires": {
217 | "ansi-styles": "^3.2.1",
218 | "escape-string-regexp": "^1.0.5",
219 | "supports-color": "^5.3.0"
220 | }
221 | },
222 | "charenc": {
223 | "version": "0.0.2",
224 | "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
225 | "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
226 | },
227 | "cliui": {
228 | "version": "7.0.4",
229 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
230 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
231 | "requires": {
232 | "string-width": "^4.2.0",
233 | "strip-ansi": "^6.0.0",
234 | "wrap-ansi": "^7.0.0"
235 | }
236 | },
237 | "color-convert": {
238 | "version": "1.9.3",
239 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
240 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
241 | "requires": {
242 | "color-name": "1.1.3"
243 | }
244 | },
245 | "color-name": {
246 | "version": "1.1.3",
247 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
248 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
249 | },
250 | "crypt": {
251 | "version": "0.0.2",
252 | "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
253 | "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
254 | },
255 | "csstype": {
256 | "version": "3.0.6",
257 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.6.tgz",
258 | "integrity": "sha512-+ZAmfyWMT7TiIlzdqJgjMb7S4f1beorDbWbsocyK4RaiqA5RTX3K14bnBWmmA9QEM0gRdsjyyrEmcyga8Zsxmw==",
259 | "dev": true
260 | },
261 | "debug": {
262 | "version": "4.3.1",
263 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
264 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
265 | "requires": {
266 | "ms": "2.1.2"
267 | }
268 | },
269 | "emoji-regex": {
270 | "version": "8.0.0",
271 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
272 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
273 | },
274 | "escalade": {
275 | "version": "3.1.1",
276 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
277 | "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
278 | },
279 | "escape-string-regexp": {
280 | "version": "1.0.5",
281 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
282 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
283 | },
284 | "fast-glob": {
285 | "version": "3.2.5",
286 | "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz",
287 | "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==",
288 | "requires": {
289 | "@nodelib/fs.stat": "^2.0.2",
290 | "@nodelib/fs.walk": "^1.2.3",
291 | "glob-parent": "^5.1.0",
292 | "merge2": "^1.3.0",
293 | "micromatch": "^4.0.2",
294 | "picomatch": "^2.2.1"
295 | }
296 | },
297 | "fastq": {
298 | "version": "1.10.0",
299 | "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz",
300 | "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==",
301 | "requires": {
302 | "reusify": "^1.0.4"
303 | }
304 | },
305 | "fill-range": {
306 | "version": "7.0.1",
307 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
308 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
309 | "requires": {
310 | "to-regex-range": "^5.0.1"
311 | }
312 | },
313 | "get-caller-file": {
314 | "version": "2.0.5",
315 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
316 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
317 | },
318 | "glob-parent": {
319 | "version": "5.1.1",
320 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
321 | "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
322 | "requires": {
323 | "is-glob": "^4.0.1"
324 | }
325 | },
326 | "globals": {
327 | "version": "11.12.0",
328 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
329 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
330 | },
331 | "has-flag": {
332 | "version": "3.0.0",
333 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
334 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
335 | },
336 | "is-buffer": {
337 | "version": "1.1.6",
338 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
339 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
340 | },
341 | "is-extglob": {
342 | "version": "2.1.1",
343 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
344 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
345 | },
346 | "is-fullwidth-code-point": {
347 | "version": "3.0.0",
348 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
349 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
350 | },
351 | "is-glob": {
352 | "version": "4.0.1",
353 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
354 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
355 | "requires": {
356 | "is-extglob": "^2.1.1"
357 | }
358 | },
359 | "is-number": {
360 | "version": "7.0.0",
361 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
362 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
363 | },
364 | "js-tokens": {
365 | "version": "4.0.0",
366 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
367 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
368 | },
369 | "jsesc": {
370 | "version": "2.5.2",
371 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
372 | "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
373 | },
374 | "lodash": {
375 | "version": "4.17.20",
376 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz",
377 | "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA=="
378 | },
379 | "md5": {
380 | "version": "2.3.0",
381 | "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz",
382 | "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==",
383 | "requires": {
384 | "charenc": "0.0.2",
385 | "crypt": "0.0.2",
386 | "is-buffer": "~1.1.6"
387 | }
388 | },
389 | "merge2": {
390 | "version": "1.4.1",
391 | "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
392 | "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
393 | },
394 | "micromatch": {
395 | "version": "4.0.2",
396 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
397 | "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
398 | "requires": {
399 | "braces": "^3.0.1",
400 | "picomatch": "^2.0.5"
401 | }
402 | },
403 | "ms": {
404 | "version": "2.1.2",
405 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
406 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
407 | },
408 | "picomatch": {
409 | "version": "2.2.2",
410 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
411 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg=="
412 | },
413 | "prettier": {
414 | "version": "2.2.1",
415 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
416 | "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
417 | "dev": true
418 | },
419 | "require-directory": {
420 | "version": "2.1.1",
421 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
422 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
423 | },
424 | "reusify": {
425 | "version": "1.0.4",
426 | "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
427 | "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
428 | },
429 | "run-parallel": {
430 | "version": "1.1.10",
431 | "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz",
432 | "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw=="
433 | },
434 | "source-map": {
435 | "version": "0.5.7",
436 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
437 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
438 | },
439 | "string-width": {
440 | "version": "4.2.0",
441 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
442 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
443 | "requires": {
444 | "emoji-regex": "^8.0.0",
445 | "is-fullwidth-code-point": "^3.0.0",
446 | "strip-ansi": "^6.0.0"
447 | }
448 | },
449 | "strip-ansi": {
450 | "version": "6.0.0",
451 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
452 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
453 | "requires": {
454 | "ansi-regex": "^5.0.0"
455 | }
456 | },
457 | "stylis": {
458 | "version": "4.0.6",
459 | "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.6.tgz",
460 | "integrity": "sha512-1igcUEmYFBEO14uQHAJhCUelTR5jPztfdVKrYxRnDa5D5Dn3w0NxXupJNPr/VV/yRfZYEAco8sTIRZzH3sRYKg=="
461 | },
462 | "supports-color": {
463 | "version": "5.5.0",
464 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
465 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
466 | "requires": {
467 | "has-flag": "^3.0.0"
468 | }
469 | },
470 | "to-fast-properties": {
471 | "version": "2.0.0",
472 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
473 | "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4="
474 | },
475 | "to-regex-range": {
476 | "version": "5.0.1",
477 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
478 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
479 | "requires": {
480 | "is-number": "^7.0.0"
481 | }
482 | },
483 | "typescript": {
484 | "version": "4.2.0-beta",
485 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.0-beta.tgz",
486 | "integrity": "sha512-0nYjpkQ6bKjHoJTQkUHcDtGLNenqc5rfTcl3ISUnJXPkGa0115FcVJABmodfMKHLyDBmzDr8IGLbDv5m7sbYgw==",
487 | "dev": true
488 | },
489 | "wrap-ansi": {
490 | "version": "7.0.0",
491 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
492 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
493 | "requires": {
494 | "ansi-styles": "^4.0.0",
495 | "string-width": "^4.1.0",
496 | "strip-ansi": "^6.0.0"
497 | },
498 | "dependencies": {
499 | "ansi-styles": {
500 | "version": "4.3.0",
501 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
502 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
503 | "requires": {
504 | "color-convert": "^2.0.1"
505 | }
506 | },
507 | "color-convert": {
508 | "version": "2.0.1",
509 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
510 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
511 | "requires": {
512 | "color-name": "~1.1.4"
513 | }
514 | },
515 | "color-name": {
516 | "version": "1.1.4",
517 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
518 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
519 | }
520 | }
521 | },
522 | "y18n": {
523 | "version": "5.0.5",
524 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.5.tgz",
525 | "integrity": "sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg=="
526 | },
527 | "yargs": {
528 | "version": "16.2.0",
529 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
530 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
531 | "requires": {
532 | "cliui": "^7.0.2",
533 | "escalade": "^3.1.1",
534 | "get-caller-file": "^2.0.5",
535 | "require-directory": "^2.1.1",
536 | "string-width": "^4.2.0",
537 | "y18n": "^5.0.5",
538 | "yargs-parser": "^20.2.2"
539 | }
540 | },
541 | "yargs-parser": {
542 | "version": "20.2.4",
543 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
544 | "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA=="
545 | }
546 | }
547 | }
548 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lightwindcss",
3 | "version": "0.1.0",
4 | "description": "Write light CSS in the right way.",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "sideEffects": false,
8 | "bin": "dist/cli/index.js",
9 | "scripts": {
10 | "build": "tsc",
11 | "watch": "tsc --watch",
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "keywords": [],
15 | "author": "uhyo ",
16 | "license": "MIT",
17 | "files": [
18 | "dist",
19 | "src",
20 | "babel.js"
21 | ],
22 | "devDependencies": {
23 | "@types/babel__traverse": "^7.11.0",
24 | "@types/md5": "^2.2.1",
25 | "@types/react": "^17.0.0",
26 | "@types/stylis": "^4.0.0",
27 | "@types/yargs": "^15.0.12",
28 | "prettier": "^2.2.1",
29 | "typescript": "^4.2.0-beta"
30 | },
31 | "dependencies": {
32 | "@babel/parser": "^7.12.11",
33 | "@babel/traverse": "^7.12.12",
34 | "@babel/types": "^7.12.12",
35 | "fast-glob": "^3.2.5",
36 | "md5": "^2.3.0",
37 | "stylis": "^4.0.6",
38 | "yargs": "^16.2.0"
39 | }
40 | }
--------------------------------------------------------------------------------
/src/IDLAttribute.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
3 | */
4 | export function cssPropertyToIDLAttribute(property: string): string {
5 | return property.replace(/-([a-z])/g, (_, char) => char.toUpperCase());
6 | }
7 |
--------------------------------------------------------------------------------
/src/ast/analyzeStylisElement.ts:
--------------------------------------------------------------------------------
1 | import { Element } from "stylis";
2 | import { RULESET_DELIMITER } from "../cli/analyze/context";
3 |
4 | /**
5 | * Analyze given element.
6 | */
7 | export function* analyzeStylisElement(
8 | element: Element,
9 | ruleContext: string = ""
10 | ): Generator<[string, string, string], void, void> {
11 | // TODO: stylis typing is not very good
12 | switch (element.type) {
13 | case "decl": {
14 | // prop: value;
15 | yield [ruleContext, String(element.props), String(element.children)];
16 | break;
17 | }
18 | case "rule": {
19 | // selector { ... }
20 | const nextRuleContext = `${ruleContext}${RULESET_DELIMITER}${element.value}`;
21 | for (const c of element.children as Element[]) {
22 | yield* analyzeStylisElement(c, nextRuleContext);
23 | }
24 | break;
25 | }
26 | case "comm": {
27 | // ignore comments
28 | break;
29 | }
30 | default: {
31 | // @media and else
32 | // @media (...) { ... }
33 | const nextRuleContext = `${ruleContext}${RULESET_DELIMITER}${element.value}`;
34 | for (const c of element.children as Element[]) {
35 | yield* analyzeStylisElement(c, nextRuleContext);
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/ast/findCssReferences.ts:
--------------------------------------------------------------------------------
1 | import { NodePath } from "@babel/traverse";
2 | import * as types from "@babel/types";
3 | import { ImportDeclaration, Node } from "@babel/types";
4 |
5 | export function findCssReferences(
6 | path: NodePath,
7 | t: typeof types
8 | ): NodePath[] {
9 | if (path.node.source.value !== "lightwindcss") {
10 | return [];
11 | }
12 | // found a lightwindcss import
13 | // Look for 'css' import
14 | const csses = path.node.specifiers.flatMap((spec) => {
15 | if (!t.isImportSpecifier(spec)) {
16 | return [];
17 | }
18 | const importedName = spec.imported;
19 | const isCss = t.isStringLiteral(importedName)
20 | ? importedName.value === "css"
21 | : importedName.name === "css";
22 | if (!isCss) {
23 | return [];
24 | }
25 | // Found css.
26 | return [spec.local];
27 | });
28 | // List all references to css.
29 | const references = csses.flatMap((id) => {
30 | const binding = path.scope.getBinding(id.name);
31 | return binding?.referencePaths || [];
32 | });
33 | return references;
34 | }
35 |
--------------------------------------------------------------------------------
/src/ast/parseCss.ts:
--------------------------------------------------------------------------------
1 | import { Node, NodePath } from "@babel/traverse";
2 | import { compile, Element } from "stylis";
3 |
4 | export type ParseCssResult = {
5 | ast: Element[];
6 | replacePath: NodePath;
7 | };
8 |
9 | /**
10 | * Convert 'css' reference to a parsed Stylis AST.
11 | */
12 | export function parseCss(
13 | reference: NodePath
14 | ): ParseCssResult | undefined {
15 | // Support css`...` calls
16 | if (!reference.isIdentifier()) {
17 | return;
18 | }
19 | const { parentPath } = reference;
20 | if (!parentPath.isTaggedTemplateExpression()) {
21 | return;
22 | }
23 | if (parentPath.node.tag !== reference.node) {
24 | return;
25 | }
26 | // Currently we only support no hole template literals
27 | const cssStr = String(parentPath.node.quasi.quasis[0]?.value.cooked);
28 |
29 | const ast = compile(cssStr);
30 | return {
31 | ast,
32 | replacePath: parentPath,
33 | };
34 | }
35 |
--------------------------------------------------------------------------------
/src/babel/index.ts:
--------------------------------------------------------------------------------
1 | import { Visitor } from "@babel/traverse";
2 | import type * as types from "@babel/types";
3 | import { readFileSync } from "fs";
4 | import path from "path";
5 | import { analyzeStylisElement } from "../ast/analyzeStylisElement";
6 | import { findCssReferences } from "../ast/findCssReferences";
7 | import { parseCss } from "../ast/parseCss";
8 | import { AnalyzeResult } from "../cli/analyze";
9 | import { CONTEXT_DELIMITER } from "../cli/analyze/context";
10 | import { iterJoin } from "../util/iter/iterJoin";
11 |
12 | type PluginThis = {
13 | opts: {
14 | /**
15 | * File to load.
16 | */
17 | analysisFile?: string;
18 | };
19 | data: AnalyzeResult;
20 | };
21 |
22 | type PluginInput = {
23 | types: typeof types;
24 | };
25 | type PluginResult = {
26 | pre(this: PluginThis): void;
27 | visitor: Visitor;
28 | };
29 |
30 | const plugin = function (babel: PluginInput): PluginResult {
31 | const types = babel.types;
32 |
33 | const data = new Map();
34 |
35 | return {
36 | pre() {
37 | const analyzeFile = path.resolve(
38 | this.opts.analysisFile || "./lightwindcss.json"
39 | );
40 | if (data.has(analyzeFile)) {
41 | this.data = data.get(analyzeFile)!;
42 | } else {
43 | data.set(
44 | analyzeFile,
45 | (this.data = JSON.parse(readFileSync(analyzeFile, "utf8")))
46 | );
47 | }
48 | },
49 | visitor: {
50 | ImportDeclaration(path) {
51 | // console.log(state);
52 | const references = findCssReferences(path, types);
53 | for (const ref of references) {
54 | const res = parseCss(ref);
55 | if (!res) {
56 | continue;
57 | }
58 | const classNames = new Set();
59 | for (const c of res.ast) {
60 | for (const k of analyzeStylisElement(c)) {
61 | const key = k.join(CONTEXT_DELIMITER);
62 | const className = this.data.declarationMap[key];
63 | if (!className) {
64 | throw path.buildCodeFrameError(
65 | "Could not find className for this."
66 | );
67 | }
68 | classNames.add(className);
69 | }
70 | }
71 | res.replacePath.replaceWith(
72 | types.stringLiteral(iterJoin(classNames, " "))
73 | );
74 | }
75 | },
76 | },
77 | };
78 | };
79 |
80 | export default plugin;
81 |
--------------------------------------------------------------------------------
/src/cli/analyze/context.ts:
--------------------------------------------------------------------------------
1 | import { Splitter } from "../../util/Splitter";
2 |
3 | export const CONTEXT_DELIMITER = "::::";
4 |
5 | export const RULESET_DELIMITER = "$$$$";
6 |
7 | // context:prop:value
8 | type NodeKey = `${string}${typeof CONTEXT_DELIMITER}${string}${typeof CONTEXT_DELIMITER}${string}`;
9 |
10 | export type AnalyzeContext = {
11 | nodes: Splitter<
12 | NodeKey,
13 | {
14 | count: number;
15 | }
16 | >;
17 | };
18 |
--------------------------------------------------------------------------------
/src/cli/analyze/generate.ts:
--------------------------------------------------------------------------------
1 | import { AnalyzeContext, CONTEXT_DELIMITER } from "./context";
2 | import { generateClassnames } from "./generateClassnames";
3 |
4 | export type GenerateResult = {
5 | /**
6 | * Map from rule context to classnames.
7 | */
8 | ruleContextMap: Record;
9 | /**
10 | * Map from classname and ruleContext to declaration.
11 | */
12 | classnameMap: Record>;
13 | /**
14 | * Map from declaration to classname.
15 | */
16 | declarationMap: Record;
17 | };
18 |
19 | /**
20 | * Generate analysis result.
21 | */
22 | export function generate(context: AnalyzeContext): GenerateResult {
23 | const entries = Array.from(context.nodes.entries());
24 | // sort desc by occurences
25 | entries.sort((a, b) => b.payload.count - a.payload.count);
26 |
27 | // assign classnames
28 | const classnameGenerator = generateClassnames();
29 | const ruleContextMap: Record = Object.create(null);
30 | const classnameMap: Record> = Object.create(
31 | null
32 | );
33 | const declarationMap: Record = Object.create(null);
34 |
35 | for (const node of entries) {
36 | const assignedClassnameRes = classnameGenerator.next();
37 | if (assignedClassnameRes.done) {
38 | // unreachable
39 | throw new Error("Cannot assign more classnames");
40 | }
41 | const assignedClassname = assignedClassnameRes.value;
42 | const declPerContext: Record = {};
43 | classnameMap[assignedClassname] = declPerContext;
44 | for (const key of node.keys) {
45 | const [ruleContext, decl, value] = key.split(CONTEXT_DELIMITER) as [
46 | string,
47 | string,
48 | string
49 | ];
50 | const a = ruleContextMap[ruleContext];
51 | if (a) {
52 | a.push(assignedClassname);
53 | } else {
54 | ruleContextMap[ruleContext] = [assignedClassname];
55 | }
56 | declPerContext[ruleContext] =
57 | (declPerContext[ruleContext] || "") + `${decl}:${value};`;
58 | declarationMap[key] = assignedClassname;
59 | }
60 | }
61 |
62 | return {
63 | ruleContextMap,
64 | classnameMap,
65 | declarationMap,
66 | };
67 | }
68 |
--------------------------------------------------------------------------------
/src/cli/analyze/generateClassnames.ts:
--------------------------------------------------------------------------------
1 | // character ranges, avoiding unused code points
2 | // https://ja.wikipedia.org/wiki/Unicode%E4%B8%80%E8%A6%A7_0000-0FFF
3 | const defaultCharRanges: readonly [number, number][] = [
4 | // ascii a-z
5 | [0x61, 0x7a],
6 | // non-ascii chars
7 | [0xa1, 0x377],
8 | [0x3a3, 0x52f],
9 | [0x61f, 0x6ff],
10 | ];
11 |
12 | /**
13 | * Generate valid classnames.
14 | * Earlier names have shorter length.
15 | */
16 | export function* generateClassnames(
17 | prefix = "",
18 | charRanges = defaultCharRanges
19 | ) {
20 | for (let length = 1; ; length++) {
21 | yield* generateClassnamesOfLength(prefix, charRanges, length);
22 | }
23 | }
24 |
25 | export function* generateClassnamesOfLength(
26 | prefix: string,
27 | charRanges: readonly [number, number][],
28 | length: number
29 | ): Generator {
30 | if (length === 1) {
31 | yield* classnameChar(charRanges, prefix);
32 | return;
33 | }
34 | for (const c of classnameChar(charRanges, "")) {
35 | yield* generateClassnamesOfLength(prefix + c, charRanges, length - 1);
36 | }
37 | }
38 |
39 | function* classnameChar(
40 | charRanges: readonly [number, number][],
41 | prefix: string
42 | ) {
43 | for (const [start, end] of charRanges) {
44 | for (let char = start; char <= end; char++)
45 | yield prefix + String.fromCharCode(char);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/cli/analyze/index.ts:
--------------------------------------------------------------------------------
1 | import fastGlob from "fast-glob";
2 | import { writeFile } from "fs/promises";
3 | import path from "path";
4 | import { Splitter } from "../../util/Splitter";
5 | import { AnalyzeContext } from "./context";
6 | import { generate, GenerateResult } from "./generate";
7 | import { parseFile } from "./parseFile";
8 |
9 | type Options = {
10 | srcDir: string;
11 | out: string;
12 | };
13 |
14 | export type AnalyzeResult = GenerateResult;
15 |
16 | export async function analyze({ srcDir, out }: Options) {
17 | const context: AnalyzeContext = {
18 | nodes: new Splitter({
19 | createPayload: () => ({ count: 1 }),
20 | splitPayload: ({ count }) => [
21 | {
22 | count: count + 1,
23 | },
24 | { count },
25 | ],
26 | }),
27 | };
28 | const cwd = path.resolve(srcDir);
29 | for await (const file of fastGlob.stream("**/*.{js,jsx,ts,tsx}", {
30 | cwd,
31 | ignore: ["node_modules"],
32 | })) {
33 | const filePath = path.join(cwd, String(file));
34 | await parseFile(filePath, context);
35 | }
36 | const result = generate(context);
37 | await writeFile(path.resolve(out), JSON.stringify(result));
38 | }
39 |
--------------------------------------------------------------------------------
/src/cli/analyze/parseFile.ts:
--------------------------------------------------------------------------------
1 | import { parse } from "@babel/parser";
2 | import traverse from "@babel/traverse";
3 | import * as types from "@babel/types";
4 | import { readFile } from "fs/promises";
5 | import { findCssReferences } from "../../ast/findCssReferences";
6 | import { AnalyzeContext } from "./context";
7 | import { analyzeReference } from "./references";
8 |
9 | export async function parseFile(filePath: string, context: AnalyzeContext) {
10 | const parsed = parse(await readFile(filePath, "utf8"), {
11 | strictMode: true,
12 | sourceType: "module",
13 | plugins: ["jsx", "typescript"],
14 | });
15 |
16 | traverse(parsed, {
17 | ImportDeclaration(path) {
18 | const references = findCssReferences(path, types);
19 | for (const ref of references) {
20 | analyzeReference(ref, context);
21 | }
22 | },
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/src/cli/analyze/references.ts:
--------------------------------------------------------------------------------
1 | import { Node, NodePath } from "@babel/traverse";
2 | import { analyzeStylisElement } from "../../ast/analyzeStylisElement";
3 | import { parseCss } from "../../ast/parseCss";
4 | import { iterFlatMap } from "../../util/iter/iterFlatMap";
5 | import { iterMap } from "../../util/iter/iterMap";
6 | import { AnalyzeContext, CONTEXT_DELIMITER } from "./context";
7 |
8 | export function analyzeReference(
9 | reference: NodePath,
10 | context: AnalyzeContext
11 | ) {
12 | const res = parseCss(reference);
13 | if (!res) {
14 | return;
15 | }
16 |
17 | context.nodes.apply(
18 | Array.from(
19 | iterFlatMap(res.ast, (elm) =>
20 | iterMap(
21 | analyzeStylisElement(elm),
22 | ([ruleContext, prop, value]) =>
23 | `${ruleContext}${CONTEXT_DELIMITER}${prop}${CONTEXT_DELIMITER}${value}`
24 | )
25 | )
26 | )
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/src/cli/generate/index.ts:
--------------------------------------------------------------------------------
1 | import { readFile, writeFile } from "fs/promises";
2 | import path from "path";
3 | import { AnalyzeResult } from "../analyze";
4 | import { RULESET_DELIMITER } from "../analyze/context";
5 |
6 | type Options = {
7 | input: string;
8 | out: string;
9 | };
10 |
11 | export async function generate({ input, out }: Options) {
12 | const file = await readFile(path.resolve(input), "utf8");
13 | const analysisResult: AnalyzeResult = JSON.parse(file);
14 | const { ruleContextMap, classnameMap } = analysisResult;
15 |
16 | let result = "";
17 |
18 | for (const [ruleContext, classes] of Object.entries(ruleContextMap)) {
19 | const rulesets = ruleContext
20 | .split(RULESET_DELIMITER)
21 | .filter((x) => x !== "");
22 | const wrapper = convertRulesetsToWrapper(rulesets);
23 | for (const c of classes) {
24 | result += wrapper(c, classnameMap[c]?.[ruleContext] || "");
25 | }
26 | }
27 | await writeFile(path.resolve(out), result);
28 | }
29 |
30 | function convertRulesetsToWrapper(
31 | rulesets: string[]
32 | ): (c: string, v: string) => string {
33 | let prefix = "";
34 | let selector = "`.${c}`";
35 | let suffix = "";
36 | for (const r of rulesets) {
37 | if (r.startsWith("@")) {
38 | // at-rule
39 | prefix += escape(r) + "{";
40 | suffix = "}" + suffix;
41 | } else {
42 | // not at-rule
43 | selector = `\`${
44 | // &\f is marked by stylis
45 | r.replace(/&\f/g, "${" + selector + "}")
46 | }\``;
47 | }
48 | }
49 |
50 | return new Function(
51 | "c",
52 | "v",
53 | `return \`${prefix}\${${selector}}{\${v}}${suffix}\``
54 | ) as (c: string, v: string) => string;
55 |
56 | function escape(str: string) {
57 | return str.replace(/`/g, "\\`");
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/cli/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import yargs from "yargs/yargs";
4 | import { analyze } from "./analyze";
5 | import { generate } from "./generate";
6 |
7 | const args = yargs(process.argv.slice(2))
8 | .scriptName("lightwindcss")
9 | .command<{
10 | dir: string;
11 | input: string;
12 | out: string;
13 | }>("analyze [dir]", "analyze given directory", (yargs) => {
14 | yargs
15 | .positional("dir", {
16 | description: "Directory to analyze",
17 | default: "./",
18 | })
19 | .option("out", {
20 | alias: "o",
21 | description: "File to write analysis result",
22 | type: "string",
23 | default: "./lightwindcss.json",
24 | });
25 | })
26 | .command("generate [input]", "generate CSS file", (yargs) => {
27 | yargs
28 | .positional("input", {
29 | description: "Analyzed JSON file",
30 | default: "./lightwindcss.json",
31 | })
32 | .option("out", {
33 | alias: "o",
34 | description: "File to write generated CSS",
35 | type: "string",
36 | default: "./lightwind.css",
37 | });
38 | })
39 | .demandCommand(1)
40 | .help()
41 | .strict().argv;
42 |
43 | (async () => {
44 | switch (args._[0]) {
45 | case "analyze": {
46 | await analyze({
47 | srcDir: args.dir,
48 | out: args.out,
49 | });
50 | break;
51 | }
52 | case "generate": {
53 | await generate({
54 | input: args.input,
55 | out: args.out,
56 | });
57 | break;
58 | }
59 | }
60 | })().catch((err) => {
61 | console.error(err);
62 | process.exit(1);
63 | });
64 |
--------------------------------------------------------------------------------
/src/convertCSSStringToStyle.ts:
--------------------------------------------------------------------------------
1 | import md5 from "md5";
2 | import { compile, serialize, stringify } from "stylis";
3 |
4 | type Result = {
5 | style?: HTMLStyleElement;
6 | className: string;
7 | };
8 |
9 | export function convertCSSStringToStyle(str: string): Result {
10 | const className = `c-${md5(str)}`;
11 | if (typeof document === "undefined") {
12 | return {
13 | className,
14 | };
15 | }
16 |
17 | const existingStyle = document.querySelector(
18 | `style[data-lightwind="${className}"]`
19 | );
20 | if (existingStyle) {
21 | return {
22 | style: existingStyle as HTMLStyleElement,
23 | className,
24 | };
25 | }
26 | const compiled = serialize(compile(`.${className}{${str}}`), stringify);
27 |
28 | const style = document.createElement("style");
29 | style.textContent = compiled;
30 | style.nonce = document.currentScript?.nonce;
31 | style.dataset.lightwind = className;
32 | document.head.appendChild(style);
33 |
34 | return {
35 | style,
36 | className,
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { convertCSSStringToStyle } from "./convertCSSStringToStyle";
2 |
3 | const cache = new WeakMap();
4 | const registry =
5 | typeof FinalizationRegistry !== "undefined"
6 | ? new FinalizationRegistry((heldValue: HTMLStyleElement) => {
7 | heldValue.remove();
8 | })
9 | : undefined;
10 |
11 | /**
12 | * Convert given CSS string to a style object.
13 | */
14 | export function css(
15 | arr: TemplateStringsArray,
16 | ..._args: readonly never[]
17 | ): string {
18 | const c = cache.get(arr);
19 | if (c) {
20 | return c;
21 | }
22 |
23 | const { style, className } = convertCSSStringToStyle(arr[0] || "");
24 | cache.set(arr, className);
25 |
26 | if (style && registry) {
27 | registry.register(arr, style);
28 | }
29 | return className;
30 | }
31 |
--------------------------------------------------------------------------------
/src/random.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Create random classname.
3 | */
4 | export function createRandomClassname(): string {
5 | return `c-${random16bytes()}${random16bytes()}-${random16bytes()}-${random16bytes()}-${random16bytes()}-${random16bytes()}${random16bytes()}${random16bytes()}`;
6 | }
7 |
8 | function random16bytes(): string {
9 | return String((Math.random() * 65536) | 0);
10 | }
11 |
--------------------------------------------------------------------------------
/src/util/Graph/index.ts:
--------------------------------------------------------------------------------
1 | import { updateMap } from "../updateMap";
2 |
3 | export type GraphNode = {
4 | key: Key;
5 | payload: Payload;
6 | adjacents: Map;
7 | };
8 |
9 | /**
10 | * Indirect graph.
11 | * Allows multiple edges between a single pair of edges.
12 | */
13 | export class Graph {
14 | readonly nodeMap = new Map>();
15 | readonly createDefault: (key: Key) => Payload;
16 |
17 | constructor(createDefault: (key: Key) => Payload) {
18 | this.createDefault = createDefault;
19 | }
20 |
21 | /**
22 | * Update key of given node.
23 | */
24 | public upsert(key: Key, updater: (payload: Payload) => void) {
25 | const node = this.nodeMap.get(key);
26 | if (node !== undefined) {
27 | updater(node.payload);
28 | } else {
29 | const newNode: GraphNode = {
30 | key,
31 | payload: this.createDefault(key),
32 | adjacents: new Map(),
33 | };
34 | updater(newNode.payload);
35 | this.nodeMap.set(key, newNode);
36 | }
37 | }
38 |
39 | /**
40 | * Connect two edges.
41 | * Nodes must have already been inserted to this graph.
42 | */
43 | public connect(key1: Key, key2: Key): void {
44 | const node1 = this.nodeMap.get(key1);
45 | if (!node1) {
46 | throw new Error(`Node for '${key1}' is not found`);
47 | }
48 | const node2 = this.nodeMap.get(key2);
49 | if (!node2) {
50 | throw new Error(`Node for '${key2}' is not found`);
51 | }
52 | updateMap(node1.adjacents, key2, 0, (c) => c + 1);
53 | updateMap(node2.adjacents, key1, 0, (c) => c + 1);
54 | }
55 |
56 | /**
57 | * Returns an iterator of all nodes.
58 | */
59 | public entries(): IterableIterator<[Key, GraphNode]> {
60 | return this.nodeMap.entries();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/util/Splitter/index.ts:
--------------------------------------------------------------------------------
1 | import { updateMap } from "../updateMap";
2 |
3 | type SplitterNode = {
4 | keys: Set;
5 | payload: Payload;
6 | };
7 |
8 | type SplitterOptions = {
9 | createPayload: (keys: ReadonlySet) => Payload;
10 | splitPayload: (
11 | payload: Payload,
12 | keys1: ReadonlySet,
13 | keys2: ReadonlySet
14 | ) => [yesPayload: Payload, noPayload: Payload];
15 | };
16 |
17 | export class Splitter {
18 | private readonly nodes = new Set>();
19 | private readonly keyToNode = new Map>();
20 |
21 | private readonly createPayload: (keys: ReadonlySet) => Payload;
22 | private readonly splitPayload: (
23 | payload: Payload,
24 | keys1: ReadonlySet,
25 | keys2: ReadonlySet
26 | ) => [Payload, Payload];
27 |
28 | constructor(options: SplitterOptions) {
29 | this.createPayload = options.createPayload;
30 | this.splitPayload = options.splitPayload;
31 | }
32 |
33 | public apply(keys: readonly Key[]) {
34 | // sort keys by belonging nodes.
35 | const newKeys = new Set();
36 | const nodeMap = new Map, Key[]>();
37 | for (const key of keys) {
38 | const node = this.keyToNode.get(key);
39 | if (node === undefined) {
40 | newKeys.add(key);
41 | } else {
42 | updateMap(nodeMap, node, [], (c) => (c.push(key), c));
43 | }
44 | }
45 |
46 | // keys that has not been recognized yet are treated as a set for new node.
47 | if (newKeys.size > 0) {
48 | this.add(newKeys, this.createPayload(newKeys));
49 | }
50 |
51 | // Existing keys may split existing set.
52 | for (const [node, keys] of nodeMap) {
53 | const [yes, no] = filterBySet(node.keys, new Set(keys));
54 | if (no.length === 0) {
55 | // not split
56 | continue;
57 | }
58 | // split the node into two
59 | const keys1 = new Set(yes);
60 | const keys2 = new Set(no);
61 | const [payload1, payload2] = this.splitPayload(
62 | node.payload,
63 | keys1,
64 | keys2
65 | );
66 | this.nodes.delete(node);
67 | this.add(keys1, payload1);
68 | this.add(keys2, payload2);
69 | }
70 | }
71 |
72 | private add(keys: Set, payload: Payload) {
73 | const node: SplitterNode = { keys, payload };
74 | this.nodes.add(node);
75 | for (const key of keys) {
76 | this.keyToNode.set(key, node);
77 | }
78 | }
79 |
80 | public entries(): IterableIterator> {
81 | return this.nodes.values();
82 | }
83 | }
84 |
85 | /**
86 | * Filter given array by whether it belongs to given set.
87 | */
88 | function filterBySet(values: Iterable, set: ReadonlySet): [T[], T[]] {
89 | const yes = [];
90 | const no = [];
91 | for (const v of values) {
92 | if (set.has(v)) {
93 | yes.push(v);
94 | } else {
95 | no.push(v);
96 | }
97 | }
98 | return [yes, no];
99 | }
100 |
--------------------------------------------------------------------------------
/src/util/iter/iterFlatMap.ts:
--------------------------------------------------------------------------------
1 | export function* iterFlatMap(
2 | iter: Iterable,
3 | mapper: (elm: T) => Iterable
4 | ): Generator {
5 | for (const v of iter) {
6 | yield* mapper(v);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/util/iter/iterJoin.ts:
--------------------------------------------------------------------------------
1 | export function iterJoin(iter: Iterable, delimiter: string): string {
2 | let result = "";
3 | let first = true;
4 | for (const v of iter) {
5 | if (!first) {
6 | result += delimiter;
7 | }
8 | first = false;
9 | result += v;
10 | }
11 | return result;
12 | }
13 |
--------------------------------------------------------------------------------
/src/util/iter/iterMap.ts:
--------------------------------------------------------------------------------
1 | export function* iterMap(
2 | iter: Iterable,
3 | mapper: (elm: T) => U
4 | ): Generator {
5 | for (const v of iter) {
6 | yield mapper(v);
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/util/updateMap.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Update Map value using given function.
3 | */
4 | export function updateMap(
5 | map: Map,
6 | key: K,
7 | defaultValue: V,
8 | update: (value: V) => V
9 | ) {
10 | map.set(key, update(map.has(key) ? (map.get(key) as V) : defaultValue));
11 | }
12 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | "lib": ["esnext", "dom"], /* Specify library files to be included in the compilation. */
10 | // "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
13 | "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | "outDir": "./dist/", /* Redirect output structure to the directory. */
18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
44 |
45 | /* Module Resolution Options */
46 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
47 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
48 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
49 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
50 | // "typeRoots": [], /* List of folders to include type definitions from. */
51 | // "types": [], /* Type declaration files to be included in compilation. */
52 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
53 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
54 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
55 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
56 |
57 | /* Source Map Options */
58 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
60 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
61 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
62 |
63 | /* Experimental Options */
64 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
65 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
66 |
67 | /* Advanced Options */
68 | "skipLibCheck": true, /* Skip type checking of declaration files. */
69 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
70 | },
71 | "include": ["./src/**/*.ts"]
72 | }
73 |
--------------------------------------------------------------------------------