TypeScript
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
For a large number of enterprises' applications, TypeScript's static type checking, intellisense, friendly IDE are valuable. For more please see System Research Report For TypeScript.
However, we've met some problems influencing users' experience when developing Egg in TypeScript:
- The most outstanding Loader Mechanism (Auto-loading) makes TS not analyze dependencies in static.
- How to validate and show intellisense in
config.{env}.js
, when we modify settings by plugin and these configurations are automatically merged? - During the period of developing,
tsc -w
is created as an independent process to build up codes, it makes us entangled about where to save the temporary files, and the complicatednpm scripts
. - How to map to the TS source files instead of compiled js files in unit tests, coverage tests and error stacks online?
This article mainly describes:
- Developing principles of TS for the application layer.
- How do we solve the problem for developers with the help of the tool chain so that they have no scene about it and keep in consistency
For more about this tossing process, please see [RFC] TypeScript tool support.
# Quick Start
A quick initialization through the boilerplate:
$ mkdir showcase && cd showcase |
The boilerplate above will create a very simple example, for a detailed one please see eggjs/examples/hackernews-async-ts
# Principles of Catalogs
Some constraints:
- We've no plans for re-writing Egg in TS yet.
- Egg itself, with its plugin, will have corresponding
index.d.ts
for users to use easily. - TypeScript only belongs to a communication practice. We support it to some extent with our tool chain.
- TypeScript's version MUST BE 2.8 at least.
There's no obvious difference between TS's project and Egg's in js:
- The suffix is
ts
intypescript
style typings
folder is used to putd.ts
files (most of them are automatically created)
showcase |
# Controller
// app/controller/home.ts |
# Router
// app/router.ts |
# Service
// app/service/news.ts |
# Middleware
// app/middleware/robot.ts |
When some property's name in config matches your middleware files's, Egg will automatically read out all of its sub properties.
Let's assume you've got a middleware named uuid
, and its config.default.js is:
; |
In uuid
middleware:
// app/middleware/uuid.ts |
Notice: The return value of any middleware must be any
now, otherwise there's a compiling error about the compatibility of context between Koa's context in route.get/all and Egg's Context.
# Extend
// app/extend/context.ts |
# Config
Config is a little complicated, because it supports:
- In Controller and Service, we need "multi-layer" intellisense configurations, they are automatically related to each other.
- In Config,
config.view = {}
will also support intellisense. - In
config.{env}.ts
, we can use customized configuration settings with intellisense inconfig.default.ts
.
// app/config/config.default.ts |
Notice: We need egg-ts-helper
to merge the returned config type from config.default.js
into egg's EggAppConfig
.
When EggAppConfig
is merged with the returned type of config.default.ts
, we can also get the intellisenses of our customized configs in config.default.ts
like this following:
// app/config/config.local.ts |
Remarks:
Conditional Types
is the KEY to solving config's intellisense.- Anyone if interested in this, have a look at the implement of
PowerPartial
at egg/index.d.ts.
// {egg}/index.d.ts |
# Plugin
// config/plugin.ts |
# Lifecycle
// app.ts |
# Typings
The folder is the principle of TS, where **/*.d.ts
are automatically recognized.
- Put developers' hand-writing suggestions in
typings/index.d.ts
. - Tools will automatically generate
typings/{app,config}/**.d.ts
. Please DO NOT change manually (See below).
# Developing period
# ts-node
egg-bin
has built ts-node in, and egg loader
will automatically load *.ts
and compile them in memory during the period of development.
Now dev
/ debug
/ test
/ cov
are supported.
Developers only need to config in package.json
simply:
{ |
# egg-ts-helper
Due to the automatic loading mechanism, TS cannot analyze dependencies in static or relationship intellisense.
Luckily, TS has many tricks to cope with it. We can use Declaration Merging to write d.ts
as a helper.
E.g: app/service/news.ts
will automatically load ctx.service.news
and recognize it, if you write like this below:
// typings/app/service/index.d.ts |
It's a bit too bothering to write them manually, so we offer you a tool egg-ts-helper, with which we can analyze and generate d.ts
files automatically.
What we do is just to do some configs in package.json
:
{ |
The corresponding d.ts
files are automatically generated in typings/{app,config}/
during the developing period. DO NOT modify manually in case of being overwritten.
The tool nowadays can support egg projects in ts and js with intellisenses.
# Unit Test and Cov
Unit Test is a MUST in development:
// test/app/service/news.test.ts |
Run commands as what you do before, and we've built Error stacks and coverages
in.
{ |
# Debug
There's no main difference for debugging in TS, it can reach correct positions through sourcemap
.
{ |
# Deployment
# Building
- In a PROD env, we tend to build ts to js, and recommend to build on
ci
and make packages.
Configs in package.json
:
{ |
And the corresponding tsconfig.json
:
{ |
Notice: When ts and js files are with the same name, egg will first load js ones. So during the development period, egg-ts-helper
will be automatically called to clear up all the js files with the same names, of course you can use npm run clean
to clear them manually.
# Error Stacks
Codes online are js after compilation, however what we expect is to see error stacks pointing at TS source codes. So:
- When buiding the project, we should insert info of
sourcemap
ininlineSourceMap: true
. - For developers, there's no need to worry about correcting the positions to the right error stacks in a built-in
egg-scripts
.
For more detailed info:
# Guides to the Developments of Plugin/Framework
Principles:
- DO NOT recommend to develop plugin/framework in TS directly, we should publish them in js to npm.
- When you write a plugin/framework, the corresponding
index.d.ts
should be included. - By Declaration Merging we can inject the functions of plugin/framework into Egg.
- All are mounted on
egg
module, DO NOT use the outer layer.
# Plugin
Styles can be referred from the automatically generated egg-ts-helper
:
// {plugin_root}/index.d.ts |
# The Outer Framework
Definitions:
// {framework_root}/index.d.ts |
For developers, they can directly import your framework:
// app/service/news.ts |
# Frequently-asked questions
Here're some questions asked by many people with answers one by one:
# ts
won't be loaded when running npm start
npm start
actually runs egg-scripts start
, however we ONLY integrate ts-node
in our egg-bin
, it means ts won'be loaded until we use egg-bin
.
egg-scripts
is the cli for PROD, and we suggest you compiling all the ts to js before running because of robustness and capbility. That's the reason why we don't suggest you using ts-node
to run the application in PROD.
On the contrary, ts-node
can reduce the cost of management for compiled files from tsc
in DEV, and the performance loss can almost be ignored, so ts-node
is integrated into egg-bin
.
In summary: Please use tsc
to compile all ts files into js through npm run tsc
, and then run npm start
.
# There's no loaded objects when using egg's plugin
There're mainly two reasons causing this problem:
1. No related defination d.ts
file for the plugin
If you want to load some object into egg, you MUST follow the Plugin / Framework Development Instructions
below by making a declaration file into your own plugin.
You can also create a new declaration file to solve this problem when you are eager to deploy to PROD. Suppose I'm using the plugin of egg-dashboard
and it has a loading object in egg's app without any declarations, so if you directly use app.dashboard
, there'll be a type error occuring, and you want to solve it eagerly, you can create index.d.ts
in 'typings' by writing this following:
// typings/index.d.ts |
Now it's solved! Of course your PRs for plugins without declaration files are welcomed to help others!
2. egg's plugin has the declaration but not loaded
If the egg's plugin has the right declaration, we need to import it exclipitly and ts can load the related object.
If you use egg-ts-helper
, it will automatically generate the exclipit importing declarations according to what plugins you've enabled in the application. If you don't use that, you have to import the declaration of plugins manually.
// typings/index.d.ts |
Notice: You MUST use 'import' in d.ts
, because most of egg's plugins are without main entry points. There'll be errors occuring if you import directly in ts.
# paths
is invalid in tsconfig.json
Strictly speaking, this has nothing to do with egg but with many people's questions, and we'll give our answer to it. The reason is tsc
WON'T convert the import path when compiling ts to js, so when you config paths
in tsconfig.json
and if you use paths
to import the related modules, you are running the high risk that you cannot find them when compiled to js.
The solution is either you don't use paths
, or you can ONLY import some declarations instead of detailed values. Another way is that you can use tsconfig-paths to hook the process logic in node's path module to support paths
in tsconfig.json
.
You can directly import tsconfig-paths
in config/plugin.ts
, because plugin.ts
is ALWAYS loaded firstly in both App and Agent.
// config/plugin.ts |
# How to write unit tests for declarations of egg's plugins?
Many contributors don't know how to write unit tests for their plugin's declaration files, so we also have a discussion about it here:
When you finish writing a declaration for an egg's plugin, you can write your own application in test/fixures
(you can refer https://github.com/eggjs/egg-view/tree/master/test/fixtures/apps/ts). Do remember to add paths
configs into tsconfig.json
to do imports in the fixture. Take egg-view
as an example:
"paths": { |
Do remember DO NOT CONFIG "skipLibCheck": true
in the tsconfig.json
, and if you set it to true, tsc
will ignore the type checks when compiling, and there's no meaning for unit tests for your plugin's declarations.
In the end, add a test case to check whether your declaration works properly or not. See egg-view
example below:
describe('typescript', () => { |
Some other unit test projects as your references: