The practice of typescript in react project

Source: Internet
Author: User
Tags diff


Some time ago, I wrote a typescript in the node project.
There is an explanation of why you should use itTS, andNodeWhat is the structure of a project in.
But that is just a pure interface project, happen to catch up with another recent project refactoring also by me to host, after the last practice, tasted theTSsweetness, not hesitate to useTS+Reactto reconstruct the project.
This refactoring includes not only refactoringNode(previousExpressprojects), but also front-end refactoring (previouslyjQuerydriven by multi-page applications).


Project structure


Because the current project is not intended to be separated (an internal tool platform class project), the approximate structure is based on the structure of the lastNodeproject, above which a numberFrontEndof directory structures have been added:


.
  ├── README.md
  ├── copy-static-assets.ts
  ├── nodemon.json
  ├── package.json
+ ├── client-dist
+ │   ├── bundle.js
+ │   ├── bundle.js.map
+ │   ├── logo.png
+ │   └── vendors.dll.js
  ├── dist
  ├── src
  │   ├── config
  │   ├── controllers
  │   ├── entity
  │   ├── models
  │   ├── middleware
  │   ├── public
  │   ├── app.ts
  │   ├── server.ts
  │   ├── types
+ │   ├── common
  │   └── utils
+ ├── client-src
+ │   ├── components
+ │   │   └── Header.tsx
+ │   ├── conf
+ │   │   └── host.ts
+ │   ├── dist
+ │   ├── utils
+ │   ├── index.ejs
+ │   ├── index.tsx
+ │   ├── webpack
+ │   ├── package.json
+ │   └── tsconfig.json
+ ├── views
+ │   └── index.ejs
  ├── tsconfig.json
  └── tslint.json


where the green (or possibly a+number) of the file for this new.
client-distandviewsboth are generated by thewebpackactual source files areclient-srcunder. There's really no cost in separating this structure from before and after it's split .
Some of these folders are divided below:


Dir/file desc
index.ejs Entry file for the projecthtml, adoptedejsas the rendering engine
index.tsx Entry file for the projectjs, suffix usedtsx, for two reasons:
1. We will usetsReactthe development of the program
2. The.tsxfile looks better on vs codeicon:p
tsconfig.json Aretscsome of the configuration files used for compilation execution
components directory where components are stored
config The location of various configuration items, such as the request interfacehostor the mapping of various statesmap(which can be understood as enumerated objects are here)
utils Where some public functions are stored, various reusable codes should be placed here.
dist Storage location of various static resources, pictures and other files
webpack It containswebpackscript commands for various environments anddllgenerates
An attempt to reuse code in front and back


In fact, the side also missed a new folder, we havesrca new directory under the directorycommon, this directory is to store some public functions and publicconfig, different fromutilsor, theconfigcode here is the front and back sharing, So the function of this side must be completely without any environment dependencies, not including any business logic.



Similar digital kilobits, date formatting, or the port number of the service listener, these do not contain any logic, and the environment is not strongly dependent on the code, we can put here.
This is also not done before and after the separation of a small sweet bar, can share a portion of the code before and after.



To implement such a configuration, the following items need to be modified based on:


    1. srcutilsand part of theconfigcode is migrated to thecommonfolder, primarily to differentiate
    2. In order to minimize the impact on the previousnodestructure, we need tocommonadd an index file under the folderindex.tsandutils/index.tsreference it below, so that for the use of thenodeaspect, it is not necessary to care whether the file is fromutilsorcommon
// src/common/utils/comma.ts
Export default (num: number): string => String(num).replace(/\B(?=(\d{3})+$)/g, ',')

// src/common/utils/index.ts
Export { default as comma } from './comma'

// src/utils.index.ts
Export * from '../common/utils'

// src/app.ts
Import { comma } from './utils' // don't need to care whether it comes from common or from utils

Console.log(comma(1234567)) // 1,234,567
    1. Then the configuredwebpackaliasproperty is used towebpackcorrectly locate its path
// client-src/webpack/base.js
module.exports = {
  resolve: {
    alias: {
       '@Common': path.resolve(__dirname, '../../src/common'),
    }
  }
}
    1. At the same time we need totsconfig.jsonbe configured tovs codefind the corresponding directory, otherwise you will be prompted in the editorcan't find module XXX
// client-src/tsconfig.json
{
   "compilerOptions": {
     "paths": {
       // used to introduce a `module`
       "@Common/*": [
         "../src/common/*"
       ]
     }
   }
}
    1. And at the endclient-src/utils/index.tsof the process of writing a similarserverend.
// client-src/utils/index.ts
export * from '@Common/utils'

// client-src/index.tsx
import { comma } from './utils'

console.log(comma(1234567)) // 1,234,567
Construction of the environment


If you use itvs codefor development and use itESLint, you need to modify theTSsyntax support suffix and addtypescriptreactsome processing so that some rules are automatically fixedESLint:


