大概在 2019 年,本身搭建 React
開發環境的想法萌芽,到目前爲止,公司的不少項目上,也在使用中,比較穩定。爲何要本身造輪子?起初是由於本身並不滿意市面上的腳手架。另外,造輪子對於本身也有一些技術上的幫助,學別人二次封裝的東西,不如直接使用底層的庫,這樣也有助於本身系統的學習一遍知識,最近 Vite
很火,因此用 Vite
搭建一波,廢話很少說,直接進入正文,如何搭建本身的開發環境。javascript
建立文件夾並進入:css
$ mkdir tristana && cd tristana
複製代碼
初始化 package.json
html
$ npm init
複製代碼
安裝 vite
java
$ npm install vite vite-plugin-babel-import vite-plugin-imp --save-dev
複製代碼
建立如下目錄結構、文件和內容:node
tristana
|- package.json
|- index.html
|- vite.config.js
|- /src
|- index.js
複製代碼
document.getElementById("root").append("React");
複製代碼
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>tristana</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.jsx"></script>
<script>
window.global = window;
</script>
</body>
</html>
複製代碼
import { defineConfig } from 'vite';
const path = require('path');
export default defineConfig({
plugins: [
reactRefresh()
]
});
複製代碼
{
// ...
"scripts": {
"build": "vite build",
},
}
複製代碼
而後根目錄終端輸入:npm run build
react
在瀏覽器中打開 dist
目錄下的 index.html
,若是一切正常,你應該能看到如下文本:'React'
webpack
index.html
目前放在 dist
目錄下,但它是手動建立的,下面會教你如何生成 index.html
而非手動編輯它。git
$ npm install @vitejs/plugin-react-refresh --save-dev
複製代碼
import reactRefresh from '@vitejs/plugin-react-refresh';
export default defineConfig({
// ...
plugins: [
reactRefresh(),
],
});
複製代碼
import { defineConfig } from 'vite';
const path = require('path');
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
});
複製代碼
{
// ...
"scripts": {
"dev": "vite",
},
}
複製代碼
$ npm install @babel/preset-react react react-dom --save-dev
複製代碼
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
複製代碼
在 src
目錄下,新增 App.jsx
文件:es6
import React, { Component } from "react";
class App extends Component {
render() {
return (
<div>
<h1> Hello, World! </h1>
</div>
);
}
}
export default App;
複製代碼
import React from "react";
import ReactDOM from "react-dom";
import App from "./App.jsx";
ReactDOM.render(<App />, document.getElementById("root"));
複製代碼
$ npm install react-router history --save
複製代碼
import React from "react";
import ReactDOM from "react-dom";
import { Router, Route, Link } from "react-router";
import { createBrowserHistory } from "history";
import App from "./App.jsx";
const About = () => {
return <>About</>;
};
ReactDOM.render(
<Router history={createBrowserHistory()}>
<Route path="/" component={App} />
<Route path="/about" component={About} />
</Router>,
document.getElementById("root")
);
複製代碼
$ npm install mobx mobx-react babel-preset-mobx --save
複製代碼
{
"presets": ["@babel/preset-env", "@babel/preset-react", "mobx"]
}
複製代碼
在 src
目錄下新建 store.js
github
import { observable, action, makeObservable } from "mobx";
class Store {
constructor() {
makeObservable(this);
}
@observable
count = 0;
@action("add")
add = () => {
this.count = this.count + 1;
};
@action("reduce")
reduce = () => {
this.count = this.count - 1;
};
}
export default new Store();
複製代碼
import { Provider } from "mobx-react";
import Store from "./store";
// ...
ReactDOM.render(
<Provider store={Store}>
<Router history={createBrowserHistory()}>
<Route path="/" component={App} />
<Route path="/about" component={About} />
</Router>
</Provider>,
document.getElementById("root")
);
複製代碼
import React, { Component } from "react";
import { observer, inject } from "mobx-react";
@inject("store")
@observer
class App extends Component {
render() {
return (
<div>
<div>{this.props.store.count}</div>
<button onClick={this.props.store.add}>add</button>
<button onClick={this.props.store.reduce}>reduce</button>
</div>
);
}
}
export default App;
複製代碼
$ npm install antd vite-plugin-babel-import vite-plugin-imp --save
複製代碼
import { defineConfig } from 'vite';
import vitePluginImp from 'vite-plugin-imp';
const path = require('path');
export default defineConfig({
// ...
plugins: [
vitePluginImp({
libList: [
{
libName: 'antd',
libDirectory: 'es',
style: name => `antd/es/${name}/style`
}
]
})
],
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true
}
}
}
});
複製代碼
// ...
import { DatePicker } from "antd";
import "antd/dist/antd.css";
@inject("store")
@observer
class App extends Component {
render() {
return (
<div>
<DatePicker />
</div>
);
}
}
export default App;
複製代碼
$ npm install typescript @babel/preset-typescript --save-dev
複製代碼
{
"presets": [
// ...
"@babel/preset-typescript"
]
}
複製代碼
在根目錄下,新增 tsconfig.json
文件:
{
"compilerOptions": {
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "ES5",
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"outDir": "./dist/",
"esModuleInterop": true,
"noImplicitAny": false,
"sourceMap": true,
"module": "esnext",
"moduleResolution": "node",
"isolatedModules": true,
"importHelpers": true,
"lib": ["esnext", "dom", "dom.iterable"],
"skipLibCheck": true,
"jsx": "react",
"typeRoots": ["node", "node_modules/@types"],
"rootDirs": ["./src"],
"baseUrl": "./src"
},
"include": ["./src/**/*"],
"exclude": ["node_modules"]
}
複製代碼
更換文件後綴 App.jsx
-> App.tsx
import React, { Component } from "react";
import { observer, inject } from "mobx-react";
import { DatePicker } from "antd";
import "antd/dist/antd.css";
@inject("store")
@observer
class App extends Component {
props: any;
render() {
return (
<div>
<DatePicker />
<div>{this.props.store.count}</div>
<button onClick={this.props.store.add}>add</button>
<button onClick={this.props.store.reduce}>reduce</button>
</div>
);
}
}
export default App;
複製代碼
代碼校驗、代碼格式化、Git
提交前校驗、Vscode
配置、編譯校驗
$ npm install @typescript-eslint/parser eslint eslint-plugin-standard @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-promise --save-dev
複製代碼
在根目錄下,新增 .eslintrc.js
文件:
module.exports = {
extends: ["eslint:recommended", "plugin:react/recommended"],
env: {
browser: true,
commonjs: true,
es6: true,
},
globals: {
$: true,
process: true,
__dirname: true,
},
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
modules: true,
},
sourceType: "module",
ecmaVersion: 6,
},
plugins: ["react", "standard", "promise", "@typescript-eslint"],
settings: {
"import/ignore": ["node_modules"],
react: {
version: "latest",
},
},
rules: {
quotes: [2, "single"],
"no-console": 0,
"no-debugger": 1,
"no-var": 1,
semi: ["error", "always"],
"no-irregular-whitespace": 0,
"no-trailing-spaces": 1,
"eol-last": 0,
"no-unused-vars": [
1,
{
vars: "all",
args: "after-used",
},
],
"no-case-declarations": 0,
"no-underscore-dangle": 0,
"no-alert": 2,
"no-lone-blocks": 0,
"no-class-assign": 2,
"no-cond-assign": 2,
"no-const-assign": 2,
"no-delete-var": 2,
"no-dupe-keys": 2,
"use-isnan": 2,
"no-duplicate-case": 2,
"no-dupe-args": 2,
"no-empty": 2,
"no-func-assign": 2,
"no-invalid-this": 0,
"no-redeclare": 2,
"no-spaced-func": 2,
"no-this-before-super": 0,
"no-undef": 2,
"no-return-assign": 0,
"no-script-url": 2,
"no-use-before-define": 2,
"no-extra-boolean-cast": 0,
"no-unreachable": 1,
"comma-dangle": 2,
"no-mixed-spaces-and-tabs": 2,
"prefer-arrow-callback": 0,
"arrow-parens": 0,
"arrow-spacing": 0,
camelcase: 0,
"jsx-quotes": [1, "prefer-double"],
"react/display-name": 0,
"react/forbid-prop-types": [
2,
{
forbid: ["any"],
},
],
"react/jsx-boolean-value": 0,
"react/jsx-closing-bracket-location": 1,
"react/jsx-curly-spacing": [
2,
{
when: "never",
children: true,
},
],
"react/jsx-indent": ["error", 4],
"react/jsx-key": 2,
"react/jsx-no-bind": 0,
"react/jsx-no-duplicate-props": 2,
"react/jsx-no-literals": 0,
"react/jsx-no-undef": 1,
"react/jsx-pascal-case": 0,
"react/jsx-sort-props": 0,
"react/jsx-uses-react": 1,
"react/jsx-uses-vars": 2,
"react/no-danger": 0,
"react/no-did-mount-set-state": 0,
"react/no-did-update-set-state": 0,
"react/no-direct-mutation-state": 2,
"react/no-multi-comp": 0,
"react/no-set-state": 0,
"react/no-unknown-property": 2,
"react/prefer-es6-class": 2,
"react/prop-types": 0,
"react/react-in-jsx-scope": 2,
"react/self-closing-comp": 0,
"react/sort-comp": 0,
"react/no-array-index-key": 0,
"react/no-deprecated": 1,
"react/jsx-equals-spacing": 2,
},
};
複製代碼
在根目錄下,新增 .eslintignore
文件:
src/assets
複製代碼
在根目錄下新增 .vscode 文件夾
,而後新增 .vscode/settings.json
{
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]
}
複製代碼
$ npm install prettier --save-dev
複製代碼
在根目錄下,新增 prettier.config.js
文件:
module.exports = {
// 一行最多 100 字符
printWidth: 100,
// 使用 4 個空格縮進
tabWidth: 4,
// 不使用縮進符,而使用空格
useTabs: false,
// 行尾須要有分號
semi: true,
// 使用單引號
singleQuote: true,
// 對象的 key 僅在必要時用引號
quoteProps: 'as-needed',
// jsx 不使用單引號,而使用雙引號
jsxSingleQuote: false,
// 末尾不須要逗號
trailingComma: 'none',
// 大括號內的首尾須要空格
bracketSpacing: true,
// jsx 標籤的反尖括號須要換行
jsxBracketSameLine: false,
// 箭頭函數,只有一個參數的時候,也須要括號
arrowParens: 'avoid',
// 每一個文件格式化的範圍是文件的所有內容
rangeStart: 0,
rangeEnd: Infinity,
// 不須要寫文件開頭的 @prettier
requirePragma: false,
// 不須要自動在文件開頭插入 @prettier
insertPragma: false,
// 使用默認的折行標準
proseWrap: 'preserve',
// 根據顯示樣式決定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// 換行符使用 lf
endOfLine: 'lf'
};
複製代碼
$ npm install stylelint stylelint-config-standard stylelint-config-prettier --save-dev
複製代碼
在根目錄下,新增 stylelint.config.js
文件:
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
ignoreFiles: [
'**/*.ts',
'**/*.tsx',
'**/*.png',
'**/*.jpg',
'**/*.jpeg',
'**/*.gif',
'**/*.mp3',
'**/*.json'
],
rules: {
'at-rule-no-unknown': [
true,
{
ignoreAtRules: ['extends', 'ignores']
}
],
indentation: 4,
'number-leading-zero': null,
'unit-allowed-list': ['em', 'rem', 's', 'px', 'deg', 'all', 'vh', '%'],
'no-eol-whitespace': [
true,
{
ignore: 'empty-lines'
}
],
'declaration-block-trailing-semicolon': 'always',
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global']
}
],
'block-closing-brace-newline-after': 'always',
'declaration-block-semicolon-newline-after': 'always',
'no-descending-specificity': null,
'selector-list-comma-newline-after': 'always',
'selector-pseudo-element-colon-notation': 'single'
}
};
複製代碼
$ npm install lint-staged prettier eslint pre-commit --save-dev
複製代碼
{
// ...
"scripts": {
"lint:tsx": "eslint --ext .tsx src && eslint --ext .ts src",
"lint:css": "stylelint --aei .less .css src",
"precommit": "lint-staged",
"precommit-msg": "echo 'Pre-commit checks...' && exit 0"
},
"pre-commit": [
"precommit",
"precommit-msg"
],
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write",
"git add"
],
"*.{css,less}": [
"stylelint --fix",
"prettier --write",
"git add"
]
}
}
複製代碼
$ npm install eslint-webpack-plugin --save-dev
複製代碼
import { defineConfig } from 'vite';
const ESLintPlugin = require('eslint-webpack-plugin');
const path = require('path');
export default defineConfig({
// ...
plugins: [
new ESLintPlugin()
]
});
複製代碼
搭建這個的過程,也是遇到了很多坑,收穫也是蠻多的,但願這個教程可以幫助更多的同窗,少採點坑。基於這套項目框架能夠開發各類各樣的業務頁面,很是流暢,集成了很是很是多的庫,完善了路由配置,網絡請求,組件點擊加載等等。 完整的能夠看這個tristana,大佬們以爲不錯的話,能夠給個 Star 🌟,也歡迎給項目提 issues ~