preface

Recently, my tutor arranged a task to add TypeScript to a template configured with React, Webpack4 and other common functions in the market. As the saying goes: environment configuration three hours, Coding less than five minutes. Configuring the environment can be a real waste of time, so I wrote an article that I hope will help you to configure the TS front end.

The template directory

Start with the fully configured project address: github.com/dcison/Reac…

├─..Bass Exercises ─.Bass Exercises..Bass Exercises ─.Index.html ├─ Package. json ├─ SRC │ ├─ Reducers │ ├─ Components │ ├ ─ ─ actions. Ts │ ├ ─ ─ global. Styl │ ├ ─ ─ index. The TSX │ └ ─ ─ store. Ts └ ─ ─ the staticCopy the code

The article directories

  1. Basic configuration of Babel and Webpack
  2. TypeScript configuration
  3. Eslint validates configurations based on Vscode
  4. React
  5. Style related
  6. React-Router
  7. Code Splitting
  8. Webpack hot update
  9. Redux
  10. Git commit verification

Babel, Webpack

  1. Start by creating the entry file index.js in SRC and writing some random code, such as
// src/index.js
import type from './components/Apple'  
console.log(type)

// src/components/Apple.js
export default {
    type: 'apple'
}
Copy the code
  1. Configuration webpack. Config. Babel. Js
