前言
为什么要自己造轮子?起初是因为自己并不满意市面上的脚手架。另外,造轮子对于自己也有一些技术上的帮助,学别人二次封装的东西,不如直接使用底层的库,这样也有助于自己系统的学习一遍知识,废话不多说,直接进入正文,如何搭建自己的开发环境。
创建文件夹并进入:
$ mkdir tristana && cd tristana
初始化 package.json
$ npm init
安装 Webpack
$ npm install webpack webpack-cli --save-dev
创建以下目录结构、文件和内容:
tristana
|- package.json
|- /dist
|- index.html
|- /script
|- webpack.config.js
|- index.html
|- /src
|- index.js
document.getElementById("root").append("React");
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>tristana</title>
</head>
<body>
<script src="../src/index.js"></script>
<div id="root"></div>
</body>
</html>
module.exports = {
mode: "development",
entry: "./src/index.js",
};
{
// ...
"scripts": {
"build": "webpack --mode=development --config script/webpack.config.js"
},
}
然后根目录终端输入:npm run build
在浏览器中打开 dist
目录下的 index.html
,如果一切正常,你应该能看到以下文本:'React'
index.html
目前放在 dist
目录下,但它是手动创建的,下面会教你如何生成 index.html
而非手动编辑它。
$ npm install @babel/cli @babel/core babel-loader @babel/preset-env --save-dev
module.exports = {
// ...
module: {
rules: [
{
test: /\.(js|jsx)$/,
loader: "babel-loader",
exclude: /node_modules/,
},
],
},
};
在根目录下添加 .babelrc
文件:
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
$ npm install style-loader css-loader less less-loader --save-dev
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
module.exports = {
// ...
module: {
rules: [
{
test: /\.(png|svg|jpg|gif|jpeg)$/,
loader: 'file-loader'
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: 'file-loader'
}
],
},
};
$ npm install html-webpack-plugin --save-dev
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
plugins: {
html: new HtmlWebpackPlugin({
title: 'tristana',
template: 'public/index.html'
}),
}
};
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>tristana</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
$ npm install webpack-dev-server --save-dev
const path = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
// ...
devServer: {
contentBase: path.resolve(__dirname, "dist"),
hot: true,
historyApiFallback: true,
compress: true,
},
};
{
// ...
"scripts": {
"start": "webpack serve --mode=development --config script/webpack.config.js"
},
// ...
}
$ npm install clean-webpack-plugin --save-dev
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// ...
plugins: {
new CleanWebpackPlugin()
}
};
由于 webpack
使用的是^5.21.2
版本,在使用该插件时,会提示clean-webpack-plugin: options.output.path not defined. Plugin disabled...
,暂时还未解决。
$ npm install cross-env --save-dev
{
// ...
"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"
},
// ...
}
$ npm install @babel/preset-react react react-dom --save-dev
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
在 src
目录下,新增 App.jsx
文件:
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
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 babel-plugin-import --save
{
// ...
"plugins": [
[
"import",
{
"libraryName": "antd",
"libraryDirectory": "es",
"style": 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
const ESLintPlugin = require('eslint-webpack-plugin');
module.exports = {
// ...
plugins: [new ESLintPlugin()],
};
搭建这个的过程,也是遇到了不少坑,收获也是蛮多的,希望这个教程能够帮助更多的同学,少采点坑,完整的 React
开发环境可以看这个tristana。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/WcxFMooHxxStFP4VORl5GQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。