從0搭建React開(kāi)發(fā)環(huán)境
前言
為什么要自己造輪子?起初是因?yàn)樽约翰⒉粷M意市面上的腳手架。另外,造輪子對(duì)于自己也有一些技術(shù)上的幫助,學(xué)別人二次封裝的東西,不如直接使用底層的庫(kù),這樣也有助于自己系統(tǒng)的學(xué)習(xí)一遍知識(shí),廢話不多說(shuō),直接進(jìn)入正文,如何搭建自己的開(kāi)發(fā)環(huán)境。
初始化
創(chuàng)建文件夾并進(jìn)入:
$?mkdir?tristana?&&?cd?tristana
初始化?package.json
$?npm?init
安裝?Webpack
$?npm?install?webpack?webpack-cli?--save-dev
創(chuàng)建以下目錄結(jié)構(gòu)、文件和內(nèi)容:
project
tristana
|-?package.json
|-?/dist
???|-?index.html
|-?/script
???|-?webpack.config.js
|-?index.html
|-?/src
???|-?index.js
src/index.js
document.getElementById("root").append("React");
index.html && dist/index.html
????
????????"utf-8"?/>
????????tristana
????
????
????????
????????"root">
????
script/webpack.config.js
module.exports?=?{
????mode:?"development",
????entry:?"./src/index.js",
};
package.json
{
????//?...
????"scripts":?{
????????"build":?"webpack?--mode=development?--config?script/webpack.config.js"
????},
}
然后根目錄終端輸入:npm run build
在瀏覽器中打開(kāi)?dist?目錄下的?index.html,如果一切正常,你應(yīng)該能看到以下文本:'React'
index.html?目前放在?dist?目錄下,但它是手動(dòng)創(chuàng)建的,下面會(huì)教你如何生成?index.html?而非手動(dòng)編輯它。
Webpack 核心功能
Babel
$?npm?install?@babel/cli?@babel/core?babel-loader?@babel/preset-env?--save-dev
script/webpack.config.js
module.exports?=?{
????//?...
????module:?{
????????rules:?[
????????????{
????????????????test:?/\.(js|jsx)$/,
????????????????loader:?"babel-loader",
????????????????exclude:?/node_modules/,
????????????},
????????],
????},
};
.babelrc
在根目錄下添加?.babelrc?文件:
{
????"presets":?["@babel/preset-env",?"@babel/preset-react"]
}
樣式
$?npm?install?style-loader?css-loader?less?less-loader?--save-dev
script/webpack.config.js
module.exports?=?{
????//?...
????module:?{
????????rules:?[
????????????{
????????????????test:?/\.(css|less)$/,
????????????????use:?[
????????????????????{
????????????????????????loader:?"style-loader",
????????????????????},
????????????????????{
????????????????????????loader:?"css-loader",
????????????????????????options:?{
????????????????????????????importLoaders:?1,
????????????????????????},
????????????????????},
????????????????????{
????????????????????????loader:?"less-loader",
????????????????????????lessOptions:?{
????????????????????????????javascriptEnabled:?true,
????????????????????????},
????????????????????},
????????????????],
????????????},
????????],
????},
};
圖片字體
$?npm?install?file-loader?--save-dev
script/webpack.config.js
module.exports?=?{
????//?...
????module:?{
????????rules:?[
????????????{
????????????????test:?/\.(png|svg|jpg|gif|jpeg)$/,
????????????????loader:?'file-loader'
????????????},
????????????{
????????????????test:?/\.(woff|woff2|eot|ttf|otf)$/,
????????????????loader:?'file-loader'
????????????}
????????],
????},
};
HTML
$?npm?install?html-webpack-plugin?--save-dev
script/webpack.config.js
const?HtmlWebpackPlugin?=?require('html-webpack-plugin');
module.exports?=?{
????//?...
????plugins:?{
????????html:?new?HtmlWebpackPlugin({
????????????title:?'tristana',
????????????template:?'public/index.html'
????????}),
????}
};
index.html
????
????????"utf-8"?/>
????????tristana
????
????
????????"root">
????
開(kāi)發(fā)服務(wù)
$?npm?install?webpack-dev-server?--save-dev
script/webpack.config.js
const?path?=?require("path");
const?HtmlWebpackPlugin?=?require('html-webpack-plugin');
module.exports?=?{
????//?...
????devServer:?{
????????contentBase:?path.resolve(__dirname,?"dist"),
????????hot:?true,
????????historyApiFallback:?true,
????????compress:?true,
????},
};
package.json
{
????//?...
????"scripts":?{
????????"start":?"webpack?serve?--mode=development?--config?script/webpack.config.js"
????},
????//?...
}
清理 dist
$?npm?install?clean-webpack-plugin?--save-dev
script/webpack.config.js
const?{?CleanWebpackPlugin?}?=?require('clean-webpack-plugin');
module.exports?=?{
????//?...
????plugins:?{
????????new?CleanWebpackPlugin()
????}
};
Tips
由于?webpack?使用的是^5.21.2?版本,在使用該插件時(shí),會(huì)提示clean-webpack-plugin: options.output.path not defined. Plugin disabled...,暫時(shí)還未解決。
環(huán)境變量
$?npm?install?cross-env?--save-dev
package.json
{
????//?...
????"scripts":?{
????????"start":?"cross-env?ENV_LWD=development?webpack?serve??--mode=development?--config?script/webpack.config.js",
????????"build":?"cross-env?ENV_LWD=production?webpack?--mode=production?--config?script/webpack.config.js"
????},
????//?...
}
.jsx 文件
安裝依賴
$?npm?install?@babel/preset-react?react?react-dom?--save-dev
.babelrc
{
??"presets":?["@babel/preset-env",?"@babel/preset-react"]
}
src/App.jsx
在?src?目錄下,新增?App.jsx?文件:
import?React,?{?Component?}?from?"react";
class?App?extends?Component?{
????render()?{
????????return?(
????????????
?????????????????Hello,?World!?
????????????
????????);
????}
}
export?default?App;
src/index.js
import?React?from?"react";
import?ReactDOM?from?"react-dom";
import?App?from?"./App.jsx";
ReactDOM.render( ,?document.getElementById("root"));
React Router
安裝依賴
$?npm?install?react-router?history?--save
src/index.js
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(
????history={createBrowserHistory()}>
????????"/"?component={App}?/>
????????"/about"?component={About}?/>
???? ,
????document.getElementById("root")
);
MobX
安裝依賴
$?npm?install?mobx?mobx-react?babel-preset-mobx?--save
.babelrc
{
??"presets":?["@babel/preset-env",?"@babel/preset-react",?"mobx"]
}
src/store.js
在?src?目錄下新建?store.js
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();
index.js
import?{?Provider?}?from?"mobx-react";
import?Store?from?"./store";
//?...
ReactDOM.render(
????
????????history={createBrowserHistory()}>
????????"/"?component={App}?/>
????????"/about"?component={About}?/>
????????
???? ,
????document.getElementById("root")
);
src/App.jsx
import?React,?{?Component?}?from?"react";
import?{?observer,?inject?}?from?"mobx-react";
@inject("store")
@observer
class?App?extends?Component?{
????render()?{
????????return?(
????????????
????????????????{this.props.store.count}
????????????????
????????????????
????????????
????????);
????}
}
export?default?App;
Ant Design
安裝依賴
$?npm?install?antd?babel-plugin-import?--save
.babelrc
{
????//?...
????"plugins":?[
????????[
????????????"import",
????????????{
????????????????"libraryName":?"antd",
????????????????"libraryDirectory":?"es",
????????????????"style":?true
????????????}
????????]
????]
}
src/App.jsx
//?...
import?{?DatePicker?}?from?"antd";
import?"antd/dist/antd.css";
@inject("store")
@observer
class?App?extends?Component?{
????render()?{
????????return?(
????????????
????????????????
????????????
????????);
????}
}
export?default?App;
TypeScript
安裝依賴
$?npm?install?typescript?@babel/preset-typescript?--save-dev
.babelrc
{
????"presets":?[
????????//?...
????????"@babel/preset-typescript"
????]
}
tsconfig.json
在根目錄下,新增?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"]
}
src/App.jsx
更換文件后綴?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?(
????????????
????????????????
????????????????{this.props.store.count}
????????????????
????????????????
????????????
????????);
????}
}
export?default?App;
代碼規(guī)范
代碼校驗(yàn)、代碼格式化、Git?提交前校驗(yàn)、Vscode配置、編譯校驗(yàn)
ESLint
安裝依賴
$?npm?install?@typescript-eslint/parser?eslint?eslint-plugin-standard?@typescript-eslint/parser?@typescript-eslint/eslint-plugin?eslint-plugin-promise??--save-dev
.eslintrc.js
在根目錄下,新增?.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
在根目錄下,新增?.eslintignore?文件:
src/assets
.vscode
在根目錄下新增?.vscode 文件夾,然后新增?.vscode/settings.json
{
????"eslint.validate":?[
????????"javascript",
????????"javascriptreact",
????????"typescript",
????????"typescriptreact"
????]
}
Perttier
安裝依賴
$?npm?install?prettier?--save-dev
prettier.config.js
在根目錄下,新增?prettier.config.js?文件:
module.exports?=?{
????//?一行最多?100?字符
????printWidth:?100,
????//?使用?4?個(gè)空格縮進(jìn)
????tabWidth:?4,
????//?不使用縮進(jìn)符,而使用空格
????useTabs:?false,
????//?行尾需要有分號(hào)
????semi:?true,
????//?使用單引號(hào)
????singleQuote:?true,
????//?對(duì)象的?key?僅在必要時(shí)用引號(hào)
????quoteProps:?'as-needed',
????//?jsx?不使用單引號(hào),而使用雙引號(hào)
????jsxSingleQuote:?false,
????//?末尾不需要逗號(hào)
????trailingComma:?'none',
????//?大括號(hào)內(nèi)的首尾需要空格
????bracketSpacing:?true,
????//?jsx?標(biāo)簽的反尖括號(hào)需要換行
????jsxBracketSameLine:?false,
????//?箭頭函數(shù),只有一個(gè)參數(shù)的時(shí)候,也需要括號(hào)
????arrowParens:?'avoid',
????//?每個(gè)文件格式化的范圍是文件的全部?jī)?nèi)容
????rangeStart:?0,
????rangeEnd:?Infinity,
????//?不需要寫(xiě)文件開(kāi)頭的?@prettier
????requirePragma:?false,
????//?不需要自動(dòng)在文件開(kāi)頭插入?@prettier
????insertPragma:?false,
????//?使用默認(rèn)的折行標(biāo)準(zhǔn)
????proseWrap:?'preserve',
????//?根據(jù)顯示樣式?jīng)Q定?html?要不要折行
????htmlWhitespaceSensitivity:?'css',
????//?換行符使用?lf
????endOfLine:?'lf'
};
stylelint
安裝依賴
$?npm?install?stylelint?stylelint-config-standard?stylelint-config-prettier?--save-dev
stylelint.config.js
在根目錄下,新增?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'
????}
};
lint-staged、pre-commit
安裝依賴
$?npm?install?lint-staged?prettier?eslint?pre-commit?--save-dev
package.json
{
????//?...
????"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"
????????]
????}
}
eslint-webpack-plugin
安裝依賴
$?npm?install?eslint-webpack-plugin?--save-dev
script/webpack.config.js
const?ESLintPlugin?=?require('eslint-webpack-plugin');
module.exports?=?{
????//?...
????plugins:?[new?ESLintPlugin()],
};
總結(jié)
搭建這個(gè)的過(guò)程,也是遇到了不少坑,收獲也是蠻多的,希望這個(gè)教程能夠幫助更多的同學(xué),少采點(diǎn)坑,完整的?React?開(kāi)發(fā)環(huán)境可以看這個(gè)tristana,求點(diǎn)贊,求關(guān)注!