import path from 'path';
export default {
	mode: 'development'.devtool: 'eval'.entry: {
		index: './src/index.js',},output: {
		publicPath: '/'.filename: 'js/[name].js'.chunkFilename: 'js/[name].js'.path: path.resolve(__dirname, 'dist')},module: {
		rules: [{test: /\.js$/.exclude: /node_modules/.use: {
				loader: 'babel-loader'}}},devServer: {
		port: 3000
	},
	resolve: {
		extensions: [
			'.jsx'.'.js'.'.ts'.'.tsx']}};Copy the code
  1. Write babelrc.
{
    "presets": [
        "@babel/preset-env"]."plugins": []}Copy the code
  1. Write “scripts” and “devDependencies” in package.json
{
  "scripts": {
    "start": "webpack-dev-server --content-base"
  },
  "devDependencies": {
    "@babel/cli": "^ 7.2.3." "."@babel/core": "^ 7.2.2." "."@babel/preset-env": "^ 7.3.1"."@babel/register": "^ 7.0.0." "."babel-loader": "^ 8.0.5"."webpack": "^ 4.29.0"."webpack-cli": "^ 3.2.1." "."webpack-dev-server": "^ 3.1.14"}},Copy the code
  1. Run yarn start or NPM start to install dependencies. If success is displayed, the Babel and Webpack configurations are successful.

TypeScript

  1. Change the JS file suffix to TSX
  2. Configure webpack.config.babel.js, including rules, entry file suffix: change to TSX.
entry: {
		index: './src/index.tsx'}, {test: /\.tsx? $/.exclude: /node_modules/.use: {
   	loader: 'ts-loader'}}Copy the code
  1. Create tsconfig.json in the root directory
{
    "compilerOptions": {
      "module": "esnext"."target": "es5"."lib": ["es6"."dom"]."sourceMap": true."jsx": "react"."noImplicitAny": true."allowJs": true."moduleResolution": "node"
    },
    "include": [
      "src/*"]."exclude": [
      "node_modules"]}Copy the code
  1. Install the dependency yarn add -d TS-loader typescript
  2. Yarn start If success persists, the operation succeeds

Eslint

  1. Add the.eslintrc configuration file as follows:
{ "env": { "browser": true, "es6": true }, "extends": "eslint:recommended", "parserOptions": { "ecmaFeatures": { "experimentalObjectRestSpread": true, "jsx": true }, "sourceType": "module" }, "parser": "typescript-eslint-parser", "plugins": [ "react", "typescript" ], "rules": { //... Add your own rules}}Copy the code
  1. Install dependency yarn add -d eslint eslint-plugin-typescript typescript eslint-parser eslint-plugin-react
  2. Json, find eslint.validate, add typescript and typescriptreact to listen for TS and TSX files, respectively, as follows:
"eslint.validate": [
  "javascript",
  "javascriptreact",
  {
      "language": "typescript",
      "autoFix": true
  },
  {
      "language": "typescriptreact",
      "autoFix": true
  }
]
Copy the code

React

  1. Use yarn add -d html-webpack-plugin to configure html-webpack-plugin
  2. Configure in webpack.config.babel.js
import HtmlWebpackPlugin from 'html-webpack-plugin';

plugins: [	
		new HtmlWebpackPlugin({
			title: 'template'.hash: false.filename: 'index.html'.template: './index.html',})]Copy the code
  1. Write an HTML template in the root directory

      
<html>
<head>
  <meta charset="utf-8">
  <title><% = htmlWebpackPlugin.options.title| | '% ></title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
Copy the code
  1. Install dependencies related to React: yarn add React @types/ React React – dom@types/React – dom@babel /preset- React
  2. Add a rule to.babelrc
"Presets ": ["@babel/preset-env", "@babel/preset-react" // add this],Copy the code
  1. Modify the SRC/index. The TSX
import * as React from 'react';
import * as ReactDOM from "react-dom";
class SomeComponent extends React.Component<{}, {}> {
	render () {
		return <div>hello</div>;
	}
}
ReactDOM.render(
	<SomeComponent/>,
	document.getElementById('root')
);
Copy the code
  1. Run YARN start in the root directory. If Hello is displayed, the React environment is configured

CSS and Style preprocessing (take Stylus as an example)

  1. Install the CSS-related loader: yarn add -d CSs-loader style-loader
  2. Configure rules in webpack.config.babel.js
{
	test: /\.css$/.use: [
		'style-loader',
		{
			loader: 'css-loader'.options: {
				localIdentName: '[path][name]__[local]--[hash:base64:5]'}}}]Copy the code
  1. Test: Create a CSS file under SRC and reference it in index.tsx
import './global.css';
Copy the code
  1. Seeing the style applied indicates that the CSS is properly configured. Next, configure the Stylus
  2. Installation-related dependencies: YARN add -d stylus-loader stylus
  3. Continue to configure rules in webpack.config.babel.js
{
	test: /\.styl$/.use: [
		'style-loader',
		{
			loader: 'css-loader'.options: {
				modules: true.import: true.importLoaders: 1.localIdentName: '[path][name]__[local]--[hash:base64:5]'}}, {loader: 'stylus-loader'}},],Copy the code
  1. Change the CSS suffix file to styl and the file in index reference to styl. If the style is still changed, the style preprocessor is configured successfully.

2.15 Day Add: Add typings-for-CSs-modules-loader

  1. Install dependenciesYarn add -d typings-for-CSS-modules-loader CSs-loader @^1.0.1(CsS-Loader 2.x or later has problems)
  2. Modify rules in webpack.config.babel.js (here only modify stylus)
{
	test: /\.styl$/.use: [
		'style-loader',
		{
			loader: 'typings-for-css-modules-loader'.options: {
				modules: true.namedExport: true.camelCase: true.minimize: true.localIdentName: "[local]_[hash:base64:5]".stylus: true}}, {loader: 'stylus-loader'}},],Copy the code
  1. Usage:
import * as styles from './apple.styl'; <div className={styles.redFont}> Test text </div>Copy the code
  1. pit

    1. Here we run into a pit where all the CSS-moduels/typings-for-CSs-modules-loader tutorials don’t tell you what happens when the include configuration isn’t written. When we did not write this sentence before, because the index speed of D. TS is not fast enough, or the connection is not automatically established internally (the reason is confusing here), it will lead to the error that the type is not found directly in the command line and window, and we need to manually recompile once, which is very inefficient. Include :path.resolve(‘ SRC /’)

      For some reason, after adding the inclue configuration, an error was reported that the corresponding style module was not found (but the corresponding.d.ts declaration file had already been generated). Since hot update is enabled, there is no need to recompile, and a small change triggers a hot update of Webpack.

    2. The second pit, if you only reference it without using it, will not generate the corresponding.d.ts file and will therefore fail to package

React-Router

  1. Install dependencies: yarn add react-router-dom@types /react-router-dom
  2. Modify devServer in webpack.config.babel.js and add historyApiFallback
devServer: {
   historyApiFallback: true
}
Copy the code
  1. In Component apple. TSX and Xiaomi. TSX modify the code as follows
// src/components/Apple.tsx import * as React from 'react'; class Apple extends React.Component<any, any> { state = { text: 'iphone XR' } handleChange = () => { this.setState({ text: 'iphone8' }); } render () {return <> <button onClick={this.state.text} </>; } } export default Apple; / / SRC/components/xiaomi TSX composite structure similar to / / code, just changed the content of the text, so don't put the / / text: 'xiaomi 8'Copy the code
  1. Modify the code in index.tsx as follows
import * as React from 'react'; import * as ReactDOM from "react-dom"; import { BrowserRouter, Route, Redirect, Switch, Link } from 'react-router-dom'; import Apple from './components/Apple'; import Xiaomi from './components/XiaoMi'; import './global.styl'; Const Header = () = > (< ul > < li > < Link to = "/" > apple < / Link > < / li > < li > < Link to = "/ phone/xiaomi" > millet < / Link > < / li > < / ul >); ReactDOM.render( <div> <BrowserRouter> <> <Header /> <Switch> <Route path="/phone/apple" component={Apple} /> <Route path="/phone/xiaomi" component={Xiaomi} /> <Redirect to="/phone/apple" /> </Switch> </> </BrowserRouter> </div>, document.getElementById('root') );Copy the code
  1. Start your YARN Start to test

code splitting

  1. Add a new file in the Component directory asyncComponent.tsx
import * as React from "react";
export default function asyncComponent (importComponent: any) {
	class asyncComponent extends React.Component<any.any> {
		constructor (props: any) {
			super(props);
  
			this.state = {
				component: null
			};
		}
  
		async componentDidMount () {
			const { default: component } = await importComponent();
  
			this.setState({
				component: component
			});
		}
  
		render () {
			const C = this.state.component;
  
			returnC ? <C {... this.props} /> :null; }}return asyncComponent;
}
Copy the code
  1. Replace the reference in index.tsx with the following
import asyncComponent from "./components/asyncComponent";
const Apple =  asyncComponent((a)= > import("./components/Apple"));
const XiaoMi =  asyncComponent((a)= > import("./components/XiaoMi"));
Copy the code
  1. Success is indicated if the following image appears

Webpack hot update

  1. Install dependency yarn add -d @types/webpack-env
  2. Enable hot updates, modify plugins and devServer in webpack.config.babel.js, and add two attributes
import webpack from 'webpack';
plugins: [	
		new webpack.HotModuleReplacementPlugin()
],
devServer: {
		hot: true
},
Copy the code
  1. Add the following code to the entry file. more details can be found on the official website
if (module.hot) {
	module.hot.accept();
}
Copy the code
  1. Modify some text content freely. If the following type is displayed, the configuration is successful

Redux

  1. Installation dependency: yarn add @types/react-redux redux redux-thunk
  2. Add an actions.ts under SRC/to hold all actions
export const initPhone = (data: object) = > {
	return {
		type: 'INIT_PHONE',
		data
	};
};
export const setPhoneMoney = (data: object) = > {
	return {
		type: 'SET_MONEY',
		data
	};
};
Copy the code
  1. Create a reducers directory under SRC/to save the reducer services
// src/reducers/Phone.ts
var initState = {
	name: ' ',
	money: 0
};
export default function (state = initState, action: any) {
	switch (action.type) {
			case 'INIT_PHONE':
				return {
					...state,
					name: action.data.name,
					money: action.data.money
				};
			case 'SET_MONEY':
				return {
					...state,
					money: action.data.money
				};
			default:
				returnstate; }}Copy the code
  1. Create store.ts under SRC /
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunkMiddleware from 'redux-thunk';
import Phone from './reducers/Phone';
const rootReducer =  combineReducers({
	Phone
});
const createStoreWithMiddleware = applyMiddleware(
	thunkMiddleware,
)(createStore);
function configureStore (initialState? :any) {
	return createStoreWithMiddleware(rootReducer, initialState);
}
export default configureStore();
Copy the code
  1. Modify the code in the Component, using Component/ apple.tsx as an example
import * as React from 'react'; import { connect } from 'react-redux'; import * as action from '.. /actions'; Class Apple extends React.Component<any, any> {componentDidMount () {this.props. InitPhone ({name: 'componentDidMount ', money: 10000}); } handleChange = () => { this.props.setPhoneMoney({ money: this.props.money - 20 }); } render () {return <> <button onClick={this.props. Name} now {this.props. Money} < / a >; } } function mapStateToProps (state: any) { return { name: state.Phone.name, money: state.Phone.money }; } export default connect(mapStateToProps, action)(Apple);Copy the code
  1. Modify the entry code (SRC /index.tsx)
import { Provider } from 'react-redux'; import store from './store'; <Provider store={store}> // Wrap this container around <BrowserRouter> <> <Header /> <Switch> <Route path="/phone/apple" component={apple} /> <Route path="/phone/xiaomi" component={XiaoMi} /> <Redirect to="/phone/apple" /> </Switch> </> </BrowserRouter> </Provider>Copy the code

Husky and lint – staged

  1. Installation relies on YARN Add -D Lint-staged Husky
  2. Just add the following code to package.json
"scripts": {
    "precommit": "lint-staged"
  },
"lint-staged": {
 "*.{ts,tsx}": [
   "eslint --fix",
   "git add"
 ]
}
Copy the code
  1. Submit your own test

reference

  • Code Splitting
  • Eslint integrates Typescript prompts for React code
  • Build super-smooth code from Husky and Lint-staged to review the workflow
  • About CSS Modularity

The latter

Because this article is written side configuration, the time span is also a little big, resulting in the writing of the train of thought off and on, also did not do too much text embellish, to see the last readers pass eye drops.