This is my first nuggets blog post, the beginning of nuggets writing. Hi, everybody. I’m a new member of the “Two Smelly cats” team of the Nuggets.
Without authorization, prohibit reprinting ~
The opening
This time, we will discuss how to build a modern front-end engineering from scratch and summarize the key points. There will be a lot of tools, libraries, frameworks, and a lot of writing to do. In the article, more attention is paid to how to set up and how to operate, so the detailed configuration of some tools may not be so detailed, but also need to explore.
Suitable for some front-end entry knowledge of the people, if it is a new friend, when do not know a certain is to do what, check the information, check until you understand his role (also spend not much time) ~
Again, if you don’t know, it’s very important to look it up
The technologies used in this paper are the latest version of the current period (January 2022). If the time is long, the configuration may be different.
This article may be quite long, but also please calm down and slowly read (don’t turn over the hero), the best practice again
Thank you again
(I set the size of the image of the nuggets editor, but it doesn’t seem to work.)
Catalogue and key technologies
First of all, the whole technology to be used roughly listed, but also for friends to understand the first look:
HTML、CSS、JS、Webpack、Babel、TS、Less、Postcss、React、Redux、Axios、Eslint、Husky、Git
That’s about it
Ok, let’s get down to business: Joy:
I. Principle of project operation
First of all, we need to understand the essence of a project or a project. At present, the essential operation logic of front-end engineering remains unchanged: reading HTML and CSS files for parsing and rendering, and loading JS for execution. Therefore, the purpose of the construction project is to come up with the same old three, put in the UserAgent to run.
At present, the popular project management mode is the package management mechanism of Node. At present, there are many kinds of management mechanisms: NPM, CNPM, YARN, PNPM, etc. We will choose the most familiar NPM.
Any project starts with a new folder. First we create a new folder and initialize it as the NPM project:
mkdir demo && cd demo
npm init
Copy the code
Initialization will require you to fill in some information. Here, we will select some project properties. Check the Node documentation if you don’t understand the specific field meanings. After initialization, the project is formed, remember that we are already a project at this point. After the mkdir demo folder is created, we are a project in which we can write HTML, CSS, JS files to run. The latter is initialized in the form of NPM packages just to keep pace with modern front ends.
The whole article
Just kidding
What do you do when you initialize it as a project? Recall that in a normal project, when we run NPM run Start, a local server is started to pull up the code we wrote, and the code we wrote is hosted on an HTML template. Among them, the following work was done:
node
throughnpm
runpackage.json
This command is usually a scaffold custom command or a self-configured packaging tool command.- When the added packaging tool or scaffold receives the command, it reads the packaging tool configuration and generates the packaging artifacts.
- The packaged product will be displayed as an entry in our template file, and we can preview it in the browser.
- Add a local server, use a local domain name to pull up our template file, give you a feeling of home, take off 🛫.
Two, practical operation run first
In the new demo folder, create a new index.html file and write something random. Install a packaging tool and configure it according to the rules of the packaging tool. We choose the commonly used packaging tool Webpack, directly open the document guide Webpack (this document is the best update relative to the official website, the translation is also relatively ok, I suggest to see this.) :
npm install webpack webpack-cli --save-dev
Copy the code
After installing Webpack, if you are not familiar with it, you can follow the official document to go through. Webpack essentially deals with the various resources used in your project, packages and compresses the artifacts and hangs them on top of HTML templates. Since we are just starting the build project, here we manually mount the artifacts to the template file first.
This /dist/main.js is what we packaged, and here is the current project directory:
A few more important files:
Note 📢 : the following part of the code, may only be posted changes.
Webpack.config.js is the configuration of our packaging tool, here we add only the most basic configuration:
// webpack.config.js
// In the latest version of Webpack you can even have default configurations without writing them
const path = require('path');
module.exports = {
mode: 'development'.entry: path.join(__dirname, 'src'.'index'),
output: {
path: path.join(__dirname, 'dist'),}};Copy the code
Index.html is our entry file, and our logic is very simple: manually mount the js file, and then click a button to trigger the method in our attached JS file:
<! DOCTYPEhtml>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Start a project from scratch</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="">
</head>
<body>
<script src="./dist/main.js"></script>
<h1>A project from scratch</h1>
<button onclick="change()">Click on the</button>
<div id="app"></div>
</body>
</html>
Copy the code
SRC index.js, which is equivalent to our business file, has a single function:
function say(){
console.log("say hello")}function change(){
console.log("Call change")
const app = document.getElementById('app')
app.innerHTML = "Change the App copy"
}
Copy the code
Package. json, after installing webpack, we just need to add a package command under scripts to start webpack, and the package command for Webpack is webpack:
{
"name": "demo"."version": "1.0.0"."description": "A sample project"."main": "index.js"."scripts": {
"start": "webpack"
},
"author": "awefeng"."license": "MIT"."devDependencies": {
"webpack": "^ 4.29.0"."webpack-cli": "^ 3.2.1." "}}Copy the code
Configure the environment and change the entrance
This step is relatively simple but still needs to be done.
Although Webpack supports passing –env directly, we pass environment variables in scripts for environmental considerations in subsequent projects. – Added a dependency package cross-env to offset the issue of running commands on different platforms (Windows, Linux, macOS, etc.) :
npm install --save-dev cross-env
Copy the code
// package.json
"scripts": {
"start": "cross-env ENV=dev webpack"."build:test": "cross-env ENV=test webpack"."build": "cross-env ENV=prod webpack"
},
Copy the code
Webpack sets mode to the environment parameter passed in by the command, and Webpack automatically sets the mode value in the configuration file to process.env.node_env.
To set the webpack entry, note that we have changed the entry name to app:
entry: {
app: resolve(__dirname, '.. /src'.'app') // multi: XXXX The multiple entries are similar
},
Copy the code
4. Add CSS and image processing configurations
First we add CSS preprocessor, here choose less (preferably not sASS, domestic garbage network + SASS own implementation issues + SASS version compatibility issues…) :
npm install --save-dev style-loader css-loader less-loader less
Copy the code
Css-loader: handles @import and URL () in the same way that JS interprets import/require(). By default, it generates an array of strings to store the processed style strings and export them.
Style-loader: inserts CSS into the DOM. It handles the array of modules exported by CSS-Loader, and then inserts styles into the DOM via style tags or other forms.
Less-loader: a loader generated by less for WebPack. Less-loader uses the core library less.
The loader for Webpack is a chain pipelined call implemented from right to left, so we configure it like this:
module: {
rules: [{test: /.less$/i,
use: [
'style-loader'.'css-loader'.'less-loader',]}]}Copy the code
Here the simple webpack handles the configuration of less, we write a less file in the project, import it in index.js, and then use it in our function:
// src/index.js
import './index.less'
function change(){
const app = document.getElementById('app')
app.innerHTML = "Change the App copy"
app.classList.add("container")
}
change()
Copy the code
// index.less
.container{ width: 100px; height: 100px; border: 1pxsolid black; }Copy the code
At this point, we run the NPM run start command and wait for the package to complete. The browser opens THE HTML:
Optimize:
- add
css module
- will
css
andjs
Separate (production environment) - The compression
css
css
Carry out on-demand import- Browser compatibility
css
Here is a simple first to each of the scheme, solution to say, and then in the final paste configuration file, the relevant detailed configuration also need to go to the detailed study.
Add CSS Module
There are two options:
- all
less
,css
All files are modular - Partial files (e.g. only
.module.(le/c)ss
Suffix) is modular
We use the first option because we can globalize styles by adding :global to the CSS Module.
Keep CSS and JS separate
Webpack generates bundles that mix CSS and JS together. We need to add the mini-CSS-extract-plugin for WebPack to handle the packaged bundles and separate CSS and JS.
Mini-css-extract-plugin and style-loader cannot be used together. In dev mode, style-loader is more advantageous. So let’s judge the environment here. Use style-loader in dev mode; Use the Mini-CSS-extract-Plugin in PROD environment. CSS references to entry files need to be imported automatically using html-webpack-plugin. After adding htML-wbpack-plugin, you need to specify the plugin to generate new index.html from the corresponding template HTML.
The mini-CSs-extract-plugin supports on-demand loading of CSS without configuration. The clean-webpack-plugin is installed to clean up the bundles left over from the last package before each package.
npm install --save-dev mini-css-extract-plugin html-wbpack-plugin clean-webpack-plugin
Copy the code
Compress CSS
npm install css-minimizer-webpack-plugin --save-dev
Copy the code
CSS compatibility with Browser
Browserslistrc is a file that provides the browser baseline information that tools like PostCSS and Babel need to reference.
npm install --save-dev postcss-loader postcss postcss-preset-env
Copy the code
After less, CSS processing, we will talk about image processing, image processing we directly use webpack asset Module function (check the information).
The entire configuration
<! -- index.html -->
<! doctypehtml>
<html>
<head>
<meta charset="utf-8">
<title>Build a project from zero</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
<! -- Add a node -->
<! -- There is no need to import JS manually because we use htMl-webpack-plugin -->
<div id="app"></div>
</body>
</html>
Copy the code
// index.js
import styles from './index.less' // css module
import temp from './temp.css' // Tests whether the CSS file is processed by postCSS
import tans from './asset/image/tans.jpeg' //asset module
function change(){
const app = document.getElementById('app')
app.innerHTML = "Change the App copy"
app.classList.add(styles.container)
app.classList.add(temp.temp)
const img = document.createElement('img')
img.src = tans
app.parentElement.append(img)
}
change()
Copy the code
// index.less
.container{
width: 100px;
height: 100px;
border: 1px solid black;
background-image: url("./asset/image/tans.jpeg");
background-size: cover;
user-select: none;
}
Copy the code
// webpack.config.js
const {CleanWebpackPlugin} = require('clean-webpack-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('path')
const {ProgressPlugin} = require('webpack')
const config = require("./config/index")
/** * gets the enumeration * of the current environment@returns development test production
*/
function getEnv() { return config.ENV_ENUM[process.env.ENV]}
const isProd = getEnv() === config.ENV_ENUM.prod
/ / webpack plug-in
const plugins = [
/ / the progress bar
new ProgressPlugin(),
Clear dist before packing
new CleanWebpackPlugin(),
// Automatically generate HTML from the template
new HtmlWebpackPlugin({template: 'index.html'}),
// Extract CSS files and load CSS on demand
new MiniCssExtractPlugin({
filename: "./css/[name].[contenthash].css",}),]module.exports = {
// Environment configuration
mode: isProd ? config.ENV_ENUM.prod : config.ENV_ENUM.dev,
/ / the entry
entry: {
app: path.join(__dirname, 'src'.'index')},/ / output
output: {
filename: "[name].[hash].js".path: path.join(__dirname, 'dist'),},module: {
rules: [
// less CSS file processing
{
test: /.(le|c)ss$/i,
exclude: /node_modules/,
use: [
// Production environment to compress the local dev directly using style-loader
isProd ? MiniCssExtractPlugin.loader : 'style-loader',
{
loader: "css-loader"./** * Enable CSS module * Load the previous loader (postCSs-loader) to handle compatibility issues */
options: {
importLoaders: 1.modules: true}}, {loader: 'postcss-loader'.options: {
postcssOptions: {
// Automatically adds different browser light and handles new features
plugins: ['postcss-preset-env']}}},"less-loader"],}, {test: /.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'.generator: {
filename: "[file]"
}
},
]
},
plugins,
optimization: {
minimizer: [
/ / compress CSS
new CssMinimizerPlugin(),
],
},
}
Copy the code
5. Build the development environment
First, open source-map. There are too many options. Search online. Devtool is recommended for dev environment eval-cheap-module-source-map and production environment nosource-source-map.
module.exports = {
devtool: isProd ? "nosources-source-map" : "eval-cheap-module-source-map",}Copy the code
Manual NPM run start every time, and then to refresh, very troublesome. So we added the development tool (local server), webpack is used more is web-dev-server:
npm install --save-dev webpack-dev-server
Copy the code
module.exports = {
devServer: {
static: resolve(__dirname, '.. /dist'),
compress: true.hot: true.port: 8080}},Copy the code
Json: "cross-env env =dev webpack serve --open",Copy the code
Rerun NPM run start, the project will be pulled up automatically and the browser will refresh automatically. At the same time when we debugger, we see our source code (because we configured devtool).
6. Configure HMR
Webpack-dev-server is with module hot replacement and all we need to do is turn it on. Enabling Method Add hot: true to the devServer configuration. Create a new js file, write something in it, and reference it in index.js. The module referenced in index.js is updated and needs to be retrieved with module.hot.accpet, which requires manual hook writing.
// Add a student.js
export function studentSay(){
console.log('students'."Said"."Don't want to take a test")}Copy the code
// index.js
import {studentSay} from './student.js'
if (module.hot) {
module.hot.accept('./student.js'.function() {
console.log('Accepting the updated student module! ');
studentSay()
})
}
Copy the code
After the NPM run start project, make any changes to student.js and you will see the hot update.
As for why we need to write hooks manually, we can refer to this article. Fortunately, we need to use the framework, framework implements loader to handle, so do not worry.
Seven, Tree Shaking
It’s like shaking the dead leaves off a tree. The idea is to make the bundle smaller by removing unnecessary code from the project. It works on static structure analysis, so it can only be used for ESM. For globally referenced files, be aware of any side effects, such as global CSS, global JS, some polyfill, IIFE files, etc., which have side effects. Webpack needs to remove these files from the Settings.
Webpack has Tree Shaking turned on by default. If your files have no side effects, Tree Shaking will work and remove unwanted code. However, if there is only one place in your file that WebPack detects as having a side effect, it will not tree shaking the file. He doesn’t remove code that doesn’t work, doesn’t follow logic or fact. So, if you know exactly which files have side effects and which don’t, you can set them manually. This is what it says:
Math-effect. js and math-no-effect.js, one for side effects and one for no side effects:
// math-effect.js
// console.log in case the printed sentence is a business requirement
// So this sentence caused a side effect in Webpack's view
console.log("aaa")
export function addNum(num1, num2){ return num1 + num2}
Copy the code
// math-no-effect.js
export const name = 'awe'
export function calcNameLen(){ return name.length }
Copy the code
// only references are made in index.js without making any calls
import "./math-effect"
import './math-no-effect'
Copy the code
If we run start app.xxxx.js in the browser, we will not remove any code from these two files, because tree shaking is not done in the development environment.
Run the NPM run build and check app.xxx.js in the dist of the generated production environment. You’ll see that only math-effe.js is typed in. This is when Tree shaking works for files with no side effects.
In real projects, where there are global CSS, global JS, Polyfill, IIFE, etc., Webpack provides a configuration that can be explicitly configured in package.json: sideEffects: \[‘xxx.js, *.css’] (which matches the configuration item has side effects), because we know that we wrote math-effect.js with no business side effects, we set it empty:
// package.json
{
"sideEffects": [],}Copy the code
Repackage and look at the generated Dist and you’ll see that math-effect-js is no longer packaged either.
Note that this means that all files have no side effects, and you need to know that the rest of the project really has no side effects.
There is another configuration method is to configure in loader, this check information will know.
In general, there is no special processing in the project, because the first is global CSS, global JS, Polyfill, IIFE, etc. Files with side effects are not a large proportion, and the second is that Webpack will tree shaking those files with no side effects by default. In fact, this is about it, the official version of the documentation recommended to install a compression plug-in, should be considered performance optimization.
npm install terser-webpack-plugin --save-dev
Copy the code
// webpack.config.js
module.exports = {
optimization: {
minimize: true.minimizer: [new TerserPlugin()]
},
}
Copy the code
Webpack configuration file optimization
Because we put all the WebPack configuration in webpack.config.js, but there are some configurations that I don’t want in some environments (like I don’t want the development environment to compress my code), so we split the configuration for different environments into different files. Then use Webpack-Merge to merge the common configuration.
npm install --save-dev webpack-merge
Copy the code
Create webpack.prod.js and webpack.dev.js. The test environment just needs to do the proxy division in webpack.dev.js.
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const path = require('path')
const { ProgressPlugin} = require('webpack')
const config = require("./index")
const { merge} = require('webpack-merge')
const devConfig = require("./webpack.dev")
const prodConfig = require("./webpack.prod")
/** * gets the enumeration * of the current environment@returns development test production
*/
function getEnv() { return config.ENV_ENUM[process.env.ENV] }
const isProd = getEnv() === config.ENV_ENUM.prod
const commonConfig = {
/ / the entry
entry: {
app: path.join(__dirname, '.. /src'.'index')},/ / output
output: {
filename: "[name].[hash].js".path: path.join(__dirname, '.. /dist'),},module: {
rules: [{test: /.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource'.generator: {
filename: "[file]"}}},],plugins: [
new ProgressPlugin(),
Clear dist before packing
new CleanWebpackPlugin(),
// Automatically generate HTML from the template
new HtmlWebpackPlugin({ template: 'index.html'}})]module.exports = () = >{
if(isProd){
return merge(commonConfig, prodConfig)
}
return merge(commonConfig, devConfig)
}
Copy the code
// webpack.dev.js
const config = require('./index')
const { HotModuleReplacementPlugin } = require('webpack')
const path = require('path')
const { merge } = require('webpack-merge')
const isTestEnv = config.ENV_ENUM[process.env.ENV] === config.ENV_ENUM.test
const devConfig = {
mode: config.ENV_ENUM.dev,
devtool: 'eval-source-map'.module: {
rules: [
// less CSS file processing
{
test: /\.(le|c)ss$/i,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader'.options: {
importLoaders: 1.modules: {
localIdentName: '[path][name]__[local]'}}}, {loader: 'postcss-loader'.options: {
postcssOptions: {
plugins: ['postcss-preset-env']}}},'less-loader']]}},plugins: [new HotModuleReplacementPlugin()]
}
const getConfig = () = > {
if (isTestEnv) return devConfig
return merge(devConfig, {
devServer: {
static: path.join(__dirname, '.. /dist'),
compress: true.hot: true.port: 8080}})}module.exports = getConfig()
Copy the code
// webpack.prod.js
const config = require('./index')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
mode: config.ENV_ENUM.prod,
devtool: 'nosources-source-map'.module: {
rules: [
// less CSS file processing
{
test: /\.(le|c)ss$/i,
exclude: /node_modules/,
use: [
// Production environment to compress the local dev directly using style-loader
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'./** * Enable CSS module * Load the previous loader (postCSs-loader) to handle compatibility issues */
options: {
importLoaders: 1.modules: {
localIdentName: '[hash:base64]'}}}, {loader: 'postcss-loader'.options: {
postcssOptions: {
// Automatically add different browser prefixes and handle new features
plugins: ['postcss-preset-env']}}},'less-loader']]}},optimization: {
minimize: true.minimizer: [
/ / compress CSS
new CssMinimizerPlugin(),
JS / / compression
new TerserPlugin()
]
},
plugins: [
new MiniCssExtractPlugin({
filename: './css/[name].[contenthash].css'}})]Copy the code
// package.json command change"
scripts": {"start":"cross-env ENV=dev webpack serve --open --config ./config/webpack.config.js","build:test":"cross-env ENV=test webpack --config ./config/webpack.config.js","build":"cross-env ENV=prod webpack --config ./config/webpack.config.js"}Copy the code
Configure bundle analysis
After packaging, we usually need to know which resources occupy a lot of resources. Here we configure an analysis command to analyze the packages produced in the production environment. We use the plug-in Webpack-bundle-Analyzer to complete this work.
Add a webpack.analyzer.js configuration under config, which is a plug-in configuration on top of the prod configuration:
// Scripts adds a command to start the Analyzer
// Don't add this plugin to webpack.config because it will run a devServer to display your bundle statistics
"scripts": {
"analyzer": "cross-env ENV=prod webpack --config ./config/webpack.analyzer.js"
},
Copy the code
//webpack.analyzer.js
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
const { merge} = require('webpack-merge')
const getConfig = require("./webpack.config")
const analyzerConfig = {
plugins: [
new BundleAnalyzerPlugin()
]
}
// Note that webpack.config is an exported function after we optimize it
module.exports = merge(getConfig(), analyzerConfig)
Copy the code
At this point, our NPM Run Analyzer launches an interface to analyze the packaged bundles. See this address for more information about the plugin.
Configure code separation
The product generated by webpack is called bundle, and the code we wrote is called Module. In the process from Module to bundle, Webpack will generate an intermediate product called chunk. The whole packaging process is from Module to chunk to bundle. Chunk is a chunk, like a building block. Somewhere in the code the logic uses a chunk. Just load that part. Breaking up the code into smaller chunks and referring to each chunk as needed is where code separation comes in. Code separation allows us to get smaller and thinner bundles that can then be loaded on demand or in parallel.
There are three ways to separate code: dynamic import, entry starting point, prevent duplication, and so on. Webpack dynamic introduction requires anti-human written code that is not very readable; The separation of entry points only increases the number of entrances, but cannot prevent the problem of repeated introduction. So we can extract the common dependencies as a bundle in a way that prevents duplication.
There are two ways to prevent duplication: one is the entry dependency method. Specify the dependent item under each entry and configure the specific content of the dependent item, as shown in this address. SplitChunksPlugin (SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin, SplitChunksPlugin)
Here we have a very simple configuration on the line, to make good enough, need to go into the study.
// webpack.prod.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',,}}}Copy the code
11. Modify it as TS project
TypeScript has two main functions. First, it provides type extension and static checking (a strongly typed language front-end really needs to be written, such as Java) by installing TypeScript packages in development dependencies. The other is to provide compilation capability (TSC), which compiles the higher version syntax into the lower version syntax to be compatible with different browsers or other runtime environments. So the second ability is a rival to Babel.
Babel has a richer ecology and a more nuanced compilation than TSC, so check it out (a quick article).
So we’ll just use TS static checking and type extension, and for compilation we’ll stick with Babel, which we’ll configure later.
npm i --save-dev typescript @types/node
Copy the code
Since we want to change the webpack file configuration to TS as well, we also need to install the types package associated with Webpack. And we need to run TS directly (webpack.config. TS, etc.), so we need to install TS-Node.
npm install --save-dev ts-node @types/webpack @types/webpack-dev-server
Copy the code
Add tsconfig. Json
The first step is to add tsconfig.json to the project so that it is already a TS project by definition. Then go to modify the file we wrote originally (only a few more important notes, not TS advice or first go to see).
{
"compilerOptions": {
"noEmit": true.// Do static typing without compiling content
"module": "esnext"."target": "es5"."lib": [
"dom"."esnext"]."baseUrl": "."."sourceMap": true.// Source-map must be enabled in TS
"allowJs": true.// Writing js files is also allowed
"checkJs": true.// Allow checking of js files
"noImplicitAny": false."skipLibCheck": true."esModuleInterop": true."allowSyntheticDefaultImports": true."strict": true."forceConsistentCasingInFileNames": true."noFallthroughCasesInSwitch": true."moduleResolution": "node"."resolveJsonModule": true."incremental": true."isolatedModules": true
},
"ts-node": { // Ts-Node overloading of modules will be covered later
"compilerOptions": {
"module": "CommonJS"}},// Scope of action
"include": [
"src/**/*"."config/**/*",].// Exclude what
"exclude": [
"node_modules"."build"."dist"."scripts"]},Copy the code
Webpack adds processing ts
The second step is to transform our files, including some business configuration files, environment configuration files, Webpack configuration files, etc.
Two things to note:
After our project became A TS project, some custom variables such as process.env.env could not be defined by checking, and resource files such as.less,.css and.png could not be considered as modules by definition. SRC /react-app-env.d.ts. SRC /react-app-env.d.ts.
Since we changed the webpack configuration file to.ts, we need to run it with TS-Node. Ts-node is simply an extension of TS that runs on node, and the Node environment is the CommonJS specification. In tsconfig.json the module we specify is esNext, so we need to override the ts-Node configuration (see tsconfig.json above). If we do not do this step, Import {XXX} from ‘XXX’ : import {XXX} from ‘XXX’ : import {XXX} from ‘XXX’
I also tweaked the WebPack configuration here.
// src/typing.d.ts
// There are some problems with the typing.d.ts code in the picture
// The following is the correction
declare module '*.less'
declare module '*.css'
declare module '*.png'
declare module '*.svg'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
declare namespace NodeJS {
interface ProcessEnv {
ENV: "dev" | "test" | "prod"; }}Copy the code
// config/index.ts
// Environment enumeration
export const ENV_LIST = {
DEV: "dev".TEST: "test".PROD: "prod"
}
Copy the code
// config/webpack.analyzer.ts
import {BundleAnalyzerPlugin} from 'webpack-bundle-analyzer'
import { merge } from 'webpack-merge'
import {Configuration} from 'webpack'
import prodConfig from './webpack.prod'
const analyzerConfig: Configuration = {
plugins: [
new BundleAnalyzerPlugin()
]
}
export default merge<Configuration>(prodConfig, analyzerConfig)
Copy the code
// config/webpack.common.ts
import {CleanWebpackPlugin} from 'clean-webpack-plugin'
import HtmlWebpackPlugin from 'html-webpack-plugin'
import { join} from 'path'
import {Configuration, ProgressPlugin} from 'webpack'
const commonConfig: Configuration = {
/ / the entry
entry: {
app: join(__dirname, '.. /src'.'index')},/ / output
output: {
filename: "[name].[chunkhash].js".path: join(__dirname, '.. /dist'),},module: {
rules: [{test: /\.(png|svg|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$/i.type: 'asset/resource'.generator: {
filename: "[file]"}}},],plugins: [
new ProgressPlugin(),
Clear dist before packing
new CleanWebpackPlugin(),
// Automatically generate HTML from the template
new HtmlWebpackPlugin({ template: 'index.html'}})]export default commonConfig
Copy the code
// webpack.dev.ts
import { HotModuleReplacementPlugin, Configuration } from 'webpack'
import { join } from 'path'
import { merge } from 'webpack-merge'
import commonConfig from './webpack.common'
// in case you run into any typescript error when configuring `devServer`
import 'webpack-dev-server'
//https://github.com/DefinitelyTyped/DefinitelyTyped/issues/27570
const devConfig: Configuration & { devServer: { [key: string] :any}} = {mode: 'development'.devtool: 'eval-source-map'.devServer: {
static: join(__dirname, '.. /dist'),
compress: true.hot: true.port: 8080
},
module: {
rules: [
// less CSS file processing
{
test: /\.(le|c)ss$/i,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader'.options: {
importLoaders: 1.modules: {
localIdentName: '[path][name]__[local]'}}}, {loader: 'postcss-loader'.options: {
postcssOptions: {
plugins: ['postcss-preset-env']}}},'less-loader']]}},plugins: [new HotModuleReplacementPlugin()]
}
export default merge<Configuration>(commonConfig, devConfig)
Copy the code
// webpack.prod.ts
import MiniCssExtractPlugin from 'mini-css-extract-plugin'
import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
import TerserPlugin from 'terser-webpack-plugin'
import { Configuration } from 'webpack'
import commonConfig from './webpack.common'
import { merge } from 'webpack-merge'
const prodConfig: Configuration = {
mode: 'production'.devtool: 'nosources-source-map'.module: {
rules: [
// less CSS file processing
{
test: /\.(le|c)ss$/i,
exclude: /node_modules/,
use: [
// Production environment to compress the local dev directly using style-loader
MiniCssExtractPlugin.loader,
{
loader: 'css-loader'./** * Enable CSS module * Load the previous loader (postCSs-loader) to handle compatibility issues */
options: {
importLoaders: 1.modules: {
localIdentName: '[hash:base64]'}}}, {loader: 'postcss-loader'.options: {
postcssOptions: {
// Automatically add different browser prefixes and handle new features
plugins: ['postcss-preset-env']}}},'less-loader']]}},optimization: {
splitChunks: {
chunks: 'all'
},
minimize: true.minimizer: [
/ / compress CSS
new CssMinimizerPlugin(),
JS / / compression
new TerserPlugin()
]
},
plugins: [
new MiniCssExtractPlugin({
filename: './css/[name].[contenthash].css'}})]export default merge<Configuration>(commonConfig, prodConfig)
Copy the code
"scripts": {
"start": "cross-env ENV=dev webpack serve --open --config ./config/webpack.dev.ts"."start:prod": "cross-env ENV=prod webpack serve --open --config ./config/webpack.dev.ts"."build:test": "cross-env ENV=test webpack --config ./config/webpack.prod.ts"."build": "cross-env ENV=prod webpack --config ./config/webpack.prod.ts"."analyzer": "cross-env ENV=prod webpack --config ./config/webpack.analyzer.ts"
},
Copy the code
The other.js files should also be changed to.ts. Add some type of ts to these files, not.ts files with a.js suffix to continue to run (this will be used for verification later, this step is required). Some configuration files, such as those in the root directory that end in.js, cannot be changed.
When we run NPM run start, we find that the project will not run:
Item not foundsrc
Under theindex
Because thewebpack
Not supported by defaultTypeScript
He doesn’t know.ts
. In the officialModules and module resolutionThere are also instructions.
So at this time we need to do two steps first:
The first step is to add the parsed extension so that WebPack can find it when looking for the path of the module we reference, so we add the configuration to webpack.common.ts:
resolve: { extensions: [ '.tsx'.'.ts'.'.jsx'.'.js'.'.less'.'.css' ],},
Copy the code
If we add a. Ts suffix instead of omiting the suffix, we will get an error:
This is an interesting question, Google searchTS 2691
You will find the official openinghereThis one is not fixed, which means let’s leave out the suffix. All right, let’s do it
After the project arrived at this point, NPM run start found that it was still running, because Webpack knew.ts, but did not know how to deal with it. The previous step just added a parsing rule, hence our second step.
The second step is that we need to let Webpack know how to handle.ts. Reviewing the principle of Webpack, we need to add loader to enable Webpack to process resources it does not know, so we need to add loader to process.ts. As mentioned earlier, there are two choices: TSC and Babel. We chose Babel. In the next section, we will configure babel-loader.
Add Babel
At the beginning, Babel was just dealing with JS, not with.TS, so it was a tough phase, mixing Babel and TS, and adding various configurations. TSC is used to convert TS to JS, and Babel is used to process the lower version of JS. Babel7 solved this problem by simply erecting all ts and compiling as JS. Babel does compilation itself, so the idea is that you can just do your own type definition, check it statically, and when it comes to compiling code, I don’t care about your type or anything, just get rid of it and compile it in JS.
npm install --save-dev babel-loader @babel/core @babel/preset-env @babel/preset-typescript
Copy the code
Add babel.config.json to the project root directory
// babel.config.json
// Preset -typescript is an official set of recommendations for TS
// Preset -env uses.browserslistrc to generate lower-version code for the target environment
{ "presets": ["@babel/preset-typescript"."@babel/preset-env"]}
Copy the code
This time we NPM run start, the project is running again, of course you can install some Babel plugin, continue to optimize.
Add React
Install the React dependency package. React distinguishes the core from the rendering environment. The core package is React (react-core), and the rendering environment package is react-dom and react-native. So we use react-dom.
npm i react react-dom
npm i -D @types/react @types/react-dom
Copy the code
Then we changed the project to app. TSX and global.less in the root directory. We removed the redundant files left by the previous validation function, added the views view folder and wrote a welcome.
I want to emphasize that.tsx
Extended syntax andvue
The difference between that template, please understand thattsx
It’s extension syntax, syntax. It’s going to be natural if we figure it out.
//app.tsx
import ReactDOM from 'react-dom'
import React from 'react'
import './global.less'
import Welcome from './views/welcome'
ReactDOM.render(
<React.StrictMode>
<Welcome />
</React.StrictMode>.document.getElementById('app'))Copy the code
// src/welcome/index.tsx import React, {FC} from 'react' import styles from './index.less' // index.less const Welcome: FC = () => {return <div className={styles.welcome}> Welcome home </div>} export default welcomeCopy the code
Configure tsconfig.json and babel.config.json to support React:
//tsconfig.json adds JSX conversions to compilerOptions
{
"compilerOptions": {
"jsx": "react-jsx",}}Copy the code
// Add react Babel set NPM install --save-dev @babel/preset-reactCopy the code
/ / configuration Babel. Config. Json
{
"presets": ["@babel/preset-react"."@babel/preset-typescript"."@babel/preset-env"]}Copy the code
Modify webPack configuration to support TSX and JSX, modify entry to app.tsx:
// Change the entry to app
entry: {
app: join(__dirname, '.. /src'.'app')},// Add the.tsx.jsx extension
resolve: {
extensions: [ '.ts'.'.tsx' , '.js'.'.jsx'.'.less'.'.css'],},module: {
rules: [{// Add extension TSX JSX extension to add presets
test: /\.(tsx? |jsx?) $/,
exclude: /node_modules/,
use: {
loader: "babel-loader".options: {
presets: ['@babel/preset-react'.'@babel/preset-env'.'@babel/preset-typescript']}}},]},Copy the code
Add UI library
The UI library can be selected according to their own, we take the use of more ANTD for example, open their official website guidance document.
Antd says it is a direct reference, but we actively ignore node_modules when configuring webpack to handle less and CSS files, so we need to handle them separately in Webpack. Secondly, the style files of antD component library are compiled and not applicable to the CSS Module configured by us, so the CSS Module should be removed when configured separately:
// Modify the less-loader configuration in webpack.dev.ts and webpack.prod.ts
{
loader: "less-loader".options: {
lessOptions: {
javascriptEnabled: true}}}Copy the code
// Style files in node_modules are handled separately in wepack.mon.ts
{
test: /\.(le|c)ss$/i,
include: /node_modules/,
use: [
'style-loader',
{
loader: "css-loader".options: {
importLoaders: 1,}}, {loader: "less-loader".options: {
lessOptions: {
javascriptEnabled: true}}}]},Copy the code
// Introduce antD style files in app.tsx
import 'antd/dist/antd.less';
Copy the code
15. Add state management
We’ll use Redux as an example, but if you have other state management libraries that you like, you can add others.
Add redux, React-redux, @reduxJS/Toolkit: Redux is a state management library for React, vUE, native JS and other frameworks. React – Redux is a middleware developed to facilitate redux use in React. And because redux is a little bit cumbersome, I developed @reduxJS/Toolkit (something like DVA, called little DVA) to simplify it, and that’s it.
As mentioned earlier, if the package is written in TS, it will ship with the type file. If not, download the package corresponding to @types.
npm i redux react-redux @reduxjs/toolkit
Copy the code
Ts, rootReducer. Ts, and our business module state. Here is an example of the user module (note that this requires some basic elements of react-Redux and @reduxjs/ Toolkit. If it says “laborious,” go and look it up.)
// state/index.ts
import { configureStore } from '@reduxjs/toolkit';
import rootReducer from './rootReducer';
const store = configureStore({
reducer: rootReducer,
});
export type AppDispatch = typeof store.dispatch;
export const dispatch = store.dispatch;
export default store;
Copy the code
// state/rootReducer.ts
import { combineReducers } from '@reduxjs/toolkit';
import userReducer from './user';
const rootReducer = combineReducers({
user: userReducer,
});
export type RootState = ReturnType<typeof rootReducer>;
declare module 'react-redux' {
// The state type of the corresponding service module can be read directly in the service
// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface DefaultRootState extends RootState {}
}
export default rootReducer;
Copy the code
// state/user.ts
import type { PayloadAction } from '@reduxjs/toolkit'
import { createSlice } from '@reduxjs/toolkit'
export interface UserStateProps {
userId: string
name: string
phone: string
}
// Initialize the value
const initState = (): UserStateProps= > {
return {
userId: ' '.name: ' '.phone: ' '}}const userSlice = createSlice({
name: 'user'.initialState: initState(),
reducers: {
init(state) {
const initS = initState()
Object.keys(initS).forEach((key) = > {
state[key] = initS[key]
})
},
setUserInfo(state, action: PayloadAction<Partial<UserStateProps>>) {
const inputState = action.payload
Object.keys(inputState).forEach((key) = > {
state[key] = inputState[key]
})
}
}
})
export const { init, setUserInfo } = userSlice.actions
export default userSlice.reducer
Copy the code
TSX: App.tsx: app.tsx:
// src/app.tsx
import ReactDOM from 'react-dom'
import React from 'react'
import store from './store'
import './global.less'
import 'antd/dist/antd.less'
import Welcome from './views/welcome'
import { Provider } from 'react-redux'
ReactDOM.render(
<React.StrictMode>/ / mount store<Provider store={store}>
<Welcome />
</Provider>
</React.StrictMode>.document.getElementById('app'))Copy the code
On the business component let’s test:
import { Button, message } from 'antd'
import React, { FC, Fragment } from 'react'
import styles from './index.less'
import { useDispatch, useSelector } from 'react-redux'
import { setUserInfo } from '.. /.. /store/user'
const Welcome: FC = () = > {
const dispath = useDispatch()
const { name } = useSelector((state) = > state.user)
return (
<Fragment>
<Button
type='primary'
danger
onClick={()= >{dispath(setUserInfo({name: 'awefeng'})) message.success(' login succeeded ')}} > Login to aweFeng</Button>
<div className={styles.welcome}>Welcome: {name}</div>
</Fragment>)}export default Welcome
Copy the code
You can see the setup read problem, and reduxdev can also see the process.
Add a Router
React-router-core and react-router-dom are both installed in the browser, and the react-router-core and react-router-dom are installed in the browser. But I’m not going to talk about it today, because the routing section is quite large and involves configuration writing, permission control, ICONS, layout menus, lazy loading, etc. Therefore, we will write a series of router designs for the project separately. Then we will use the React-Router, so we will first bury a pit here.
React-router is also easy to use. Check out their website.
Add request processing file
To add a unified request file to handle requests, it usually selects a certain library and writes some processing logic in combination with the business. Here we use Axios to write a simple example:
npm install axios
Copy the code
Create the request folder in the SRC directory and create index.ts. I’ll just paste the writing steps here and give you a simple example file.
- First, we write a general hollow shelf. When we request, we fill in the URL, request method, query parameters, data, other configuration, etc.
- Then we need to complete the configuration of the incoming AXIos, at which point we also need to add the type.
- Handle the return of axois according to the API specification documentation for front-end alignment.
- Continue to refine the parameters passed in from config to refine the request (including authentication, headers, timeout, interception, cancellation, concurrency, etc.).
- Finally, pull out different configuration files based on different environments (development, testing, pre-release, online).
- The optimization point is that multiple AXIOS instances can be pulled out for different scenarios to call.
// src/request/index.ts
// A simple example is not necessarily accurate
import axios from 'axios';
import type { Method, AxiosRequestConfig, AxiosResponse, } from 'axios';
import { notification } from 'antd';
const fetch = <R>(
url: string,
{
method,
baseUrl = ' ',
params = undefined,
data = undefined,
then = undefined. others }: {method: Method; baseUrl? :string; params? :Object; data? :any; then: Function, [key: string] :any },
): Promise<R> => {
/** * 1. Whether there is a custom header or other configuration passed in from config * improve from here */
let headers = {
'Content-Type': 'application/json'./ / TODO authentication?
};
if (others.headers) {
headers = Object.assign(headers, others.headers);
}
// 2. Define the requested Config
const requestConfig: AxiosRequestConfig = {
method,
url,
timeout: 10000, data, headers, ... others, };// 3. Baseurl is determined based on the environment and input
// Optimize points to extract different AXIos instances
if (baseUrl) {
requestConfig.baseURL = baseUrl;
} else {
// The development environment is replaced by devServer proxy
// Other environments read from the configuration
if (process.env.ENV !== ENV.DEV) {
requestConfig.baseURL = config.baseUrl;
}
}
// If you pass in a subsequent method, use the subsequent method
if (then) {
return axios(requestConfig).then(then);
}
return axios(requestConfig)
.then((res) = > {
return new Promise((resolve, reject) = > {
// Process according to the API specification document
const { code } = res.data;
if (code === 0 ) {
resolve(res.data as R);
} else{ reject(res); }}); }) .catch(async (res: AxiosResponse) => {
// Unified error request handling
// TODO login expired API specification document error code error etc
const { status, data } = res;
if (status === 401) {
await new Promise<void> ((resolve) = > {
notification.error({ message: data? .msg ??'Authentication expired, please log in again! ' });
setTimeout(() = > {
resolve();
}, 2000);
}).then(logOut);
} else {
// TODO other cases
// Throws a business error message
throw new Error(data? .msg ??'Request error ~ server is distracted'); }}); };export default fetch;
Copy the code
Then we create the API folder under SRC for the request. Write a request example:
// src/api/user.ts
import fetch from '.. /request/index'
// Get user information
export function getUserInfo(
userId: string
) :Promise<{ data: { userId: string; name: string; blabala: any } }> {
return fetch('/user/info', { method: 'GET'.params: { userId } })
}
Copy the code
18. Unify NPM source and version
It is very simple to unify the NPM source. Add. NPMRC under the project directory. Considering the reasons of runner packaging and wall in CI/CD, it is suggested to use Taobao mirror:
.npmrcregistry=https://registry.npm.taobao.org/
@company:registry=https://registry.npm.company.com/
Copy the code
Node unity is essential. Node updates, no matter how big or small, may change. Some dependencies may require node versions, so it is necessary to keep node and NPM versions unified in order to ensure that the project is consistent across everyone (or even the server). Package-lock. json (NPM) : package-lock.json (NPM) : package-lock.json (NPM) : package-lock.json (NPM) : package-lock.json As for how to unify, you can script constraints, you can enforce them (or you can just crash, whatever it is, run), and NVM is managed.
Path alias
Json: tsconfig.json: tsconfig.json: tsconfig.json:
// config/webpack.common.ts
resolve: {
alias: {
"@": resolve(__dirname, '.. /src'),
"#": resolve(__dirname, '.. /config')},extensions: [ '.ts'.'.tsx' , '.js'.'.jsx'.'.less'.'.css'],},// tsconfig.json
{
"compilerOptions": {
"paths": {
"@ / *": [
"./src/*"]."# / *": [
"./config/*"]}}}Copy the code
At this point we can use it in our code:
// For example
import { setUserInfo } from "@/store/user";
import {ENV_LIST} from "#/index"
Copy the code
Editorconfig and gitignore
The editorConfig configuration file is a coding constraint on the IDE and requires IDE support (the supported ides and those that require plug-ins are detailed on the official website). For example, if I’m using VS Code, I need to install the EditorConfig for VS Code plug-in in order to use this configuration.
// In the root directory. Editorconfig
# see http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
Copy the code
Git ignore files are files that need to be ignored during the configuration project submission. The Git official recommends different configurations for different development environments
# see https://github.com/github/gitignore/blob/main/Node.gitignore
/dist
.DS_Store
*.lock
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Dependency directories
node_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
Copy the code
Add esLint, prettier, and stylelint
Eslint is primarily used to regulate code quality, requiring us to install our own packages
npm i --save-dev eslint
Copy the code
Write an initial configuration and place it in the root directory
// .eslintrc.js
module.exports = {
// Set the current directory to the eslint root directory
root: true.// Set esLint's env
env: {
es6: true.browser: true.node: true
},
parserOptions: {
ecmaVersion: 6.sourceType: "module".jsx: true
},
extends: ["eslint:recommended"].rules: {
// Do not write}};Copy the code
I am using VS Code as the IDE and need to install the plugin ESLint and check whether the ESLint Server starts successfully and the configuration file fails:
Eslint will be able to detect this by creating a new test js file in the root directory and writing a bit of code. The initial file is ignored by default, as is node_modules, but I didn’t find the official description, so check it out if you’re interested.
Then continue to modify the configuration above to support React and TS, give two references:
Eslint-TS
Eslint-React
npm i -D @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react
Copy the code
Add some custom rules to rules. The modified rules are as follows:
module.exports = {
// Set the current directory to the eslint root directory
root: true.// Set esLint's env
env: {
es2021: true.browser: true.node: true
},
parserOptions: {
ecmaVersion: 'latest'.sourceType: 'module'.ecmaFeatures: {
jsx: true}},parser: '@typescript-eslint/parser'.plugins: ['@typescript-eslint'.'react'].extends: [
'eslint:recommended'.'plugin:react/recommended'.// The react-jsX mode is used in the project
'plugin:react/jsx-runtime'.'plugin:@typescript-eslint/recommended'].settings: {
react: {
pragma: 'React'.version: 'detect'}},rules: {
// See the official website for details
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off'.'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'.'no-inner-declarations': 'warn'.'block-scoped-var': 'error'.'default-case': 'error'.'no-caller': 'error'.'no-eval': 'error'.'array-element-newline': ['warn', { multiline: true.minItems: 4}].'comma-dangle': ['error'.'never'].'max-len': [
'error',
{
code: 100.tabWidth: 2.comments: 80}].semi: ['error'.'never'].'padding-line-between-statements': [
'error'.// Add the necessary blank line style to the code
{
blankLine: 'always'.prev: ['const'.'let'.'var'.'block'.'block-like'].next: The '*'
},
{
blankLine: 'always'.prev: ['import'].next: ['const'.'let'.'var'.'block'.'block-like'.'expression'] {},blankLine: 'never'.prev: ['import'].next: ['import'] {},blankLine: 'never'.prev: ['const'].next: ['const'}]}}Copy the code
Configure ignore files:
# .eslintignore
#Three will do for now
#Node_modules esLint can also be ignored without writing node_modules
node_modules
/dist
/src/asset
Copy the code
Prettier, prettier, prettier, prettier, prettier, prettier, prettier, prettier
Eslint-Prettier
npm i -D eslint-plugin-prettier prettier eslint-config-prettier
Copy the code
Lint supports prettier:
// Paste only the changes
module.exports = {
// The plugin added prettier
plugins: ["@typescript-eslint"."react"."prettier"].// Use the new recommended configuration mode
/ / the use of "plugin: prettier/it"
extends: [
"eslint:recommended"."plugin:react/recommended"."plugin:react/jsx-runtime"."plugin:@typescript-eslint/recommended"."plugin:prettier/recommended"]},Copy the code
Create.prettier. Js to configure prettier, see the official documentation
// .prettier.js
module.exports = {
printWidth: 80.// The number of characters in a line. If more than 80 characters are added to the line, the default value is 80
tabWidth: 2.// A TAB represents several Spaces. The default is 80
useTabs: false.// Whether to use TAB for indentation. The default value is false, indicating that Spaces are used for indentation
singleQuote: true.// Whether to use single quotation marks for strings. The default is false. Use double quotation marks
jsxSingleQuote: true.// Whether to use single quotes in JSX
semi: false.// Whether to use semicolons on line bits. Default is true
trailingComma: 'none'.Tail / / whether or not to use commas, there are three optional value "< none | es5 | all >"
bracketSpacing: true.{foo: bar} {foo: bar}
parser: 'typescript' // Parsing engine for code
}
Copy the code
Ignore files are also configured:
# .prettierignore
package.json
/dist
.DS_Store
.eslintignore
*.png
.editorconfig
.gitignore
.prettierignore
Copy the code
I’ll leave it up to you to add styleint.
22. Formatting
Configuring formatting Commands
After the above work, the project code specification is available, but it’s just a hint that you’ll need to run your own commands to make changes, so we’ll add a command in package.json to fix the problem esLint detected:
"scripts": {
"lint": "eslint --fix --ext .js,.jsx,.ts,.tsx ."
},
Copy the code
The pitfall here is that esLint has requirements for node versions, so if you run this command incorrectly, check to see if the node version is correct, which is one of the reasons we said to control the version above.
After running the command, you will find that some areas may still not be fixed, which is when you point to the error to see why. There’s a case where prettier’s rule conflicts with our own ESLint rule in Rules.
For example, prettier is in conflict with this rule
'array-element-newline': ['warn', { multiline: true.minItems: 4 }]
Copy the code
The reason for this is that prettier does not prettier before something she writes, and prettier does not take precedence over what she writes. The official solution for prettier is to ignore it yourself Ah yes yes yes, you can look this up, I remember someone mentioned “issue” on Github. So prettier is just a reference when it comes to actual customization for herself or her team.
IDE automatic formatting
This requires different configurations for different ides. Some ides may support it by default and do not require configuration and will format it if you detect that you have an ESLint configuration. Some ides may still require you to write a workspace configuration file. I take VS Code as an example, VS Code needs to be configured, and there are two ways to configure the workspace: the first way is to create. Vscode under the root directory, add settings.json and write it manually. The second option is menu -code- Preferences – Settings – Workspace, check Settings.
// .vscode/settings.json
{
"eslint.alwaysShowStatus": true."editor.codeActionsOnSave": {
"source.fixAll": true}}Copy the code
At this point, when we write the code and save it, it will be automatically formatted.
Another optimization point is that there is no need to passively format all the files each time you commit; instead, you only need to format the files that have been changed (lint-staged). This section can be done independently.
Git constraint
If a team member’s IDE requires manual configuration or is not supported, or the team member is not configured either. At this point, his code may be pushed to a remote repository without being formatted. To avoid this, we need to run our commands to format the code before git commits, and it is best to be able to do the canonical commit information. With these two requirements in mind, we called on “Husky”, a Git hook library that can be used for lint code, spec commit information, run tests, and more before committing. Husky uses a configuration file script after version 7.0, and we use the official installation instructions:
npx husky-init
Copy the code
To modify the contents of the pre-commit hook, we need to reformat and recommit the hook in the pre-commit script.
//. Husky /pre-commit simple example is for reference only#! /bin/sh
. "$(dirname "$0")/_/husky.sh"
echo '1. Perform eslint'
npm run lint
echo '2. Perform prettier'
npx prettier --write
echo 'Execute done, add file again'
git add -A
Copy the code
// package.json
{
"scripts": {
"lint": "npm run lint:js && npm run lint:style"."lint:js":"eslint --fix --ext .js,.jsx,.ts,.tsx ."."lint:style": "Echo 'can't write, use the styleling command to format less CSS",}}Copy the code
At this point we go through the submission process:
After the formatting of the first job, we need to detect the submission information for the second job to prevent scribbling and maintain consistency. Again, follow the official example to add onecommit-msg
Hook:
npx husky add .husky/commit-msg
Copy the code
Then we’ll edit the commit-msg script to check if the committed information meets our requirements:
// /husky/commit-msg
#! /bin/sh
. "$(dirname "$0")/_/husky.sh"
I am not familiar with the bash script
So run the constraint script we wrote with Node
node ./commit-msg.js The $1
Copy the code
// root directory commit-msg.js
X * NPM I -d [email protected] * Remember to ignore this file under ESlint ** I copied umiJS fabric detection * He copied other people's */, too
const chalk = require("chalk")
const msgPath = process.argv[2]
const msg = require('fs').readFileSync(msgPath, 'utf-8').trim()
const commitRE =
/^(((\ud83c[\udf00-\udfff])|(\ud83d[\udc00-\ude4f\ude80-\udeff])|[\u2600-\u2B55]) )? (revert: )? (feat | fix | computer | | UI refactor | ⚡ perf | workflow | build | CI | typos | chore | tests | types | the wip | release | dep | locale) :. / {1, 50}
if(! commitRE.test(msg)) {console.error(
` ${chalk.bgRed.white(' ERROR ')} ${chalk.red('Submit log not conforming to specification')}\n\n${chalk.red(
'The legal submission log format is: \n\n' (emoji optional))},
${chalk.green('💥 feat: Added a great feature)}
${chalk.green('🐛 fix: fixed some bugs')}
${chalk.green('📝 docs: Updated the document')}
${chalk.green('🌷 UI: Modified the style)}
${chalk.green('🏰 chore: Made some changes to scaffolding')}
${chalk.green('🌐 locale: makes a small contribution to internationalization')}
${chalk.red(For more commit prefixes see commit-msg.js\n)}`
)
process.exit(1)}Copy the code
conclusion
Here we build almost, mellow mellow can be used, so far the whole from zero construction project is done, the whole series of content may be in and out, there are mistakes, there are not configured, omissions, please forgive, point out, thank you.
Complete project catalogue:
Summarize the general steps and ideas:
- Create a new folder and initialize it as an NPM project
- Introduce the packaging tool and write a very basic configuration file
- Configure the environment and the corresponding packaging script commands
- Packaging profiles for different environments are distinguished
- Added resource file handling to the package configuration file
- Setting up a Development Environment (devServer)
- Configure HMR, Tree Shaking, and Code Splitting
- Added packaged Product Analysis (Webpack-bundle-Analyzer)
- The project was changed to TS project
- Add babel-loader to handle TS
- Add React and modify the configuration to support TSX and JSX
- Add UI library
- Adding Status Management
- Add the routing
- Add request handling
- Add project constraint files
- Repackage the configuration file, optimize the extraction, and remove the configuration file
- Add automatic build/deployment
- Add a project description file (what the project does, what technologies are used, how to start and play, submit reviews, workflow, deployment instructions, etc.)
These configurations are not difficult at all, you just need to know what each technology does and find the appropriate configurations. Building a project from scratch, except for the routing part, should be done. I put a demo on Github for your reference:
Github.com/awefeng/fe-…
Welcome star(more ⭐️, gentlemen)
Welcome to follow my official account: Mi Zai and Tangyuan
I am “have two smelly cats”, one has two cats, and we learn front end friend ~