├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── azure-pipelines.yml ├── jestconfig.json ├── package-lock.json ├── package.json ├── rollup.base.config.js ├── rollup.config.js ├── rollup.test.config.js ├── src ├── IReactAISettings.ts ├── ReactAI.ts ├── index.ts └── withAITracking.tsx ├── test ├── ReactAI.test.ts ├── TestComponent.tsx └── withAITracking.test.tsx ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | test-report.xml 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | # built output 65 | dist 66 | dist-* 67 | test-dist 68 | test-dist* 69 | browser 70 | test-browser -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | dist-esm/test 2 | test 3 | test-browser 4 | test-dist 5 | legacy -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest All", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "args": [ 10 | "--runInBand", 11 | "--config", 12 | "jestconfig.json" 13 | ], 14 | "console": "integratedTerminal", 15 | "internalConsoleOptions": "neverOpen", 16 | "disableOptimisticBPs": true, 17 | "windows": { 18 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 19 | } 20 | }, 21 | { 22 | "type": "node", 23 | "request": "launch", 24 | "name": "Jest Current File", 25 | "program": "${workspaceFolder}/node_modules/.bin/jest", 26 | "args": [ 27 | "${relativeFile}", 28 | "--config", 29 | "jestconfig.json" 30 | ], 31 | "console": "integratedTerminal", 32 | "internalConsoleOptions": "neverOpen", 33 | "disableOptimisticBPs": true, 34 | "windows": { 35 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 36 | } 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.organizeImports": true 4 | }, 5 | "prettier.printWidth": 120, 6 | "typescript.updateImportsOnFileMove.enabled": "always", 7 | "editor.formatOnSave": true, 8 | "[markdown]": { 9 | "editor.formatOnSave": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # react-appinsights Change Log 2 | 3 | All notable changes to this project are documented in this file. 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [3.0.0-rc6] - 2019-04-09 7 | 8 | ### Change 9 | - Upgraded Application Insights SDK to [latest release candidate](ai-latest-rc) 10 | - Upgraded various package 11 | - Updated readme 12 | 13 | ## [3.0.0-rc5] - 2019-03-18 14 | 15 | ### Change 16 | - Upgraded the underlying Application Insights reference to the [new beta version](ai-beta) 17 | - Completely rewritten in TypeScript 18 | 19 | ## [2.0.2] - 2019-01-20 20 | 21 | ### Change 22 | 23 | - Dependencies' versions bump - add support for React 16.8.x 24 | 25 | ## [2.0.1] - 2018-12-10 26 | 27 | ### Fixed 28 | 29 | - Fix `setAppContext` null issue on AI.queue #22 (thanks to @hiraldesai) 30 | - Make history `init` parameter optional in the TypeScript typings (thanks to @milohansen) 31 | - Bump `merge` indirect dependency to mitigate security vulnerability [772](https://www.npmjs.com/advisories/722) 32 | 33 | ## [2.0.0] - 2018-11-20 34 | 35 | ### Add 36 | 37 | - Component usage tracking API based on [higher-order components](https://reactjs.org/docs/higher-order-components.html): the `withTracking` function has to be used to add tracking to each component class 38 | - TypeScript types definition file 39 | 40 | ### Remove 41 | 42 | - Component usage tracking API based on inheritance 43 | - `ReactAI.trackRouterChange`, since `react-router` dropped support for `onXXX` properties [in v4](https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/guides/migrating.md#on-properties) 44 | 45 | ### Change 46 | 47 | - Dependencies' versions bump - add support for React 16.5.x 48 | 49 | ## 1.x.x - 2017 50 | 51 | ### Add 52 | 53 | - Component usage tracking API based on class inheritance: tracked components have to inherit from the `TrackedComponent` class 54 | - Tracking router changes API (compatible with `react-router` v3) 55 | - `setAppContext` function to enrich tracking with custom information 56 | - `ai` function to access Application Insights SDK API 57 | 58 | [unreleased]: https://github.com/Azure/react-appinsights/compare/v2.0.2...HEAD 59 | [2.0.0]: https://github.com/Azure/react-appinsights/compare/v1.0.4...v2.0.0 60 | [2.0.1]: https://github.com/Azure/react-appinsights/compare/v2.0.0...v2.0.1 61 | [2.0.2]: https://github.com/Azure/react-appinsights/compare/v2.0.1...v2.0.2 62 | [3.0.0-rc5]: https://github.com/Azure/react-appinsights/compare/v2.0.2...v3.0.0-rc5 63 | [3.0.0-rc6]: https://github.com/Azure/react-appinsights/compare/v3.0.0-rc5...v3.0.0-rc6 64 | [ai-beta]: https://www.npmjs.com/package/@microsoft/applicationinsights-web/v/2.0.0-beta.3 65 | [ai-latest-rc]: https://www.npmjs.com/package/@microsoft/applicationinsights-web/v/2.0.0-rc1 -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This project welcomes contributions and suggestions. Most contributions require you to 4 | agree to a Contributor License Agreement (CLA) declaring that you have the right to, 5 | and actually do, grant us the rights to use your contribution. For details, visit 6 | https://cla.microsoft.com. 7 | 8 | When you submit a pull request, a CLA-bot will automatically determine whether you need 9 | to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the 10 | instructions provided by the bot. You will only need to do this once across all repositories using our CLA. 11 | 12 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 13 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 14 | or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Corporation. All rights reserved. 2 | 3 | MIT License 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 | ## :warning: Deprecation Warning :warning: 2 | 3 | This repository has been archived and the corresponding npm package deprecated. The recommended way of using Application Insights 4 | in a React application is through its React plugin 5 | [`@microsoft/applicationinsights-react-js`][react-plugin-package], whose code and documentation reside in the 6 | [repository of Application Insights SDK for JavaScript][appinsights-repo]. 7 | The React plugin is based on version 3.x of react-appinsights. 8 | 9 | ---- 10 | 11 | # react-appinsights 12 | 13 | [![npm](https://img.shields.io/npm/v/react-appinsights.svg)](https://www.npmjs.com/package/react-appinsights) [![Build Status](https://dev.azure.com/azure-public/react-appinsights/_apis/build/status/Azure.react-appinsights)](https://dev.azure.com/azure-public/react-appinsights/_build/latest?definitionId=16) [![Greenkeeper badge](https://badges.greenkeeper.io/Azure/react-appinsights.svg)](https://greenkeeper.io/) [![Downloads per month](https://img.shields.io/npm/dm/react-appinsights.svg)](https://www.npmjs.com/package/react-appinsights) 14 | 15 | Javascript library to integrate [Application Insights][appinsights-js] in applications built with [React][react]. 16 | `react-appinsights` extends Application Insights with additional React-specific features: 17 | 18 | - tracking of router changes 19 | - React components usage statistics 20 | - API to extend the standard telemetry with additional dimensions 21 | 22 | ## Installation 23 | 24 | Using npm: 25 | 26 | ```bash 27 | npm install --save react-appinsights@beta 28 | ``` 29 | 30 | ## Usage 31 | 32 | To initialize Application Insights, add the following to the entry point 33 | file of your application (e.g. `index.js`): 34 | 35 | ```javascript 36 | import { reactAI } from "react-appinsights"; 37 | import { ApplicationInsights } from "@microsoft/applicationinsights-web"; 38 | 39 | let appInsights = new ApplicationInsights({ 40 | config: { 41 | instrumentationKey: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx", 42 | extensions: [reactAI], 43 | extensionConfig: { 44 | [reactAI.extensionId]: { debug: false } 45 | } 46 | } 47 | }); 48 | appInsights.loadAppInsights(); 49 | ``` 50 | 51 | See [this Application Insights tutorial for Node.js][appinsights-nodejs] 52 | for more details on how to obtain the instrumentation key. 53 | 54 | `IReactAISettings` has following non-mandatory configuration options to be passed into the `extensionConfig` object: 55 | 56 | ```typescript 57 | interface IReactAISettings { 58 | initialContext?: { [key: string]: any }; // Initial context to initialize with 59 | history?: History; // React router history - to enable page view tracking 60 | debug?: boolean; // Debug mode: displays debug messages from ReactAI in console 61 | } 62 | ``` 63 | 64 | #### Track router changes 65 | 66 | To track page views, pass a history object to the init method. 67 | For more information see the [documentation][react-router] of the `react-router` package. 68 | 69 | ```javascript 70 | import { reactAI } from "react-appinsights"; 71 | import { ApplicationInsights } from "@microsoft/applicationinsights-web"; 72 | import { Router } from "react-router-dom"; 73 | import { createBrowserHistory } from "history"; 74 | 75 | const history = createBrowserHistory(); 76 | 77 | /* 78 | * In the code sample above, set configuration as follows: 79 | * extensionConfig: { 80 | * [ReactAI.extensionId]: { history: history } 81 | * } 82 | */ 83 | 84 | ReactDOM.render( 85 | 86 | 87 | , 88 | document.getElementById("root") 89 | ); 90 | ``` 91 | 92 | #### Enable React components usage tracking 93 | 94 | To instrument various React components usage tracking, apply the `withAITracking` higher-order 95 | component function. 96 | 97 | ```javascript 98 | import { withAITracking } from 'react-appinsights'; 99 | 100 | class MyComponent extends React.Component { 101 | ... 102 | } 103 | 104 | export default withAITracking(MyComponent); 105 | ``` 106 | 107 | To change the name string of the component that appears in Application Insights, 108 | you can pass a custom name as second argument of `withAITracking`. 109 | 110 | ```javascript 111 | export default withAITracking(MyComponent, "CustomMyComponentName"); 112 | ``` 113 | 114 | It will measure time from the `ComponentDidMount` event through the `ComponentWillUnmount` event. 115 | However, in order to make this more accurate, it will subtract the time in which the user was idle. 116 | In other words, `React Component Engaged Time = ComponentWillUnmount timestamp - ComponentDidMount timestamp - idle time`. 117 | 118 | To see this metric in the Azure portal you need to navigate to the Application Insights resource, select "Metrics" tab and configure the empty charts to display Custom metric named "React Component Engaged Time (seconds)", select aggregation (sum, avg, etc.) of your liking and apply split by "Component Name". 119 | 120 | ![image](https://user-images.githubusercontent.com/1005174/51357010-c168ac80-1a71-11e9-8df9-348febd2d6dd.png) 121 | 122 | You can also run custom queries to slice and dice AI data to generate reports and visualizations as per your requirements. In the Azure portal, navigate to the Application Insights resource, select "Analytics" from the top menu of the Overview tab and run your query. 123 | 124 | ![image](https://user-images.githubusercontent.com/1005174/51356821-e872ae80-1a70-11e9-9e12-e56a1edcde68.png) 125 | 126 | Please note that it can take up to 10 minutes for new custom metric to appear in the Azure Portal. 127 | 128 | #### Set application context 129 | 130 | To augment all telemetry with additional properties use `setContext` method. For instance: 131 | 132 | ```javascript 133 | reactAI.setContext({ CorrelationId: "some-unique-correlation-id", Referrer: document.referrer }); 134 | ``` 135 | 136 | This will add CorrelationId and Referrer property to all page views, ajax calls, exceptions and other telemetry sent to Application Insights. 137 | 138 | 139 | 140 | #### Get original AppInsights object 141 | 142 | Use the following method to get the original AppInsights object: 143 | 144 | ```javascript 145 | var appInsights = reactAI.appInsights; 146 | ``` 147 | 148 | Refer to [this doc][appinsights-js-api] for information on the Javascript API of Application Insights. 149 | 150 | 151 | [react]: https://reactjs.org/ 152 | [appinsights-js]: https://docs.microsoft.com/en-us/azure/application-insights/app-insights-javascript 153 | [appinsights-nodejs]: https://azure.microsoft.com/en-us/documentation/articles/app-insights-nodejs/ 154 | [appinsights-js-api]: https://github.com/Microsoft/ApplicationInsights-JS/blob/master/API-reference.md 155 | [react-router]: https://github.com/ReactTraining/react-router/blob/master/FAQ.md#how-do-i-access-the-history-object-outside-of-components 156 | [react-plugin-package]: https://www.npmjs.com/package/@microsoft/applicationinsights-react-js 157 | [appinsights-repo]: https://github.com/microsoft/ApplicationInsights-JS -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Node.js with React 2 | # Build a Node.js project that uses React. 3 | # Add steps that analyze code, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/javascript 5 | 6 | pool: 7 | vmImage: 'Ubuntu 16.04' 8 | 9 | strategy: 10 | matrix: 11 | node_8_x: 12 | node_version: 8.x 13 | node_10_x: 14 | node_version: 10.x 15 | node_11_x: 16 | node_version: 11.x 17 | 18 | steps: 19 | - task: NodeTool@0 20 | inputs: 21 | versionSpec: $(node_version) 22 | displayName: 'Install Node.js' 23 | 24 | - script: | 25 | npm install 26 | npm run build 27 | npm test 28 | displayName: 'npm install, build and test' 29 | 30 | - task: PublishTestResults@2 31 | inputs: 32 | testResultsFiles: '**/test-report.xml' 33 | 34 | - task: PublishCodeCoverageResults@1 35 | inputs: 36 | codeCoverageTool: Cobertura 37 | summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml' 38 | reportDirectory: '$(System.DefaultWorkingDirectory)/coverage/lcov-report' -------------------------------------------------------------------------------- /jestconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "transform": { 3 | "^.+\\.(t|j)sx?$": "ts-jest" 4 | }, 5 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 6 | "moduleFileExtensions": [ 7 | "ts", 8 | "tsx", 9 | "js", 10 | "jsx", 11 | "json", 12 | "node" 13 | ], 14 | "testPathIgnorePatterns": [ 15 | "node_modules", 16 | "legacy", 17 | "dist", 18 | "dist-esm" 19 | ], 20 | "testResultsProcessor": "./node_modules/jest-junit-reporter", 21 | "collectCoverage": true, 22 | "coveragePathIgnorePatterns": [ 23 | "/node_modules/", 24 | "/test/" 25 | ], 26 | "coverageReporters": [ 27 | "cobertura", 28 | "lcov", 29 | "text" 30 | ] 31 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-appinsights", 3 | "version": "3.0.0", 4 | "description": "Application Insights module for React applications", 5 | "main": "dist/index.js", 6 | "module": "dist-esm/src/index.js", 7 | "browser": { 8 | "./dist/index.js": "./browser/index.js" 9 | }, 10 | "types": "dist-esm/src/index.d.ts", 11 | "scripts": { 12 | "clean": "rimraf dist dist-*", 13 | "build": "tsc -p . && rollup -c && rollup -c rollup.test.config.js", 14 | "test": "jest --config jestconfig.json", 15 | "test-watch": "jest --config jestconfig.json --watch", 16 | "format": "prettier --write \"src/**/*.ts\" \"src/**/*.js\"", 17 | "lint": "tslint -p tsconfig.json", 18 | "prepublishOnly": "npm test && npm run lint", 19 | "preversion": "npm run lint", 20 | "version": "npm run format && git add -A src", 21 | "postversion": "git push && git push --tags" 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/Azure/react-appinsights.git" 26 | }, 27 | "keywords": [ 28 | "Azure", 29 | "Cloud", 30 | "React", 31 | "ReactJS", 32 | "AppInsights", 33 | "Application Insights", 34 | "Microsoft Application Insights", 35 | "Telemetry" 36 | ], 37 | "author": "Microsoft Corporation", 38 | "license": "MIT", 39 | "bugs": { 40 | "url": "https://github.com/Azure/react-appinsights/issues" 41 | }, 42 | "engine": { 43 | "node": ">=8.0.0" 44 | }, 45 | "homepage": "https://github.com/Azure/react-appinsights#readme", 46 | "devDependencies": { 47 | "@types/enzyme": "^3.9.2", 48 | "@types/enzyme-adapter-react-16": "^1.0.5", 49 | "@types/history": "^4.7.2", 50 | "@types/jest": "^24.0.13", 51 | "@types/node": "^12.0.2", 52 | "@types/react": "^16.8.18", 53 | "@types/react-dom": "^16.8.4", 54 | "enzyme": "^3.9.0", 55 | "enzyme-adapter-react-16": "^1.13.1", 56 | "jest": "^24.8.0", 57 | "jest-junit-reporter": "^1.1.0", 58 | "prettier": "^1.17.1", 59 | "react-dom": "^16.8.6", 60 | "rimraf": "^2.6.3", 61 | "rollup": "1.12.3", 62 | "rollup-plugin-commonjs": "^9.3.4", 63 | "rollup-plugin-json": "^4.0.0", 64 | "rollup-plugin-local-resolve": "^1.0.7", 65 | "rollup-plugin-multi-entry": "^2.1.0", 66 | "rollup-plugin-node-builtins": "^2.1.2", 67 | "rollup-plugin-node-resolve": "^5.0.0", 68 | "rollup-plugin-replace": "^2.2.0", 69 | "rollup-plugin-terser": "^5.0.0", 70 | "rollup-plugin-uglify": "^6.0.2", 71 | "rollup-plugin-visualizer": "^1.1.1", 72 | "ts-jest": "^24.0.2", 73 | "tslint": "^5.16.0", 74 | "tslint-config-prettier": "^1.18.0", 75 | "typescript": "^3.4.5" 76 | }, 77 | "sideEffects": false, 78 | "dependencies": { 79 | "@microsoft/applicationinsights-common": "^2.0.0", 80 | "@microsoft/applicationinsights-web": "^2.0.0", 81 | "history": "^4.9.0", 82 | "react": "^16.8.6" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rollup.base.config.js: -------------------------------------------------------------------------------- 1 | import cjs from "rollup-plugin-commonjs"; 2 | import json from "rollup-plugin-json"; 3 | import multiEntry from "rollup-plugin-multi-entry"; 4 | import nodeBuiltins from "rollup-plugin-node-builtins"; 5 | import nodeResolve from "rollup-plugin-node-resolve"; 6 | import replace from "rollup-plugin-replace"; 7 | import { uglify } from "rollup-plugin-uglify"; 8 | import viz from "rollup-plugin-visualizer"; 9 | 10 | const pkg = require("./package.json"); 11 | const depNames = Object.keys(pkg.dependencies); 12 | const input = "dist-esm/src/index.js"; 13 | 14 | export function nodeConfig(test = false) { 15 | const externalNodeBuiltins = ["events"]; 16 | const baseConfig = { 17 | input: input, 18 | external: depNames.concat(externalNodeBuiltins), 19 | output: { file: "dist/index.js", format: "cjs", sourcemap: true }, 20 | plugins: [ 21 | json(), 22 | replace({ 23 | delimiters: ["", ""], 24 | values: { 25 | // replace dynamic checks with if (true) since this is for node only. 26 | // Allows rollup's dead code elimination to be more aggressive. 27 | "if (isNode)": "if (true)" 28 | } 29 | }), 30 | nodeResolve({ preferBuiltins: true }), 31 | cjs() 32 | ] 33 | }; 34 | 35 | if (test) { 36 | // entry point is every test file 37 | baseConfig.input = "dist-esm/test/**/*.js"; 38 | baseConfig.plugins.unshift(multiEntry({ exports: false })); 39 | 40 | // different output file 41 | baseConfig.output.file = "test-dist/index.js"; 42 | 43 | // mark assert as external 44 | baseConfig.external.push("assert"); 45 | } else { 46 | baseConfig.plugins.push(uglify()); 47 | } 48 | 49 | return baseConfig; 50 | } 51 | 52 | export function browserConfig(test = false) { 53 | const baseConfig = { 54 | input: input, 55 | external: ["ms-rest-js"], 56 | output: { 57 | file: "browser/index.js", 58 | format: "umd", 59 | name: "ExampleClient", 60 | sourcemap: true, 61 | globals: { "ms-rest-js": "msRest" } 62 | }, 63 | plugins: [ 64 | json(), 65 | replace( 66 | // ms-rest-js is externalized so users must include it prior to using this bundle. 67 | { 68 | delimiters: ["", ""], 69 | values: { 70 | // replace dynamic checks with if (false) since this is for 71 | // browser only. Rollup's dead code elimination will remove 72 | // any code guarded by if (isNode) { ... } 73 | "if (isNode)": "if (false)" 74 | } 75 | } 76 | ), 77 | nodeResolve({ 78 | preferBuiltins: false, 79 | browser: true 80 | }), 81 | cjs({ 82 | namedExports: { 83 | events: ["EventEmitter"], 84 | "node_modules/@microsoft/applicationinsights-core-js/browser/applicationinsights-core-js.min.js": [ 85 | "AppInsightsCore", 86 | "LoggingSeverity", 87 | "_InternalMessageId", 88 | "CoreUtils", 89 | "DiagnosticLogger" 90 | ], 91 | "node_modules/react/index.js": ["Children", "Component", "PropTypes", "createElement"], 92 | "node_modules/react-dom/index.js": ["render"] 93 | } 94 | }), 95 | nodeBuiltins(), 96 | viz({ filename: "browser/browser-stats.html", sourcemap: false }) 97 | ], 98 | onwarn 99 | }; 100 | 101 | if (test) { 102 | baseConfig.input = "dist-esm/test/**/*.js"; 103 | baseConfig.plugins.unshift(multiEntry({ exports: false })); 104 | baseConfig.output.file = "test-browser/index.js"; 105 | } else { 106 | baseConfig.plugins.push(uglify()); 107 | } 108 | 109 | return baseConfig; 110 | } 111 | 112 | function onwarn(warning, warn) { 113 | if (warning.code === "CIRCULAR_DEPENDENCY" && warning.importer.indexOf("duplex.js") > -1) { 114 | // circular dependencies in Stream (required by create-hmac via rollup-plugin-node-builtins) 115 | // These should be ignored per https://github.com/calvinmetcalf/rollup-plugin-node-builtins/issues/39 116 | return; 117 | } 118 | warn(warning); 119 | } 120 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import * as base from "./rollup.base.config"; 2 | 3 | export default [base.nodeConfig(), base.browserConfig()]; 4 | -------------------------------------------------------------------------------- /rollup.test.config.js: -------------------------------------------------------------------------------- 1 | import * as base from "./rollup.base.config"; 2 | 3 | export default [base.nodeConfig(true), base.browserConfig(true)]; 4 | -------------------------------------------------------------------------------- /src/IReactAISettings.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { History } from "history"; 5 | 6 | /** 7 | * Settings to initialize a ReactAI instance. 8 | * 9 | * @export 10 | * @interface IReactAISettings 11 | */ 12 | export default interface IReactAISettings { 13 | 14 | /** 15 | * Context/custom dimensions for initialization. 16 | * You can also do this post initialization using ReactAI.setContext() 17 | * 18 | * @type {{ [key: string]: any }} 19 | * @memberof IReactAISettings 20 | */ 21 | initialContext?: { [key: string]: any }; 22 | 23 | /** 24 | * React router history for enabling Application Insights PageView tracking. 25 | * 26 | * @type {History} 27 | * @memberof IReactAISettings 28 | */ 29 | history?: History; 30 | 31 | /** 32 | * Debug mode. 33 | * Enable this when developing to see debug messages from the library displayed on the console. 34 | * 35 | * @type {boolean} 36 | * @memberof IReactAISettings 37 | */ 38 | debug?: boolean; 39 | } 40 | -------------------------------------------------------------------------------- /src/ReactAI.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { PropertiesPluginIdentifier } from "@microsoft/applicationinsights-common"; 5 | import { IPlugin } from "@microsoft/applicationinsights-core-js"; 6 | import { ApplicationInsights as AppInsightsPlugin, IAppInsightsCore, IConfig, IConfiguration, IPageViewTelemetry, ITelemetryItem, ITelemetryPlugin, PropertiesPlugin } from "@microsoft/applicationinsights-web"; 7 | import { Action, History, Location } from "history"; 8 | import IReactAISettings from "./IReactAISettings"; 9 | 10 | /** 11 | * Module to include Microsoft Application Insights in React applications. 12 | * 13 | * @export 14 | * @class ReactAI 15 | */ 16 | class ReactAI implements ITelemetryPlugin { 17 | public extensionId: string = "ApplicationInsightsReactUsage"; 18 | public ApplicationInsightsAnalyticsIdentifier: string = "ApplicationInsightsAnalytics"; 19 | public processTelemetry: (env: ITelemetryItem) => void; 20 | public identifier = this.extensionId; 21 | public priority: number = 190; 22 | public appInsights!: AppInsightsPlugin; 23 | private propertiesPlugin!: PropertiesPlugin; 24 | 25 | private nextPlugin!: ITelemetryPlugin; 26 | private contextProps: { [key: string]: any } = {}; 27 | private debug: boolean = false; 28 | 29 | public constructor() { 30 | this.processTelemetry = this.customDimensionsInitializer.bind(this); 31 | } 32 | 33 | public setNextPlugin(plugin: ITelemetryPlugin) { 34 | this.nextPlugin = plugin; 35 | } 36 | 37 | /** 38 | * Returns the current value of context/custom dimensions. 39 | * 40 | * @readonly 41 | * @type {{ [key: string]: any }} 42 | * @memberof ReactAI 43 | */ 44 | public get context(): { [key: string]: any } { 45 | return this.contextProps || {}; 46 | } 47 | 48 | /** 49 | * Returns if ReactAI is in debug mode. 50 | * 51 | * @readonly 52 | * @type {boolean} 53 | * @memberof ReactAI 54 | */ 55 | public get isDebugMode(): boolean { 56 | return this.debug; 57 | } 58 | 59 | /** 60 | * Initializes a singleton instance of ReactAI based on supplied parameters. 61 | * 62 | * @param {IReactAISettings} settings 63 | * @memberof ReactAI 64 | */ 65 | public initialize( 66 | settings: IReactAISettings & IConfiguration & IConfig, 67 | core: IAppInsightsCore, 68 | extensions: IPlugin[] 69 | ): void { 70 | const reactAISettings = 71 | settings.extensionConfig && settings.extensionConfig[this.identifier] 72 | ? (settings.extensionConfig[this.identifier] as IReactAISettings) 73 | : { debug: false }; 74 | this.debug = reactAISettings.debug || false; 75 | this.setContext(reactAISettings.initialContext || {}, true); 76 | extensions.forEach(ext => { 77 | let identifier = (ext as ITelemetryPlugin).identifier; 78 | if (identifier === this.ApplicationInsightsAnalyticsIdentifier) { 79 | this.appInsights = (ext) as AppInsightsPlugin; 80 | } 81 | 82 | if (identifier === PropertiesPluginIdentifier) { 83 | this.propertiesPlugin = (ext) as PropertiesPlugin; 84 | } 85 | }); 86 | if (reactAISettings.history) { 87 | this.addHistoryListener(reactAISettings.history); 88 | const pageViewTelemetry: IPageViewTelemetry = { 89 | uri: reactAISettings.history.location.pathname, 90 | properties: this.context 91 | }; 92 | this._trackInitialPageViewInternal(pageViewTelemetry); 93 | } 94 | } 95 | 96 | // internal only, public method for testing 97 | public _trackInitialPageViewInternal(telemetry: IPageViewTelemetry) { 98 | // Record initial page view, since history.listen is not fired for the initial page 99 | // (see: https://github.com/ReactTraining/history/issues/479#issuecomment-307544999 ) 100 | this.appInsights.trackPageView(telemetry); 101 | this.debugLog("recording initial page view.", `uri: ${location.pathname}`); 102 | } 103 | 104 | /** 105 | * Set custom context/custom dimensions for Application Insights 106 | * 107 | * @param {{ [key: string]: any }} properties - custom properties to add to all outbound Application Insights telemetry 108 | * @param {boolean} [clearPrevious=false] - if false(default) multiple calls to setContext will append to/overwrite existing custom dimensions, if true the values are reset 109 | * @memberof ReactAI 110 | */ 111 | public setContext(properties: { [key: string]: any }, clearPrevious: boolean = false): void { 112 | if (clearPrevious) { 113 | this.contextProps = {}; 114 | this.debugLog("context is reset."); 115 | } 116 | properties = properties || {}; 117 | for (const key in properties) { 118 | if (properties.hasOwnProperty(key)) { 119 | this.contextProps[key] = properties[key]; 120 | } 121 | } 122 | this.debugLog("context is set to:", this.context); 123 | } 124 | 125 | private customDimensionsInitializer(envelope: ITelemetryItem): boolean | void { 126 | envelope.baseData = envelope.baseData || {}; 127 | envelope.baseData.properties = envelope.baseData.properties || {}; 128 | const properties = envelope.baseData.properties; 129 | const props = this.context; 130 | for (const key in props) { 131 | if (props.hasOwnProperty(key)) { 132 | properties[key] = props[key]; 133 | } 134 | } 135 | if (this.nextPlugin != null) { 136 | this.nextPlugin.processTelemetry(envelope); 137 | } 138 | } 139 | 140 | private addHistoryListener(history: History): void { 141 | history.listen( 142 | (location: Location, action: Action): void => { 143 | // Timeout to ensure any changes to the DOM made by route changes get included in pageView telemetry 144 | setTimeout(() => { 145 | const pageViewTelemetry: IPageViewTelemetry = { uri: location.pathname, properties: this.context }; 146 | this.appInsights.trackPageView(pageViewTelemetry); 147 | this.debugLog("recording page view.", `uri: ${location.pathname} action: ${action}`); 148 | }, 500); 149 | } 150 | ); 151 | } 152 | 153 | private debugLog(message: string, payload?: any): void { 154 | if (this.isDebugMode) { 155 | console.log(`ReactAI: ${message}`, payload === undefined ? "" : payload); 156 | } 157 | } 158 | } 159 | 160 | export const reactAI = new ReactAI(); 161 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import IReactAISettings from "./IReactAISettings"; 5 | import { reactAI } from "./ReactAI"; 6 | import withAITracking from "./withAITracking"; 7 | 8 | export { reactAI, IReactAISettings, withAITracking }; 9 | 10 | -------------------------------------------------------------------------------- /src/withAITracking.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { IMetricTelemetry } from "@microsoft/applicationinsights-web"; 5 | import * as React from "react"; 6 | import { reactAI } from "./ReactAI"; 7 | 8 | /** 9 | * Higher-order component function to hook Application Insights tracking 10 | * in a React component's lifecycle. 11 | * 12 | * @param Component the component to be instrumented with Application Insights tracking 13 | * @param componentName (optional) component name 14 | */ 15 | export default function withAITracking

