├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS └── workflows │ └── main.yml ├── .gitignore ├── .husky └── pre-commit ├── .nvmrc ├── .storybook ├── main.js └── preview.js ├── LICENSE ├── README.md ├── demo ├── index.html └── index.tsx ├── jest.config.ts ├── package-lock.json ├── package.json ├── react-log-hook-screenshot.png ├── src ├── constants.ts ├── index.stories.tsx ├── index.test.tsx ├── index.tsx ├── types.ts ├── utils.test.ts └── utils.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "plugin:react/recommended", 9 | "standard-with-typescript" 10 | ], 11 | "overrides": [], 12 | "parserOptions": { 13 | "project": "./tsconfig.json" 14 | }, 15 | "plugins": [ 16 | "react", 17 | "prettier" 18 | ], 19 | "rules": { 20 | "prettier/prettier": [ 21 | "error", 22 | { 23 | "singleQuote": true, 24 | "semi": false, 25 | "bracketSpacing": true, 26 | "trailingComma": "all", 27 | "bracketSameLine": true 28 | } 29 | ], 30 | "react/prop-types": "off", 31 | "comma-dangle": "off", 32 | "@typescript-eslint/comma-dangle": [ 33 | "error", 34 | { 35 | "arrays": "always-multiline", 36 | "objects": "always-multiline", 37 | "imports": "always-multiline", 38 | "exports": "always-multiline", 39 | "functions": "always-multiline", 40 | "enums": "always-multiline" 41 | } 42 | ], 43 | "space-before-function-paren": "off", 44 | "@typescript-eslint/space-before-function-paren": "off", 45 | "@typescript-eslint/strict-boolean-expressions": "off", 46 | "@typescript-eslint/indent": "off", 47 | "multiline-ternary": "off", 48 | "react/jsx-closing-bracket-location": [ 49 | 1, 50 | { 51 | "selfClosing": "tag-aligned", 52 | "nonEmpty": "after-props" 53 | } 54 | ] 55 | } 56 | } -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dolfbarr 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | name: Node.js ${{ matrix.node-version }} 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | - uses: actions/setup-node@v2 15 | with: 16 | node-version: ${{ matrix.node-version }} 17 | - run: npm install 18 | - run: npm run lint 19 | - run: npm run type:check 20 | - run: npm run test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # MacOS 107 | .DS_store 108 | 109 | # VS code 110 | .vscode/ 111 | 112 | # Parcel 113 | build/ 114 | dist/ 115 | .parcel-cache/ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run release:check 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16 -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../src/**/*.stories.mdx", 4 | "../src/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials", 9 | "@storybook/addon-interactions" 10 | ], 11 | "framework": "@storybook/react" 12 | } -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | export const parameters = { 2 | actions: { argTypesRegex: "^on[A-Z].*" }, 3 | controls: { 4 | matchers: { 5 | color: /(background|color)$/i, 6 | date: /Date$/, 7 | }, 8 | }, 9 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Dolf Barr 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 | React Log Hook Screenshot 4 |
5 |
6 |
7 | NPM Version 8 | License 9 | minzipped size 10 | dependency count 11 | Build Status 12 | Last Commit 13 |
14 |
15 |
🪵 React Log Hook
16 |
Lightweight & customizable logging hook for your react components lifecycle
17 | 18 |
19 | By Dolf Barr 20 |
21 |
22 |
23 |
24 |
25 | 26 | # 🪵 react-log-hook 27 | React hook for logging per component lifecycle 28 | 29 | ## Features 30 | - 🪶 **Lightweight** — under *1.5 kB* gzipped & minified 31 | - 🗂️ **Typed** — made with TypeScript, shipped with types 32 | - 🥰 **Simple** — don't worry about any changes in your props & state 33 | - 🔧 **Customizable** — able to change everything you see in the logs 34 | - 🔬 **Tested** — up to 💯% unit test coverage 35 | - 🏎️ **Fast** — native react hooks & optimized 36 | - 📭 **No dependecies** 37 | 38 | 39 | 40 | ## Install 41 | 42 | With npm 43 | 44 | ```sh 45 | npm install -D react-log-hook 46 | ``` 47 | 48 | With yarn 49 | 50 | ```sh 51 | yarn add -D react-log-hook 52 | ``` 53 | 54 | ## Usage 55 | 56 | ### Basic usage 57 | 58 | ```javascript 59 | import { useLog } from 'react-log-hook' 60 | 61 | const App = () => { 62 | // Add a logger 63 | const { log } = useLog() 64 | 65 | const [state, setState] = useState(null) 66 | 67 | // Log the changes via console in real time! 68 | log(state) 69 | 70 | return null 71 | } 72 | ``` 73 | 74 | ### Configuration options 75 | 76 | ```javascript 77 | import { useLog } from 'react-log-hook' 78 | 79 | const App = () => { 80 | // Any configuration properties are optional 81 | const { log } = useLog({ 82 | environments: [ 83 | /** Contains array of environments of `process.env.NODE_ENV` in which logging will be allowed */ 84 | 'dev', 85 | 'development', 86 | ], 87 | 88 | // Print Options 89 | 90 | styles: { 91 | /** Contains styles object with different CSS inline styles used in logging */ 92 | componentCSS: 93 | 'color: DodgerBlue' /** Inline css for rendering component name in the logs */, 94 | changeCSS: 95 | 'color: green; font-weight: bold;' /** Inline css for rendering current value in the logs */, 96 | subValueCSS: 97 | 'color: SlateGray; font-weight: thin;' /** Inline css for rendering any additional data like time or previous value in the logs */, 98 | }, 99 | printer: console /** Contains custom implementation of console */, 100 | logLevel: 'log' /** Level of logging defined by console method */, 101 | /** Render object or array inline or via interactive browser renderer */ 102 | inline: true, 103 | isGroupingEnabled: true /** Enable grouping for logs */, 104 | isGroupCollapsed: false /** Render groups collapsed */, 105 | groupLabelRenderer: ( 106 | /** A function which will be used to render labels for the group */ 107 | type /** Current stage of component lifecycle: 'Mount' | 'Change' | 'Unmount' */, 108 | componentName, 109 | ) => `${type}${componentName}`, 110 | 111 | // Custom Render Function 112 | 113 | render: function ({ 114 | /** Custom function which will be used for rendering the result, provided with useful data */ 115 | value, 116 | prevValue, 117 | type /** Current stage of component lifecycle: 'Mount' | 'Change' | 'Unmount' */, 118 | componentName, 119 | inline /** Render object or array inline or via interactive browser renderer */, 120 | flags: { 121 | isGrouped /** Enable grouping for logs */, 122 | isCollapsed /** Render groups collapsed */, 123 | }, 124 | }) { 125 | console.log(value) 126 | }, 127 | }) 128 | 129 | const [state, setState] = useState(null) 130 | 131 | // It's possible to redefine any configuration option per log call! 132 | log(state, { 133 | inline: false, 134 | logLevel: 'warn', 135 | }) 136 | 137 | return null 138 | } 139 | ``` 140 | 141 | ## FAQ 142 | 143 | ### Will it deep copy the value to make sure it will persist in the logs? 144 | 145 | - 🎉 Yes, 🪵 **react-log-hook** deep copies the value to make sure it will not be changed in the logs later 146 | 147 | ### Do i need to install @types/react-log-hook as well? 148 | 149 | - 💪 No, 🪵 **react-log-hook** comes with prebundled types 150 | 151 | ### Will it run in production evironment? 152 | 153 | - ✅ By default 🪵 **react-log-hook** will run only in `dev` or `development` node evironments defined by `NODE_ENV` 154 | 155 | ## Roadmap 156 | 157 | - [x] Add previous state checking 158 | - [x] Use object copy to persist in time 159 | - [x] Use console groups to handle all the logs 160 | - [x] Add dev environment support by default 161 | - [x] Polish the looks with component names, function calls, time etc 162 | - [x] Add more customization options 163 | - [ ] Test with SSR & Server components 164 | 165 | ## Contributing 166 | 167 | - 🌟 Stars & 📥 Pull Requests are welcome for sure! ❤️ 168 | 169 | ### Development 170 | 171 | 🪵 **react-log-hook** uses npm & npm scripts in development, the following scipts can be handy: 172 | 173 | #### `npm run start:demo` 174 | > Starts a demo app with enabled hook to check it in real environment 175 | 176 | #### `npm run storybook` 177 | > Starts storybook with example components to test against 178 | 179 | #### `npm run release:check` 180 | > Combination of linting, type-checking & tests; runs as precommit hook 181 | 182 | ## License 183 | 184 | [MIT License](LICENSE) 185 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo for React log hook 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /demo/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import { App } from '../src/index.stories' 4 | 5 | const container = document.getElementById('app') 6 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 7 | const root = createRoot(container!) 8 | root.render() 9 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | // All imported modules in your tests should be mocked automatically 8 | // automock: false, 9 | 10 | // Stop running tests after `n` failures 11 | // bail: 0, 12 | 13 | // The directory where Jest should store its cached dependency information 14 | // cacheDirectory: "/private/var/folders/z1/z44tkmdd5wzc3_q3dpdg5l_80000gn/T/jest_dx", 15 | 16 | // Automatically clear mock calls, instances, contexts and results before every test 17 | clearMocks: true, 18 | 19 | // Indicates whether the coverage information should be collected while executing the test 20 | collectCoverage: true, 21 | 22 | // An array of glob patterns indicating a set of files for which coverage information should be collected 23 | // collectCoverageFrom: undefined, 24 | 25 | // The directory where Jest should output its coverage files 26 | coverageDirectory: 'coverage', 27 | 28 | // An array of regexp pattern strings used to skip coverage collection 29 | // coveragePathIgnorePatterns: [ 30 | // "/node_modules/" 31 | // ], 32 | 33 | // Indicates which provider should be used to instrument code for coverage 34 | // coverageProvider: "babel", 35 | 36 | // A list of reporter names that Jest uses when writing coverage reports 37 | // coverageReporters: [ 38 | // "json", 39 | // "text", 40 | // "lcov", 41 | // "clover" 42 | // ], 43 | 44 | // An object that configures minimum threshold enforcement for coverage results 45 | // coverageThreshold: undefined, 46 | 47 | // A path to a custom dependency extractor 48 | // dependencyExtractor: undefined, 49 | 50 | // Make calling deprecated APIs throw helpful error messages 51 | // errorOnDeprecated: false, 52 | 53 | // The default configuration for fake timers 54 | // fakeTimers: { 55 | // "enableGlobally": false 56 | // }, 57 | 58 | // Force coverage collection from ignored files using an array of glob patterns 59 | // forceCoverageMatch: [], 60 | 61 | // A path to a module which exports an async function that is triggered once before all test suites 62 | // globalSetup: undefined, 63 | 64 | // A path to a module which exports an async function that is triggered once after all test suites 65 | // globalTeardown: undefined, 66 | 67 | // A set of global variables that need to be available in all test environments 68 | // globals: {}, 69 | 70 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 71 | // maxWorkers: "50%", 72 | 73 | // An array of directory names to be searched recursively up from the requiring module's location 74 | // moduleDirectories: [ 75 | // "node_modules" 76 | // ], 77 | 78 | // An array of file extensions your modules use 79 | // moduleFileExtensions: [ 80 | // "js", 81 | // "mjs", 82 | // "cjs", 83 | // "jsx", 84 | // "ts", 85 | // "tsx", 86 | // "json", 87 | // "node" 88 | // ], 89 | 90 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 91 | // moduleNameMapper: {}, 92 | 93 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 94 | // modulePathIgnorePatterns: [], 95 | 96 | // Activates notifications for test results 97 | // notify: false, 98 | 99 | // An enum that specifies notification mode. Requires { notify: true } 100 | // notifyMode: "failure-change", 101 | 102 | // A preset that is used as a base for Jest's configuration 103 | preset: 'ts-jest', 104 | 105 | // Run tests from one or more projects 106 | // projects: undefined, 107 | 108 | // Use this configuration option to add custom reporters to Jest 109 | // reporters: undefined, 110 | 111 | // Automatically reset mock state before every test 112 | // resetMocks: false, 113 | 114 | // Reset the module registry before running each individual test 115 | // resetModules: false, 116 | 117 | // A path to a custom resolver 118 | // resolver: undefined, 119 | 120 | // Automatically restore mock state and implementation before every test 121 | // restoreMocks: false, 122 | 123 | // The root directory that Jest should scan for tests and modules within 124 | // rootDir: undefined, 125 | 126 | // A list of paths to directories that Jest should use to search for files in 127 | // roots: [ 128 | // "" 129 | // ], 130 | 131 | // Allows you to use a custom runner instead of Jest's default test runner 132 | // runner: "jest-runner", 133 | 134 | // The paths to modules that run some code to configure or set up the testing environment before each test 135 | // setupFiles: [], 136 | 137 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 138 | // setupFilesAfterEnv: [], 139 | 140 | // The number of seconds after which a test is considered as slow and reported as such in the results. 141 | // slowTestThreshold: 5, 142 | 143 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 144 | // snapshotSerializers: [], 145 | 146 | // The test environment that will be used for testing 147 | testEnvironment: 'jsdom', 148 | 149 | // Options that will be passed to the testEnvironment 150 | // testEnvironmentOptions: {}, 151 | 152 | // Adds a location field to test results 153 | // testLocationInResults: false, 154 | 155 | // The glob patterns Jest uses to detect test files 156 | // testMatch: [ 157 | // "**/__tests__/**/*.[jt]s?(x)", 158 | // "**/?(*.)+(spec|test).[tj]s?(x)" 159 | // ], 160 | 161 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 162 | // testPathIgnorePatterns: [ 163 | // "/node_modules/" 164 | // ], 165 | 166 | // The regexp pattern or array of patterns that Jest uses to detect test files 167 | // testRegex: [], 168 | 169 | // This option allows the use of a custom results processor 170 | // testResultsProcessor: undefined, 171 | 172 | // This option allows use of a custom test runner 173 | // testRunner: "jest-circus/runner", 174 | 175 | // A map from regular expressions to paths to transformers 176 | // transform: undefined, 177 | 178 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 179 | // transformIgnorePatterns: [ 180 | // "/node_modules/", 181 | // "\\.pnp\\.[^\\/]+$" 182 | // ], 183 | 184 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 185 | // unmockedModulePathPatterns: undefined, 186 | 187 | // Indicates whether each individual test should be reported during the run 188 | // verbose: undefined, 189 | 190 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 191 | // watchPathIgnorePatterns: [], 192 | 193 | // Whether to use watchman for file crawling 194 | // watchman: true, 195 | } 196 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-log-hook", 3 | "version": "1.2.2", 4 | "description": "React hook for logging per component lifecycle", 5 | "main": "dist/main.js", 6 | "module": "dist/module.js", 7 | "types": "dist/types.d.ts", 8 | "engines": { 9 | "node": ">= 12" 10 | }, 11 | "scripts": { 12 | "start:demo": "parcel demo/index.html --dist-dir ./build", 13 | "build": "del-cli 'dist/*' && parcel build src/index.tsx --dist-dir ./dist && npm run size", 14 | "test": "jest", 15 | "lint": "eslint \"./**/*.ts\" --cache --cache-strategy content", 16 | "type:check": "tsc --noEmit true", 17 | "storybook": "start-storybook -p 6006", 18 | "build:storybook": "build-storybook", 19 | "release:check": "run-s lint type:check test", 20 | "release:test": "npm publish --dry-run", 21 | "release": "run-s release:check release:test build && np", 22 | "size": "size-limit", 23 | "prepare": "husky install" 24 | }, 25 | "np": { 26 | "yarn": false, 27 | "testScript": "release:check" 28 | }, 29 | "files": [ 30 | "dist/*.{js,mjs,ts,map}", 31 | "LICENSE", 32 | "README.md", 33 | "package.json" 34 | ], 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/dolfbarr/react-log-hook.git" 38 | }, 39 | "keywords": [ 40 | "react", 41 | "parcel", 42 | "typescript", 43 | "hook", 44 | "react-hook", 45 | "log", 46 | "logging", 47 | "logger" 48 | ], 49 | "author": "Dolf Barr ", 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/dolfbarr/react-log-hook/issues" 53 | }, 54 | "homepage": "https://github.com/dolfbarr/react-log-hook#readme", 55 | "devDependencies": { 56 | "@babel/core": "^7.20.2", 57 | "@jest/globals": "^29.3.1", 58 | "@parcel/packager-ts": "^2.8.0", 59 | "@parcel/transformer-typescript-types": "^2.8.0", 60 | "@size-limit/preset-small-lib": "^8.1.0", 61 | "@storybook/addon-actions": "^6.5.13", 62 | "@storybook/addon-console": "^1.2.3", 63 | "@storybook/addon-essentials": "^6.5.13", 64 | "@storybook/addon-interactions": "^6.5.13", 65 | "@storybook/addon-links": "^6.5.13", 66 | "@storybook/builder-webpack4": "^6.5.13", 67 | "@storybook/manager-webpack4": "^6.5.13", 68 | "@storybook/react": "^7.4.6", 69 | "@storybook/testing-library": "^0.0.13", 70 | "@testing-library/react": "^13.4.0", 71 | "@types/jest": "^29.2.3", 72 | "@types/node": "^18.11.9", 73 | "@types/react": "^18.0.25", 74 | "@types/react-dom": "^18.0.9", 75 | "@typescript-eslint/eslint-plugin": "^5.44.0", 76 | "babel-loader": "^8.3.0", 77 | "del-cli": "^5.0.0", 78 | "eslint": "^8.28.0", 79 | "eslint-config-prettier": "^8.5.0", 80 | "eslint-config-standard-with-typescript": "^23.0.0", 81 | "eslint-plugin-import": "^2.26.0", 82 | "eslint-plugin-n": "^15.5.1", 83 | "eslint-plugin-prettier": "^4.2.1", 84 | "eslint-plugin-promise": "^6.1.1", 85 | "eslint-plugin-react": "^7.31.11", 86 | "husky": "^8.0.0", 87 | "jest": "^29.3.1", 88 | "jest-environment-jsdom": "^29.3.1", 89 | "np": "*", 90 | "npm-run-all": "^4.1.5", 91 | "parcel": "^2.8.0", 92 | "prettier": "^2.8.0", 93 | "process": "^0.11.10", 94 | "react": "^18.2.0", 95 | "react-dom": "^18.2.0", 96 | "size-limit": "^8.1.0", 97 | "ts-jest": "^29.0.3", 98 | "ts-node": "^10.9.1", 99 | "typescript": "^4.9.3" 100 | }, 101 | "peerDependencies": { 102 | "react": "^16.8.x || 17.x || 18.x", 103 | "react-dom": "^16.8.x || 17.x || 18.x" 104 | }, 105 | "size-limit": [ 106 | { 107 | "path": "dist/main.js", 108 | "limit": "2 kB" 109 | }, 110 | { 111 | "path": "dist/module.js", 112 | "limit": "2 kB" 113 | } 114 | ] 115 | } 116 | -------------------------------------------------------------------------------- /react-log-hook-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dolfbarr/react-log-hook/2ac0551e54bd4d9728cfc6fdfffc94de06e07de0/react-log-hook-screenshot.png -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | import { LogLevels } from './types' 2 | 3 | export const CSS_COMPONENT = 'color: DodgerBlue' 4 | export const CSS_CHANGE = 'color: green; font-weight: bold;' 5 | export const CSS_SUB_VALUE = 'color: SlateGray; font-weight: thin;' 6 | 7 | export const ALLOWED_NODE_ENVS = ['dev', 'development'] 8 | 9 | export const DEFAULT_LOG_LEVEL: LogLevels = 'log' 10 | 11 | export const DEFAULT_LABEL_SIZE = 14 12 | 13 | export const PREVIOUS_VALUE_LABEL = 'Previous Value' 14 | export const CURRENT_VALUE_LABEL = 'Current Value' 15 | -------------------------------------------------------------------------------- /src/index.stories.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import { ComponentMeta } from '@storybook/react' 3 | import '@storybook/addon-console' 4 | import '@storybook/addon-actions/register' 5 | import { useLog } from '.' 6 | 7 | export function App(): React.ReactElement { 8 | const [isExampleMounted, setIsExampleMounted] = useState(true) 9 | 10 | setTimeout(function setIsMounted() { 11 | setIsExampleMounted(false) 12 | }, 3000) 13 | 14 | return ( 15 |
16 |

