从 0 搭建一个前端项目

一、项目启动

  1. 了解需求背景
  2. 了解业务流程

二、项目搭建初始化

本案例使用脚手架 create-react-app 初始化了项目。此脚手架有利有弊吧,项目目录结构简洁,不需要太关心 webpack 令人头疼的配置;弊端在于,脚手架确实有些庞大,构建时间在 4mins 左右。各位看官择优选择吧,也可以完全自己搭建一个项目。

  1. 设置淘宝镜像仓库

$ yarn config set registry registry.npm.taobao.org/ -g

$ yarn config set sass_binary_site cdn.npm.taobao.org/dist/node-sass -g

  1. 工程目录 init

$ create-react-app qpj-web-pc –typescript

$ tree -I “node_modules”

.|-- README.md|-- package.json|-- public|   |-- favicon.ico   |   |-- index.html|   |-- logo192.png   |   |-- logo512.png   |   |-- manifest.json |   `-- robots.txt|-- src|   |-- App.css|   |-- App.test.tsx  |   |-- App.tsx|   |-- index.css|   |-- index.tsx|   |-- logo.svg|   |-- react-app-env.d.ts|   |-- reportWebVitals.ts|   `-- setupTests.ts `-- tsconfig.json
  1. yarn build 试试

$ yarn build & tree -I “node_modules”

.|-- README.md|-- build/ # 改造点(由于 `Jenkins` 构建打包脚本有可能已经写死了 `dist` 包名)|-- package.json|-- public|   |-- favicon.ico   |   |-- index.html|   |-- logo192.png   |   |-- logo512.png   |   |-- manifest.json |   `-- robots.txt|-- src|   |-- App.css|   |-- App.test.tsx  |   |-- App.tsx|   |-- index.css|   |-- index.tsx|   |-- logo.svg|   |-- react-app-env.d.ts|   |-- reportWebVitals.ts|   `-- setupTests.ts `-- tsconfig.json
  1. 连接 git 远程仓库

$ git remote add origin yuanmin.zhu%40wetax.com.cn:wd246800mm@gitlab.yunpiaoer.com/front/qpj-web-pc.git

  1. 添加 .gitignore

$ echo -e ” yarn.lock \n package-lock.json \n /dist \n .idea” >> .gitignore

  1. 添加 eslint 代码及提交评论校验

$ yarn add husky lint-staged @commitlint/cli @commitlint/config-conventional -D

$ npx husky install

$ npx husky add .husky/pre-commit “npx lint-staged”

$ npx husky add .husky/prepare-commit-msg “npx commitlint -e”

  • 项目根目录新建 commitlint.config.js
// commitlint.config.jsmodule.exports = {  extends: ['@commitlint/config-conventional'],  rules: {      'type-enum': [          2,          'always',          ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'revert'],      ],      'subject-full-stop': [0, 'never'],      'subject-case': [0, 'never'],  },}
  • vscode 扩展中搜索 ESLint 并安装,项目根目录新建 .eslintrc.js,内容可参考文章配置:zhuanlan.zhihu.com/p/84329603 看第五点
  • Commit message 格式说明

<type>: <subject>

  • type 值枚举如下:
    • feat: 添加新特性
    • fix: 修复 bug
    • docs: 仅仅修改了文档
    • style: 仅仅修改了空格、格式缩进、都好等等,不改变代码逻辑
    • refactor: 代码重构,没有加新功能或者修复 bug
    • perf: 增加代码进行性能测试
    • test: 增加测试用例
    • chore: 改变构建流程、或者增加依赖库、工具等
    • revert: 当前 commit 用于撤销以前的 commit
  • subject 是 commit 目的的简短描述,不超过 50 个字符,且结尾不加句号(.)
  • package.json 新加入如下配置:
{    ...,    "lint-staged": {        "src/**/*.{jsx,txs,ts,js,json,css,md}": [            "eslint --quiet"        ]    },}
  • 可执行 npx eslint [filePath] \--fix 进行格式修复,无法修复的需手动解决

三、项目配置一(功能配置)

  1. 安装项目常用依赖库

$ yarn add antd axios dayjs qs -S # UI 库 及工具库

$ yarn add react-router-dom redux react-redux redux-logger redux-thunk -S # 路由及状态管理

  1. webpack 配置拓展很有必要
  • 根目录新建 config-overrides.js,详细使用可访问:简书:React 之 config-overrides文件配置
  • 安装
  • $ yarn add react-app-rewired customize-cra -D
  • 修改 package.json 中启动项
// package.json"scripts": {    "start": "react-app-rewired start",    "build": "react-app-rewired build",}
  • 使用
// config-overrides.jsconst {    override, // 主函数    fixBabelImports, // 配置按需加载    addWebpackExternals, // 不做打包处理配置    addWebpackAlias, // 配置别名    addLessLoader // lessLoader 配置,可更改主题色等} = require('customize-cra')module.exports = override(/* ... */, config => config)
  1. 配置按需加载
// config-overrides.js...module.exports = override(    fixBabelImports('import', {        libraryName: 'antd',        libraryDirectory: 'es', // library 目录        style: true, // 自动打包相关的样式    }),)
  1. 更改主题色
// config-overrides.js...module.exports = override(    addLessLoader({        lessOptions: {            javascriptEnabled: true,            modifyVars: {                '@primary-color': '#1890ff',            },        }    }),)
  1. 别名配置(typescript 项目这里有坑)
