├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json ├── pnpm-lock.yaml ├── screenshots ├── bare.png ├── changed.png ├── cli.png ├── deduplication.png ├── info.png ├── life-cycle-events.png ├── markdown.png ├── message.gif ├── return-value.png └── trace.png ├── src ├── development.ts └── production.ts ├── test └── basic.test.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shu Ding 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 | # `useTilg` 2 | 3 | **Tiny Logger** is a magical React Hook to help you debug your components. 4 | 5 | You can quickly try out the [**demo**](https://codesandbox.io/s/usetilg-3kdtz8?file=/src/App.js:274-359). 6 | 7 |
8 | 9 | ## Table of Contents 10 | 11 | - [Installation](#installation) 12 | - [Features](#features) 13 | - [Lifecycle Events (What)](#1-lifecycle-events-what) 14 | - [Component Name and Props (Who)](#2-component-name-and-props-who) 15 | - [Debug Message (Why)](#3-debug-message-why) 16 | - [What Has Changed? (Why)](#4-what-has-changed-why) 17 | - [Quick Logs (Why)](#5-quick-logs-why) 18 | - [Advanced Features](#advanced-features) 19 | - [Markdown](#markdown) 20 | - [Return Original Value](#return-original-value) 21 | - [Auto Deduplication](#auto-deduplication) 22 | - [CLI Support](#cli-support) 23 | - [FAQ & Caveats](#faq--caveats) 24 | 25 |
26 | 27 | ## Installation 28 | 29 | The package is released as `tilg`, use: 30 | 31 | ```sh 32 | npm i tilg 33 | ``` 34 | 35 | to install it with npm. Or you can choose another package manager. 36 | 37 |
38 | 39 | ## Features 40 | 41 | ### 1. Lifecycle Events (What) 42 | 43 | Simply insert the `useTilg()` hook into the component, and it will log the **render**, **mount**, **unmount** events in the console: 44 | 45 | ```jsx 46 | import useTilg from 'tilg' 47 | 48 | function MyButton() { 49 | useTilg() 50 | return 51 | } 52 | ``` 53 | 54 |

55 | lifecycle event logs 56 |
57 | Logs of render and mount events. 58 |

59 | 60 | 61 | ### 2. Component Name and Props (Who) 62 | 63 | You might noticed that it also displays the **name** and **props** of the component, which is very helpful for debugging. 64 | 65 | ```jsx 66 | import useTilg from 'tilg' 67 | 68 | function MyButton({ text }) { 69 | useTilg() 70 | return 71 | } 72 | 73 | function Title({ children }) { 74 | useTilg() 75 | return

{children}