Demo for React log hook.

17 |

Please check the console for logs.

18 | {isExampleMounted ? : null} 19 |
20 | ) 21 | } 22 | 23 | export function ExampleComponent(): React.ReactElement { 24 | const { log } = useLog() 25 | const [currentState, setCurrentState] = useState({}) 26 | 27 | log(currentState) 28 | 29 | useEffect(function setStateMount() { 30 | setCurrentState({ a: 'Test', b: 'Value', state: 'OnMount' }) 31 | 32 | setTimeout(function setStateChange1() { 33 | setCurrentState({ a: 'Test', b: 'Value', state: 'onChange 1s' }) 34 | }, 1000) 35 | 36 | setTimeout(function setStateChange2() { 37 | setCurrentState({ a: 'Test', b: 'Value', state: 'onChange 2s' }) 38 | }, 2000) 39 | 40 | return function setStateUnmount() { 41 | setCurrentState({ a: 'Test', b: 'Value', state: 'onUnmount' }) 42 | } 43 | }, []) 44 | 45 | return

Test Component: {JSON.stringify(currentState)}

46 | } 47 | 48 | const DefaultMeta: ComponentMeta = { 49 | title: 'Example', 50 | component: App, 51 | parameters: { 52 | layout: 'centered', 53 | }, 54 | } 55 | 56 | export default DefaultMeta 57 | -------------------------------------------------------------------------------- /src/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { useLog } from './index' 2 | import * as utils from './utils' 3 | import { act, renderHook } from '@testing-library/react' 4 | import { useEffect, useState } from 'react' 5 | 6 | describe('useLog', () => { 7 | const OLD_ENV = process.env 8 | jest.spyOn(utils, 'getCurrentTime').mockReturnValue('09:38 PM') 9 | const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => null) 10 | const consoleGroup = jest 11 | .spyOn(console, 'group') 12 | .mockImplementation(() => null) 13 | 14 | const consoleGroupCollapsed = jest 15 | .spyOn(console, 'groupCollapsed') 16 | .mockImplementation(() => null) 17 | 18 | beforeEach(() => { 19 | jest.useFakeTimers() 20 | jest.resetModules() 21 | process.env = { ...OLD_ENV } 22 | process.env.NODE_ENV = 'dev' 23 | }) 24 | 25 | afterEach(() => { 26 | jest.runOnlyPendingTimers() 27 | jest.useRealTimers() 28 | process.env = OLD_ENV 29 | }) 30 | 31 | it('exists', () => { 32 | expect(useLog).toBeTruthy() 33 | }) 34 | 35 | it('renders hook', () => { 36 | const { result } = renderHook(useLog) 37 | expect(result.current.log).toBeTruthy() 38 | 39 | renderHook(() => result.current.log('Test')) 40 | expect(consoleLog).toBeCalledWith(' On Mount: Test') 41 | expect(consoleLog).toBeCalledWith( 42 | 'Previous Value: %cTest', 43 | 'color: SlateGray; font-weight: thin;', 44 | ) 45 | expect(consoleLog).toBeCalledWith( 46 | ' Current Value: %cTest', 47 | 'color: green; font-weight: bold;', 48 | ) 49 | expect(consoleLog).toBeCalledTimes(3) 50 | }) 51 | 52 | it('renders hook with changes', async () => { 53 | const { result } = renderHook(useLog) 54 | const { unmount: logUnmount, rerender: logRerender } = renderHook(() => { 55 | const [state, setState] = useState(null) 56 | 57 | result.current.log(state) 58 | 59 | useEffect(() => { 60 | setState('onMount') 61 | 62 | setTimeout(() => { 63 | setState('onChange 1s') 64 | }, 1000) 65 | 66 | setTimeout(() => { 67 | setState('onChange 2s') 68 | }, 2000) 69 | }, []) 70 | }) 71 | 72 | /* 73 | * Set Initial Values 74 | */ 75 | expect(consoleGroup).toBeCalledWith( 76 | 'Mount in %c %c@ 09:38 PM', 77 | 'color: DodgerBlue', 78 | 'color: SlateGray; font-weight: thin;', 79 | ) 80 | expect(consoleLog).toBeCalledWith(' On Mount: null') 81 | 82 | expect(consoleGroup).toBeCalledWith( 83 | 'Change in %c %c@ 09:38 PM', 84 | 'color: DodgerBlue', 85 | 'color: SlateGray; font-weight: thin;', 86 | ) 87 | expect(consoleLog).toBeCalledWith( 88 | 'Previous Value: %cnull', 89 | 'color: SlateGray; font-weight: thin;', 90 | ) 91 | expect(consoleLog).toBeCalledWith( 92 | ' Current Value: %cnull', 93 | 'color: green; font-weight: bold;', 94 | ) 95 | expect(consoleGroup).toBeCalledWith( 96 | 'Change in %c %c@ 09:38 PM', 97 | 'color: DodgerBlue', 98 | 'color: SlateGray; font-weight: thin;', 99 | ) 100 | expect(consoleLog).toBeCalledWith( 101 | 'Previous Value: %cnull', 102 | 'color: SlateGray; font-weight: thin;', 103 | ) 104 | expect(consoleLog).toBeCalledWith( 105 | ' Current Value: %cnull', 106 | 'color: green; font-weight: bold;', 107 | ) 108 | expect(consoleLog).toBeCalledTimes(5) 109 | expect(consoleGroup).toBeCalledTimes(3) 110 | 111 | /* 112 | * Check first change 113 | */ 114 | await act(() => { 115 | jest.advanceTimersByTime(1000) 116 | logRerender() 117 | }) 118 | expect(consoleGroup).toBeCalledWith( 119 | 'Change in %c %c@ 09:38 PM', 120 | 'color: DodgerBlue', 121 | 'color: SlateGray; font-weight: thin;', 122 | ) 123 | expect(consoleLog).toBeCalledWith( 124 | 'Previous Value: %cnull', 125 | 'color: SlateGray; font-weight: thin;', 126 | ) 127 | expect(consoleLog).toBeCalledWith( 128 | ' Current Value: %conChange 1s', 129 | 'color: green; font-weight: bold;', 130 | ) 131 | expect(consoleLog).toBeCalledTimes(7) 132 | expect(consoleGroup).toBeCalledTimes(4) 133 | 134 | /* 135 | * Check second change 136 | */ 137 | await act(() => { 138 | jest.advanceTimersByTime(1000) 139 | logRerender() 140 | }) 141 | expect(consoleGroup).toBeCalledWith( 142 | 'Change in %c %c@ 09:38 PM', 143 | 'color: DodgerBlue', 144 | 'color: SlateGray; font-weight: thin;', 145 | ) 146 | expect(consoleLog).toBeCalledWith( 147 | 'Previous Value: %conChange 1s', 148 | 'color: SlateGray; font-weight: thin;', 149 | ) 150 | expect(consoleLog).toBeCalledWith( 151 | ' Current Value: %conChange 2s', 152 | 'color: green; font-weight: bold;', 153 | ) 154 | expect(consoleLog).toBeCalledTimes(9) 155 | expect(consoleGroup).toBeCalledTimes(5) 156 | 157 | /* 158 | * Check unmount change 159 | */ 160 | await act(() => { 161 | logUnmount() 162 | }) 163 | expect(consoleGroup).toBeCalledWith( 164 | 'Unmount in %c %c@ 09:38 PM', 165 | 'color: DodgerBlue', 166 | 'color: SlateGray; font-weight: thin;', 167 | ) 168 | expect(consoleLog).toBeCalledWith( 169 | 'Previous Value: %conChange 2s', 170 | 'color: SlateGray; font-weight: thin;', 171 | ) 172 | expect(consoleLog).toBeCalledWith( 173 | ' Current Value: %cnull', 174 | 'color: green; font-weight: bold;', 175 | ) 176 | expect(consoleLog).toBeCalledTimes(11) 177 | expect(consoleGroup).toBeCalledTimes(6) 178 | }) 179 | it('renders hook with custom styles', () => { 180 | renderHook(() => { 181 | const { log } = useLog({ styles: { componentCSS: 'color: darkBlue;' } }) 182 | log('Test') 183 | }) 184 | 185 | // first call, second parameter (css for component name) should be modified 186 | expect(consoleGroup.mock.calls[0][1]).toBe('color: darkBlue;') 187 | }) 188 | 189 | it('renders log with custom styles', () => { 190 | renderHook(() => { 191 | const { log } = useLog({ styles: { componentCSS: 'color: darkBlue;' } }) 192 | log('Test', { styles: { componentCSS: 'color: darkRed;' } }) 193 | }) 194 | 195 | // first call, second parameter (css for component name) should be modified 196 | expect(consoleGroup.mock.calls[0][1]).toBe('color: darkRed;') 197 | }) 198 | 199 | it('renders log with custom styles for subValueCSS', () => { 200 | renderHook(() => { 201 | const { log } = useLog({ styles: { subValueCSS: 'color: darkBlue;' } }) 202 | log('Test', { styles: { subValueCSS: 'color: darkRed;' } }) 203 | }) 204 | 205 | // first call, third parameter (css for call time) should be modified 206 | expect(consoleGroup.mock.calls[0][2]).toBe('color: darkRed;') 207 | }) 208 | 209 | it('renders log with custom styles for changeCSS', () => { 210 | renderHook(() => { 211 | const { log } = useLog({ styles: { changeCSS: 'color: darkBlue;' } }) 212 | log('Test', { styles: { changeCSS: 'color: darkRed;' } }) 213 | }) 214 | 215 | // third call, third parameter (css for new value) should be modified 216 | expect(consoleLog.mock.calls[2][1]).toBe('color: darkRed;') 217 | }) 218 | 219 | it('does not render anything in production', () => { 220 | process.env.NODE_ENV = 'production' 221 | 222 | const { result } = renderHook(useLog) 223 | renderHook(() => result.current.log('Test')) 224 | 225 | expect(consoleLog).not.toBeCalled() 226 | }) 227 | 228 | it('renders anything in custom allowed environments', () => { 229 | process.env.NODE_ENV = 'test_env' 230 | 231 | renderHook(() => { 232 | const { log } = useLog({ environments: ['test_env'] }) 233 | log('Test') 234 | }) 235 | 236 | expect(consoleLog).toBeCalled() 237 | }) 238 | 239 | it('falls back to production for empty node_env', () => { 240 | process.env.NODE_ENV = undefined 241 | 242 | const { result } = renderHook(useLog) 243 | renderHook(() => result.current.log('Test')) 244 | 245 | expect(consoleLog).not.toBeCalled() 246 | }) 247 | 248 | it('renders log with disabled groups', () => { 249 | renderHook(() => { 250 | const { log } = useLog({ isGroupingEnabled: true }) 251 | log('Test', { isGroupingEnabled: false }) 252 | }) 253 | 254 | expect(consoleGroup).not.toHaveBeenCalled() 255 | }) 256 | 257 | it('renders log with disabled groups', () => { 258 | renderHook(() => { 259 | const { log } = useLog({ isGroupingEnabled: false }) 260 | log('Test', { isGroupingEnabled: true, isGroupCollapsed: true }) 261 | }) 262 | 263 | expect(consoleGroup).not.toHaveBeenCalled() 264 | expect(consoleGroupCollapsed).toHaveBeenCalled() 265 | // first call, first parameter for group name should exist 266 | expect(consoleGroupCollapsed.mock.calls[0][0]).toBe( 267 | 'Mount in %c %c@ 09:38 PM', 268 | ) 269 | }) 270 | 271 | it('renders hook with custom printer', () => { 272 | const printer = { log: jest.fn() } 273 | 274 | const printerLog = jest.spyOn(printer, 'log').mockImplementation(() => null) 275 | 276 | renderHook(() => { 277 | const { log } = useLog({ printer }) 278 | log('Test') 279 | }) 280 | 281 | expect(consoleLog).not.toHaveBeenCalled() 282 | 283 | expect(printerLog).toHaveBeenCalled() 284 | }) 285 | 286 | it('renders log with custom printer', () => { 287 | const printer = { log: jest.fn() } 288 | const anotherPrinter = { log: jest.fn() } 289 | 290 | const printerLog = jest.spyOn(printer, 'log').mockImplementation(() => null) 291 | const anotherPrinterLog = jest 292 | .spyOn(anotherPrinter, 'log') 293 | .mockImplementation(() => null) 294 | 295 | renderHook(() => { 296 | const { log } = useLog({ printer }) 297 | log('Test', { printer: anotherPrinter }) 298 | }) 299 | 300 | expect(consoleLog).not.toHaveBeenCalled() 301 | expect(printerLog).not.toHaveBeenCalled() 302 | 303 | expect(anotherPrinterLog).toHaveBeenCalled() 304 | }) 305 | 306 | it('renders hook with custom log level', () => { 307 | const consoleWarn = jest 308 | .spyOn(console, 'warn') 309 | .mockImplementation(() => null) 310 | 311 | renderHook(() => { 312 | const { log } = useLog({ logLevel: 'warn' }) 313 | log('Test') 314 | }) 315 | 316 | expect(consoleLog).not.toHaveBeenCalled() 317 | expect(consoleWarn).toHaveBeenCalled() 318 | }) 319 | 320 | it('renders log with custom log level', () => { 321 | const consoleWarn = jest 322 | .spyOn(console, 'warn') 323 | .mockImplementation(() => null) 324 | const consoleError = jest 325 | .spyOn(console, 'error') 326 | .mockImplementation(() => null) 327 | 328 | renderHook(() => { 329 | const { log } = useLog({ logLevel: 'error' }) 330 | log('Test', { logLevel: 'warn' }) 331 | }) 332 | 333 | expect(consoleLog).not.toHaveBeenCalled() 334 | expect(consoleError).not.toHaveBeenCalled() 335 | expect(consoleWarn).toHaveBeenCalled() 336 | }) 337 | 338 | it('renders log with custom group name', () => { 339 | renderHook(() => { 340 | const { log } = useLog() 341 | log('Test', { groupLabelRenderer: (type, name) => `${type} ${name}` }) 342 | }) 343 | 344 | // first call, first parameter (group label) should be modified 345 | expect(consoleGroup.mock.calls[0][0]).toBe('Mount TestComponent') 346 | }) 347 | 348 | it('renders log with custom print function', () => { 349 | const render = jest.fn() 350 | const anotherRender = jest.fn() 351 | const printerSpy = jest.spyOn(utils, 'print') 352 | 353 | renderHook(() => { 354 | const { log } = useLog({ render }) 355 | log('Test', { render: anotherRender }) 356 | }) 357 | 358 | expect(render).not.toHaveBeenCalled() 359 | expect(printerSpy).not.toHaveBeenCalled() 360 | expect(anotherRender).toHaveBeenCalledWith({ 361 | componentName: 'TestComponent', 362 | flags: { 363 | isCollapsed: false, 364 | isGrouped: true, 365 | }, 366 | inline: true, 367 | prevValue: undefined, 368 | type: 'Mount', 369 | value: 'Test', 370 | }) 371 | }) 372 | 373 | it('renders log without inline rendering', () => { 374 | const consoleDir = jest.spyOn(console, 'dir').mockImplementation(() => null) 375 | renderHook(() => { 376 | const { log } = useLog({ inline: false }) 377 | log({ test: 'value' }, { inline: false }) 378 | }) 379 | 380 | expect(consoleDir).toHaveBeenCalled() 381 | }) 382 | }) 383 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Dolf Barr . All rights reserved. Licensed under the MIT license. 2 | 3 | /** 4 | * A react hook for logging through component lifecycle. 5 | * 6 | * @packageDocumentation 7 | */ 8 | 9 | import { useEffect, useRef } from 'react' 10 | import { 11 | UseLogConfig, 12 | UseLogReturn, 13 | LogConfig, 14 | ComponentLifecycleLabels, 15 | _PrintConfig, 16 | Printer, 17 | } from './types' 18 | import { getComponentName, getRenderFunctionProps, print } from './utils' 19 | import { 20 | ALLOWED_NODE_ENVS, 21 | CSS_CHANGE, 22 | CSS_COMPONENT, 23 | CSS_SUB_VALUE, 24 | DEFAULT_LOG_LEVEL, 25 | } from './constants' 26 | 27 | /** 28 | * Provides a function to log through react component lifecycle. 29 | * 30 | * @param config - component level configuration for any log function in the component 31 | * @see {@link UseLogConfig} for the config data structure 32 | * 33 | * @returns set of functions suitable for logging 34 | * 35 | * @example 36 | * ```ts 37 | * const {log} = useLog({environments: ['dev']}) 38 | * ``` 39 | */ 40 | export function useLog({ 41 | styles: { 42 | componentCSS = CSS_COMPONENT, 43 | changeCSS = CSS_CHANGE, 44 | subValueCSS = CSS_SUB_VALUE, 45 | } = {}, 46 | environments = ALLOWED_NODE_ENVS, 47 | isGroupingEnabled = true, 48 | isGroupCollapsed = false, 49 | printer = console as Printer, 50 | logLevel = DEFAULT_LOG_LEVEL, 51 | groupLabelRenderer, 52 | render, 53 | inline = true, 54 | }: UseLogConfig = {}): UseLogReturn { 55 | const componentName = getComponentName() 56 | 57 | /** 58 | * Logging function to log through react component lifecycle. 59 | * 60 | * @param value - a value which changes will be logged 61 | * @typeParam T - type of the tracking value 62 | * @param config - component level configuration for any log function in the component 63 | * @see {@link LogConfig} for the config data structure 64 | * 65 | * @example 66 | * ```ts 67 | * log(someState, {environments: ['production']}) 68 | * ``` 69 | */ 70 | function log(value: T, config?: LogConfig): void { 71 | const clonedValue = JSON.parse(JSON.stringify(value)) as T 72 | const prevValueRef = useRef() 73 | const printProps: Pick< 74 | _PrintConfig, 75 | | 'value' 76 | | 'styles' 77 | | 'componentName' 78 | | 'flags' 79 | | 'printer' 80 | | 'logLevel' 81 | | 'groupLabelRenderer' 82 | | 'inline' 83 | > = { 84 | value: clonedValue, 85 | styles: { 86 | componentCSS: config?.styles?.componentCSS ?? componentCSS, 87 | subValueCSS: config?.styles?.subValueCSS ?? subValueCSS, 88 | changeCSS: config?.styles?.changeCSS ?? changeCSS, 89 | }, 90 | componentName, 91 | flags: { 92 | isGrouped: config?.isGroupingEnabled ?? isGroupingEnabled, 93 | isCollapsed: config?.isGroupCollapsed ?? isGroupCollapsed, 94 | }, 95 | printer: config?.printer ?? printer, 96 | logLevel: config?.logLevel ?? logLevel, 97 | groupLabelRenderer: config?.groupLabelRenderer ?? groupLabelRenderer, 98 | inline: config?.inline ?? inline, 99 | } 100 | 101 | if (environments.includes(process.env.NODE_ENV ?? 'production')) { 102 | function logHooks(): void { 103 | const isUnmounting = useRef(false) 104 | 105 | const printFunc = (printProps: _PrintConfig): void => 106 | (config?.render ?? render ?? print)( 107 | getRenderFunctionProps( 108 | printProps, 109 | Boolean(config?.render ?? render), 110 | ), 111 | ) 112 | 113 | useEffect(function setIsUnmounting() { 114 | return function setIsUnmountingOnMount() { 115 | isUnmounting.current = true 116 | } 117 | }, []) 118 | 119 | useEffect(function onMount() { 120 | printFunc({ 121 | type: ComponentLifecycleLabels.Mount, 122 | ...printProps, 123 | }) 124 | 125 | prevValueRef.current = value 126 | 127 | return function onUnmount() { 128 | printFunc({ 129 | type: ComponentLifecycleLabels.Unmount, 130 | prevValue: prevValueRef.current, 131 | ...printProps, 132 | }) 133 | } 134 | }, []) 135 | 136 | useEffect( 137 | function onChange() { 138 | printFunc({ 139 | type: ComponentLifecycleLabels.Change, 140 | prevValue: prevValueRef.current, 141 | ...printProps, 142 | }) 143 | 144 | prevValueRef.current = value 145 | }, 146 | [value], 147 | ) 148 | } 149 | 150 | return logHooks() 151 | } 152 | } 153 | 154 | return { log } 155 | } 156 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Styles { 2 | /** 3 | * Inline css for rendering component name in the logs 4 | * 5 | * @defaultValue Blue text color 6 | * 7 | * @example 8 | * ```css 9 | * color:green;font-weight:bold;background-color:black; 10 | * ``` 11 | */ 12 | componentCSS?: string 13 | /** 14 | * Inline css for rendering current value in the logs 15 | * 16 | * @defaultValue Green text color with bold font 17 | * 18 | * @example 19 | * ```css 20 | * color:green;font-weight:bold;background-color:black; 21 | * ``` 22 | */ 23 | changeCSS?: string 24 | /** 25 | * Inline css for rendering any additional data like time or previous value in the logs 26 | * 27 | * @defaultValue Gray text color with thin font 28 | * 29 | * @example 30 | * ```css 31 | * color:green;font-weight:bold;background-color:black; 32 | * ``` 33 | */ 34 | subValueCSS?: string 35 | } 36 | 37 | export type UseLogConfig = { 38 | /** Contains styles object with different CSS inline styles used in logging */ 39 | styles?: Styles 40 | /** Contains array of environments of `process.env.NODE_ENV` in which logging will be allowed */ 41 | environments?: string[] 42 | /** Contains custom implementation of console */ 43 | printer?: Printer | Console 44 | logLevel?: LogLevels 45 | /** Render object or array inline or via interactive browser renderer */ 46 | inline?: boolean 47 | /** Custom function which will be used for rendering the result, provided with useful data */ 48 | render?: (props: RenderProps) => void 49 | } & ( 50 | | { 51 | /** Enable grouping for logs */ 52 | isGroupingEnabled?: boolean 53 | /** Render groups collapsed */ 54 | isGroupCollapsed?: boolean 55 | /** A function which will be used to render labels for the group */ 56 | groupLabelRenderer?: ( 57 | /** Current stage of component lifecycle */ 58 | type: ComponentLifecycleLabels, 59 | componentName: string, 60 | ) => string 61 | } 62 | | { 63 | /** Disable grouping for logs */ 64 | isGroupingEnabled?: false 65 | isGroupCollapsed?: never 66 | groupLabelRenderer?: never 67 | } 68 | ) 69 | 70 | /** Describes configuration object at call level, can be used to override configuration */ 71 | export type LogConfig = UseLogConfig 72 | 73 | export interface UseLogReturn { 74 | /** Used for logging per component lifecycle */ 75 | log: (value: T, props?: LogConfig) => void 76 | } 77 | 78 | /** Describes input parameters for custom printer function */ 79 | export type RenderProps = Pick< 80 | _PrintConfig, 81 | 'value' | 'prevValue' | 'type' | 'componentName' | 'inline' 82 | > & { 83 | flags?: Pick<_PrintFlags, 'isGrouped' | 'isCollapsed'> 84 | } 85 | 86 | /** 87 | * Describes configuration object of the inner print function 88 | * @internal 89 | * 90 | * @typeParam T - type of the tracking value 91 | */ 92 | export interface _PrintConfig { 93 | value: T 94 | prevValue?: T 95 | type?: ComponentLifecycleLabels 96 | styles?: Styles 97 | componentName: string 98 | flags?: _PrintFlags 99 | printer?: Printer | Console 100 | logLevel?: LogLevels 101 | inline?: boolean 102 | groupLabelRenderer?: ( 103 | type: ComponentLifecycleLabels, 104 | componentName: string, 105 | ) => string 106 | } 107 | 108 | /** 109 | * Describes possible flags for internal print configuration 110 | * @internal 111 | */ 112 | export type _PrintFlags = 113 | | { 114 | isGrouped?: boolean 115 | isCollapsed?: boolean 116 | } 117 | | { 118 | isGrouped?: false 119 | isCollapsed?: never 120 | } 121 | export enum ComponentLifecycleLabels { 122 | Mount = 'Mount', 123 | Unmount = 'Unmount', 124 | Change = 'Change', 125 | } 126 | 127 | /** Supported log levels which can be used in the console or custom console implementation */ 128 | export type LogLevels = keyof Pick< 129 | Console, 130 | 'log' | 'info' | 'error' | 'warn' | 'debug' 131 | > 132 | 133 | /** 134 | * Supported console methods 135 | * @internal 136 | */ 137 | export type _SupportedConsole = Pick< 138 | Console, 139 | 'group' | 'groupCollapsed' | 'groupEnd' | 'dir' | LogLevels 140 | > 141 | 142 | /** Describes custom implementation of console object with only supported methods used to render logs */ 143 | export type Printer = Partial<_SupportedConsole> 144 | -------------------------------------------------------------------------------- /src/utils.test.ts: -------------------------------------------------------------------------------- 1 | import * as utils from './utils' 2 | import { _PrintConfig, ComponentLifecycleLabels, Printer } from './types' 3 | 4 | const { getGroupLabel, getComponentName, print, getPrinter, getMessage } = utils 5 | 6 | describe('utils', () => { 7 | jest.spyOn(utils, 'getCurrentTime').mockReturnValue('09:38 PM') 8 | describe('getGroupLabel', () => { 9 | it('renders', () => { 10 | expect(getGroupLabel(ComponentLifecycleLabels.Change)).toEqual( 11 | 'Change @ 09:38 PM', 12 | ) 13 | }) 14 | 15 | it('renders with component name', () => { 16 | expect( 17 | getGroupLabel(ComponentLifecycleLabels.Mount, 'TestComponent'), 18 | ).toEqual('Mount in @ 09:38 PM') 19 | }) 20 | }) 21 | 22 | describe('getComponentName', () => { 23 | it('gets component name', () => { 24 | expect(getComponentName()).toEqual('_callCircusTest') 25 | }) 26 | 27 | it('returns empty string if error.stack is not supported by the browser', () => { 28 | const customError = new Error('error without stack') 29 | delete customError.stack 30 | expect(getComponentName(customError)).toEqual('') 31 | }) 32 | 33 | it('returns empty string if error.stack has different format', () => { 34 | const customError = new Error('error with unsupported stack') 35 | customError.stack = 36 | 'This is custom implementation of stack: calledThis > calledThat' 37 | expect(getComponentName(customError)).toEqual('') 38 | }) 39 | }) 40 | 41 | describe('getPrinter', () => { 42 | it('returns printer for existing printer method', () => { 43 | const printer: Printer = { log: jest.fn() } 44 | expect(getPrinter(printer, 'log')).toBe(printer.log) 45 | }) 46 | 47 | it('returns console for non-existing printer method', () => { 48 | const printer: Printer = { log: jest.fn() } 49 | expect(getPrinter(printer, 'warn')).toBe(console.warn) 50 | }) 51 | 52 | it('returns console for empty printer', () => { 53 | const printer: Printer = {} 54 | expect(getPrinter(printer, 'log')).toBe(console.log) 55 | }) 56 | 57 | it('returns console for empty printer method', () => { 58 | const printer: Printer = { log: undefined } 59 | expect(getPrinter(printer, 'log')).toBe(console.log) 60 | }) 61 | }) 62 | 63 | describe('print', () => { 64 | const consoleLog = jest.spyOn(console, 'log').mockImplementation(() => null) 65 | const consoleGroup = jest 66 | .spyOn(console, 'group') 67 | .mockImplementation(() => null) 68 | const consoleGroupCollapsed = jest 69 | .spyOn(console, 'groupCollapsed') 70 | .mockImplementation(() => null) 71 | const consoleGroupEnd = jest 72 | .spyOn(console, 'groupEnd') 73 | .mockImplementation(() => null) 74 | 75 | const printProps: _PrintConfig = { 76 | value: 'Test Value', 77 | componentName: 'SomeComponentName', 78 | } 79 | 80 | it('prints', () => { 81 | print(printProps) 82 | 83 | expect(consoleGroup).toHaveBeenCalledWith( 84 | 'Change in @ 09:38 PM', 85 | undefined, 86 | undefined, 87 | ) 88 | expect(consoleLog).toHaveBeenCalledWith(' On Change: Test Value') 89 | expect(consoleLog).toHaveBeenCalledTimes(1) 90 | expect(consoleGroupEnd).toHaveBeenCalled() 91 | }) 92 | 93 | it('prints previous value', () => { 94 | print({ ...printProps, prevValue: 'Some Previous value' }) 95 | 96 | expect(consoleGroup).toHaveBeenCalledWith( 97 | 'Change in @ 09:38 PM', 98 | undefined, 99 | undefined, 100 | ) 101 | expect(consoleLog).toHaveBeenCalledWith( 102 | 'Previous Value: Some Previous value', 103 | ) 104 | expect(consoleLog).toHaveBeenCalledWith(' Current Value: Test Value') 105 | expect(consoleLog).toHaveBeenCalledTimes(2) 106 | expect(consoleGroupEnd).toHaveBeenCalled() 107 | }) 108 | 109 | it('does not print group per config', () => { 110 | print({ 111 | ...printProps, 112 | flags: { 113 | isGrouped: false, 114 | }, 115 | }) 116 | 117 | expect(consoleGroup).not.toHaveBeenCalled() 118 | expect(consoleLog).toHaveBeenCalledWith(' On Change: Test Value') 119 | expect(consoleGroupEnd).not.toHaveBeenCalled() 120 | }) 121 | 122 | it('prints collapsed group per config', () => { 123 | print({ 124 | ...printProps, 125 | flags: { 126 | isGrouped: true, 127 | isCollapsed: true, 128 | }, 129 | }) 130 | 131 | expect(consoleGroup).not.toHaveBeenCalled() 132 | expect(consoleGroupCollapsed).toHaveBeenCalledWith( 133 | 'Change in @ 09:38 PM', 134 | undefined, 135 | undefined, 136 | ) 137 | expect(consoleLog).toHaveBeenCalledWith(' On Change: Test Value') 138 | expect(consoleGroupEnd).toHaveBeenCalled() 139 | }) 140 | 141 | it('prints with custom printer', () => { 142 | const printer: Printer = { log: jest.fn() } 143 | const printPropsWithPrinter: _PrintConfig = { 144 | ...printProps, 145 | printer, 146 | } 147 | const printerLog = jest 148 | .spyOn(printer, 'log') 149 | .mockImplementation(() => 'Some logs') 150 | 151 | print(printPropsWithPrinter) 152 | 153 | expect(consoleLog).not.toHaveBeenCalled() 154 | 155 | expect(consoleGroup).toHaveBeenCalled() 156 | expect(printerLog).toHaveBeenCalled() 157 | expect(consoleGroupEnd).toHaveBeenCalled() 158 | }) 159 | 160 | it('prints with custom empty printer', () => { 161 | const printer: Printer = {} 162 | const printPropsWithPrinter: _PrintConfig = { 163 | ...printProps, 164 | printer, 165 | } 166 | 167 | print(printPropsWithPrinter) 168 | 169 | expect(consoleGroup).toHaveBeenCalled() 170 | expect(consoleLog).toHaveBeenCalled() 171 | expect(consoleGroupEnd).toHaveBeenCalled() 172 | }) 173 | 174 | it('prints with custom log level', () => { 175 | const consoleWarn = jest 176 | .spyOn(console, 'warn') 177 | .mockImplementation(() => null) 178 | print({ 179 | ...printProps, 180 | logLevel: 'warn', 181 | }) 182 | 183 | expect(consoleGroup).toHaveBeenCalled() 184 | expect(consoleLog).not.toHaveBeenCalled() 185 | expect(consoleWarn).toHaveBeenCalled() 186 | expect(consoleGroupEnd).toHaveBeenCalled() 187 | }) 188 | 189 | it('prints without label', () => { 190 | print(printProps) 191 | 192 | expect(consoleLog).toHaveBeenCalledWith(' On Change: Test Value') 193 | }) 194 | 195 | it('prints with custom group label', () => { 196 | print({ 197 | ...printProps, 198 | groupLabelRenderer: (type, name) => `${type} ${name}`, 199 | }) 200 | 201 | expect(consoleGroup).toHaveBeenCalledWith( 202 | 'Change SomeComponentName', 203 | undefined, 204 | undefined, 205 | ) 206 | }) 207 | }) 208 | 209 | describe('getMessage', () => { 210 | it('returns message', () => { 211 | expect(getMessage('Test Value', 'Some Label')).toEqual( 212 | ' Some Label: Test Value', 213 | ) 214 | }) 215 | 216 | it('returns message without label', () => { 217 | expect(getMessage('Test Value')).toEqual(' Test Value') 218 | }) 219 | 220 | it('returns message with css', () => { 221 | expect(getMessage('Test Value', 'Some Label', true)).toEqual( 222 | ' Some Label: %cTest Value', 223 | ) 224 | }) 225 | 226 | it('returns message with object', () => { 227 | expect(getMessage({ a: 'Test', b: 'Value' }, 'Some Label')).toEqual( 228 | ' Some Label: {"a":"Test","b":"Value"}', 229 | ) 230 | }) 231 | 232 | it('returns message with array', () => { 233 | expect(getMessage(['Test', 'Value'], 'Some Label')).toEqual( 234 | ' Some Label: ["Test","Value"]', 235 | ) 236 | }) 237 | 238 | it('returns message with array of objects', () => { 239 | expect( 240 | getMessage( 241 | [ 242 | { a: 'Test', b: 'Value' }, 243 | { c: 'Test', d: 'Value' }, 244 | ], 245 | 'Some Label', 246 | ), 247 | ).toEqual( 248 | ' Some Label: [{"a":"Test","b":"Value"},{"c":"Test","d":"Value"}]', 249 | ) 250 | }) 251 | }) 252 | 253 | describe('getRenderFunctionProps', () => { 254 | const printProps: _PrintConfig = { 255 | value: 'Test Value', 256 | componentName: 'SomeComponentName', 257 | printer: console, 258 | logLevel: 'warn', 259 | } 260 | 261 | it('returns print props', () => { 262 | expect(utils.getRenderFunctionProps(printProps, false)).toHaveProperty( 263 | 'logLevel', 264 | ) 265 | }) 266 | 267 | it('returns print props', () => { 268 | expect(utils.getRenderFunctionProps(printProps, true)).not.toHaveProperty( 269 | 'logLevel', 270 | ) 271 | }) 272 | }) 273 | }) 274 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as utils from './utils' 2 | import { 3 | Printer, 4 | _PrintConfig, 5 | ComponentLifecycleLabels, 6 | _SupportedConsole, 7 | RenderProps, 8 | } from './types' 9 | import { 10 | CURRENT_VALUE_LABEL, 11 | DEFAULT_LABEL_SIZE, 12 | PREVIOUS_VALUE_LABEL, 13 | } from './constants' 14 | 15 | export function isObjectOrArray(value: T): boolean { 16 | return Array.isArray(value) || (typeof value === 'object' && value !== null) 17 | } 18 | 19 | /* istanbul ignore next */ 20 | export function getCurrentTime(): string { 21 | // No need in testing Date module 22 | return new Date().toLocaleTimeString() 23 | } 24 | 25 | export function stylePlaceholder(withCss?: boolean): string { 26 | return withCss ? '%c' : '' 27 | } 28 | 29 | export function getLabel(type: ComponentLifecycleLabels): string { 30 | return `On ${type}` 31 | } 32 | 33 | export function getMessage( 34 | value: T, 35 | label?: string, 36 | withCss?: boolean, 37 | inline = true, 38 | ): string { 39 | const printLabel = label 40 | ? `${label.padStart(DEFAULT_LABEL_SIZE, ' ')}: ` 41 | : ''.padStart(DEFAULT_LABEL_SIZE + 2, ' ') 42 | 43 | const printValue = isObjectOrArray(value) 44 | ? JSON.stringify(value) 45 | : String(value) 46 | 47 | return `${printLabel}${stylePlaceholder(withCss)}${inline ? printValue : ''}` 48 | } 49 | 50 | export function getGroupLabel( 51 | type: ComponentLifecycleLabels, 52 | componentName?: string, 53 | withComponentCSS?: boolean, 54 | withSubValueCSS?: boolean, 55 | ): string { 56 | const componentCssPlaceholder = stylePlaceholder(withComponentCSS) 57 | const subValueCssPlaceholder = stylePlaceholder(withComponentCSS) 58 | 59 | const componentNameWrapper = componentName 60 | ? `in ${componentCssPlaceholder}<${String(componentName)} /> ` 61 | : `${componentCssPlaceholder}` 62 | const typeWrapper = `${String(type)} ` 63 | const timeWrapper = `${subValueCssPlaceholder}@ ${utils.getCurrentTime()}` 64 | 65 | return `${typeWrapper}${componentNameWrapper}${timeWrapper}` 66 | } 67 | 68 | export function getComponentName( 69 | error = new Error( 70 | 'Getting the stack of error to parse it for component name', 71 | ), 72 | ): string { 73 | if (!error.stack) { 74 | return '' 75 | } 76 | 77 | const re = /(\w+)@|at (\w+) \(/g 78 | 79 | re.exec(error.stack) 80 | re.exec(error.stack) 81 | const m = re.exec(error.stack) 82 | 83 | return m ? String(m[1] || m[2]) : '' 84 | } 85 | 86 | export function getRenderFunctionProps( 87 | props: _PrintConfig, 88 | isRender?: boolean, 89 | ): _PrintConfig | RenderProps { 90 | if (isRender) { 91 | const renderProps: RenderProps = { 92 | value: props.value, 93 | prevValue: props.prevValue, 94 | type: props.type, 95 | componentName: props.componentName, 96 | inline: props.inline, 97 | flags: { 98 | isGrouped: props.flags?.isGrouped, 99 | isCollapsed: props.flags?.isCollapsed, 100 | }, 101 | } 102 | 103 | return renderProps 104 | } 105 | 106 | return props 107 | } 108 | 109 | export function getPrinter( 110 | printer: Printer | Console, 111 | method: keyof _SupportedConsole, 112 | ): _SupportedConsole[keyof _SupportedConsole] { 113 | return ( 114 | (printer && method in printer ? printer[method] : console[method]) ?? 115 | console[method] 116 | ) 117 | } 118 | 119 | export function print({ 120 | value, 121 | prevValue, 122 | componentName, 123 | flags = { 124 | isCollapsed: false, 125 | isGrouped: true, 126 | }, 127 | type = ComponentLifecycleLabels.Change, 128 | styles: { componentCSS, subValueCSS, changeCSS } = {}, 129 | printer = {}, 130 | logLevel = 'log', 131 | groupLabelRenderer, 132 | inline = true, 133 | }: _PrintConfig): void { 134 | const getCurrentPrinter = ( 135 | method: keyof _SupportedConsole, 136 | ): _SupportedConsole[keyof _SupportedConsole] => getPrinter(printer, method) 137 | 138 | if (flags.isGrouped) { 139 | getCurrentPrinter(flags.isCollapsed ? 'groupCollapsed' : 'group')( 140 | groupLabelRenderer 141 | ? groupLabelRenderer(type, componentName) 142 | : getGroupLabel( 143 | type, 144 | componentName, 145 | Boolean(componentCSS), 146 | Boolean(subValueCSS), 147 | ), 148 | componentCSS, 149 | subValueCSS, 150 | ) 151 | } 152 | 153 | const printAtLevel = (printValue: T, label?: string, css?: string): void => { 154 | const printer = getCurrentPrinter(logLevel) 155 | const message = getMessage(printValue, label, Boolean(css), inline) 156 | 157 | if (!css) printer(message) 158 | if (css) printer(message, css) 159 | } 160 | 161 | const printArDirLevel = (printValue: T): void => { 162 | const printer = getCurrentPrinter('dir') 163 | printer(printValue) 164 | } 165 | 166 | if ('prevValue' in arguments[0]) { 167 | printAtLevel(arguments[0].prevValue, PREVIOUS_VALUE_LABEL, subValueCSS) 168 | if (!inline) { 169 | printArDirLevel(arguments[0].prevValue) 170 | } 171 | 172 | printAtLevel(value, CURRENT_VALUE_LABEL, changeCSS) 173 | if (!inline) { 174 | printArDirLevel(value) 175 | } 176 | } else { 177 | printAtLevel(value, getLabel(type)) 178 | if (!inline) { 179 | printArDirLevel(value) 180 | } 181 | } 182 | 183 | if (flags.isGrouped) getCurrentPrinter('groupEnd')() 184 | } 185 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | /* Projects */ 5 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 7 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 11 | /* Language and Environment */ 12 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 13 | "lib": [ 14 | "ES2015", 15 | "ES2016", 16 | "ES2017", 17 | "DOM" 18 | ], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 19 | "jsx": "react", /* Specify what JSX code is generated. */ 20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 25 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 28 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 29 | /* Modules */ 30 | "module": "commonjs", /* Specify what module code is generated. */ 31 | // "rootDir": "./", /* Specify the root folder within your source files. */ 32 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 36 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 37 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 39 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 40 | // "resolveJsonModule": true, /* Enable importing .json files. */ 41 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 42 | /* JavaScript Support */ 43 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 44 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 45 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | /* Interop Constraints */ 71 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 72 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 73 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 74 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 75 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 76 | /* Type' Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 79 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 84 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | /* Completeness */ 97 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 98 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 99 | }, 100 | "include": [ 101 | "./**/*" 102 | ], 103 | } --------------------------------------------------------------------------------