从零搭建 Vite + React 开发环境 #112

简介: 从零搭建 Vite + React 开发环境 #112

前言


大概在 2019 年,自己搭建 开发环境的想法萌芽,到目前为止,公司的很多项目上,也在使用中,比较稳定。为什么要自己造轮子?起初是因为自己并不满意市面上的脚手架。另外,造轮子对于自己也有一些技术上的帮助,学别人二次封装的东西,不如直接使用底层的库,这样也有助于自己系统的学习一遍知识,最近 很火,所以用 搭建一波,废话不多说,直接进入正文,如何搭建自己的开发环境。ReactViteVite


初始化


创建文件夹并进入:

$ mkdir tristana && cd tristana

初始化package.json

$ npm init

安装vite

$ npm install vite vite-plugin-babel-import vite-plugin-imp --save-dev

创建以下目录结构、文件和内容:


项目

tristana
|- package.json
|- index.html
|- vite.config.js
|- /src
   |- index.js


src/index.js

document.getElementById("root").append("React");


索引.html

<!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>


vite.config.js

import { defineConfig } from 'vite';
const path = require('path');
export default defineConfig({
    plugins: [
        reactRefresh()
    ]
});


package.json

{
    // ...
    "scripts": {
        "build": "vite build",
    },
}


然后根目录终端输入:npm run build

在浏览器中打开 目录下的 ,如果一切正常,你应该能看到以下文本:distindex.html'React'

index.html目前放在 目录下,但它是手动创建的,下面会教你如何生成 而非手动编辑它。distindex.html


Vite 核心功能


热更新

$ npm install @vitejs/plugin-react-refresh --save-dev


vite.config.js

import reactRefresh from '@vitejs/plugin-react-refresh';
export default defineConfig({
    // ...
    plugins: [
        reactRefresh(),
    ],
});


别名

vite.config.js

import { defineConfig } from 'vite';
const path = require('path');
export default defineConfig({
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src')
        }
    }
});


开发服务

包.json

{
    // ...
    "scripts": {
        "dev": "vite",
    },
}


.jsx 文件


安装依赖

$ npm install @babel/preset-react react react-dom --save-dev


.babelrc

{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}


src/App.jsx

在 目录下,新增 文件:srcApp.jsx

import React, { Component } from "react";
class App extends Component {
    render() {
        return (
            <div>
                <h1> Hello, World! </h1>
            </div>
        );
    }
}
export default App;


src/index.js

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


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(
    <Router history={createBrowserHistory()}>
        <Route path="/" component={App} />
        <Route path="/about" component={About} />
    </Router>,
    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

在 目录下新建 srcstore.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(
    <Provider store={Store}>
        <Router history={createBrowserHistory()}>
        <Route path="/" component={App} />
        <Route path="/about" component={About} />
        </Router>
    </Provider>,
    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 (
            <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;


Ant Design


安装依赖

$ npm install antd vite-plugin-babel-import vite-plugin-imp --save


vite.config.js

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
            }
        }
    }
});


src/App.jsx

// ...
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


.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 (
            <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;


代码规范


代码校验、代码格式化、 提交前校验、配置、编译校验GitVscode

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 个空格缩进
    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'
};


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

包.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


vite.config.ts

import { defineConfig } from 'vite';
const ESLintPlugin = require('eslint-webpack-plugin');
const path = require('path');
export default defineConfig({
    // ...
    plugins: [
        new ESLintPlugin()
    ]
});


总结


搭建这个的过程,也是遇到了不少坑,收获也是蛮多的,希望这个教程能够帮助更多的同学,少采点坑。基于这套项目框架可以开发各种各样的业务页面,非常流畅,集成了非常非常多的库,完善了路由配置,网络请求,组件点击加载等等。

完整的可以看这个tristana,大佬们觉得不错的话,可以给个 Star🌟,也欢迎给项目提 issues~

目录
相关文章
|
2月前
|
前端开发 JavaScript API
React团队回应用Vite替换Create React App的建议
React团队回应用Vite替换Create React App的建议
|
2月前
|
前端开发 JavaScript 容器
前端vw自适应解决方案,适用pc端以及移动端,适用webpack以及vite,适用vue以及react
前端vw自适应解决方案,适用pc端以及移动端,适用webpack以及vite,适用vue以及react
144 0
|
9月前
|
资源调度 前端开发 JavaScript
简单搭建react开发环境
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 目录 前言 一、pandas是什么? 二、使用步骤 1.引入库 2.读入数据 总结 前言 安装前需要安装:node.js、vscode 一、node.js使用 运行Node.js command prompt,输入命令: npx create-react-app my-app (如下图) 安装完成截图如下: 至此,react安装完成,可以直接在npom控制台运行,我放到v..
28 0
简单搭建react开发环境
|
8月前
|
资源调度 前端开发 JavaScript
三分钟搭建React开发环境
三分钟搭建React开发环境
240 0
|
8月前
|
资源调度 前端开发 JavaScript
从零开始学习React-开发环境的搭建(一)
从零开始学习React-开发环境的搭建(一)
43 0
|
9月前
|
前端开发 应用服务中间件 nginx
简单几步,将React项目脚手架Webpack换成Vite⚡⚡,附带性能比较和思考
简单几步,将React项目脚手架Webpack换成Vite⚡⚡,附带性能比较和思考
|
11月前
|
前端开发 应用服务中间件 API
解决分布式React前端在开发环境的跨域问题
解决分布式React前端在开发环境的跨域问题
170 0
|
前端开发
antd组件库封装23-配置react开发环境
antd组件库封装23-配置react开发环境
130 0
antd组件库封装23-配置react开发环境
|
前端开发 JavaScript 开发工具
基于 Vite 从 0 到 1 启动一个 React 项目
如果不使用 Vite 官方提供的 template 选项,要如何从一个 Vite 依赖的情况下逐步配置启动个 React?如何实现 Webpack 的静态资源服务,如何实现基于 Webpack 的 loader 的 JSX 的自动转换?
|
移动开发 JSON 前端开发
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境2
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境
240 0
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境2