76 | } 77 | 78 | export default function Page() { 79 | return ( 80 | <> 81 | Welcome! 82 | 83 | 84 | 85 | ) 86 | } 87 | ``` 88 | 89 | When there’re multiple elements of the same component being rendered, it adds a counter ` (2)` for distinguishing so you know **who** is logging the information: 90 | 91 |

92 | information logs 93 |
94 | Information of the logged components. 95 |

96 | 97 | 98 | ### 3. Debug Message (Why) 99 | 100 | Another critical thing is to know why does a component re-renders. `useTilg` gives you a simple but powerful API for this: 101 | 102 | ```jsx 103 | import { useState } from 'react' 104 | import useTilg from 'tilg' 105 | 106 | function Counter() { 107 | const [count, setCount] = useState(0) 108 | 109 | useTilg()`count = ${count}` 110 | 111 | return 112 | } 113 | ``` 114 | 115 | When appending a [template literal](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) to the `useTilg()` call, it will also be displayed as the debug message: 116 | 117 | ```jsx 118 | useTilg()`count = ${count}` 119 | ``` 120 | 121 |

122 | debug message 123 |
124 | Logs of “count = ?”. 125 |

126 | 127 | You can know where the message is from, too: 128 | 129 |

130 | trace 131 |
132 | Trace of the message and a link to the code location. 133 |

134 | 135 | ### 4. What Has Changed? (Why) 136 | 137 | Something troubles me a lot when debugging a component is, it’s sometimes hard to know which state has changed and triggered a re-render. `useTilg` tracks all the arguments in the debug message and tells you **which one has changed since the previous render**: 138 | 139 | ```jsx 140 | import { useState } from 'react' 141 | import useTilg from 'tilg' 142 | 143 | function MyApp() { 144 | const [input, setInput] = useState('') 145 | const [count, setCount] = useState(0) 146 | 147 | useTilg()`input = ${input}, count = ${count}` 148 | 149 | return ( 150 | <> 151 | setInput(e.target.value)} value={input} /> 152 | 153 | 154 | ) 155 | } 156 | ``` 157 | 158 |

159 | changed argument 160 |
161 | A hint for the updated part. 162 |

163 | 164 | 165 | ### 5. Quick Logs (Why) 166 | 167 | If you don't need a debug message but only want to quickly log some values, just pass them to the hook directly: 168 | 169 | ```jsx 170 | import { useState } from 'react' 171 | import useTilg from 'tilg' 172 | 173 | function MyApp() { 174 | const [input, setInput] = useState('') 175 | const [count, setCount] = useState(0) 176 | 177 | useTilg(input, count) 178 | 179 | return ( 180 | <> 181 | setInput(e.target.value)} value={input} /> 182 | 183 | 184 | ) 185 | } 186 | ``` 187 | 188 |

189 | value without message 190 |
191 | Debug values quickly. 192 |

193 | 194 | 195 |
196 | 197 | ## Advanced Features 198 | 199 | ### Markdown 200 | 201 | You can use Markdown's code (`` ` ``), italic (`_` or `*`), and bold (`__` or `**`) syntax in your debug message to make it look nicer: 202 | 203 | ```jsx 204 | function MyApp() { 205 | const [count, setCount] = useState(0) 206 | 207 | useTilg()`**Debug**: \`count\` = _${count}_.` 208 | 209 | return 210 | } 211 | ``` 212 | 213 |

214 | markdown syntax 215 |
216 | Markdown syntax in log messages. 217 |

218 | 219 | 220 | ### Return Original Value 221 | 222 | The `useTilg()` hook also returns its **first argument**, or the **first value** in the template if specified, so you can quickly debug something in-place by wrapping it with `useTilg()`: 223 | 224 | ```diff 225 | function MyApp() { 226 | const [count, setCount] = useState(0) 227 | 228 | return 231 | } 232 | ``` 233 | 234 | 235 |

236 | return original value 237 |
238 | Log and return the original value. 239 |

240 | 241 | 242 | ### Auto Deduplication 243 | 244 | Even if you have multiple `useTilg()` hooks in the same component, the lifecycle events will only be logged once per component: 245 | 246 | ```jsx 247 | function MyApp() { 248 | const [input, setInput] = useState('') 249 | const [count, setCount] = useState(0) 250 | 251 | useTilg() 252 | useTilg()`input = ${input}` 253 | useTilg()`count = ${count}` 254 | 255 | return ( 256 | <> 257 | setInput(e.target.value)} value={input} /> 258 | 259 | 260 | ) 261 | } 262 | ``` 263 | 264 |

265 | deduplication 266 |
267 | Render, mount, and unmount events will not be duplicated even if you have multiple useTilg() hooks. 268 |

269 | 270 | ### CLI Support 271 | 272 | If you are running your component during SSR, or running server-side tests, `useTilg()` properly outputs the result in Node.js CLI too: 273 | 274 | ```jsx 275 | function App() { 276 | const [count, setCount] = useState(42) 277 | 278 | useTilg()`The answer is ${{ answer: count }}` 279 | 280 | return 281 | } 282 | ``` 283 | 284 |

285 | deduplication 286 |
287 | Node.js CLI output. 288 |

289 | 290 |
291 | 292 | ## FAQ & Caveats 293 | 294 | - **Is it safe to ship code with `useTilg` to production?** 295 | Although `useTilg()` does nothing in a production build (`process.env.NODE_ENV === 'production'`) but only an empty function, I encourge you to remove the hook from the source code after finishing the development of your app. 296 | 297 | - **How do you implement this hook? What can I learn from the code?** 298 | It is very hacky. Don't do the same thing especially try it in production, or [you will be fired](https://github.com/facebook/react/blob/0568c0f8cde4ac6657dff9a5a8a7112acc35a748/packages/react/index.js#L35). 299 | 300 | - **Why not design the API as `` useTilg`message` ``?** 301 | Then it will not be identified as a hook, React Refresh and HMR will not work correctly. 302 | 303 |
304 | 305 | ## License 306 | 307 | The MIT License (MIT). 308 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./dist/production.js') 3 | } else { 4 | module.exports = require('./dist/development.js') 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tilg", 3 | "version": "0.1.1", 4 | "description": "A tiny logger hook for debugging React components.", 5 | "keywords": [ 6 | "react", 7 | "react hook", 8 | "logger", 9 | "debug" 10 | ], 11 | "main": "index.js", 12 | "types": "dist/development.d.ts", 13 | "files": [ 14 | "LICENSE", 15 | "README.md", 16 | "dist/**" 17 | ], 18 | "scripts": { 19 | "build": "pnpm build:dev && pnpm build:prod", 20 | "build:dev": "tsup src/development.ts --dts --env.NODE_ENV development", 21 | "build:prod": "tsup src/production.ts --env.NODE_ENV production", 22 | "coverage": "vitest run --coverage", 23 | "test": "vitest" 24 | }, 25 | "repository": { 26 | "type": "git", 27 | "url": "git+https://github.com/shuding/tilg.git" 28 | }, 29 | "author": "Shu Ding", 30 | "license": "MIT", 31 | "bugs": { 32 | "url": "https://github.com/shuding/tilg/issues" 33 | }, 34 | "homepage": "https://github.com/shuding/tilg#readme", 35 | "devDependencies": { 36 | "@types/node": "^17.0.23", 37 | "@types/react": "^18.0.0", 38 | "@types/react-test-renderer": "^17.0.1", 39 | "react": "^18.0.0", 40 | "react-test-renderer": "^18.0.0", 41 | "tsup": "^5.12.4", 42 | "typescript": "^4.6.3", 43 | "vitest": "^0.9.2" 44 | }, 45 | "peerDependencies": { 46 | "react": "^18.0.0 || ^17.0.0" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.3 2 | 3 | specifiers: 4 | '@types/node': ^17.0.23 5 | '@types/react': ^18.0.0 6 | '@types/react-test-renderer': ^17.0.1 7 | react: ^18.0.0 8 | react-test-renderer: ^18.0.0 9 | tsup: ^5.12.4 10 | typescript: ^4.6.3 11 | vitest: ^0.9.2 12 | 13 | devDependencies: 14 | '@types/node': 17.0.23 15 | '@types/react': 18.0.0 16 | '@types/react-test-renderer': 17.0.1 17 | react: 18.0.0 18 | react-test-renderer: 18.0.0_react@18.0.0 19 | tsup: 5.12.4_typescript@4.6.3 20 | typescript: 4.6.3 21 | vitest: 0.9.2 22 | 23 | packages: 24 | 25 | /@nodelib/fs.scandir/2.1.5: 26 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 27 | engines: {node: '>= 8'} 28 | dependencies: 29 | '@nodelib/fs.stat': 2.0.5 30 | run-parallel: 1.2.0 31 | dev: true 32 | 33 | /@nodelib/fs.stat/2.0.5: 34 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 35 | engines: {node: '>= 8'} 36 | dev: true 37 | 38 | /@nodelib/fs.walk/1.2.8: 39 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 40 | engines: {node: '>= 8'} 41 | dependencies: 42 | '@nodelib/fs.scandir': 2.1.5 43 | fastq: 1.13.0 44 | dev: true 45 | 46 | /@types/chai-subset/1.3.3: 47 | resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} 48 | dependencies: 49 | '@types/chai': 4.3.0 50 | dev: true 51 | 52 | /@types/chai/4.3.0: 53 | resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==} 54 | dev: true 55 | 56 | /@types/node/17.0.23: 57 | resolution: {integrity: sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==} 58 | dev: true 59 | 60 | /@types/prop-types/15.7.5: 61 | resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} 62 | dev: true 63 | 64 | /@types/react-test-renderer/17.0.1: 65 | resolution: {integrity: sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw==} 66 | dependencies: 67 | '@types/react': 18.0.0 68 | dev: true 69 | 70 | /@types/react/18.0.0: 71 | resolution: {integrity: sha512-7+K7zEQYu7NzOwQGLR91KwWXXDzmTFODRVizJyIALf6RfLv2GDpqpknX64pvRVILXCpXi7O/pua8NGk44dLvJw==} 72 | dependencies: 73 | '@types/prop-types': 15.7.5 74 | '@types/scheduler': 0.16.2 75 | csstype: 3.0.11 76 | dev: true 77 | 78 | /@types/scheduler/0.16.2: 79 | resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} 80 | dev: true 81 | 82 | /any-promise/1.3.0: 83 | resolution: {integrity: sha1-q8av7tzqUugJzcA3au0845Y10X8=} 84 | dev: true 85 | 86 | /anymatch/3.1.2: 87 | resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} 88 | engines: {node: '>= 8'} 89 | dependencies: 90 | normalize-path: 3.0.0 91 | picomatch: 2.3.1 92 | dev: true 93 | 94 | /array-union/2.1.0: 95 | resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 96 | engines: {node: '>=8'} 97 | dev: true 98 | 99 | /assertion-error/1.1.0: 100 | resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} 101 | dev: true 102 | 103 | /balanced-match/1.0.2: 104 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 105 | dev: true 106 | 107 | /binary-extensions/2.2.0: 108 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 109 | engines: {node: '>=8'} 110 | dev: true 111 | 112 | /brace-expansion/1.1.11: 113 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 114 | dependencies: 115 | balanced-match: 1.0.2 116 | concat-map: 0.0.1 117 | dev: true 118 | 119 | /braces/3.0.2: 120 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 121 | engines: {node: '>=8'} 122 | dependencies: 123 | fill-range: 7.0.1 124 | dev: true 125 | 126 | /bundle-require/3.0.4_esbuild@0.14.34: 127 | resolution: {integrity: sha512-VXG6epB1yrLAvWVQpl92qF347/UXmncQj7J3U8kZEbdVZ1ZkQyr4hYeL/9RvcE8vVVdp53dY78Fd/3pqfRqI1A==} 128 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 129 | peerDependencies: 130 | esbuild: '>=0.13' 131 | dependencies: 132 | esbuild: 0.14.34 133 | load-tsconfig: 0.2.3 134 | dev: true 135 | 136 | /cac/6.7.12: 137 | resolution: {integrity: sha512-rM7E2ygtMkJqD9c7WnFU6fruFcN3xe4FM5yUmgxhZzIKJk4uHl9U/fhwdajGFQbQuv43FAUo1Fe8gX/oIKDeSA==} 138 | engines: {node: '>=8'} 139 | dev: true 140 | 141 | /chai/4.3.6: 142 | resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} 143 | engines: {node: '>=4'} 144 | dependencies: 145 | assertion-error: 1.1.0 146 | check-error: 1.0.2 147 | deep-eql: 3.0.1 148 | get-func-name: 2.0.0 149 | loupe: 2.3.4 150 | pathval: 1.1.1 151 | type-detect: 4.0.8 152 | dev: true 153 | 154 | /check-error/1.0.2: 155 | resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=} 156 | dev: true 157 | 158 | /chokidar/3.5.3: 159 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 160 | engines: {node: '>= 8.10.0'} 161 | dependencies: 162 | anymatch: 3.1.2 163 | braces: 3.0.2 164 | glob-parent: 5.1.2 165 | is-binary-path: 2.1.0 166 | is-glob: 4.0.3 167 | normalize-path: 3.0.0 168 | readdirp: 3.6.0 169 | optionalDependencies: 170 | fsevents: 2.3.2 171 | dev: true 172 | 173 | /commander/4.1.1: 174 | resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} 175 | engines: {node: '>= 6'} 176 | dev: true 177 | 178 | /concat-map/0.0.1: 179 | resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 180 | dev: true 181 | 182 | /cross-spawn/7.0.3: 183 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 184 | engines: {node: '>= 8'} 185 | dependencies: 186 | path-key: 3.1.1 187 | shebang-command: 2.0.0 188 | which: 2.0.2 189 | dev: true 190 | 191 | /csstype/3.0.11: 192 | resolution: {integrity: sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==} 193 | dev: true 194 | 195 | /debug/4.3.4: 196 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 197 | engines: {node: '>=6.0'} 198 | peerDependencies: 199 | supports-color: '*' 200 | peerDependenciesMeta: 201 | supports-color: 202 | optional: true 203 | dependencies: 204 | ms: 2.1.2 205 | dev: true 206 | 207 | /deep-eql/3.0.1: 208 | resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==} 209 | engines: {node: '>=0.12'} 210 | dependencies: 211 | type-detect: 4.0.8 212 | dev: true 213 | 214 | /dir-glob/3.0.1: 215 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 216 | engines: {node: '>=8'} 217 | dependencies: 218 | path-type: 4.0.0 219 | dev: true 220 | 221 | /esbuild-android-64/0.14.34: 222 | resolution: {integrity: sha512-XfxcfJqmMYsT/LXqrptzFxmaR3GWzXHDLdFNIhm6S00zPaQF1TBBWm+9t0RZ6LRR7iwH57DPjaOeW20vMqI4Yw==} 223 | engines: {node: '>=12'} 224 | cpu: [x64] 225 | os: [android] 226 | requiresBuild: true 227 | dev: true 228 | optional: true 229 | 230 | /esbuild-android-arm64/0.14.34: 231 | resolution: {integrity: sha512-T02+NXTmSRL1Mc6puz+R9CB54rSPICkXKq6+tw8B6vxZFnCPzbJxgwIX4kcluz9p8nYBjF3+lSilTGWb7+Xgew==} 232 | engines: {node: '>=12'} 233 | cpu: [arm64] 234 | os: [android] 235 | requiresBuild: true 236 | dev: true 237 | optional: true 238 | 239 | /esbuild-darwin-64/0.14.34: 240 | resolution: {integrity: sha512-pLRip2Bh4Ng7Bf6AMgCrSp3pPe/qZyf11h5Qo2mOfJqLWzSVjxrXW+CFRJfrOVP7TCnh/gmZSM2AFdCPB72vtw==} 241 | engines: {node: '>=12'} 242 | cpu: [x64] 243 | os: [darwin] 244 | requiresBuild: true 245 | dev: true 246 | optional: true 247 | 248 | /esbuild-darwin-arm64/0.14.34: 249 | resolution: {integrity: sha512-vpidSJEBxx6lf1NWgXC+DCmGqesJuZ5Y8aQVVsaoO4i8tRXbXb0whChRvop/zd3nfNM4dIl5EXAky0knRX5I6w==} 250 | engines: {node: '>=12'} 251 | cpu: [arm64] 252 | os: [darwin] 253 | requiresBuild: true 254 | dev: true 255 | optional: true 256 | 257 | /esbuild-freebsd-64/0.14.34: 258 | resolution: {integrity: sha512-m0HBjePhe0hAQJgtMRMNV9kMgIyV4/qSnzPx42kRMQBcPhgjAq1JRu4Il26czC+9FgpMbFkUktb07f/Lwnc6CA==} 259 | engines: {node: '>=12'} 260 | cpu: [x64] 261 | os: [freebsd] 262 | requiresBuild: true 263 | dev: true 264 | optional: true 265 | 266 | /esbuild-freebsd-arm64/0.14.34: 267 | resolution: {integrity: sha512-cpRc2B94L1KvMPPYB4D6G39jLqpKlD3noAMY4/e86iXXXkhUYJJEtTuyNFTa9JRpWM0xCAp4mxjHjoIiLuoCLA==} 268 | engines: {node: '>=12'} 269 | cpu: [arm64] 270 | os: [freebsd] 271 | requiresBuild: true 272 | dev: true 273 | optional: true 274 | 275 | /esbuild-linux-32/0.14.34: 276 | resolution: {integrity: sha512-8nQaEaoW7MH/K/RlozJa+lE1ejHIr8fuPIHhc513UebRav7HtXgQvxHQ6VZRUkWtep23M6dd7UqhwO1tMOfzQQ==} 277 | engines: {node: '>=12'} 278 | cpu: [ia32] 279 | os: [linux] 280 | requiresBuild: true 281 | dev: true 282 | optional: true 283 | 284 | /esbuild-linux-64/0.14.34: 285 | resolution: {integrity: sha512-Y3of4qQoLLlAgf042MlrY1P+7PnN9zWj8nVtw9XQG5hcLOZLz7IKpU35oeu7n4wvyaZHwvQqDJ93gRLqdJekcQ==} 286 | engines: {node: '>=12'} 287 | cpu: [x64] 288 | os: [linux] 289 | requiresBuild: true 290 | dev: true 291 | optional: true 292 | 293 | /esbuild-linux-arm/0.14.34: 294 | resolution: {integrity: sha512-9lpq1NcJqssAF7alCO6zL3gvBVVt/lKw4oetUM7OgNnRX0OWpB+ZIO9FwCrSj/dMdmgDhPLf+119zB8QxSMmAg==} 295 | engines: {node: '>=12'} 296 | cpu: [arm] 297 | os: [linux] 298 | requiresBuild: true 299 | dev: true 300 | optional: true 301 | 302 | /esbuild-linux-arm64/0.14.34: 303 | resolution: {integrity: sha512-IlWaGtj9ir7+Nrume1DGcyzBDlK8GcnJq0ANKwcI9pVw8tqr+6GD0eqyF9SF1mR8UmAp+odrx1H5NdR2cHdFHA==} 304 | engines: {node: '>=12'} 305 | cpu: [arm64] 306 | os: [linux] 307 | requiresBuild: true 308 | dev: true 309 | optional: true 310 | 311 | /esbuild-linux-mips64le/0.14.34: 312 | resolution: {integrity: sha512-k3or+01Rska1AjUyNjA4buEwB51eyN/xPQAoOx1CjzAQC3l8rpjUDw55kXyL63O/1MUi4ISvtNtl8gLwdyEcxw==} 313 | engines: {node: '>=12'} 314 | cpu: [mips64el] 315 | os: [linux] 316 | requiresBuild: true 317 | dev: true 318 | optional: true 319 | 320 | /esbuild-linux-ppc64le/0.14.34: 321 | resolution: {integrity: sha512-+qxb8M9FfM2CJaVU7GgYpJOHM1ngQOx+/VrtBjb4C8oVqaPcESCeg2anjl+HRZy8VpYc71q/iBYausPPbJ+Keg==} 322 | engines: {node: '>=12'} 323 | cpu: [ppc64] 324 | os: [linux] 325 | requiresBuild: true 326 | dev: true 327 | optional: true 328 | 329 | /esbuild-linux-riscv64/0.14.34: 330 | resolution: {integrity: sha512-Y717ltBdQ5j5sZIHdy1DV9kieo0wMip0dCmVSTceowCPYSn1Cg33Kd6981+F/3b9FDMzNWldZFOBRILViENZSA==} 331 | engines: {node: '>=12'} 332 | cpu: [riscv64] 333 | os: [linux] 334 | requiresBuild: true 335 | dev: true 336 | optional: true 337 | 338 | /esbuild-linux-s390x/0.14.34: 339 | resolution: {integrity: sha512-bDDgYO4LhL4+zPs+WcBkXph+AQoPcQRTv18FzZS0WhjfH8TZx2QqlVPGhmhZ6WidrY+jKthUqO6UhGyIb4MpmA==} 340 | engines: {node: '>=12'} 341 | cpu: [s390x] 342 | os: [linux] 343 | requiresBuild: true 344 | dev: true 345 | optional: true 346 | 347 | /esbuild-netbsd-64/0.14.34: 348 | resolution: {integrity: sha512-cfaFGXdRt0+vHsjNPyF0POM4BVSHPSbhLPe8mppDc7GDDxjIl08mV1Zou14oDWMp/XZMjYN1kWYRSfftiD0vvQ==} 349 | engines: {node: '>=12'} 350 | cpu: [x64] 351 | os: [netbsd] 352 | requiresBuild: true 353 | dev: true 354 | optional: true 355 | 356 | /esbuild-openbsd-64/0.14.34: 357 | resolution: {integrity: sha512-vmy9DxXVnRiI14s8GKuYBtess+EVcDALkbpTqd5jw4XITutIzyB7n4x0Tj5utAkKsgZJB22lLWGekr0ABnSLow==} 358 | engines: {node: '>=12'} 359 | cpu: [x64] 360 | os: [openbsd] 361 | requiresBuild: true 362 | dev: true 363 | optional: true 364 | 365 | /esbuild-sunos-64/0.14.34: 366 | resolution: {integrity: sha512-eNPVatNET1F7tRMhii7goL/eptfxc0ALRjrj9SPFNqp0zmxrehBFD6BaP3R4LjMn6DbMO0jOAnTLFKr8NqcJAA==} 367 | engines: {node: '>=12'} 368 | cpu: [x64] 369 | os: [sunos] 370 | requiresBuild: true 371 | dev: true 372 | optional: true 373 | 374 | /esbuild-windows-32/0.14.34: 375 | resolution: {integrity: sha512-EFhpXyHEcnqWYe2rAHFd8dRw8wkrd9U+9oqcyoEL84GbanAYjiiIjBZsnR8kl0sCQ5w6bLpk7vCEIA2VS32Vcg==} 376 | engines: {node: '>=12'} 377 | cpu: [ia32] 378 | os: [win32] 379 | requiresBuild: true 380 | dev: true 381 | optional: true 382 | 383 | /esbuild-windows-64/0.14.34: 384 | resolution: {integrity: sha512-a8fbl8Ky7PxNEjf1aJmtxdDZj32/hC7S1OcA2ckEpCJRTjiKslI9vAdPpSjrKIWhws4Galpaawy0nB7fjHYf5Q==} 385 | engines: {node: '>=12'} 386 | cpu: [x64] 387 | os: [win32] 388 | requiresBuild: true 389 | dev: true 390 | optional: true 391 | 392 | /esbuild-windows-arm64/0.14.34: 393 | resolution: {integrity: sha512-EYvmKbSa2B3sPnpC28UEu9jBK5atGV4BaVRE7CYGUci2Hlz4AvtV/LML+TcDMT6gBgibnN2gcltWclab3UutMg==} 394 | engines: {node: '>=12'} 395 | cpu: [arm64] 396 | os: [win32] 397 | requiresBuild: true 398 | dev: true 399 | optional: true 400 | 401 | /esbuild/0.14.34: 402 | resolution: {integrity: sha512-QIWdPT/gFF6hCaf4m7kP0cJ+JIuFkdHibI7vVFvu3eJS1HpVmYHWDulyN5WXwbRA0SX/7ZDaJ/1DH8SdY9xOJg==} 403 | engines: {node: '>=12'} 404 | hasBin: true 405 | requiresBuild: true 406 | optionalDependencies: 407 | esbuild-android-64: 0.14.34 408 | esbuild-android-arm64: 0.14.34 409 | esbuild-darwin-64: 0.14.34 410 | esbuild-darwin-arm64: 0.14.34 411 | esbuild-freebsd-64: 0.14.34 412 | esbuild-freebsd-arm64: 0.14.34 413 | esbuild-linux-32: 0.14.34 414 | esbuild-linux-64: 0.14.34 415 | esbuild-linux-arm: 0.14.34 416 | esbuild-linux-arm64: 0.14.34 417 | esbuild-linux-mips64le: 0.14.34 418 | esbuild-linux-ppc64le: 0.14.34 419 | esbuild-linux-riscv64: 0.14.34 420 | esbuild-linux-s390x: 0.14.34 421 | esbuild-netbsd-64: 0.14.34 422 | esbuild-openbsd-64: 0.14.34 423 | esbuild-sunos-64: 0.14.34 424 | esbuild-windows-32: 0.14.34 425 | esbuild-windows-64: 0.14.34 426 | esbuild-windows-arm64: 0.14.34 427 | dev: true 428 | 429 | /execa/5.1.1: 430 | resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 431 | engines: {node: '>=10'} 432 | dependencies: 433 | cross-spawn: 7.0.3 434 | get-stream: 6.0.1 435 | human-signals: 2.1.0 436 | is-stream: 2.0.1 437 | merge-stream: 2.0.0 438 | npm-run-path: 4.0.1 439 | onetime: 5.1.2 440 | signal-exit: 3.0.7 441 | strip-final-newline: 2.0.0 442 | dev: true 443 | 444 | /fast-glob/3.2.11: 445 | resolution: {integrity: sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==} 446 | engines: {node: '>=8.6.0'} 447 | dependencies: 448 | '@nodelib/fs.stat': 2.0.5 449 | '@nodelib/fs.walk': 1.2.8 450 | glob-parent: 5.1.2 451 | merge2: 1.4.1 452 | micromatch: 4.0.5 453 | dev: true 454 | 455 | /fastq/1.13.0: 456 | resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} 457 | dependencies: 458 | reusify: 1.0.4 459 | dev: true 460 | 461 | /fill-range/7.0.1: 462 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 463 | engines: {node: '>=8'} 464 | dependencies: 465 | to-regex-range: 5.0.1 466 | dev: true 467 | 468 | /fs.realpath/1.0.0: 469 | resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=} 470 | dev: true 471 | 472 | /fsevents/2.3.2: 473 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 474 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 475 | os: [darwin] 476 | requiresBuild: true 477 | dev: true 478 | optional: true 479 | 480 | /function-bind/1.1.1: 481 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 482 | dev: true 483 | 484 | /get-func-name/2.0.0: 485 | resolution: {integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=} 486 | dev: true 487 | 488 | /get-stream/6.0.1: 489 | resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} 490 | engines: {node: '>=10'} 491 | dev: true 492 | 493 | /glob-parent/5.1.2: 494 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 495 | engines: {node: '>= 6'} 496 | dependencies: 497 | is-glob: 4.0.3 498 | dev: true 499 | 500 | /glob/7.1.6: 501 | resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} 502 | dependencies: 503 | fs.realpath: 1.0.0 504 | inflight: 1.0.6 505 | inherits: 2.0.4 506 | minimatch: 3.1.2 507 | once: 1.4.0 508 | path-is-absolute: 1.0.1 509 | dev: true 510 | 511 | /globby/11.1.0: 512 | resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 513 | engines: {node: '>=10'} 514 | dependencies: 515 | array-union: 2.1.0 516 | dir-glob: 3.0.1 517 | fast-glob: 3.2.11 518 | ignore: 5.2.0 519 | merge2: 1.4.1 520 | slash: 3.0.0 521 | dev: true 522 | 523 | /has/1.0.3: 524 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 525 | engines: {node: '>= 0.4.0'} 526 | dependencies: 527 | function-bind: 1.1.1 528 | dev: true 529 | 530 | /human-signals/2.1.0: 531 | resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 532 | engines: {node: '>=10.17.0'} 533 | dev: true 534 | 535 | /ignore/5.2.0: 536 | resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} 537 | engines: {node: '>= 4'} 538 | dev: true 539 | 540 | /inflight/1.0.6: 541 | resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=} 542 | dependencies: 543 | once: 1.4.0 544 | wrappy: 1.0.2 545 | dev: true 546 | 547 | /inherits/2.0.4: 548 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 549 | dev: true 550 | 551 | /is-binary-path/2.1.0: 552 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 553 | engines: {node: '>=8'} 554 | dependencies: 555 | binary-extensions: 2.2.0 556 | dev: true 557 | 558 | /is-core-module/2.8.1: 559 | resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==} 560 | dependencies: 561 | has: 1.0.3 562 | dev: true 563 | 564 | /is-extglob/2.1.1: 565 | resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=} 566 | engines: {node: '>=0.10.0'} 567 | dev: true 568 | 569 | /is-glob/4.0.3: 570 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 571 | engines: {node: '>=0.10.0'} 572 | dependencies: 573 | is-extglob: 2.1.1 574 | dev: true 575 | 576 | /is-number/7.0.0: 577 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 578 | engines: {node: '>=0.12.0'} 579 | dev: true 580 | 581 | /is-stream/2.0.1: 582 | resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 583 | engines: {node: '>=8'} 584 | dev: true 585 | 586 | /isexe/2.0.0: 587 | resolution: {integrity: sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=} 588 | dev: true 589 | 590 | /joycon/3.1.1: 591 | resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} 592 | engines: {node: '>=10'} 593 | dev: true 594 | 595 | /js-tokens/4.0.0: 596 | resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 597 | dev: true 598 | 599 | /lilconfig/2.0.5: 600 | resolution: {integrity: sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==} 601 | engines: {node: '>=10'} 602 | dev: true 603 | 604 | /lines-and-columns/1.2.4: 605 | resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} 606 | dev: true 607 | 608 | /load-tsconfig/0.2.3: 609 | resolution: {integrity: sha512-iyT2MXws+dc2Wi6o3grCFtGXpeMvHmJqS27sMPGtV2eUu4PeFnG+33I8BlFK1t1NWMjOpcx9bridn5yxLDX2gQ==} 610 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 611 | dev: true 612 | 613 | /local-pkg/0.4.1: 614 | resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==} 615 | engines: {node: '>=14'} 616 | dev: true 617 | 618 | /loose-envify/1.4.0: 619 | resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 620 | hasBin: true 621 | dependencies: 622 | js-tokens: 4.0.0 623 | dev: true 624 | 625 | /loupe/2.3.4: 626 | resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} 627 | dependencies: 628 | get-func-name: 2.0.0 629 | dev: true 630 | 631 | /merge-stream/2.0.0: 632 | resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 633 | dev: true 634 | 635 | /merge2/1.4.1: 636 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 637 | engines: {node: '>= 8'} 638 | dev: true 639 | 640 | /micromatch/4.0.5: 641 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 642 | engines: {node: '>=8.6'} 643 | dependencies: 644 | braces: 3.0.2 645 | picomatch: 2.3.1 646 | dev: true 647 | 648 | /mimic-fn/2.1.0: 649 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 650 | engines: {node: '>=6'} 651 | dev: true 652 | 653 | /minimatch/3.1.2: 654 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 655 | dependencies: 656 | brace-expansion: 1.1.11 657 | dev: true 658 | 659 | /ms/2.1.2: 660 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 661 | dev: true 662 | 663 | /mz/2.7.0: 664 | resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} 665 | dependencies: 666 | any-promise: 1.3.0 667 | object-assign: 4.1.1 668 | thenify-all: 1.6.0 669 | dev: true 670 | 671 | /nanoid/3.3.2: 672 | resolution: {integrity: sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==} 673 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 674 | hasBin: true 675 | dev: true 676 | 677 | /normalize-path/3.0.0: 678 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 679 | engines: {node: '>=0.10.0'} 680 | dev: true 681 | 682 | /npm-run-path/4.0.1: 683 | resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 684 | engines: {node: '>=8'} 685 | dependencies: 686 | path-key: 3.1.1 687 | dev: true 688 | 689 | /object-assign/4.1.1: 690 | resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=} 691 | engines: {node: '>=0.10.0'} 692 | dev: true 693 | 694 | /once/1.4.0: 695 | resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} 696 | dependencies: 697 | wrappy: 1.0.2 698 | dev: true 699 | 700 | /onetime/5.1.2: 701 | resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 702 | engines: {node: '>=6'} 703 | dependencies: 704 | mimic-fn: 2.1.0 705 | dev: true 706 | 707 | /path-is-absolute/1.0.1: 708 | resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=} 709 | engines: {node: '>=0.10.0'} 710 | dev: true 711 | 712 | /path-key/3.1.1: 713 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 714 | engines: {node: '>=8'} 715 | dev: true 716 | 717 | /path-parse/1.0.7: 718 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 719 | dev: true 720 | 721 | /path-type/4.0.0: 722 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 723 | engines: {node: '>=8'} 724 | dev: true 725 | 726 | /pathval/1.1.1: 727 | resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} 728 | dev: true 729 | 730 | /picocolors/1.0.0: 731 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 732 | dev: true 733 | 734 | /picomatch/2.3.1: 735 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 736 | engines: {node: '>=8.6'} 737 | dev: true 738 | 739 | /pirates/4.0.5: 740 | resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} 741 | engines: {node: '>= 6'} 742 | dev: true 743 | 744 | /postcss-load-config/3.1.4: 745 | resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} 746 | engines: {node: '>= 10'} 747 | peerDependencies: 748 | postcss: '>=8.0.9' 749 | ts-node: '>=9.0.0' 750 | peerDependenciesMeta: 751 | postcss: 752 | optional: true 753 | ts-node: 754 | optional: true 755 | dependencies: 756 | lilconfig: 2.0.5 757 | yaml: 1.10.2 758 | dev: true 759 | 760 | /postcss/8.4.12: 761 | resolution: {integrity: sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==} 762 | engines: {node: ^10 || ^12 || >=14} 763 | dependencies: 764 | nanoid: 3.3.2 765 | picocolors: 1.0.0 766 | source-map-js: 1.0.2 767 | dev: true 768 | 769 | /queue-microtask/1.2.3: 770 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 771 | dev: true 772 | 773 | /react-is/18.0.0: 774 | resolution: {integrity: sha512-yUcBYdBBbo3QiPsgYDcfQcIkGZHfxOaoE6HLSnr1sPzMhdyxusbfKOSUbSd/ocGi32dxcj366PsTj+5oggeKKw==} 775 | dev: true 776 | 777 | /react-shallow-renderer/16.15.0_react@18.0.0: 778 | resolution: {integrity: sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==} 779 | peerDependencies: 780 | react: ^16.0.0 || ^17.0.0 || ^18.0.0 781 | dependencies: 782 | object-assign: 4.1.1 783 | react: 18.0.0 784 | react-is: 18.0.0 785 | dev: true 786 | 787 | /react-test-renderer/18.0.0_react@18.0.0: 788 | resolution: {integrity: sha512-SyZTP/FSkwfiKOZuTZiISzsrC8A80KNlQ8PyyoGoOq+VzMAab6Em1POK/CiX3+XyXG6oiJa1C53zYDbdrJu9fw==} 789 | peerDependencies: 790 | react: ^18.0.0 791 | dependencies: 792 | react: 18.0.0 793 | react-is: 18.0.0 794 | react-shallow-renderer: 16.15.0_react@18.0.0 795 | scheduler: 0.21.0 796 | dev: true 797 | 798 | /react/18.0.0: 799 | resolution: {integrity: sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==} 800 | engines: {node: '>=0.10.0'} 801 | dependencies: 802 | loose-envify: 1.4.0 803 | dev: true 804 | 805 | /readdirp/3.6.0: 806 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 807 | engines: {node: '>=8.10.0'} 808 | dependencies: 809 | picomatch: 2.3.1 810 | dev: true 811 | 812 | /resolve-from/5.0.0: 813 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 814 | engines: {node: '>=8'} 815 | dev: true 816 | 817 | /resolve/1.22.0: 818 | resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} 819 | hasBin: true 820 | dependencies: 821 | is-core-module: 2.8.1 822 | path-parse: 1.0.7 823 | supports-preserve-symlinks-flag: 1.0.0 824 | dev: true 825 | 826 | /reusify/1.0.4: 827 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 828 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 829 | dev: true 830 | 831 | /rollup/2.70.1: 832 | resolution: {integrity: sha512-CRYsI5EuzLbXdxC6RnYhOuRdtz4bhejPMSWjsFLfVM/7w/85n2szZv6yExqUXsBdz5KT8eoubeyDUDjhLHEslA==} 833 | engines: {node: '>=10.0.0'} 834 | hasBin: true 835 | optionalDependencies: 836 | fsevents: 2.3.2 837 | dev: true 838 | 839 | /run-parallel/1.2.0: 840 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 841 | dependencies: 842 | queue-microtask: 1.2.3 843 | dev: true 844 | 845 | /scheduler/0.21.0: 846 | resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==} 847 | dependencies: 848 | loose-envify: 1.4.0 849 | dev: true 850 | 851 | /shebang-command/2.0.0: 852 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 853 | engines: {node: '>=8'} 854 | dependencies: 855 | shebang-regex: 3.0.0 856 | dev: true 857 | 858 | /shebang-regex/3.0.0: 859 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 860 | engines: {node: '>=8'} 861 | dev: true 862 | 863 | /signal-exit/3.0.7: 864 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 865 | dev: true 866 | 867 | /slash/3.0.0: 868 | resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 869 | engines: {node: '>=8'} 870 | dev: true 871 | 872 | /source-map-js/1.0.2: 873 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 874 | engines: {node: '>=0.10.0'} 875 | dev: true 876 | 877 | /source-map/0.7.3: 878 | resolution: {integrity: sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==} 879 | engines: {node: '>= 8'} 880 | dev: true 881 | 882 | /strip-final-newline/2.0.0: 883 | resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 884 | engines: {node: '>=6'} 885 | dev: true 886 | 887 | /sucrase/3.21.0: 888 | resolution: {integrity: sha512-FjAhMJjDcifARI7bZej0Bi1yekjWQHoEvWIXhLPwDhC6O4iZ5PtGb86WV56riW87hzpgB13wwBKO9vKAiWu5VQ==} 889 | engines: {node: '>=8'} 890 | hasBin: true 891 | dependencies: 892 | commander: 4.1.1 893 | glob: 7.1.6 894 | lines-and-columns: 1.2.4 895 | mz: 2.7.0 896 | pirates: 4.0.5 897 | ts-interface-checker: 0.1.13 898 | dev: true 899 | 900 | /supports-preserve-symlinks-flag/1.0.0: 901 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 902 | engines: {node: '>= 0.4'} 903 | dev: true 904 | 905 | /thenify-all/1.6.0: 906 | resolution: {integrity: sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=} 907 | engines: {node: '>=0.8'} 908 | dependencies: 909 | thenify: 3.3.1 910 | dev: true 911 | 912 | /thenify/3.3.1: 913 | resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} 914 | dependencies: 915 | any-promise: 1.3.0 916 | dev: true 917 | 918 | /tinypool/0.1.2: 919 | resolution: {integrity: sha512-fvtYGXoui2RpeMILfkvGIgOVkzJEGediv8UJt7TxdAOY8pnvUkFg/fkvqTfXG9Acc9S17Cnn1S4osDc2164guA==} 920 | engines: {node: '>=14.0.0'} 921 | dev: true 922 | 923 | /tinyspy/0.3.2: 924 | resolution: {integrity: sha512-2+40EP4D3sFYy42UkgkFFB+kiX2Tg3URG/lVvAZFfLxgGpnWl5qQJuBw1gaLttq8UOS+2p3C0WrhJnQigLTT2Q==} 925 | engines: {node: '>=14.0.0'} 926 | dev: true 927 | 928 | /to-regex-range/5.0.1: 929 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 930 | engines: {node: '>=8.0'} 931 | dependencies: 932 | is-number: 7.0.0 933 | dev: true 934 | 935 | /tree-kill/1.2.2: 936 | resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 937 | hasBin: true 938 | dev: true 939 | 940 | /ts-interface-checker/0.1.13: 941 | resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} 942 | dev: true 943 | 944 | /tsup/5.12.4_typescript@4.6.3: 945 | resolution: {integrity: sha512-uUraITfIj2h6rXAdeaVUYrZ2Is9joLFyEGZN5mGAke874JojCizb2MCUcE0wGdcERtyob5mbbFUKkMgal8SlFw==} 946 | hasBin: true 947 | peerDependencies: 948 | typescript: ^4.1.0 949 | peerDependenciesMeta: 950 | typescript: 951 | optional: true 952 | dependencies: 953 | bundle-require: 3.0.4_esbuild@0.14.34 954 | cac: 6.7.12 955 | chokidar: 3.5.3 956 | debug: 4.3.4 957 | esbuild: 0.14.34 958 | execa: 5.1.1 959 | globby: 11.1.0 960 | joycon: 3.1.1 961 | postcss-load-config: 3.1.4 962 | resolve-from: 5.0.0 963 | rollup: 2.70.1 964 | source-map: 0.7.3 965 | sucrase: 3.21.0 966 | tree-kill: 1.2.2 967 | typescript: 4.6.3 968 | transitivePeerDependencies: 969 | - postcss 970 | - supports-color 971 | - ts-node 972 | dev: true 973 | 974 | /type-detect/4.0.8: 975 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} 976 | engines: {node: '>=4'} 977 | dev: true 978 | 979 | /typescript/4.6.3: 980 | resolution: {integrity: sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==} 981 | engines: {node: '>=4.2.0'} 982 | hasBin: true 983 | dev: true 984 | 985 | /vite/2.9.1: 986 | resolution: {integrity: sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ==} 987 | engines: {node: '>=12.2.0'} 988 | hasBin: true 989 | peerDependencies: 990 | less: '*' 991 | sass: '*' 992 | stylus: '*' 993 | peerDependenciesMeta: 994 | less: 995 | optional: true 996 | sass: 997 | optional: true 998 | stylus: 999 | optional: true 1000 | dependencies: 1001 | esbuild: 0.14.34 1002 | postcss: 8.4.12 1003 | resolve: 1.22.0 1004 | rollup: 2.70.1 1005 | optionalDependencies: 1006 | fsevents: 2.3.2 1007 | dev: true 1008 | 1009 | /vitest/0.9.2: 1010 | resolution: {integrity: sha512-XgR42njw350OxBfKD4MK0cNIzgQrhSUKJq9sgbgRR+bD8GonPCyjpFFmPejptaiEyjmQ2FXpEvFHN37b9X2HJA==} 1011 | engines: {node: '>=v14.16.0'} 1012 | hasBin: true 1013 | peerDependencies: 1014 | '@vitest/ui': '*' 1015 | c8: '*' 1016 | happy-dom: '*' 1017 | jsdom: '*' 1018 | peerDependenciesMeta: 1019 | '@vitest/ui': 1020 | optional: true 1021 | c8: 1022 | optional: true 1023 | happy-dom: 1024 | optional: true 1025 | jsdom: 1026 | optional: true 1027 | dependencies: 1028 | '@types/chai': 4.3.0 1029 | '@types/chai-subset': 1.3.3 1030 | chai: 4.3.6 1031 | local-pkg: 0.4.1 1032 | tinypool: 0.1.2 1033 | tinyspy: 0.3.2 1034 | vite: 2.9.1 1035 | transitivePeerDependencies: 1036 | - less 1037 | - sass 1038 | - stylus 1039 | dev: true 1040 | 1041 | /which/2.0.2: 1042 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1043 | engines: {node: '>= 8'} 1044 | hasBin: true 1045 | dependencies: 1046 | isexe: 2.0.0 1047 | dev: true 1048 | 1049 | /wrappy/1.0.2: 1050 | resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} 1051 | dev: true 1052 | 1053 | /yaml/1.10.2: 1054 | resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} 1055 | engines: {node: '>= 6'} 1056 | dev: true 1057 | -------------------------------------------------------------------------------- /screenshots/bare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/bare.png -------------------------------------------------------------------------------- /screenshots/changed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/changed.png -------------------------------------------------------------------------------- /screenshots/cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/cli.png -------------------------------------------------------------------------------- /screenshots/deduplication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/deduplication.png -------------------------------------------------------------------------------- /screenshots/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/info.png -------------------------------------------------------------------------------- /screenshots/life-cycle-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/life-cycle-events.png -------------------------------------------------------------------------------- /screenshots/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/markdown.png -------------------------------------------------------------------------------- /screenshots/message.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/message.gif -------------------------------------------------------------------------------- /screenshots/return-value.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/return-value.png -------------------------------------------------------------------------------- /screenshots/trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shuding/tilg/935729825b64015b4a344bc09ed16206e8826dc7/screenshots/trace.png -------------------------------------------------------------------------------- /src/development.ts: -------------------------------------------------------------------------------- 1 | // Created by Shu Ding (shud.in). 2 | 3 | import React, { useState, useEffect, useLayoutEffect, useRef } from 'react' 4 | 5 | function inIframe() { 6 | try { 7 | return window.self !== window.top 8 | } catch (e) { 9 | return true 10 | } 11 | } 12 | 13 | const IS_BROWSER = 14 | typeof window !== 'undefined' && 15 | window.navigator.product === 'Gecko' 16 | const IS_UNSUPPORTED_CONSOLE = 17 | IS_BROWSER && 18 | /(\.stackblitz\.io|\.csb\.app)$/.test(location.host) && 19 | inIframe() 20 | 21 | /** 22 | * This function is a `console.log` formatter with a subset of Markdown syntax 23 | * support, for both browser and CLI: 24 | */ 25 | const SEPARATOR = /[-–—!$%^&*()_+|~=`{}\[\]:\/\\"'“”‘’;<>?,.@#\s\n\t\r]$/ 26 | function md(strings, args = [], hints = {}, trace = '') { 27 | const disableStyling = 28 | IS_UNSUPPORTED_CONSOLE && 29 | args.some((arg) => { 30 | return typeof arg === 'function' || (arg && typeof arg === 'object') 31 | }) 32 | 33 | let tokens: Record = {} 34 | let formatted = '' 35 | let char = '' 36 | 37 | const result = [] 38 | const styles = [] 39 | 40 | function setStylesAndFormatted( 41 | type: string, 42 | value: string, 43 | tokenType: boolean 44 | ) { 45 | if (!disableStyling) { 46 | if (IS_BROWSER) { 47 | if (formatted.endsWith('%c')) { 48 | styles[styles.length - 1] += value 49 | } else { 50 | formatted += '%c' 51 | styles.push(value) 52 | } 53 | } else { 54 | formatted += value 55 | } 56 | } 57 | tokens[type] = tokenType 58 | char = undefined 59 | } 60 | 61 | function checkNextOrPrev(value: string | undefined) { 62 | return typeof value === 'undefined' || SEPARATOR.test(value) 63 | } 64 | 65 | function process( 66 | type: string, 67 | open: string, 68 | close: string, 69 | next?: string, 70 | prev?: string 71 | ) { 72 | if (tokens[type] && checkNextOrPrev(next)) { 73 | setStylesAndFormatted(type, close, false) 74 | } else if (!tokens[type] && checkNextOrPrev(prev)) { 75 | setStylesAndFormatted(type, open, true) 76 | } else { 77 | char = type 78 | } 79 | } 80 | 81 | for (let i = 0; i < strings.length; i++) { 82 | formatted = '' 83 | let prev = undefined 84 | const str = strings[i] 85 | 86 | for (let j = 0; j < str.length; j++) { 87 | char = str[j] 88 | 89 | if (char === '*') { 90 | if (str[j + 1] === '*') { 91 | j++ 92 | process( 93 | '**', 94 | IS_BROWSER ? 'font-weight: bold;' : '\u001B[1m', 95 | IS_BROWSER ? 'font-weight: normal;' : '\u001B[22m', 96 | str[j + 1], 97 | prev 98 | ) 99 | } else { 100 | process( 101 | '*', 102 | IS_BROWSER ? 'font-style: italic;' : '\u001B[3m', 103 | IS_BROWSER ? 'font-style: normal;' : '\u001B[23m', 104 | str[j + 1], 105 | prev 106 | ) 107 | } 108 | } else if (char === '_') { 109 | if (str[j + 1] === '_') { 110 | j++ 111 | process( 112 | '__', 113 | IS_BROWSER ? 'font-weight: bold;' : '\u001B[1m', 114 | IS_BROWSER ? 'font-weight: normal;' : '\u001B[22m', 115 | str[j + 1], 116 | prev 117 | ) 118 | } else { 119 | process( 120 | '_', 121 | IS_BROWSER ? 'font-style: italic;' : '\u001B[3m', 122 | IS_BROWSER ? 'font-style: normal;' : '\u001B[23m', 123 | str[j + 1], 124 | prev 125 | ) 126 | } 127 | } else if (char === '`') { 128 | process( 129 | '`', 130 | IS_BROWSER 131 | ? 'background: hsla(0,0%,70%,.3); border-radius:3px; padding: 0 2px;' 132 | : '\u001B[96m\u001B[1m', 133 | IS_BROWSER ? 'background: unset;' : '\u001B[39m\u001B[22m', 134 | str[j + 1], 135 | prev 136 | ) 137 | } 138 | 139 | prev = char 140 | if (typeof char !== 'undefined') { 141 | formatted += char 142 | } 143 | } 144 | 145 | const hasPrevSlot = i > 0 146 | const hasNextSlot = i < args.length 147 | if (disableStyling) { 148 | if (hasNextSlot && formatted.endsWith(' ')) { 149 | formatted = formatted.slice(0, -1) 150 | } 151 | if (hasPrevSlot && formatted.startsWith(' ')) { 152 | formatted = formatted.slice(1) 153 | } 154 | 155 | if (formatted !== '') { 156 | result.push(formatted) 157 | } 158 | 159 | if (hasNextSlot) { 160 | if (typeof args[i] === 'string') { 161 | result.push(JSON.stringify(args[i])) 162 | } else { 163 | result.push(args[i]) 164 | } 165 | } 166 | } else { 167 | if (!result.length) result.push('') 168 | if (hasNextSlot && hints[i]) { 169 | process( 170 | '~', 171 | IS_BROWSER 172 | ? 'text-decoration: underline; text-decoration-color: green; text-decoration-style: wavy; padding-bottom: 1px; text-decoration-skip-ink: none;' 173 | : '', 174 | '' 175 | ) 176 | } 177 | 178 | if (formatted !== '') { 179 | if (result.length) { 180 | result[result.length - 1] += formatted 181 | } else { 182 | result.push(formatted) 183 | } 184 | } 185 | 186 | if (hasNextSlot) { 187 | let serialized 188 | if ( 189 | args[i] && 190 | (typeof args[i] === 'object' || typeof args[i] === 'function') 191 | ) { 192 | result[result.length - 1] += '%o' 193 | styles.push(args[i]) 194 | } else { 195 | try { 196 | serialized = JSON.stringify(args[i]) 197 | } catch (e) { 198 | serialized = '' + args[i] 199 | } 200 | result[result.length - 1] += serialized 201 | } 202 | } 203 | 204 | if (hasNextSlot && hints[i]) { 205 | formatted = '' 206 | process( 207 | '~', 208 | IS_BROWSER ? 'text-decoration: none; padding-bottom: 0;' : '', 209 | '' 210 | ) 211 | result[result.length - 1] += formatted 212 | } 213 | } 214 | } 215 | 216 | if (trace) { 217 | if (disableStyling) { 218 | result.push(` (@ ${trace})`) 219 | } else { 220 | if (!result.length) result.push('') 221 | if (IS_BROWSER) { 222 | result[result.length - 1] += `%c(@ ${trace})` 223 | styles.push( 224 | 'color: #999; font-style: italic; font-size: 0.9em; padding-left: 2em;' 225 | ) 226 | } else { 227 | result[result.length - 1] += ` \u001B[2m(@ ${trace})\u001B[22m` 228 | } 229 | } 230 | } 231 | 232 | result.push(...styles) 233 | 234 | return result 235 | } 236 | 237 | function log(...args) { 238 | console.log(...args) 239 | } 240 | 241 | /** 242 | * This function gets the name of the component which calls the useTilg hook. 243 | * Returns null if not able to retrieve the information. 244 | */ 245 | const components = new WeakMap() 246 | const instances = new Map() 247 | function useTilgCurrentComponentContext() { 248 | const owner = (React as any) 249 | .__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner 250 | .current || { type: { name: 'unknown' }, memoizedProps: undefined } 251 | 252 | const name = owner.type.name 253 | const id = owner.type 254 | 255 | let path = '' 256 | let logPath = '' 257 | 258 | const stack = new Error().stack.split('\n') 259 | for (const line of stack) { 260 | const match = line.match(/^\s*at\s+(.*?)\s+\((.*?)\)$/) 261 | const callerName = match?.[1] 262 | const callerPath = match?.[2] 263 | 264 | if (callerPath) path += callerPath + ',' 265 | 266 | if (callerName && !callerName.startsWith('useTilg')) { 267 | logPath = match[2] 268 | } 269 | 270 | if ( 271 | callerName && 272 | !callerName.startsWith('use') && 273 | !/.+\.use.+/.test(callerName) 274 | ) { 275 | break 276 | } 277 | } 278 | 279 | return [name, owner, id, path, logPath] 280 | } 281 | 282 | export default function useTilg(...inlined: any[]) { 283 | const mark = useState(Math.random())[0] 284 | const [name, owner, id, hookPath, logPath] = useTilgCurrentComponentContext() 285 | 286 | const compute = () => { 287 | let hookId = 0 288 | 289 | if (!components.has(id)) { 290 | components.set(id, []) 291 | } 292 | const hooks = components.get(id) 293 | hookId = hooks.indexOf(hookPath) 294 | if (hookId === -1) { 295 | hookId = hooks.length 296 | hooks.push(hookPath) 297 | } 298 | 299 | const componentName = name ? '`<' + name + '/>`' : 'Component' 300 | 301 | // Only log the life cycle message for the first hook. 302 | if (hookId !== 0) return [componentName, hookId, 0] 303 | 304 | // Which component instance is this hook located in. 305 | if (!instances.has(id)) { 306 | instances.set(id, []) 307 | } 308 | const instanceMarks = instances.get(id) 309 | let index = instanceMarks.indexOf(mark) 310 | if (index === -1) { 311 | index = instanceMarks.length 312 | instanceMarks.push(mark) 313 | } 314 | 315 | return [componentName, hookId, index] 316 | } 317 | 318 | useEffect(() => { 319 | const [componentName, hookId, instanceId] = compute() 320 | 321 | // Only log the life cycle message for the first hook. 322 | if (hookId !== 0) return 323 | const note = instanceId > 0 ? ` (${instanceId + 1})` : '' 324 | 325 | log(...md([`${componentName}${note} mounted.`])) 326 | return () => { 327 | if (!instances.has(id)) { 328 | instances.set(id, []) 329 | } 330 | const instanceMarks = instances.get(id) 331 | let index = instanceMarks.indexOf(mark) 332 | if (index !== -1) { 333 | instanceMarks[index] = instanceMarks[instanceMarks.length - 1] 334 | instanceMarks.length-- 335 | } 336 | 337 | if (!components.has(id)) { 338 | components.set(id, []) 339 | } 340 | const hooks = components.get(id) 341 | index = hooks.indexOf(hookPath) 342 | if (index !== -1) { 343 | hooks[index] = hooks[hooks.length - 1] 344 | hooks.length-- 345 | } 346 | 347 | log(...md([`${componentName}${note} unmounted.`])) 348 | } 349 | }, []) 350 | 351 | const loggerEffectContent = useRef(null) 352 | loggerEffectContent.current = null 353 | const loggerPrevArgsContent = useRef(null) 354 | const loggerArgsContent = useRef([]) 355 | loggerArgsContent.current = [] 356 | 357 | useLayoutEffect(() => { 358 | const [componentName, hookId, instanceId] = compute() 359 | 360 | // Only log the life cycle message for the first hook. 361 | if (hookId === 0) { 362 | const note = instanceId > 0 ? ` (${instanceId + 1})` : '' 363 | log( 364 | ...md( 365 | [`${componentName}${note} rendered with props: \``, '`.'], 366 | [owner.memoizedProps] 367 | ) 368 | ) 369 | } 370 | 371 | let changed = false 372 | let changedHint = {} 373 | 374 | const prev = loggerPrevArgsContent.current 375 | const now = loggerArgsContent.current 376 | if (!prev || prev.length !== now.length) { 377 | // An arg has been removed or added, do nothing. 378 | changed = true 379 | } else { 380 | for (let i = 0; i < prev.length; i++) { 381 | if (prev[i] !== now[i]) { 382 | changed = true 383 | changedHint[i] = true 384 | } 385 | } 386 | } 387 | loggerPrevArgsContent.current = now 388 | 389 | if (changed && loggerEffectContent.current) { 390 | loggerEffectContent.current(changedHint) 391 | } 392 | }) 393 | 394 | if (inlined.length > 0) { 395 | loggerArgsContent.current = inlined 396 | loggerEffectContent.current = (hints) => { 397 | const message = md( 398 | inlined.map((_, i) => (i > 0 ? ', ' : '')).concat(''), 399 | inlined, 400 | hints, 401 | logPath 402 | ) 403 | 404 | log(...message) 405 | } 406 | return inlined[0] 407 | } 408 | 409 | return function useTilgInner(strings, ...args: any[]) { 410 | loggerEffectContent.current = (hints: Record) => { 411 | const message = strings.length ? md(strings, args, hints, logPath) : '' 412 | 413 | if (message) { 414 | // This is a work around to get rid of the hook count mismatch bug. 415 | log(...message) 416 | } 417 | } 418 | 419 | loggerArgsContent.current = args 420 | 421 | return args[0] 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /src/production.ts: -------------------------------------------------------------------------------- 1 | export default function useTilg(...args) { 2 | if (args.length) return args[0] 3 | return (_strings, ...innerArgs) => { 4 | if (innerArgs && innerArgs.length) return innerArgs[0] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/basic.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { act, create } from 'react-test-renderer' 3 | import { afterEach, describe, expect, it, vi } from 'vitest' 4 | 5 | import useTilg from '../src/development' 6 | 7 | describe('useTilg', () => { 8 | afterEach(() => { 9 | vi.restoreAllMocks() 10 | }) 11 | 12 | it('should log the render, mount and unmount events', async () => { 13 | const logSpy = vi.spyOn(console, 'log') 14 | 15 | function App() { 16 | useTilg() 17 | return null 18 | } 19 | const app = create() 20 | app.toJSON() 21 | act(() => app.unmount()) 22 | 23 | expect(logSpy.mock.calls[0][0]).toEqual( 24 | '\x1B[96m\x1B[1m\x1B[39m\x1B[22m rendered with props: \x1B[96m\x1B[1m%o\x1B[39m\x1B[22m.', 25 | ) 26 | expect(logSpy.mock.calls[0][1]).toEqual({}) 27 | expect(logSpy.mock.calls[1][0]).toEqual( 28 | '\x1B[96m\x1B[1m\x1B[39m\x1B[22m mounted.', 29 | ) 30 | expect(logSpy.mock.calls[2][0]).toEqual( 31 | '\x1B[96m\x1B[1m\x1B[39m\x1B[22m unmounted.', 32 | ) 33 | }) 34 | 35 | it('should log the message', async () => { 36 | const logSpy = vi.spyOn(console, 'log') 37 | 38 | function App() { 39 | useTilg()`The we we are` 40 | return null 41 | } 42 | const app = create() 43 | app.toJSON() 44 | act(() => app.unmount()) 45 | 46 | expect(logSpy.mock.calls[1][0]).toContain('The we we are') 47 | }) 48 | 49 | it('should log number arguments', async () => { 50 | const logSpy = vi.spyOn(console, 'log') 51 | 52 | function App() { 53 | useTilg()`The answer is ${42}` 54 | return null 55 | } 56 | const app = create() 57 | app.toJSON() 58 | act(() => app.unmount()) 59 | 60 | expect(logSpy.mock.calls[1][0]).toContain('The answer is 42') 61 | }) 62 | 63 | it('should log string arguments', async () => { 64 | const logSpy = vi.spyOn(console, 'log') 65 | 66 | function App() { 67 | useTilg()`The answer is ${'42'}` 68 | return null 69 | } 70 | const app = create() 71 | app.toJSON() 72 | act(() => app.unmount()) 73 | 74 | expect(logSpy.mock.calls[1][0]).toContain('The answer is "42"') 75 | }) 76 | 77 | it('should log object arguments', async () => { 78 | const logSpy = vi.spyOn(console, 'log') 79 | 80 | function App() { 81 | useTilg()`The answer is ${{ answer: 42 }}` 82 | return null 83 | } 84 | const app = create() 85 | app.toJSON() 86 | act(() => app.unmount()) 87 | 88 | expect(logSpy.mock.calls[1][0]).toContain('The answer is %o') 89 | expect(logSpy.mock.calls[1][1]).toEqual({ answer: 42 }) 90 | }) 91 | 92 | describe('props', () => { 93 | it('should log props', async () => { 94 | const logSpy = vi.spyOn(console, 'log') 95 | 96 | function App({ answer }) { 97 | useTilg() 98 | return answer 99 | } 100 | const app = create() 101 | app.toJSON() 102 | act(() => app.unmount()) 103 | 104 | expect(logSpy.mock.calls[0][1]).toEqual({ answer: 42 }) 105 | }) 106 | 107 | it('should log props updates', async () => { 108 | const logSpy = vi.spyOn(console, 'log') 109 | 110 | function App({ answer }) { 111 | useTilg() 112 | return answer 113 | } 114 | const app = create() 115 | app.toJSON() 116 | act(() => app.update()) 117 | act(() => app.unmount()) 118 | 119 | expect(logSpy.mock.calls[0][1]).toEqual({ answer: 42 }) 120 | expect(logSpy.mock.calls[2][1]).toEqual({ answer: 43 }) 121 | }) 122 | }) 123 | }) 124 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "esModuleInterop": true, 5 | "lib": ["esnext", "dom"] 6 | } 7 | } 8 | --------------------------------------------------------------------------------