├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .github
├── FUNDING.yml
├── images
│ ├── react-freeze.png
│ └── react-freeze.svg
└── workflows
│ └── main.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── package.json
├── src
├── .eslintrc
├── index.test.tsx
├── index.tsx
└── react-app-env.d.ts
├── tsconfig.json
├── tsconfig.test.json
└── yarn.lock
/.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
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | build/
2 | dist/
3 | node_modules/
4 | .snapshots/
5 | *.min.js
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": [
4 | "standard",
5 | "standard-react",
6 | "plugin:prettier/recommended",
7 | "prettier/standard",
8 | "prettier/react",
9 | "plugin:@typescript-eslint/eslint-recommended"
10 | ],
11 | "env": {
12 | "node": true
13 | },
14 | "parserOptions": {
15 | "ecmaVersion": 2020,
16 | "ecmaFeatures": {
17 | "legacyDecorators": true,
18 | "jsx": true
19 | }
20 | },
21 | "settings": {
22 | "react": {
23 | "version": "16"
24 | }
25 | },
26 | "rules": {
27 | "space-before-function-paren": 0,
28 | "react/prop-types": 0,
29 | "react/jsx-handler-names": 0,
30 | "react/jsx-fragments": 0,
31 | "react/no-unused-prop-types": 0,
32 | "import/export": 0
33 | },
34 | "overrides": [
35 | {
36 | "files": ["**/*.ts", "**/*.tsx"],
37 | "plugins": ["@typescript-eslint"],
38 | "rules": {
39 | "no-use-before-define": "off",
40 | "@typescript-eslint/no-use-before-define": ["error"]
41 | }
42 | }
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: software-mansion
2 |
--------------------------------------------------------------------------------
/.github/images/react-freeze.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gaearon/react-freeze/b080a83e94f7d26228f589ff801ef3b5f8e2a4e3/.github/images/react-freeze.png
--------------------------------------------------------------------------------
/.github/images/react-freeze.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 | on:
3 | pull_request:
4 | branches:
5 | - main
6 | paths:
7 | - "src/**"
8 | - "*"
9 | push:
10 | branches:
11 | - main
12 | jobs:
13 | check:
14 | runs-on: ubuntu-latest
15 | concurrency:
16 | group: test-${{ github.ref }}
17 | cancel-in-progress: true
18 | steps:
19 | - name: checkout
20 | uses: actions/checkout@v2
21 | - name: Use Node.js 16
22 | uses: actions/setup-node@v2
23 | with:
24 | node-version: 16
25 | cache: "yarn"
26 | - name: Install dependencies
27 | run: yarn
28 | - name: Run unit tests
29 | run: yarn test:unit
30 | - name: Run linter
31 | run: yarn test:lint
32 | - name: Check types
33 | run: yarn test:types
34 | - name: Build
35 | run: yarn test:build
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | *.tgz
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Software Mansion
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 Freeze
4 |
5 |
6 | Prevent React component subtrees from rendering.
7 |
8 |
9 | # What is this? 🤔
10 |
11 | This library lets you freeze the renders of parts of the React component tree using `Suspense` mechanism introduced in React 17.
12 | The main use case of this library is to avoid unnecessary re-renders of parts of the app that are not visible to the user at a given moment.
13 | The frozen components **are not unmounted** when they are replaced with a placeholder view, so their React state and corresponding native view instances are retained during the freeze (DOM elements for `react-dom` or platform-native views for React Native apps) keeping things like scroll position, input state, or loaded images (for `` components) unchanged.
14 |
15 | The most prominent use case is the navigation in React Native apps, which typically is based on a stack.
16 | When opening a new screen, we push the screen onto the stack but also keep the previous screens on the stack in case the user goes back to them later.
17 | Since we want to keep the state of those previous screens, the components rendering it are retained on the stack, which results in them receiving updates (e.g. redux store changes) and getting re-rendered, even though they are completely covered by other screens.
18 | With `react-freeze`, we are able to suspend renders for the hidden screens and, as a result, save React from doing unnecessary computation (such as reconciliation, sending view change updates, etc.).
19 |
20 | # Quick start with react-navigation (React Native) 🧭
21 |
22 | > You have to be using React Native 0.64 or higher and react-navigation 5.x or 6.x.
23 |
24 | Thanks to the fact [react-navigation](https://reactnavigation.org/) relies on [react-native-screens](https://github.com/software-mansion/react-native-screens) we published an updated version of the screens library that takes advantage of the information about which screens should be active.
25 | In order to try react-freeze in your React Native app that uses React Navigation you need to upgrade react-native-screens to version 3.9.0:
26 |
27 | ```bash
28 | yarn upgrade react-native-screens@3.9.0
29 | ```
30 |
31 | If you're building for ios you also need to run
32 |
33 | ```bash
34 | pod install --project-directory=ios
35 | ```
36 |
37 | Then enable experimental freeze support using new method we added to screens library.
38 | In order to do that, add the following snippet (along with the import) to the main file of your application:
39 |
40 | ```js
41 | import { enableFreeze } from "react-native-screens";
42 |
43 | enableFreeze(true);
44 | ```
45 |
46 | The new react-native-screens library is compatible with React Navigation v5 and v6, however, when using React Navigation v5 you also need to enable "screens" support. This can be done by adding call to `enableScreens(true)` in your main application file and passing `detachInactiveScreens` option to your navigators (stack / tabs / etc.).
47 |
48 | **IMPORTANT:** The current version of screens/freeze integration, freezes updates only on views that are more than one level deep the stack hierarchy (the top and second to top screens are not freezing). This is necessary for slide-to-go-back functionality to work as by sliding back we may reveal the content that is displayed below. This is something we plan on improving in the future such that only the top screen is not frozen.
49 |
50 | # Quick start - using Freeze directly (React and React Native) ⚡
51 |
52 | > In order to use this package you'll have to be using React 17 or higher, or React Native 0.64 or higher.
53 |
54 | Install `react-freeze` package from npm:
55 |
56 | ```bash
57 | yarn add react-freeze
58 | ```
59 |
60 | Import `Freeze` component in your app:
61 |
62 | ```js
63 | import { Freeze } from "react-freeze";
64 | ```
65 |
66 | Wrap some components you want to freeze and pass `freeze` option to control whether renders in that components should be suspended:
67 |
68 | ```js
69 | function SomeComponent({ shouldSuspendRendering }) {
70 | return (
71 |
72 |
73 |
74 | );
75 | }
76 | ```
77 |
78 | # Component docs 📚
79 |
80 | The `react-freeze` library exports a single component called ``.
81 | It can be used as a boundary for components for which we want to suspend rendering.
82 | This takes the following options:
83 |
84 | ### `freeze: boolean`
85 |
86 | This options can be used to control whether components rendered under `Freeze` should or should not re-render.
87 | If set to `true`, all renders for components from `Freeze` subtree will be suspended until the prop changes to `false`.
88 | Additionally, during the time components are "frozen", `Freeze` component instead of rendering children will render component provided as `placeholder` parameter removing frozen components from screen while retaining their native view instances and state.
89 |
90 | ### `placeholder?: React.ReactNode`
91 |
92 | This parameter can be used to customize what `Freeze` component should render when it is in the frozen state (`freeze={true}`).
93 | This is an optional parameter and by default it renders `null`.
94 | Note, that it is best to "freeze" only components that are not visible to the user at given moment, so in general customizing this should not be necessary.
95 | However, if replacing frozen views with just `null` can break layout of you app, you can use this parameter to show a placeholder that will keep all non-frozen parts of your application in the same place.
96 |
97 | # Known issues 😭
98 |
99 | ## React Native Debugger does not allow profiling
100 |
101 | When profiling React-Native apps with [React Native Debugger](https://github.com/jhen0409/react-native-debugger) starting React profiler for the app with frozen components throws an error ("Error: Could not find ID for Fiber ...").
102 |
103 | > Have other problems with react-freeze? Start a [New issue](//github.com/software-mansion-labs/react-freeze/issues).
104 |
105 | # FAQ ❓
106 |
107 | ## When component subtree is frozen what happens to state changes that are executed on that subtree
108 |
109 | All state changes are executed as usual, they just won't trigger a render of the updated component until the component comes back from the frozen state.
110 |
111 | ## What happens to the non-react state of the component after defrost? Like for example scroll position?
112 |
113 | Since all the "native views" (DOM elements or platform-specific views in react native) are kept when the component is frozen their state (such as scroll position, text typed into text input fields, etc.) is restored when they come back from the frozen state.
114 | In fact, they are just the same component (same DOM nodes for react-dom / same views for react-native).
115 |
116 | ## What happens when there is an update in a redux store that frozen component is subscribed to?
117 |
118 | Redux and other state-management solutions rely on manually triggering re-renders for components that they want to update when the store state changes.
119 | When component is frozen it won't render even when redux requests it, however methods such as `mapStateToProps` or selectors provided to `useSelector` will still run on store updates.
120 | After the component comes back from frozen state, it will render and pick up the most up-to-date data from the store.
121 |
122 | ## Can freezing some of my app components break my app? And how?
123 |
124 | There are few ways that we are aware of when `` can alter the app behavior:
125 |
126 | 1. When attempting to freeze parts of the app that is visible to the user at a given moment -- in this case the frozen part is going to be replaced by the placeholder (or just by nothing if no placeholder is provided). So unless you really want this behavior make sure to only set `freeze` to `true` when the given subtree should not be visible and you expect user to not interact with it. A good example are screens on the navigation stack that are down the stack hierarchy when you push more content.
127 | 2. When you rely on the frozen parts layout to properly position the unfrozen parts. Note that when component is in frozen state it gets replaced by a placeholder (or by nothing if you don't provide one). This may impact the layout of the rest of your application. This can be workaround by making placeholder take the same amount of space as the view it replaces, or by only freezing parts that are positioned absolutely (e.g. the component that takes up the whole screen).
128 | 3. When component render method has side-effects that relay on running for all prop/state updates. Typically, performing side-effects in render is undesirable when writing react code but can happen in your codebase nonetheless. Note that when subtree is frozen your component may not be rendered for all the state updates and render method won't execute for some of the changes that happen during that phase. However, when the component gets back from the frozen state it will render with the most up-to-date version of the state, and if that suffice for the side-effect logic to be correct you should be ok.
129 |
130 |
131 |
132 |
133 | Made by [Software Mansion](https://github.com/software-mansion) and licenced under [The MIT License](LICENSE).
134 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-freeze",
3 | "version": "1.0.3",
4 | "description": "React Freeze",
5 | "license": "MIT",
6 | "repository": "software-mansion/react-freeze",
7 | "main": "dist/index.js",
8 | "module": "dist/index.modern.js",
9 | "source": "src/index.tsx",
10 | "react-native": "src/index.tsx",
11 | "engines": {
12 | "node": ">=10"
13 | },
14 | "scripts": {
15 | "build": "microbundle-crl --no-compress --format modern,cjs",
16 | "start": "microbundle-crl watch --no-compress --format modern,cjs",
17 | "prepare": "run-s build",
18 | "test": "run-s test:unit test:lint test:types test:build",
19 | "test:build": "run-s build",
20 | "test:lint": "eslint --ext '.js,.ts,.tsx' --fix src",
21 | "test:unit": "cross-env CI=1 react-scripts test --env=jsdom",
22 | "test:types": "tsc --noEmit",
23 | "test:watch": "react-scripts test --env=jsdom"
24 | },
25 | "keywords": [
26 | "react",
27 | "freeze"
28 | ],
29 | "peerDependencies": {
30 | "react": ">=17.0.0"
31 | },
32 | "devDependencies": {
33 | "@types/jest": "^25.1.4",
34 | "@types/node": "^12.12.38",
35 | "@types/react": "^18.0.15",
36 | "@types/react-test-renderer": "^18.0.0",
37 | "@typescript-eslint/eslint-plugin": "^5.3.0",
38 | "@typescript-eslint/parser": "^5.3.0",
39 | "babel-eslint": "^10.0.3",
40 | "cross-env": "^7.0.2",
41 | "eslint": "^6.8.0",
42 | "eslint-config-prettier": "^6.7.0",
43 | "eslint-config-standard": "^14.1.0",
44 | "eslint-config-standard-react": "^9.2.0",
45 | "eslint-plugin-import": "^2.18.2",
46 | "eslint-plugin-node": "^11.0.0",
47 | "eslint-plugin-prettier": "^3.1.1",
48 | "eslint-plugin-promise": "^4.2.1",
49 | "eslint-plugin-react": "^7.17.0",
50 | "eslint-plugin-standard": "^4.0.1",
51 | "microbundle-crl": "^0.13.10",
52 | "npm-run-all": "^4.1.5",
53 | "prettier": "^2.0.4",
54 | "react": "^18.2.0",
55 | "react-scripts": "^3.4.1",
56 | "react-test-renderer": "^18.2.0",
57 | "typescript": "^3.7.5"
58 | },
59 | "files": [
60 | "dist",
61 | "!dist/*.test.d.ts",
62 | "src/index.tsx"
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/src/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "jest": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.test.tsx:
--------------------------------------------------------------------------------
1 | import React, { Dispatch, useEffect, useState } from "react";
2 | import {
3 | create,
4 | act,
5 | ReactTestRenderer,
6 | ReactTestRendererJSON,
7 | } from "react-test-renderer";
8 |
9 | import { Freeze } from ".";
10 |
11 | test("Renders stuff not frozen", () => {
12 | function Content() {
13 | return ;
14 | }
15 | function A() {
16 | return (
17 |
18 |
19 |
20 | );
21 | }
22 | const testRenderer = create();
23 | const testInstance = testRenderer.root;
24 | expect(testInstance.findByType(Content)).toBeTruthy();
25 | });
26 |
27 | test("Does not render stuff when frozen", () => {
28 | function Content() {
29 | return ;
30 | }
31 | function A() {
32 | return (
33 |
34 |
35 |
36 | );
37 | }
38 | const testRenderer = create();
39 | const testInstance = testRenderer.root;
40 | expect(testInstance.findAllByType(Content)).toHaveLength(0);
41 | });
42 |
43 | test("Stuff is gone after freeze", () => {
44 | function Content() {
45 | return ;
46 | }
47 | function A({ freeze }: { freeze: boolean }) {
48 | return (
49 |
50 |
51 |
52 | );
53 | }
54 | let testRenderer: ReactTestRenderer | undefined;
55 | act(() => {
56 | testRenderer = create();
57 | });
58 | const testInstance = testRenderer?.root;
59 | expect(testInstance?.findByType(Content)).toBeTruthy();
60 | act(() => testRenderer?.update());
61 | expect(testRenderer?.toJSON()).toBe(null);
62 | });
63 |
64 | test("Updates work when not frozen", () => {
65 | let subscription: React.Dispatch;
66 | // @ts-ignore unused prop
67 | function Inner({ value }: { value: number }) {
68 | return <>>;
69 | }
70 | let renderCount = 0;
71 | function Subscriber() {
72 | const [value, setValue] = useState(0);
73 | useEffect(() => {
74 | subscription = setValue;
75 | }, []);
76 | renderCount = renderCount + 1;
77 | return ;
78 | }
79 | function Container({ freeze }: { freeze: boolean }) {
80 | return (
81 |
82 |
83 |
84 | );
85 | }
86 | let testRenderer: ReactTestRenderer | undefined;
87 | act(() => {
88 | testRenderer = create();
89 | });
90 | const testInstance = testRenderer?.root;
91 | expect(testInstance?.findByType(Inner).props.value).toEqual(0);
92 | act(() => subscription(1));
93 | expect(testInstance?.findByType(Inner).props.value).toEqual(1);
94 | expect(renderCount).toBe(2);
95 | });
96 |
97 | test("Updates does not propagate when frozen", () => {
98 | let subscription: Dispatch;
99 | // @ts-ignore unused prop
100 | function Inner({ value }: { value: number }) {
101 | return ;
102 | }
103 | let renderCount = 0;
104 | function Subscriber() {
105 | const [value, setValue] = useState(0);
106 | useEffect(() => {
107 | subscription = setValue;
108 | }, []);
109 | renderCount = renderCount + 1;
110 | return ;
111 | }
112 | function Container({ freeze }: { freeze: boolean }) {
113 | return (
114 |
115 |
116 |
117 | );
118 | }
119 | let testRenderer: ReactTestRenderer | undefined;
120 | act(() => {
121 | testRenderer = create();
122 | });
123 | const testInstance = testRenderer?.root;
124 | expect(testInstance?.findByType(Inner).props.value).toEqual(0);
125 | act(() => testRenderer?.update());
126 | act(() => subscription(1));
127 | expect(testInstance?.findByType(Inner).props.value).toEqual(0);
128 | expect(renderCount).toBe(1);
129 | });
130 |
131 | test("State persists after defrost", () => {
132 | let subscription: Dispatch;
133 | // @ts-ignore unused prop
134 | function Inner({ value }: { value: number }) {
135 | return ;
136 | }
137 | let renderCount = 0;
138 | function Subscriber() {
139 | const [value, setValue] = useState(0);
140 | useEffect(() => {
141 | subscription = setValue;
142 | }, []);
143 | renderCount = renderCount + 1;
144 | return ;
145 | }
146 | function Container({ freeze }: { freeze: boolean }) {
147 | return (
148 |
149 |
150 |
151 | );
152 | }
153 | let testRenderer: ReactTestRenderer | undefined;
154 | act(() => {
155 | testRenderer = create();
156 | });
157 | const testInstance = testRenderer?.root;
158 | expect(testInstance?.findByType(Inner).props.value).toEqual(0);
159 | act(() => subscription(1));
160 | expect(testInstance?.findByType(Inner).props.value).toEqual(1);
161 | act(() => testRenderer?.update());
162 | expect(testRenderer?.toJSON()).toBe(null);
163 | act(() => testRenderer?.update());
164 | expect((testRenderer?.toJSON() as ReactTestRendererJSON).type).toBe("div");
165 | expect(testInstance?.findByType(Inner).props.value).toEqual(1);
166 | });
167 |
168 | test("Update propagate after defrrost", () => {
169 | let subscription: Dispatch;
170 | // @ts-ignore unused prop
171 | function Inner({ value }: { value: number }) {
172 | return ;
173 | }
174 | let renderCount = 0;
175 | function Subscriber() {
176 | const [value, setValue] = useState(0);
177 | useEffect(() => {
178 | subscription = setValue;
179 | }, []);
180 | renderCount = renderCount + 1;
181 | return ;
182 | }
183 | function Container({ freeze }: { freeze: boolean }) {
184 | return (
185 |
186 |
187 |
188 | );
189 | }
190 | let testRenderer: ReactTestRenderer | undefined;
191 | act(() => {
192 | testRenderer = create();
193 | });
194 | const testInstance = testRenderer?.root;
195 | act(() => testRenderer?.update());
196 | act(() => subscription(1));
197 | act(() => subscription(2));
198 | act(() => subscription(3));
199 | expect(testInstance?.findByType(Inner).props.value).toEqual(0);
200 | act(() => testRenderer?.update());
201 | expect(testInstance?.findByType(Inner).props.value).toEqual(3);
202 | expect(renderCount).toBe(2);
203 | });
204 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, Suspense, Fragment } from "react";
2 |
3 | interface StorageRef {
4 | promise?: Promise;
5 | resolve?: (value: void | PromiseLike) => void;
6 | }
7 |
8 | function Suspender({
9 | freeze,
10 | children,
11 | }: {
12 | freeze: boolean;
13 | children: React.ReactNode;
14 | }) {
15 | const promiseCache = useRef({}).current;
16 | if (freeze && !promiseCache.promise) {
17 | promiseCache.promise = new Promise((resolve) => {
18 | promiseCache.resolve = resolve;
19 | });
20 | throw promiseCache.promise;
21 | } else if (freeze) {
22 | throw promiseCache.promise;
23 | } else if (promiseCache.promise) {
24 | promiseCache.resolve!();
25 | promiseCache.promise = undefined;
26 | }
27 |
28 | return {children};
29 | }
30 |
31 | interface Props {
32 | freeze: boolean;
33 | children: React.ReactNode;
34 | placeholder?: React.ReactNode;
35 | }
36 |
37 | export function Freeze({ freeze, children, placeholder = null }: Props) {
38 | return (
39 |
40 | {children}
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "dist",
4 | "module": "esnext",
5 | "lib": [
6 | "dom",
7 | "esnext"
8 | ],
9 | "moduleResolution": "node",
10 | "jsx": "react",
11 | "sourceMap": true,
12 | "declaration": true,
13 | "esModuleInterop": true,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noImplicitAny": true,
17 | "strictNullChecks": true,
18 | "suppressImplicitAnyIndexErrors": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true,
21 | "allowSyntheticDefaultImports": true,
22 | "target": "es5",
23 | "allowJs": true,
24 | "skipLibCheck": true,
25 | "strict": true,
26 | "forceConsistentCasingInFileNames": true,
27 | "resolveJsonModule": true,
28 | "isolatedModules": true,
29 | "noEmit": true
30 | },
31 | "include": [
32 | "src"
33 | ],
34 | "exclude": [
35 | "node_modules",
36 | "dist",
37 | "example"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------