├── .gitignore
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── react-typescript-firebase-crud-demo.png
├── src
├── App.css
├── App.tsx
├── components
│ ├── add-tutorial.component.tsx
│ ├── tutorial.component.tsx
│ └── tutorials-list.component.tsx
├── firebase.ts
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
├── reportWebVitals.ts
├── services
│ └── tutorial.service.ts
├── setupTests.ts
└── types
│ └── tutorial.type.ts
├── tsconfig.json
└── yarn.lock
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Typescript Firebase example: CRUD with Realtime Database
2 |
3 | Build a React Typescript and Firebase Realtime Database CRUD example with Router & Bootstrap 4.
4 |
5 | It is the React Tutorial Application in that:
6 | - Each Tutorial has id, title, description, published status.
7 | - We can create, retrieve, update, delete Tutorials.
8 |
9 | 
10 |
11 | For instruction, please visit:
12 | > [React Typescript Firebase example: CRUD App](https://www.bezkoder.com/firebase-typescript-react/)
13 |
14 | Related Posts:
15 | > [React Typescript Firestore example: CRUD App](https://www.bezkoder.com/react-typescript-firestore/)
16 |
17 | > [React Typescript example Project with Axios and Web API](https://www.bezkoder.com/react-typescript-axios/)
18 |
19 | > [React Hooks Typescript example Project with Axios and Web API](https://www.bezkoder.com/react-typescript-api-call/)
20 |
21 | > [React (Javascript) CRUD example to consume Web API](https://www.bezkoder.com/react-crud-web-api/)
22 |
23 | > [React Redux CRUD App example with Rest API](https://www.bezkoder.com/react-redux-crud-example/)
24 |
25 | > [React (Hooks) CRUD example to consume Web API](https://www.bezkoder.com/react-hooks-crud-axios-api/)
26 |
27 | > [React Table example: CRUD App with react-table v7](https://www.bezkoder.com/react-table-example-hooks-crud/)
28 |
29 | Using Material UI instead of Bootstrap:
30 | > [React Material UI examples with a CRUD Application](https://www.bezkoder.com/react-material-ui-examples-crud/)
31 |
32 | More Practice:
33 | > [React Pagination example](https://www.bezkoder.com/react-pagination-material-ui/)
34 |
35 | > [React File Upload example](https://www.bezkoder.com/react-file-upload-axios/)
36 |
37 | > [React JWT Authentication & Authorization example](https://www.bezkoder.com/react-jwt-auth/)
38 |
39 | > [React + Redux: JWT Authentication & Authorization example](https://www.bezkoder.com/react-redux-jwt-auth/)
40 |
41 | Fullstack with Node Express:
42 | > [React + Node Express + MySQL](https://www.bezkoder.com/react-node-express-mysql/)
43 |
44 | > [React + Node Express + PostgreSQL](https://www.bezkoder.com/react-node-express-postgresql/)
45 |
46 | > [React + Node Express + MongoDB](https://www.bezkoder.com/react-node-express-mongodb-mern-stack/)
47 |
48 | Fullstack with Spring Boot:
49 | > [React + Spring Boot + MySQL](https://www.bezkoder.com/react-spring-boot-crud/)
50 |
51 | > [React + Spring Boot + PostgreSQL](https://www.bezkoder.com/spring-boot-react-postgresql/)
52 |
53 | > [React + Spring Boot + MongoDB](https://www.bezkoder.com/react-spring-boot-mongodb/)
54 |
55 | Fullstack with Django:
56 |
57 | > [React + Django Rest Framework](https://www.bezkoder.com/django-react-axios-rest-framework/)
58 |
59 | Integration (run back-end & front-end on same server/port)
60 | > [Integrate React with Spring Boot](https://www.bezkoder.com/integrate-reactjs-spring-boot/)
61 |
62 | > [Integrate React with Node Express](https://www.bezkoder.com/integrate-react-express-same-server-port/)
63 |
64 |
65 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
66 |
67 | ### Set port
68 | .env
69 | ```
70 | PORT=8081
71 | ```
72 |
73 | ## Project setup
74 |
75 | In the project directory, you can run:
76 |
77 | ```
78 | npm install
79 | # or
80 | yarn install
81 | ```
82 |
83 | or
84 |
85 | ### Compiles and hot-reloads for development
86 |
87 | ```
88 | npm start
89 | # or
90 | yarn start
91 | ```
92 |
93 | Open [http://localhost:8081](http://localhost:8081) to view it in the browser.
94 |
95 | The page will reload if you make edits.
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-typescript-firebase",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.14.1",
7 | "@testing-library/react": "^11.2.7",
8 | "@testing-library/user-event": "^12.8.3",
9 | "@types/jest": "^26.0.23",
10 | "@types/node": "^12.20.15",
11 | "@types/react": "^17.0.13",
12 | "@types/react-dom": "^17.0.8",
13 | "@types/react-router-dom": "^5.1.8",
14 | "axios": "^0.21.1",
15 | "bootstrap": "^4.6.0",
16 | "firebase": "^8.10.0",
17 | "react": "^17.0.2",
18 | "react-dom": "^17.0.2",
19 | "react-router-dom": "^5.2.1",
20 | "react-scripts": "4.0.3",
21 | "typescript": "^4.3.5",
22 | "web-vitals": "^1.0.1"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test",
28 | "eject": "react-scripts eject"
29 | },
30 | "eslintConfig": {
31 | "extends": [
32 | "react-app",
33 | "react-app/jest"
34 | ]
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/react-typescript-firebase-crud/c18a187f8c32bf6c6725149232426a78f6b63044/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/react-typescript-firebase-crud/c18a187f8c32bf6c6725149232426a78f6b63044/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/react-typescript-firebase-crud/c18a187f8c32bf6c6725149232426a78f6b63044/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/react-typescript-firebase-crud-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bezkoder/react-typescript-firebase-crud/c18a187f8c32bf6c6725149232426a78f6b63044/react-typescript-firebase-crud-demo.png
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .container h2 {
2 | text-align: center;
3 | margin: 25px auto;
4 | }
5 |
6 | .list {
7 | text-align: left;
8 | max-width: 750px;
9 | margin: auto;
10 | }
11 |
12 | .submit-form {
13 | max-width: 300px;
14 | margin: auto;
15 | }
16 |
17 | .edit-form {
18 | max-width: 300px;
19 | margin: auto;
20 | }
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import { Switch, Route, Link } from "react-router-dom";
3 | import "bootstrap/dist/css/bootstrap.min.css";
4 | import './App.css';
5 |
6 | import AddTutorial from "./components/add-tutorial.component";
7 | import TutorialsList from "./components/tutorials-list.component";
8 |
9 | class App extends Component {
10 | render() {
11 | return (
12 |
13 |
30 |
31 |
32 |
React Typescript Firebase example
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 | }
42 |
43 | export default App;
44 |
--------------------------------------------------------------------------------
/src/components/add-tutorial.component.tsx:
--------------------------------------------------------------------------------
1 | import { Component, ChangeEvent } from "react";
2 | import TutorialDataService from "../services/tutorial.service";
3 | import ITutorialData from '../types/tutorial.type';
4 |
5 | type Props = {};
6 |
7 | type State = ITutorialData & {
8 | submitted: boolean
9 | };
10 |
11 | export default class AddTutorial extends Component {
12 | constructor(props: Props) {
13 | super(props);
14 | this.onChangeTitle = this.onChangeTitle.bind(this);
15 | this.onChangeDescription = this.onChangeDescription.bind(this);
16 | this.saveTutorial = this.saveTutorial.bind(this);
17 | this.newTutorial = this.newTutorial.bind(this);
18 |
19 | this.state = {
20 | title: "",
21 | description: "",
22 | published: false,
23 |
24 | submitted: false,
25 | };
26 | }
27 |
28 | onChangeTitle(e: ChangeEvent) {
29 | this.setState({
30 | title: e.target.value,
31 | });
32 | }
33 |
34 | onChangeDescription(e: ChangeEvent) {
35 | this.setState({
36 | description: e.target.value,
37 | });
38 | }
39 |
40 | saveTutorial() {
41 | let data = {
42 | title: this.state.title,
43 | description: this.state.description,
44 | published: false
45 | };
46 |
47 | TutorialDataService.create(data)
48 | .then(() => {
49 | console.log("Created new item successfully!");
50 | this.setState({
51 | submitted: true,
52 | });
53 | })
54 | .catch((e: Error) => {
55 | console.log(e);
56 | });
57 | }
58 |
59 | newTutorial() {
60 | this.setState({
61 | title: "",
62 | description: "",
63 | published: false,
64 |
65 | submitted: false,
66 | });
67 | }
68 |
69 | render() {
70 | return (
71 |
72 | {this.state.submitted ? (
73 |
74 |
You submitted successfully!
75 |
78 |
79 | ) : (
80 |
81 |
82 |
83 |
92 |
93 |
94 |
95 |
96 |
105 |
106 |
107 |
110 |
111 | )}
112 |
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/components/tutorial.component.tsx:
--------------------------------------------------------------------------------
1 | import { Component, ChangeEvent } from "react";
2 |
3 | import TutorialDataService from "../services/tutorial.service";
4 | import ITutorialData from "../types/tutorial.type";
5 |
6 | type Props = {
7 | tutorial: ITutorialData,
8 | refreshList: Function
9 | };
10 |
11 | type State = {
12 | currentTutorial: ITutorialData;
13 | message: string;
14 | }
15 |
16 | export default class Tutorial extends Component {
17 | constructor(props: Props) {
18 | super(props);
19 | this.onChangeTitle = this.onChangeTitle.bind(this);
20 | this.onChangeDescription = this.onChangeDescription.bind(this);
21 | this.updatePublished = this.updatePublished.bind(this);
22 | this.updateTutorial = this.updateTutorial.bind(this);
23 | this.deleteTutorial = this.deleteTutorial.bind(this);
24 |
25 | this.state = {
26 | currentTutorial: {
27 | key: null,
28 | title: "",
29 | description: "",
30 | published: false,
31 | },
32 | message: "",
33 | };
34 | }
35 |
36 | static getDerivedStateFromProps(nextProps: Props, prevState: State) {
37 | const { tutorial } = nextProps;
38 | if (prevState.currentTutorial.key !== tutorial.key) {
39 | return {
40 | currentTutorial: tutorial,
41 | message: ""
42 | };
43 | }
44 |
45 | return prevState.currentTutorial;
46 | }
47 |
48 | componentDidMount() {
49 | this.setState({
50 | currentTutorial: this.props.tutorial,
51 | });
52 | }
53 |
54 | onChangeTitle(e: ChangeEvent) {
55 | const title = e.target.value;
56 |
57 | this.setState(function (prevState: State) {
58 | return {
59 | currentTutorial: {
60 | ...prevState.currentTutorial,
61 | title: title,
62 | },
63 | };
64 | });
65 | }
66 |
67 | onChangeDescription(e: ChangeEvent) {
68 | const description = e.target.value;
69 |
70 | this.setState((prevState) => ({
71 | currentTutorial: {
72 | ...prevState.currentTutorial,
73 | description: description,
74 | },
75 | }));
76 | }
77 |
78 | updatePublished(status: boolean) {
79 | if (this.state.currentTutorial.key) {
80 | TutorialDataService.update(this.state.currentTutorial.key, {
81 | published: status,
82 | })
83 | .then(() => {
84 | this.setState((prevState) => ({
85 | currentTutorial: {
86 | ...prevState.currentTutorial,
87 | published: status,
88 | },
89 | message: "The status was updated successfully!",
90 | }));
91 | })
92 | .catch((e: Error) => {
93 | console.log(e);
94 | });
95 | }
96 | }
97 |
98 | updateTutorial() {
99 | if (this.state.currentTutorial.key) {
100 | const data = {
101 | title: this.state.currentTutorial.title,
102 | description: this.state.currentTutorial.description,
103 | };
104 |
105 | TutorialDataService.update(this.state.currentTutorial.key, data)
106 | .then(() => {
107 | this.setState({
108 | message: "The tutorial was updated successfully!",
109 | });
110 | })
111 | .catch((e: Error) => {
112 | console.log(e);
113 | });
114 | }
115 | }
116 |
117 | deleteTutorial() {
118 | if (this.state.currentTutorial.key) {
119 | TutorialDataService.delete(this.state.currentTutorial.key)
120 | .then(() => {
121 | this.props.refreshList();
122 | })
123 | .catch((e: Error) => {
124 | console.log(e);
125 | });
126 | }
127 | }
128 |
129 | render() {
130 | const { currentTutorial } = this.state;
131 |
132 | return (
133 |
134 |
Tutorial
135 | {currentTutorial ? (
136 |
137 |
166 |
167 | {currentTutorial.published ? (
168 |
174 | ) : (
175 |
181 | )}
182 |
183 |
189 |
190 |
197 |
{this.state.message}
198 |
199 | ) : (
200 |
201 |
202 |
Please click on a Tutorial...
203 |
204 | )}
205 |
206 | );
207 | }
208 | }
209 |
--------------------------------------------------------------------------------
/src/components/tutorials-list.component.tsx:
--------------------------------------------------------------------------------
1 | import { Component } from "react";
2 | import TutorialDataService from "../services/tutorial.service";
3 | import Tutorial from "./tutorial.component";
4 | import ITutorialData from '../types/tutorial.type';
5 |
6 | type Props = {};
7 |
8 | type State = {
9 | tutorials: Array,
10 | currentTutorial: ITutorialData | null,
11 | currentIndex: number
12 | };
13 |
14 | export default class TutorialsList extends Component {
15 | constructor(props: Props) {
16 | super(props);
17 | this.refreshList = this.refreshList.bind(this);
18 | this.setActiveTutorial = this.setActiveTutorial.bind(this);
19 | this.removeAllTutorials = this.removeAllTutorials.bind(this);
20 | this.onDataChange = this.onDataChange.bind(this);
21 |
22 | this.state = {
23 | tutorials: [],
24 | currentTutorial: null,
25 | currentIndex: -1,
26 | };
27 | }
28 |
29 | componentDidMount() {
30 | TutorialDataService.getAll().on("value", this.onDataChange);
31 | }
32 |
33 | componentWillUnmount() {
34 | TutorialDataService.getAll().off("value", this.onDataChange);
35 | }
36 |
37 | onDataChange(items: any) {
38 | let tutorials = new Array();
39 |
40 | items.forEach((item: any) => {
41 | let key = item.key;
42 | let data = item.val();
43 | tutorials.push({
44 | key: key,
45 | title: data.title,
46 | description: data.description,
47 | published: data.published,
48 | });
49 | });
50 |
51 | this.setState({
52 | tutorials: tutorials,
53 | });
54 | }
55 |
56 | refreshList() {
57 | this.setState({
58 | currentTutorial: null,
59 | currentIndex: -1,
60 | });
61 | }
62 |
63 | setActiveTutorial(tutorial: ITutorialData, index: number) {
64 | this.setState({
65 | currentTutorial: tutorial,
66 | currentIndex: index,
67 | });
68 | }
69 |
70 | removeAllTutorials() {
71 | TutorialDataService.deleteAll()
72 | .then(() => {
73 | this.refreshList();
74 | })
75 | .catch((e: Error) => {
76 | console.log(e);
77 | });
78 | }
79 |
80 | render() {
81 | const { tutorials, currentTutorial, currentIndex } = this.state;
82 |
83 | return (
84 |
85 |
86 |
Tutorials List
87 |
88 |
89 | {tutorials &&
90 | tutorials.map((tutorial, index) => (
91 | - this.setActiveTutorial(tutorial, index)}
97 | key={index}
98 | >
99 | {tutorial.title}
100 |
101 | ))}
102 |
103 |
104 |
110 |
111 |
112 | {currentTutorial ? (
113 |
117 | ) : (
118 |
119 |
120 |
Please click on a Tutorial...
121 |
122 | )}
123 |
124 |
125 | );
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/firebase.ts:
--------------------------------------------------------------------------------
1 | import firebase from "firebase/app";
2 | import "firebase/database";
3 |
4 | let config = {
5 | apiKey: "xxx",
6 | authDomain: "bezkoder-firebase.firebaseapp.com",
7 | databaseURL: "https://bezkoder-firebase.firebaseio.com",
8 | projectId: "bezkoder-firebase",
9 | storageBucket: "bezkoder-firebase.appspot.com",
10 | messagingSenderId: "xxx",
11 | appId: "xxx",
12 | };
13 |
14 | firebase.initializeApp(config);
15 |
16 | export default firebase.database();
17 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import { BrowserRouter } from "react-router-dom";
3 | import App from './App';
4 | import './index.css';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | ReactDOM.render(
8 |
9 |
10 | ,
11 | document.getElementById('root')
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | import { ReportHandler } from 'web-vitals';
2 |
3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
4 | if (onPerfEntry && onPerfEntry instanceof Function) {
5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
6 | getCLS(onPerfEntry);
7 | getFID(onPerfEntry);
8 | getFCP(onPerfEntry);
9 | getLCP(onPerfEntry);
10 | getTTFB(onPerfEntry);
11 | });
12 | }
13 | };
14 |
15 | export default reportWebVitals;
16 |
--------------------------------------------------------------------------------
/src/services/tutorial.service.ts:
--------------------------------------------------------------------------------
1 | import firebase from "../firebase";
2 | import ITutorialData from "../types/tutorial.type"
3 |
4 | const db = firebase.ref("/tutorials");
5 |
6 | class TutorialDataService {
7 | getAll() {
8 | return db;
9 | }
10 |
11 | create(tutorial: ITutorialData) {
12 | return db.push(tutorial);
13 | }
14 |
15 | update(key: string, value: any) {
16 | return db.child(key).update(value);
17 | }
18 |
19 | delete(key: string) {
20 | return db.child(key).remove();
21 | }
22 |
23 | deleteAll() {
24 | return db.remove();
25 | }
26 | }
27 |
28 | export default new TutorialDataService();
29 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/types/tutorial.type.ts:
--------------------------------------------------------------------------------
1 | export default interface ITutorialData {
2 | key?: string | null,
3 | title: string,
4 | description: string,
5 | published?: boolean,
6 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------