Objectives
In this tutorial, I'd like to talk about Typescript and after reading, you will learn:
- JavaScript is an interpreted language and the pain it might bring to us
- What is Typescript and what problem it can help us solve
- How to add Typescript to the frontend project in Django (without touching React, Vue or other heavy frontend frameworks)
- The solution provided in this tutorial can also work with Flask, another web framework in the Python community.
Background
JavaScript is an interpreted language, not a compiled language. A program such as C++ or Java needs to be compiled before it is run. The source code is passed through a program called a compiler, which translates it into bytecode that the machine understands and can execute. In contrast, JavaScript has no compilation step. Instead, an interpreter in the browser reads over the JavaScript code, interprets each line, and runs it. More modern browsers use a technology known as Just-In-Time (JIT) compilation, which compiles JavaScript to executable bytecode just as it is about to run.
Let's assume you wrote a function foo
, if developer pass in the wrong type of arguments (age
here), the function will still run (without raising exception), but the result is not what you expected.
function foo(name, age) {
console.log(`Hello ${name}, after 2 years, your age will be ${age + 2}`);
}
foo('michael', 30);
// Hello michael, after 2 years, your age will be 32
foo('michael', '30');
// Hello michael, after 2 years, your age will be 302
You can add comments to the foo
function, so developer can know what type of arguments he can pass in, or you can convert the string type to int type, but the solutions are still not good enough.
And the bug is not easy to detect if your frontend project is big.
Is there any better way to solve this problem?
How about this:
function foo(name: string, age: number) {
console.log(`Hello ${name}, after 2 years, your age will be ${age + 2}`);
}
foo('michael', '30');
Notes:
- With
name: string, age: number
, we defined the arguments type. (Typing Annotations) - Then we can use some tool to help us do static type checking and help us find the bug before we run the code.
Actually, Python already supports this feature, and it's called type hints
, and you can check Support for type hints to learn more.
What is TypeScript
For now, Javascript does not support the Type Annotations
feature (https://github.com/tc39/proposal-type-annotations), so we need to use TypeScript
to help us do the job.
TypeScript is a free and open source high-level programming language developed and maintained by Microsoft. It is a strict syntactical superset of JavaScript and adds optional static typing to the language.
Notes:
Javascript
is an interpreted language, and it does not need a compilation step.TypeScript
, however, it needs a compilation step.TypeScript compiler
(tsc) will do type checking, and compile theTypeScript
toJavascript
code, which can run on the browser.
For example, below is the sample TypeScript
code:
// app.ts
function foo(name: string, age: number) {
console.log(`Hello ${name}, after 2 years, your age will be ${age + 2}`);
}
After compilation, it will look like this in the final built js file, and the Typing Annotations
are removed since they are not needed anymore.
// app.js
function foo(name, age) {
console.log("Hello ".concat(name, ", after 2 years, your age will be ").concat(age + 2));
}
Can TypeScript Work with Existing Projects?
Yes, you do not need to rewrite your project to make it work.
Even for a legacy project which is written in jQuery, you can still add TypeScript to it.
- If you want to add new business logic, you can create
*.ts
files and write your code in TypeScript. - Or you can rename your existing
*.js
files to*.ts
, and addTyping Annotations
to the existing code. - Using TypeScript does not mean you need to use React, Vue or other heavy frontend frameworks, you can still use jQuery, Bootstrap, or any other frontend libraries.
Pre-requisite
For Python developers, I'd like to introduce a boilerplate project which contains all the frontend tools, and you can use it to create your own frontend project within minutes.
https://github.com/AccordBox/python-webpack-boilerplate
This tool can work with Django, Flask smoothly.
Step 1: Install TypeScript
After we create the frontend project using python manage.py webpack_init
from https://github.com/AccordBox/python-webpack-boilerplate, let's go to the directory which contains package.json
, run command to install TypeScript
$ npm install --save-dev typescript ts-loader
Step 2: Config TypeScript
Create config file next to the package.json
, and name it tsconfig.json
, which is config file for TypeScript compiler
{
"compilerOptions": {
"strictNullChecks": true,
"sourceMap": true,
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"jsx": "react",
"allowJs": true,
"moduleResolution": "node"
},
"include": ["./src"]
}
The include
property specifies the files to be included in the TypeScript project, you might need to change it to your own project path.
.
├── package.json
├── src
├── tsconfig.json
└── webpack
Update frontend/webpack/webpack.common.js
const getEntryObject = () => {
const entries = {};
// for javascript entry file
glob.sync(Path.join(__dirname, "../src/application/*.js")).forEach((path) => {
const name = Path.basename(path, ".js");
entries[name] = path;
});
// for typescript entry file
glob.sync(Path.join(__dirname, "../src/application/*.ts")).forEach((path) => {
const name = Path.basename(path, ".ts");
entries[name] = path;
});
return entries;
};
module.exports = {
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
...
],
},
};
Notes:
- In the
getEntryObject
function, we add some code to handle*.ts
entry files. - In the
resolve
property, we add.ts
to theextensions
array, so we can import*.ts
files without adding the extension. - In the
rules
property, we add a new rule to handle*.ts
files.
Change frontend/src/application/app.js to frontend/src/application/app.ts to make it has *.ts
suffix.
// add foo function
function foo(name: string, age: number) {
console.log(`Hello ${name}, after 2 years, your age will be ${age + 2}`);
}
Here we added a foo
function which has Typing Annotations
Let's run the project and see what will happen
$ npm run watch
./src/application/app.ts 531 bytes [built]
webpack 5.70.0 compiled successfully in 38 ms
Let's do a test, if we call foo
function with wrong parameter type foo('michael','12')
in the app.ts
We will get
TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
As you can see, the TypeScript compiler will do type checking for us, and we can find the bug before we deploy the code on server.
Step 3: Code Linting
ESLint is a static code analysis tool for identifying problematic patterns found in JavaScript code.
We use ESLint
to help us check our Javascript code and detect potential problem.
To check TypeScript
, we will use typescript-eslint
$ npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin
If we check package.json, we can see the packages already installed there.
Update .eslintrc
{
"parser": "@babel/eslint-parser",
"extends": [
"eslint:recommended"
],
"env": {
"browser": true,
"node": true
},
"parserOptions": {
"ecmaVersion": 8,
"sourceType": "module",
"requireConfigFile": false
},
"rules": {
"semi": 2
},
// for typescript
"overrides": [
{
"files": "**/*.+(ts|tsx)",
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"extends": [
"plugin:@typescript-eslint/eslint-recommended", // removes redundant warnings between TS & ESLint
"plugin:@typescript-eslint/recommended" // rules specific to typescript, e.g., writing interfaces
]
}
]
}
Notes:
- We add
overrides
property to handle*.ts
files. - This can make eslint work for both Javascript and Typescript in our frontend project.
# let's check our code
$ npx eslint ./src
15:10 warning 'foo' is defined but never used @typescript-eslint/no-unused-vars
As you can see, we get a warning from typescript-eslint
, which reminds us that we have a function foo
which is defined but never used.
Eslint with Webpack
Update frontend/webpack/webpack.config.watch.js and frontend/webpack/webpack.config.dev.js
new ESLintPlugin({
...
extensions: ["js", "ts"],
}),
For ESLintPlugin
, we add ts
to the extensions
array, so it can check both *.js
and *.ts
files.
Now if we run npm run watch
or npm run dev
, we will get the same result as we run npx eslint ./src
Webpack -> ESLintPlugin -> Eslint -> Typescript-eslint for *.ts and Eslint for *.js
Step 4: Code Formatting
To format our Typescript code, we will use Prettier
Please check https://github.com/pre-commit/mirrors-prettier and install it with pre-commit
TypeScript Declaration File For 3-rd Party Libraries
The "*.d.ts" file is a TypeScript file that contains type declarations for JavaScript libraries.
For example, you wrote a Javascript lib jQuery many years ago, now you want to use it in a Typescript project, you can write a *.d.ts
file to declare the type of jQuery.
Actually, some people already did that, and you can find many *.d.ts
files for you to use in DefinitelyTyped
For example, for jQuery, you can install
$ npm install --save @types/jquery
The type declaration files would be placed under node_modules/@types
folder, by default (set by moduleResolution
in tsconfig.json
), Typescript will look for *.d.ts
files in node_modules/@types
folder.
Custom Declaration File
Let's add below code to the frontend/src/application/app.ts
window.AccordBox = {
hello: function () {
console.log('hello from AccordBox');
}
};
We added a AccordBox
object to the global window
object, and we want to use it in other Typescript files via window.Accordbox
If we build using Webpack, we got below error
TS2339: Property 'AccordBox' does not exist on type 'Window & typeof globalThis'.
The Window
interface is defined in the lib.dom.d.ts
of TypeScript, since it does not have the AccordBox
property, that is why we got the above error.
We can extend the Window
interface to solve this issue
Create frontend/src/types/window.d.ts
interface Window {
// Allow us to put arbitrary objects in window
/* eslint-disable @typescript-eslint/no-explicit-any */
[key: string]: any;
// someProperty: SomeType;
}
You can also explicitly declare the property if you like.
Now if we build using Webpack, the error will be resolved.
Conclusion
Typescript is a great tool which can help us to write better frontend code. It can work better with IDE code intellisense, and improve the productivity of frontend development.
You do not need to rewrite your whole frontend project, you can just start with a small part, and gradually migrate to Typescript step by step.