"eslint.validate": [
  "javascript",
  "javascriptreact",
  {
    "language": "typescript",
    "autoFix": true
  },
  {
    "language": "typescriptreact",
    "autoFix": true
  }
]
Configuration of the Webpack


Because in front of the use ofReact, according to the current mainstream,webpackis certainly essential.
There is no choice of maturecra(Create-react-app) to build the environment, for the following reasons:


    1. webpackUpdated to 4 have not tried, want to play a trick on their own
    2. Combined withTSthings inside the company, there will be some custom configuration situations, worrying about two development too cumbersome


But in fact there is not much configuration, this refactoring selected UI framework for the implementation of Google material: Material-ui
And they're using JSS to write style, so it doesn't involve the same set of previous habitsscssloader.



webpackSome of the following files are divided:


file desc
common.js Publicwebpackconfiguration, similarenvoptions
dll.js For early packaging of some third-party libraries that will not be modified, speeding up compilation efficiency at development time
base.js Can be understood as awebpackbasic configuration file, genericloaderas well aspluginshere
pro.js Special configurations for production environments (code compression, resource uploads)
dev.js Special configuration of the development environment (source-map)


dllis a very early routine, probably need to modify so many places:


    1. Create a separatewebpackfile to generate thedllfile
    2. webpackmake a reference to a file in a normal filedll
// dll.js
{
   Entry: {
     // Library that needs to be packaged in advance
     Vendors: [
       'react',
       'react-dom',
       'react-router-dom',
       'babel-polyfill',
     ],
   },
   Output: {
     Filename: 'vendors.dll.js',
     Path: path.resolve(__dirname, '../../client-dist'),
     // Don't miss this option when exporting
     Library: 'vendors_lib',
   },
   Plugins: [
     New webpack.DllPlugin({
       Context: __dirname,
       // The specific mapping of the `vendors.dll.js` code thrown out, through which the `dll` file is referenced
       Path: path.join(__dirname, '../dist/vendors-manifest.json'),
       Name: 'vendors_lib',
     })
   ]
}

// base.js
{
   Plugins: [
     New webpack.DllReferencePlugin({
       Context: __dirname,
       Manifest: require('../dist/vendors-manifest.json'),
     }),
   ]
}


watchWhen you do this, the package will skip theverdorsones that exist in the file.
One thing to note, if you finally need to upload these static resources, remember toverdors.dll.jsupload together



In the local development, thevendorsfile is not automatically injected into thehtmltemplate, so we are useful to another plugin, Add-asset-html-webpack-plugin.
You may also encounterwebpackan unlimited number of repackaging in use, which needs to be configuredignoreto address-.-:


// dev.js
Const HtmlWebpackPlugin = require('html-webpack-plugin')
Const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin')

{
   Plugins: [
     // Put the `ejs` template file in the target folder and inject the entry `js` file
     New HtmlWebpackPlugin({
       Template: path.resolve(__dirname, '../index.ejs'),
       Filename: path.resolve(__dirname, '../../views/index.ejs'),
     }),
     // Inject the `vendors` file into the `ejs` template
     New AddAssetHtmlPlugin({
       Filepath: path.resolve(__dirname, '../../client-dist/vendors.dll.js'),
       includeSourcemap: false,
     }),
     / / Ignore the file changes of `ejs` and `js`, avoid the problem of `webpack` unlimited repackaging
     New webpack.WatchIgnorePlugin([
       /\.ejs$/,
       /\.js$/,
     ]),
   ]
}
Typescript Related Configurations


TSThe configuration is divided by two blocks, one iswebpackthe configuration, the other istsconfigthe configuration.



First of allwebpack, for thetstsxfile we used twoloader:


{
   Rules: [
     {
       Test: /\.tsx?$/,
       Use: ['babel-loader', 'ts-loader'],
       Exclude: /node_modules/,
     }
   ],
   Resolve: {
     // Don't forget to configure the ts tsx suffix
     Extensions: ['.tsx', '.ts', '.js'],
   }
}


ts-loaderUsed toTSconvert some of the attributes to aJScompatible syntax, and then execute thebabelreact/jsxcode that is related to processing, resulting in executableJScode.



Then istsconfigthe configuration,ts-loaderthe execution is based on the configuration here, the approximate configuration is as follows:


{
   "compilerOptions": {
     "module": "esnext",
     "target": "es6",
     "allowSyntheticDefaultImports": true,
     // relative starting path of import
     "baseUrl": ".",
     "sourceMap": true,
     // Build the output directory, but because of the use of `webpack`, this configuration has no use for eggs.
     "outDir": "../client-dist",
     // Turn on the `JSX` mode,
     // The `preserve` configuration lets `tsc` not handle it, but instead uses the subsequent `babel-loader`
     "jsx": "preserve",
     "strict": true,
     "moduleResolution": "node",
     // Turn on the use of the decorator
     "experimentalDecorators": true,
     "emitDecoratorMetadata": true,
     // `vs code` is required, find the corresponding path during development, the real reference is `alias` configured in `webpack`
     "paths": {
       "@Common": [
         "../src/common"
       ],
       "@Common/*": [
         "../src/common/*"
       ]
     }
   },
   "exclude": [
     "node_modules"
   ]
}
Configuration of the Eslint