(Component: React.ComponentType

, componentName?: string): React.ComponentClass

{ 16 | 17 | if (componentName === undefined || componentName === null || typeof componentName !== 'string') { 18 | componentName = Component.prototype.constructor.name; 19 | } 20 | 21 | return class extends React.Component

{ 22 | private mountTimestamp: number = 0; 23 | private firstActiveTimestamp: number = 0; 24 | private idleStartTimestamp: number = 0; 25 | private lastActiveTimestamp: number = 0; 26 | private totalIdleTime: number = 0; 27 | private idleCount: number = 0; 28 | private idleTimeout: number = 5000; 29 | private intervalId?: NodeJS.Timeout; 30 | 31 | public componentDidMount() { 32 | this.mountTimestamp = Date.now(); 33 | this.firstActiveTimestamp = 0; 34 | this.totalIdleTime = 0; 35 | this.lastActiveTimestamp = 0; 36 | this.idleStartTimestamp = 0; 37 | this.idleCount = 0; 38 | 39 | this.intervalId = setInterval(() => { 40 | if (this.lastActiveTimestamp > 0 && this.idleStartTimestamp === 0 && Date.now() - this.lastActiveTimestamp >= this.idleTimeout) { 41 | this.idleStartTimestamp = Date.now(); 42 | this.idleCount++; 43 | this.debugLog("componentDidMount", "Starting idle time."); 44 | } 45 | }, 100); 46 | } 47 | 48 | public componentWillUnmount() { 49 | if (this.mountTimestamp === 0) { 50 | throw new Error("withAITracking:componentWillUnmount: mountTimestamp isn't initialized."); 51 | } 52 | 53 | if (!reactAI.appInsights) { 54 | throw new Error("withAITracking:componentWillUnmount: ReactAI isn't initialized."); 55 | } 56 | 57 | if (this.intervalId) { 58 | clearInterval(this.intervalId); 59 | } 60 | 61 | if (this.firstActiveTimestamp === 0) { 62 | this.debugLog("componentWillUnmount", "Nothing to track."); 63 | return; 64 | } 65 | 66 | const engagementTime = this.getEngagementTimeSeconds(); 67 | const metricData: IMetricTelemetry = { 68 | average: engagementTime, 69 | name: "React Component Engaged Time (seconds)", 70 | sampleCount: 1 71 | }; 72 | 73 | const additionalProperties: { [key: string]: any } = { "Component Name": componentName }; 74 | this.debugLog( 75 | "componentWillUnmount", 76 | `Tracking ${engagementTime} seconds of engagement time for ${componentName}.` 77 | ); 78 | reactAI.appInsights.trackMetric(metricData, additionalProperties); 79 | } 80 | 81 | public render() { 82 | return ( 83 |

91 | 92 |
93 | ); 94 | } 95 | 96 | private trackActivity = (e: React.SyntheticEvent): void => { 97 | if (this.firstActiveTimestamp === 0) { 98 | this.firstActiveTimestamp = Date.now(); 99 | this.lastActiveTimestamp = this.firstActiveTimestamp; 100 | } else { 101 | this.lastActiveTimestamp = Date.now(); 102 | } 103 | 104 | if (this.idleStartTimestamp > 0) { 105 | const lastIdleTime = this.lastActiveTimestamp - this.idleStartTimestamp; 106 | this.totalIdleTime += lastIdleTime; 107 | this.debugLog("trackActivity", `Idle to active added ${lastIdleTime / 1000} seconds of idle time.`); 108 | this.idleStartTimestamp = 0; 109 | } 110 | } 111 | 112 | private debugLog(from: string, message: string): void { 113 | if (reactAI.isDebugMode) { 114 | console.log(`withAITracking:${componentName}:${from}: ${message}`, { 115 | engagementTime: this.getEngagementTimeSeconds(), 116 | firstActiveTime: this.firstActiveTimestamp, 117 | idleStartTime: this.idleStartTimestamp, 118 | idleTimeMs: this.totalIdleTime, 119 | lastActiveTime: this.lastActiveTimestamp, 120 | mountTimestamp: this.mountTimestamp 121 | }); 122 | } 123 | } 124 | 125 | private getEngagementTimeSeconds(): number { 126 | return (Date.now() - this.firstActiveTimestamp - this.totalIdleTime - this.idleCount * this.idleTimeout) / 1000; 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /test/ReactAI.test.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { ApplicationInsights } from '@microsoft/applicationinsights-web'; 5 | import createHistory from "history/createBrowserHistory"; 6 | import { IReactAISettings, reactAI } from '../src'; 7 | 8 | const IKEY: string = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx"; 9 | 10 | let appInsights: ApplicationInsights; 11 | 12 | describe("ReactAI", () => { 13 | 14 | function init(reactAIconfig: IReactAISettings) { 15 | reactAI._trackInitialPageViewInternal = jest.fn(); 16 | appInsights = new ApplicationInsights({ 17 | config: { 18 | extensionConfig: { 19 | [reactAI.extensionId]: reactAIconfig 20 | }, 21 | extensions: [reactAI], 22 | instrumentationKey: IKEY 23 | } 24 | }); 25 | appInsights.loadAppInsights(); 26 | } 27 | 28 | it("initializes correctly", () => { 29 | init({}); 30 | expect(reactAI).not.toBe(undefined); 31 | expect(appInsights).not.toBe(undefined); 32 | expect(reactAI.isDebugMode).toBe(false); 33 | }); 34 | 35 | it("sets debug mode as expected", () => { 36 | init({ debug: true }); 37 | expect(reactAI.isDebugMode).toBe(true); 38 | }); 39 | 40 | it("sets context correctly", () => { 41 | init({}); 42 | reactAI.setContext({ prop1: "value1", prop2: "value2" }); 43 | expect(reactAI.context.prop1).toBe("value1"); 44 | expect(reactAI.context.prop2).toBe("value2"); 45 | }); 46 | 47 | it("resets context correctly", () => { 48 | init({}); 49 | reactAI.setContext({ prop1: "value1" }); 50 | expect(reactAI.context.prop1).toBe("value1"); 51 | 52 | reactAI.setContext({ prop2: "value2" }, true); 53 | expect(reactAI.context.prop2).toBe("value2"); 54 | expect(reactAI.context.prop1).toBe(undefined); 55 | }); 56 | 57 | it("resets context on initialization", () => { 58 | init({ initialContext: { prop1: "value1" } }); 59 | expect(reactAI.context.prop1).toBe("value1"); 60 | expect(reactAI.context.prop2).toBe(undefined); 61 | }); 62 | 63 | it("tracks page views", () => { 64 | const emulatedHistory = createHistory(); 65 | const initialContext = { prop1: "value1" }; 66 | jest.useFakeTimers(); 67 | init({ debug: false, initialContext, history: emulatedHistory }); 68 | 69 | // Mock the internal instance of AppInsights 70 | reactAI.appInsights.trackPageView = jest.fn(); 71 | reactAI.appInsights.addTelemetryInitializer = jest.fn(); 72 | 73 | const pageViewTelemetry1 = { uri: "/", properties: initialContext }; 74 | expect(reactAI._trackInitialPageViewInternal).toHaveBeenCalledTimes(1); 75 | expect(reactAI._trackInitialPageViewInternal).toHaveBeenNthCalledWith(1, pageViewTelemetry1); 76 | 77 | // Emulate navigation to different URL-addressed pages 78 | emulatedHistory.push("/home", { some: "state" }); 79 | emulatedHistory.push("/new-fancy-page"); 80 | jest.runOnlyPendingTimers(); 81 | 82 | const pageViewTelemetry2 = { uri: "/home", properties: initialContext }; 83 | const pageViewTelemetry3 = { uri: "/new-fancy-page", properties: initialContext }; 84 | expect(reactAI.appInsights.trackPageView).toHaveBeenCalledTimes(2); 85 | expect(reactAI.appInsights.trackPageView).toHaveBeenNthCalledWith(1, pageViewTelemetry2); 86 | expect(reactAI.appInsights.trackPageView).toHaveBeenNthCalledWith(2, pageViewTelemetry3); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /test/TestComponent.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import * as React from "react"; 5 | 6 | export interface ITestComponentProps { 7 | prop1?: number; 8 | prop2?: string; 9 | } 10 | 11 | export class TestComponent extends React.Component { 12 | public render() { 13 | const { prop1, prop2 } = this.props; 14 | return ( 15 |
16 | prop1: {prop1}, prop2: {prop2} 17 |
18 | ); 19 | } 20 | } 21 | 22 | export default TestComponent; 23 | -------------------------------------------------------------------------------- /test/withAITracking.test.tsx: -------------------------------------------------------------------------------- 1 | // Copyright (c) Microsoft Corporation. All rights reserved. 2 | // Licensed under the MIT License. 3 | 4 | import { ApplicationInsights, IMetricTelemetry, IPageViewTelemetry } from "@microsoft/applicationinsights-web"; 5 | import * as Enzyme from "enzyme"; 6 | import * as Adapter from "enzyme-adapter-react-16"; 7 | import * as React from "react"; 8 | import { reactAI, withAITracking } from "../src"; 9 | import { TestComponent } from "./TestComponent"; 10 | 11 | const IKEY: string = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx"; 12 | Enzyme.configure({ adapter: new Adapter.default() }); 13 | 14 | let trackMetricSpy: jest.SpyInstance; 15 | let appInsights: ApplicationInsights; 16 | 17 | describe("withAITracking(TestComponent)", () => { 18 | const TestComponentWithTracking = withAITracking(TestComponent); 19 | const trackedTestComponentWrapper = () => Enzyme.shallow(); 20 | 21 | beforeEach(() => { 22 | appInsights = new ApplicationInsights({ 23 | config: { 24 | extensionConfig: { 25 | [reactAI.extensionId]: { debug: false } 26 | }, 27 | extensions: [reactAI], 28 | instrumentationKey: IKEY 29 | } 30 | }); 31 | appInsights.loadAppInsights(); 32 | trackMetricSpy = jest.spyOn(reactAI.appInsights, "trackMetric"); 33 | }); 34 | 35 | it("should wrap ", () => { 36 | const component = trackedTestComponentWrapper(); 37 | expect(component.find(TestComponent).length).toBe(1); 38 | }); 39 | 40 | it("shouldn't call trackMetric if there's no user interaction", () => { 41 | const component = trackedTestComponentWrapper(); 42 | component.unmount(); 43 | expect(trackMetricSpy).toHaveBeenCalledTimes(0); 44 | }); 45 | 46 | it("should call trackMetric if there is user interaction", () => { 47 | const component = trackedTestComponentWrapper(); 48 | component.simulate("keydown"); 49 | component.unmount(); 50 | 51 | expect(trackMetricSpy).toHaveBeenCalledTimes(1); 52 | const metricTelemetry: IMetricTelemetry & IPageViewTelemetry = { 53 | average: expect.any(Number), 54 | name: "React Component Engaged Time (seconds)", 55 | properties: expect.any(Object), 56 | sampleCount: 1 57 | }; 58 | expect(trackMetricSpy).toHaveBeenCalledWith(metricTelemetry, { "Component Name": "TestComponent" }); 59 | }); 60 | 61 | it("should use the passed component name in trackMetric", () => { 62 | const TestComponentWithTrackingCustomName = withAITracking(TestComponent, "MyCustomName"); 63 | const component = Enzyme.shallow(); 64 | component.simulate("mousemove"); 65 | component.unmount(); 66 | 67 | expect(trackMetricSpy).toHaveBeenCalledTimes(1); 68 | const metricTelemetry: IMetricTelemetry & IPageViewTelemetry = { 69 | average: expect.any(Number), 70 | name: "React Component Engaged Time (seconds)", 71 | properties: expect.any(Object), 72 | sampleCount: 1 73 | }; 74 | expect(trackMetricSpy).toHaveBeenCalledWith(metricTelemetry, { "Component Name": "MyCustomName" }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, 5 | "module": "es6" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | "jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */, 10 | "declaration": true /* Generates corresponding '.d.ts' file. */, 11 | "declarationMap": true /* Generates a sourcemap for each corresponding '.d.ts' file. */, 12 | "sourceMap": true /* Generates corresponding '.map' file. */, 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./dist-esm" /* Redirect output structure to the directory. */, 15 | // "rootDir": "." /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | // "noEmit": true, /* Do not emit outputs. */ 19 | "importHelpers": true /* Import emit helpers from 'tslib'. */, 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | "strict": true /* Enable all strict type-checking options. */, 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 29 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 30 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 31 | 32 | /* Additional Checks */ 33 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 34 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 35 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 36 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 37 | 38 | /* Module Resolution Options */ 39 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 40 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [] /* List of root folders whose combined content represents the structure of the project at runtime. */, 43 | "typeRoots": ["./node_modules/@types"] /* List of folders to include type definitions from. */, 44 | //"types": [], /* Type declaration files to be included in compilation. */ 45 | "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */, 46 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | 49 | /* Source Map Options */ 50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | "forceConsistentCasingInFileNames": true 59 | }, 60 | "include": ["src", "test"], 61 | "exclude": ["legacy", "node_modules", "types"] 62 | } 63 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "defaultSeverity": "warning", 4 | "rules": { 5 | "no-console": false, 6 | "object-literal-sort-keys": false 7 | } 8 | } 9 | --------------------------------------------------------------------------------