Skip to content

Commit 836be2b

Browse files
committed
Add TypeScript to the project
1 parent 915510d commit 836be2b

26 files changed

+171
-119
lines changed

.editorconfig

+1-10
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,11 @@ trim_trailing_whitespace = true
1010
insert_final_newline = true
1111
end_of_line = lf
1212

13-
[*.js]
14-
indent_size = 2
15-
16-
[*.html]
17-
indent_size = 2
18-
19-
[*.scss]
13+
[*.{js,jsx,ts,tsx,json,html,css,scss,md,yml,yaml,swcrc}]
2014
indent_size = 2
2115

2216
[*.md]
2317
trim_trailing_whitespace = false
2418

2519
[Makefile]
2620
indent_style = tab
27-
28-
[*.yml]
29-
indent_size = 2

.eslintrc.js

+25-13
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,38 @@
1-
const path = require('path');
1+
const path = require("path");
22

33
module.exports = {
44
root: true,
5-
extends: ['vinta/recommended'],
5+
extends: ["vinta/recommended-typescript"],
66
rules: {
7-
"default-param-last": "off", // due to initialState in Redux
8-
"@babel/camelcase": "off"
7+
"default-param-last": "off", // due to initialState in Redux
8+
"import/extensions": [
9+
"error",
10+
"ignorePackages",
11+
{
12+
ts: "never",
13+
tsx: "never",
14+
},
15+
],
916
},
1017
env: {
11-
es6: true,
1218
browser: true,
13-
jest: true
19+
es2021: true,
20+
jest: true,
21+
node: true,
1422
},
1523
settings: {
16-
'import/resolver': {
24+
"import/resolver": {
25+
node: {
26+
paths: [path.resolve(__dirname, "node_modules")],
27+
extensions: [".js", ".jsx", ".ts", ".tsx"],
28+
},
1729
webpack: {
18-
config: path.join(__dirname, '/webpack.config.js'),
19-
'config-index': 1
20-
}
30+
config: path.join(__dirname, "/webpack.config.js"),
31+
"config-index": 1,
32+
},
2133
},
2234
react: {
23-
"version": "detect"
35+
version: "detect",
2436
},
25-
}
26-
}
37+
},
38+
};