In recent times, our team has madeairbnbsome customizations based on theESLintrules, creating our own eslint-config-blued
There are also two derivative versions of react and typescript.



AboutESLintThe configuration file.eslintrc, there are two copies in this project. One is the root directoryblued-typescriptand the other is theclient-srcnextblued-react+blued-typescript.
Because the root directory is more used fornodeprojects, there is no need to put inreactany dependencies.


# .eslintrc
extends: blued-typescript

# client-src/.eslintrc
extends: 
  - blued-react
  - blued-typescript


A small detail to be aware of
Because we'rereacttypescriptusing it with the implementation versionparser.
reactThe babel-eslint is used, andtypescriptthe Typescript-eslint-parser is used.
Butparserthere can only be one, fromoptionthe name can be seenextends,,, therepluginsrulesparseris no plural.
So the order of the two plug-insextendsbecomes critical, and thebabelsyntax is not understood nowTS, but it seems thatbabeldevelopers have the will to support itTS.
But for now, be sure toreactuse it before,typescriptafter, toparsercover ittypescript-eslint-parser.


Change of Node layer


In addition to the above mentioned on the two ends of the common code, but also need to add acontrollerpage to spit, because the use ofrouting-controllersthis library, rendering a static page is encapsulated very good, just need to modify two pages, a template to setrenderthe root directory, The other is used to set the name of the template to spit out:


// controller/index.ts
Import {
   Get,
   Controller,
   Render,
} from 'routing-controllers'

@Controller('/')
Export default class {
   @Get('/')
   @Render('index') // Specify a template name
   Async router() {
     // some variables when rendering the page
     // Similar to the previous ctx.state = XXX
     Return {
       Title: 'First TypeScript React App',
     }
   }
}

// app.ts
Import koaViews from 'koa-views'

// Add the directory where the template is located
// and the rendering engine, file suffix used
App.use(koaViews(path.join(__dirname, '../views'), {
   Options: {
     Ext: 'ejs',
   },
   Extension: 'ejs',
}))


If it's multiple pages, it's good to create multipleRendertsfiles.


Deep Pits, note


The currentrouting-controllerKoasupport is not very good, (the original author isKoanot very understanding, resulting inRenderthe corresponding interface is requested once, all subsequent interfaces will return directly to the template file, because in charge of the template rendering of theURLtrigger, it should return data, However, the current processing is to add a middleware into theKoa, so any request will be the template file as data to return, so it does@Rendernot apply to theKoadriver.
However, I have submitted a PR, ran through the test case, waiting to be merged code, but this is a temporary modification, involving the library for external middleware registration order problem, so forapp.tsadditional modifications to be able to achieve.


// modification of app.ts
Import 'reflect-metadata'
Import Koa from 'koa'
Import koaViews from 'koa-views'
Import { useKoaServer } from 'routing-controllers'
Import { distPath } from './config'

// Manually create the koa instance, then add the `render` middleware to make sure the `ctx.render` method is added to the request header.
Const koa = new Koa()

Koa.use(koaViews(path.join(__dirname, '../views'), {
   Options: {
     Ext: 'ejs',
   },
   Extension: 'ejs',
}))

// Use `useKoaServer` instead of `createKoaServer`
Const app = useKoaServer(koa, {
   Controllers: [`${__dirname}/controllers/**/*{.js,.ts}`],
})

// The subsequent logic is the same
Export default app


Of course, this is the new version after the logic, based on the existing structure can also be around the past, but cannot use the@Renderadorner, throw awaykoa-viewsthe direct use of the internal consolidate:


// controller/index.ts
// This modification does not need to change `app.ts`, you can use `createKoaServer` directly.
Import {
   Get,
   Controller,
} from 'routing-controllers'
Import cons from 'consolidate'
Import path from 'path'

@Controller()
Export default class {
   @Get('/')
   Async router() {
     / / Get the template rendered data directly when the interface returns
     Return cons.ejs(path.resolve(__dirname, '../../views/index.ejs'), {
       Title: 'Example For TypeScript React App',
     })
   }
} 


The current example code uses the top-of-the-line scenario


Summary


At this point, a complete TS front-end project architecture has been completed (the rest of the task is to fill in the skeleton inside the code).
I have updated the previous typescript-exmaple to add some of the front-end + examples used in this refactoringTSReact, and also include@Rendersome compatibility.



TypeScriptis a great idea to solve more than a fewjavaScriptof the most notorious problems.
Using a static language for development not only improves the efficiency of development, but also reduces the chance of errors to occur.
Combined with the powerfulvs code, Enjoy it.



If there are any problems in usingTS, or if there is any better idea, welcome to communicate the discussion.


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.