// config-overrides.jsconst path = require('path')...module.exports = override(    addWebpackAlias({        '@': path.resolve(__dirname, 'src'),    }),)
  1. 去除注释、多进程打包压缩
// config-overrides.jsconst UglifyJsPlugin = require('uglifyjs-webpack-plugin')const HardSourceWebpackPlugin = require('hard-source-webpack-plugin')...module.exports = override(/* ... */, config => {    config.plugins = [...config.plugins, {        new UglifyJsPlugin({            uglifyOptions: {                warnings: false,                compress: {                    drop_debugger: true,                    drop_console: true,                },            },        }),        new HardSourceWebpackPlugin()    }]    return config})
  1. 解决埋下的两个坑
  • 修改打包出的文件夹名为 dist
// 修改打包路径除了output,这里也要修改const paths = require('react-scripts/config/paths')paths.appBuild = path.join(path.dirname(paths.appBuild), 'dist')module.exports = override(/* ... */, config => {    config.output.path = path.resolve(__dirname, 'dist')    return config})
  • 解决 typescript 别名配置
    • 查阅相关资料,需要在 tsconfig.json 中添加一项配置
{    ...    "extends": "./paths.json"}
  • 新建文件 paths.json
{    "compilerOptions": {        "baseUrl": "src",        "paths": {            "@/*": ["*"]        }    }}
  1. 配置装饰器写法
{    "compilerOptions": {        "experimentalDecorators": true,        ...    }}
  1. 配置开发代理
  • 在 src 目录新建 setupProxy.js
// src/setupProxy.jsconst proxy = require('http-proxy-middleware').createProxyMiddlewaremodule.exports = function(app) {    // app 为 Express 实例,此处可以写 Mock 数据    app.use(        proxy('/api',        {            "target": "https://qpj-test.fapiaoer.cn",            "changeOrigin": true,            "secure": false,            // "pathRewrite": {            //   "^/api": ""            // }        })    )}
  1. 加入 polyfill 和 antd 组件国际化处理
// src/index.tsximport React from 'react'import ReactDOM from 'react-dom'// 注入 storeimport { Provider } from 'react-redux'import store from '@/store/store'import { ConfigProvider, Empty } from 'antd'import App from './App'import zhCN from 'antd/es/locale/zh_CN'import 'moment/locale/zh-cn'// polyfillimport 'core-js/stable'import 'regenerator-runtime/runtime'ReactDOM.render(    <Provider store={store}>        <ConfigProvider locale={zhCN} renderEmpty={Empty}>        <App />        </ConfigProvider>    </Provider>,    document.getElementById('root'))
  1. CSS Modules

create-react-app 自带支持以 xxx.module.(c|le|sa)ss 的样式表文件,使用上 typescript 项目中要注意:

const styles = require('./index.module.less')retrun (    <div className={`${styles.container}`}>        <Table            columns={columns}            className={`${styles['border-setting']}`}            dataSource={props.store.check.items}            rowKey={record => record.id}            pagination={false}        />        <div className="type-check-box"></div>    </div>)
// index.module.less.container {    padding: 24px;    background-color: #fff;    height: 100%;    overflow: auto;    .border-setting {        tr {            td:nth-child(3) {                border-left: 1px solid #F0F0F0;                border-right: 1px solid #F0F0F0;            }        }        td {            text-align: left !important;        }    }    :global { // 这个标识之后,其子代元素可以不需要使用 `styles['type-check-box']` 的方式,直接写 `className`        .type-check-box {            .ant-checkbox-wrapper + .ant-checkbox-wrapper{                margin-left: 0;            }        }    }}
  1. 【新】配置 React jsx 指令式属性 r-ifr-forr-modelr-show,提升开发效率:
  • 安装依赖

$ yarn add babel-react-rif babel-react-rfor babel-react-rmodel babel-react-rshow -D

  • 配置 .babelrc :
// .babelrc{    ...,    "plugins": [        "babel-react-rif",        "babel-react-rfor",        "babel-react-rmodel",        "babel-react-rshow",    ]}
  • 使用示例:
  • r-if
<div>  <h1 r-if={height < 170}>good</h1>  <h1 r-else-if={height > 180}>best</h1>  <h1 r-else>other</h1></div>
  • r-for
{/* eslint-disable-next-line no-undef */}<div r-for={(item, index) in [1, 2, 3, 4]} key={index}>  内容 {item + '-' + index}</div>
  • r-model
<input onChange={this.callback} type="text" r-model={inputVale} />
  • r-show
<div r-show={true}>内容</div> # 注意:这是 `r-if` 的效果,不会渲染节点

四、项目配置二(优化配置)

  1. 实现组件懒加载 react-loadable
import Loadable from 'react-loadable'const Loading = (props: any) => {    if (props.error) {        console.error(props.error)        return <div>Error! <Button type="link" onClick={props.retry}>Retry</Button></div>    } else if (props.timedOut) {        return <div>Timeout! <Button onClick={props.retry}>Retry</Button></div>    } else if (props.pastDelay) {        return <div>Loading...</div>    } else {        return null    }}const loadable = (path: any) => {    return Loadable({        loader: () => import(`@/pages${path}`),        loading: Loading,        delay: 200,        timeout: 10000,    })}const Home = loadable('/homePage/Home')
© 版权声明
THE END
喜欢就支持一下吧
点赞15 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容