.swcrc

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"jsc": {
3+
"parser": {
4+
"jsx": true,
5+
"tsx": true,
6+
"syntax": "typescript",
7+
"decorators": true,
8+
"dynamicImport": true
9+
},
10+
"transform": {
11+
"react": {
12+
"runtime": "automatic"
13+
}
14+
}
15+
},
16+
// The 'env' field is used to specify settings for compiling JavaScript/TypeScript code to be
17+
// compatible with older environments.
18+
"env": {
19+
// 'entry' means SWC will include polyfills for all features used in the code that are not
20+
// available in the target environment.
21+
"mode": "entry",
22+
// 'corejs' specifies the version of core-js to use for polyfills.
23+
"corejs": 3
24+
}
25+
}

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
A [Django](https://www.djangoproject.com/) project boilerplate/template with a multitude of state-of-the-art libraries and tools. If pairing Django with React is a possibility for your project or spinoff, this is the best solution available. Save time with tools like:
99

1010
- [React](https://react.dev/), for building interactive UIs
11+
- [TypeScript](https://www.typescriptlang.org/), for static type checking
1112
- [Poetry](https://python-poetry.org/), for managing the environment and its dependencies
1213
- [django-js-reverse](https://github.com/vintasoftware/django-js-reverse), for generating URLs on JS
1314
- [React Bootstrap](https://react-bootstrap.github.io/), for responsive styling

babel.config.json

-14
This file was deleted.

frontend/assets/images/index.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare module "*.png" {
2+
const value: string | undefined;
3+
export = value;
4+
}

frontend/js/App.js renamed to frontend/js/App.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import * as Sentry from "@sentry/react";
2-
import React from "react";
32
import { Provider } from "react-redux";
43

54
import Home from "./pages/Home";
File renamed without changes.
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
// import pages
22
import * as Sentry from "@sentry/browser";
3-
import React from "react";
43
import { createRoot } from "react-dom/client";
54

6-
import "../sass/style.scss";
7-
85
import App from "./App";
96

7+
import "../sass/style.scss";
8+
109
Sentry.init({
1110
dsn: window.SENTRY_DSN,
1211
release: window.COMMIT_SHA,
1312
});
1413

15-
const root = createRoot(document.getElementById("react-app"));
14+
const root = createRoot(document.getElementById("react-app") as HTMLElement);
1615
root.render(<App />);

frontend/js/pages/Home.js renamed to frontend/js/pages/Home.tsx

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import React, { useState, useEffect } from "react";
1+
import { useState, useEffect } from "react";
22
import Button from "react-bootstrap/Button";
33
import { useDispatch, useSelector } from "react-redux";
44

55
import DjangoImgSrc from "../../assets/images/django-logo-negative.png";
6+
import { AppDispatch, RootState } from "../store";
67
import { fetchRestCheck } from "../store/rest_check";
78

89
const Home = () => {
9-
const dispatch = useDispatch();
10-
const restCheck = useSelector((state) => state.restCheck);
10+
const dispatch: AppDispatch = useDispatch();
11+
const restCheck = useSelector((state: RootState) => state.restCheck);
1112
useEffect(() => {
1213
const action = fetchRestCheck();
1314
dispatch(action);
@@ -35,7 +36,9 @@ const Home = () => {
3536
Click to test if Sentry is capturing frontend errors! (Should only work
3637
in Production)
3738
</Button>
38-
{showBugComponent && showBugComponent.field.notexist}
39+
{/* NOTE: The next line intentionally contains an error for testing frontend errors in Sentry. */}
40+
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
41+
{showBugComponent && (showBugComponent as any).field.notexist}
3942
</>
4043
);
4144
};

frontend/js/pages/__tests__/Home.spec.js renamed to frontend/js/pages/__tests__/Home.spec.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ jest.mock("../../store/rest_check", () => ({
1515

1616
describe("Home", () => {
1717
beforeEach(() => {
18-
useDispatch.mockReturnValue(jest.fn());
19-
useSelector.mockReturnValue({
18+
(useDispatch as jest.Mock).mockReturnValue(jest.fn());
19+
(useSelector as jest.Mock).mockReturnValue({
2020
data: { payload: { result: "Test Result" } },
2121
});
2222
});
@@ -30,7 +30,7 @@ describe("Home", () => {
3030

3131
expect(screen.getByText("Static assets")).toBeInTheDocument();
3232
expect(screen.getByText("Rest API")).toBeInTheDocument();
33-
expect(screen.getByText("Test Result")).toBeInTheDocument();
33+
expect(await screen.findByText("Test Result")).toBeInTheDocument();
3434
});
3535

3636
test("dispatches fetchRestCheck action on mount", async () => {
File renamed without changes.

frontend/js/store/api.js renamed to frontend/js/store/api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import cookie from "cookie";
44
const api = axios.create();
55
api.interceptors.request.use((config) => {
66
const { csrftoken } = cookie.parse(document.cookie);
7+
config.headers = config.headers || {};
78
if (csrftoken) {
89
config.headers["X-CSRFTOKEN"] = csrftoken;
910
}

frontend/js/store/index.js

-13
This file was deleted.

frontend/js/store/index.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { configureStore } from "@reduxjs/toolkit";
2+
3+
import { rootReducer } from "./reducers";
4+
5+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
6+
const configureReduxStore = (preloadedState: any) => {
7+
const store = configureStore({
8+
reducer: rootReducer,
9+
preloadedState,
10+
});
11+
return store;
12+
};
13+
14+
export default configureReduxStore;
15+
16+
const store = configureReduxStore({});
17+
export type RootState = ReturnType<typeof store.getState>;
18+
export type AppDispatch = typeof store.dispatch;
File renamed without changes.

frontend/js/store/rest_check.js renamed to frontend/js/store/rest_check.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,19 @@ export const fetchRestCheck = createAsyncThunk("restCheck/fetch", async () => {
99
});
1010

1111
// Reducer
12+
interface Payload {
13+
result: string;
14+
}
15+
interface RestCheckState {
16+
data: {
17+
isLoading: boolean;
18+
payload?: Payload;
19+
error?: unknown;
20+
};
21+
}
1222
export const restCheckReducer = createSlice({
1323
name: "restCheck",
14-
initialState: {},
24+
initialState: { data: { isLoading: false } } as RestCheckState,
1525
reducers: {},
1626
extraReducers: (builder) => {
1727
builder.addCase(fetchRestCheck.pending, (state) => {

frontend/js/types/index.d.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export {};
2+
3+
declare global {
4+
interface Window {
5+
SENTRY_DSN: string;
6+
COMMIT_SHA: string;
7+
8+
Urls: unknown;
9+
}
10+
}

frontend/js/utils/index.js

-3
This file was deleted.

frontend/js/utils/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import Urls from "./urls";
2+
3+
export { Urls };
File renamed without changes.

jest.config.js

+15-26
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,27 @@
1-
'use strict';
1+
"use strict";
22

33
module.exports = {
4-
transform: {
5-
'^.+\\.jsx$': 'babel-jest',
6-
'^.+\\.js$': 'babel-jest',
7-
},
84
moduleNameMapper: {
9-
'^.+\\.(css|scss|png|svg|jpg|jpeg|gif|webp)$': 'jest-transform-stub',
5+
"^.+\\.(css|scss|png|svg|jpg|jpeg|gif|webp)$": "jest-transform-stub",
106
},
11-
transformIgnorePatterns: [
12-
'node_modules/*',
13-
],
14-
modulePaths: [
15-
'frontend',
16-
'frontend/js',
17-
'frontend/js/app',
18-
],
19-
setupFilesAfterEnv: [
20-
'./jest.setup.js',
21-
],
22-
testEnvironment: 'jsdom',
23-
collectCoverageFrom: [
24-
'frontend/js/**/*.{js,jsx}',
25-
],
7+
transformIgnorePatterns: ["node_modules/*"],
8+
modulePaths: ["frontend", "frontend/js", "frontend/js/app"],
9+
setupFilesAfterEnv: ["./jest.setup.js"],
10+
testEnvironment: "jsdom",
11+
collectCoverageFrom: ["frontend/js/**/*.{js,jsx,ts,tsx}"],
2612
coveragePathIgnorePatterns: [
27-
'frontend/js/store.js',
28-
'frontend/js/index.js',
29-
'frontend/js/constants/*',
30-
'frontend/js/pages/*',
31-
'frontend/js/tests/*',
13+
"frontend/js/store.js",
14+
"frontend/js/index.js",
15+
"frontend/js/constants/*",
16+
"frontend/js/pages/*",
17+
"frontend/js/tests/*",
3218
],
3319
coverageThreshold: {
3420
global: {
3521
statements: 10,
3622
},
3723
},
24+
transform: {
25+
"^.+\\.(t|j)sx?$": "@swc/jest",
26+
},
3827
};

jest.setup.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
import '@testing-library/jest-dom';
1+
import "@testing-library/jest-dom";

package.json

+14-10
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
"node": ">=18 <21"
88
},
99
"browserslist": "> 0.25%, not dead",
10-
"main": "frontend/js/index.js",
10+
"main": "frontend/js/index.tsx",
1111
"scripts": {
1212
"test": "jest",
1313
"test:watch": "npm test -- --watch",
1414
"test:update": "npm test -- --u",
1515
"dev": "webpack serve --mode=development --hot",
16-
"build": "NODE_ENV=production webpack --progress --bail --mode=production",
16+
"build": "NODE_ENV=production tsc && webpack --progress --bail --mode=production",
1717
"lint": "eslint frontend --fix",
1818
"coverage": "jest --coverage"
1919
},
@@ -34,19 +34,21 @@
3434
"react-router": "~6.21.0"
3535
},
3636
"devDependencies": {
37-
"@babel/core": "~7.23.6",
38-
"@babel/eslint-parser": "~7.23.3",
39-
"@babel/eslint-plugin": "~7.23.5",
40-
"@babel/node": "~7.22.19",
41-
"@babel/preset-env": "~7.23.6",
42-
"@babel/preset-react": "~7.23.3",
4337
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
38+
"@swc/cli": "^0.3.12",
39+
"@swc/core": "^1.5.5",
40+
"@swc/jest": "^0.2.36",
4441
"@testing-library/jest-dom": "~6.1.5",
4542
"@testing-library/react": "~14.1.2",
4643
"@testing-library/user-event": "~14.5.1",
44+
"@types/cookie": "^0.6.0",
45+
"@types/jest": "^29.5.12",
46+
"@types/node": "^20.12.11",
47+
"@types/react": "^18.3.1",
48+
"@types/react-dom": "^18.3.0",
49+
"@typescript-eslint/eslint-plugin": "^6.21.0",
50+
"@typescript-eslint/parser": "^6.21.0",
4751
"ajv": "~8.12.0",
48-
"babel-jest": "~29.7.0",
49-
"babel-loader": "~9.1.3",
5052
"circular-dependency-plugin": "~5.2.2",
5153
"css-loader": "~6.8.1",
5254
"eslint": "~8.56.0",
@@ -74,6 +76,8 @@
7476
"sass": "~1.69.5",
7577
"sass-loader": "~13.3.2",
7678
"style-loader": "~3.3.3",
79+
"swc-loader": "^0.2.6",
80+
"typescript": "^5.4.5",
7781
"webpack": "~5.89.0",
7882
"webpack-bundle-tracker": "~3.0.0",
7983
"webpack-cli": "~5.1.4",

0 commit comments

Comments
 (0)