从零开始搭建前端项目

239 阅读8分钟

现流行项目框架搭建

在搭建前端项目之前,我们首先需要明确下面几个问题

  • 项目运行环境:服务端?移动端?PC端?
  • 编程语言:Jsp?JavaScript? TypeScript?
  • 前端框架:原生?React?Vue?Angular?Sevlet?
  • UI框架:BootStrap? ElementUI? ViewUI?Antd?
  • 样式语言:CSS?Less? Scss?
  • 请求工具:ajax?XMLHTTPRequest?axios?fetch?
  • 是否需要打包框架:Webpack?Rollup?

项目搭建初步构思

在实际开始搭建项目之前,我们需要对整个项目的运行环境、使用场景以及技术栈契合度去综合考虑,了解我们所选用每一项技术放入到项目是否合理,一个好的项目基础框架是为后续需求铺路,而不是挖坑。下面我们就先结合几个常用的需求场景去分析如何搭建一个项目框架。

场景一:一个面向大众用户的后台系统

首先我们要抓住这个需求中的两个个关键信息:大众后台。根据这两个关键信息,我们可以分析出以下问题

  1. 系统运行环境的不确定,IE?、360浏览器、Chrome、FireFox等等等。是否需要兼容浏览器版本,兼容到什么程度,这个直接确定了我们的技术选型。
  2. 后台系统一般会涉及到大量的表格、表单数据处理,这时候我们需要选择一个偏向数据展示的UI框架,而不是动画效果展示的UI框架。
  3. 数据的交互复杂程度也直接决定数据处理的方案选型。
  4. 既然是面向大众,那是否需要做多语言处理。
结论:
选型技术栈优点缺点
兼容型jQuery + BootStrap + ajax1、兼容性非常高1、技术老旧,操作复杂,主要以dom操作为主。
平衡型1、Vue2.x + ElementUI1.4.x + Vuex + axios 2、React15.x + Antd3.x + (Mobx & Redux)1、兼容性一般,可以兼容>=IE101、为满足IE兼容,框架版本较低,部分功能支持程度较低
高配型1、Vue3.x + (ElementUI2.x & ViewUI & AntVue)+ Vuex 2、React + Antd4.x + (Mbox & Redux)1、功能支持度高,社区组件丰富1、不兼容IE

场景二:官网首页

在做官网首页的时候,我们需要考虑下面几点:

  1. 官网作为一个网站的入口页面,大多数会有这几个特点:样式丰富、数据交互少、需要支持SEO
  2. 面向大众,整体兼容性要求较高,需要适配移动端等
  3. 访问流畅,整体渲染快,项目体积小。
结论:
  1. 根据数据交互少、样式丰富、加载流畅等特点,我们如果引入antd、Viewui等这些重型UI库,一方面的是否增加页面体积,另一方面由于官网需要较高程度的定制化,这些UI库的样式大部分是无法直接使用,需要做较大的样式调整,所以一般是不会引入的,大部分的样式都是根据实际情况,手写动画动画效果等,或者按需引入制定的依赖例如Swiper。

  2. 为了保证页面渲染流畅,加载高效,所以大部分的官网都会去考虑使用SSR(服务端渲染 ),这样把渲染的压力放至服务端。

  3. 同时部分官网,为了保证双端的同步,我们可以采用下面两种策略:

    1. 根据用户端类型,动态分发页面。
    2. 使用媒体查询等方式,响应式适配。
  4. 为了保证页面访问快,我们可以把整个项目部署到oss中。

总的来说就是,我们需要根据不同的需求,不同的使用场景,不同的面向对象,去选择不同的项目配置,我们需要了解每一个技术栈的特点,合理使用。

React项目构建

一、架构搭建

安装node (Node >= 14)
# 使用 nvm 安装最新稳定版 Node.js
nvm install stable
初始化项目
npm install create-react-app -g
# AND
create-react-app my-app
# OR 使用typescript
create-react-app my-app --template typescript

这时项目已经有了一个雏形了。然后我们可以看到在控制台中,提供了几种命令:

npm start
# Starts the development server.
​
npm run build
# Bundles the app into static files for production.
​
npm test
# Starts the test runner.
​
npm run eject
# Removes this tool and copies build dependencies, configuration files
# and scripts into the app directory. If you do this, you can’t go back!

如果我们想偷懒可以直接运行eject命令。这时候我们可以看到项目中已经有一个较为完善的。

规范目录结构
├── config                   # 打包配置
├── mock                     # 本地模拟数据
├── public
│   └── favicon.png          # Favicon
├── src
│   ├── assets               # 本地静态资源
│   ├── components           # 业务通用组件
│   ├── layouts              # 通用布局
│   ├── models               # 全局 dva model
│   ├── pages                # 业务页面入口和常用模板
│   ├── services             # 后台接口服务
│   ├── utils                # 工具库
│   ├── global.less          # 全局样式
│   └── global.ts            # 全局 JS
├── README.md
└── package.json
配置webpack

1、安装webpackwebpack-cli

yarn add webpack webpack-cli -D

2、安装核心babel核心模块

yarn add babel-loader @babel/core @babel/preset-react @babel/preset-typescript -D

3、添加html-webpack-plugin插件

yarn html-webpack-plugin -D

以上就是一个基础的公共配置包,最后生成的就是下面这个配置:

// webpack.base.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
​
module.exports = {
  // 入口文件
  entry: path.join(__dirname, '../src/index.tsx'),
  // 出口文件
  output: {
    filename: 'static/js/[name].js',
    path: path.join(__dirname, '../dist'),
    clean: true,
    publicPath: '/fta-react-admin-demo' // 打包后文件的公共前缀路径
  },
  module: {
    rules: [{
      // 使用babel处理ts, tsx文件
      test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
      use: {
        loader: 'babel-loader',
        options: {
          // 预设执行顺序由右往左,所以先处理ts,再处理jsx
          presets: [
            '@babel/preset-react',
            '@babel/preset-typescript'
          ]
        }
      }
    }]
  },
  resolve: {
    extensions: ['.js', '.tsx', '.ts'],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html'), // 模板取定义root节点的模板
      inject: true, // 自动注入静态资源
    })
  ]
}

4、开发环境配置,安装开发环境启动服务器

yarn add webpack-dev-server webpack-merge -D

webpack.dev.js设置如下配置,并合并公共配置

// webpack.dev.js
const path = require('path');
const { merge } = require('webpack-merge');
const baseConfig = require('./webpack.base');
const {module} = require("./webpack.base");

module.exports = merget(baseConfig, {
  mode: 'development',
  devtool: 'eval-cheap-module-source-map', // 源码调试模式
  devServer: {
    port: 8080, // 服务端口号
    compress: false, // gzip压缩,开发环境不开启,提升热更新速度
    hot: true,
    historyApiFallback: true, // 解决history路由404问题
    static: {
      directory: path.join(__dirname, '../public')
    }
  }
})

package.json中添加执行脚本"dev": "webpack-dev-server -c config/webpack.dev.js"

5、配置环境变量,安装cross-env

yarn add cross-env -D

同时修改package.json中的scripts命令:

{
  "script": {
    "dev:dev": "cross-env NODE_ENV=local BASE_ENV=dev webpack-dev-server -c build/webpack.dev.js",
    "dev:qa": "cross-env NODE_ENV=local BASE_ENV=qa webpack-dev-server -c build/webpack.dev.js",
    "dev:prod": "cross-env NODE_ENV=local BASE_ENV=prod webpack-dev-server -c build/webpack.dev.js"
  }
}

最后通过webpack.DefinePlugin插件把环境变量注入到业务代码中

plugins: [
    new webpack.DefinePlugin({
      'BASE_ENV': JSON.stringify(process.env.BASE_ENV),
      'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
    })
]

6、处理css文件,安装style-loader,css-loader

yarn add style-loader css-loader -D

同时配置webpack.base.js

rules: [{
	test: /.css$/, //匹配 css 文件
	use: ['style-loader','css-loader']
}]

7、处理less或者scss,安装对应的loader文件

yarn add less-loader less -D

同时配置webpack.base.js

rules: [{
	test: /.(css|less)$/, //匹配 css和less 文件
	use: ['style-loader','css-loader', 'less-loader']
}]

8、处理css前缀兼容

yarn add postcss-loader autoprefixer -D

配置webpack.base.js

rules: [
  // ...
  {
        test: /.(css|less)$/, //匹配 css和less 文件
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: true // 开启css-module
            }
          },
          // 新增
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: ['autoprefixer']
              }
            }
          },
          'less-loader'
        ]
  }
]

在根目录下新增文件postcss.config.js,并在文件中增加配置

module.exports = {
  plugins: ['autoprefixer']
}

9、babel预设处理js兼容

很多低版本浏览器不支持,后者非标准语法所有的浏览器都不支持。需要把最新的标准语法转换为低版本语法,把非标准语法转换为标准语法才能让浏览器识别解析。

yarn babel-loader @babel/core @babel/preset-env core-js -D

配置webpack.base.js

rules: [
      {
        // 使用babel处理ts, tsx文件
        test: /.(ts|tsx)$/, // 匹配.ts, tsx文件
        use: {
          loader: 'babel-loader',
          options: {
            // 预设执行顺序由右往左,所以先处理ts,再处理jsx
            presets: [
              [
                "@babel/preset-env",
                {
                  "useBuiltIns": "usage", // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加
                  "corejs": 3, // 配置使用core-js低版本
                }
              ],
              '@babel/preset-react',
              '@babel/preset-typescript'
            ]
          }
        }
      }
  ]

10、图片处理,由于webpack5内置了asset-module来处理,所以无需新增其他loader

// webpack.base.js
rules: [
// ...
{
        test: /.(png|jpg|jpeg|gif|svg)$/, // 匹配图片文件
        type: "asset", // type选择asset
        parser: {
          dataUrlCondition: {
            maxSize: 10 * 1024, // 小于10kb转base64位
          }
        },
        generator: {
          filename: 'static/images/[name][ext]', // 文件输出目录和命名
        },
      }
]

如果我们使用typescript,还需要在global.d.ts中声明。

declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
declare module '*.less'
declare module '*.css'

现在一个简易的webpack开发环境配置就完成了,我们还可以按需去添加:字体文件、媒体文件的处理loader,构建耗时分析(speed-measure-webpack-plugin),配置别名(alias),忽略外拓文件(externals))等等。

集成路由工具 React Router

1、安装react-router-dom

yarn add react-router-dom

2、在src目录下创建一个router/index.ts,主要是用于我们配置路由。

// app.tsx
import { useRoutes, BrowserRouter } from 'react-router-dom';
import routes from './router';
const SetRoues = () => {
	const router = useRoutes(routes);
	return router
}

root.render(
	<React.StrictMode>
		<BrowserRouter>
			<SetRoues />
		</BrowserRouter>
	</React.StrictMode>
);

// router/index.tsx
import React from 'react';
import Welcome from '../pages/welcome';
import Dashboard from '../pages/dashboard';

const routes = [{
	path: '/',
	element: <Welcome />
}, {
	path: '/dashboard',
	element: <Dashboard />
}]

export default routes;
集成UI工具

添加antd依赖

yarn add antd

根目录下添加global.less全局样式文件,并添加@import '~antd/dist/antd.css'

集成状态管理工具React Redux
yarn add react-redux @reduxjs/toolkit

新建目录models/store.ts,并配置store

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counter';

export default configureStore({
	reducer: {
		counter: counterReducer
	},
})

index.tsx中注册store

// app.tsx
import {Provider} from 'react-redux';
import store from './models/store';

root.render(
	<Provider store={store}>
		<BrowserRouter>
			<SetRoues/>
		</BrowserRouter>
	</Provider>
);

然后就可以在页面中使用react-redux提供的hooks,操作store中的数据了

import React from 'react';
import { Button } from 'antd';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement } from '../../models/counter';

const Dashboard = () => {
	const dispatch = useDispatch()
	const { count } = useSelector((state: { counter: { count: number } }) => state.counter)

	const handle = (type: string) => {
		if (type === 'increment') {
			dispatch(increment())
		} else {
			dispatch(decrement())
		}
	}

	return <div>
		<div>当前数字是:{ count }</div>
		<Button onClick={() => handle('increment')}>+1</Button>
		<Button onClick={() => handle('decrement')}>-1</Button>
	</div>
}

export default Dashboard;

项目打包与部署

Webpack打包

1、配置webpack.prod.js

// webpack.prod.js

const { merge } = require('webpack-merge')
const baseConfig = require('./webpack.base.js')
module.exports = merge(baseConfig, {
  mode: 'production', // 生产模式,会开启tree-shaking和压缩代码,以及其他优化
})

同时在package.json中添加打包命令。

2、新增copy-webpack-plugin,把public中放的一些静态资源,可以直接根据绝对路径引入,直接拷贝至打包后的static目录下

yarn add copy-webpack-plugin -D

配置config:

// webpack.prod.js
const path = require('path')
const CopyPlugin = require('copy-webpack-plugin');
module.exports = merge(baseConfig, {
  mode: 'production',
  plugins: [
    // 复制文件插件
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, '../public'), // 复制public下文件
          to: path.resolve(__dirname, '../dist'), // 复制到dist目录中
          filter: source => {
            return !source.includes('index.html') // 忽略index.html
          }
        },
      ],
    }),
  ]
})

Nginx部署

1、安装Nginx

sudo apt-get update
sudo apt-get install nginx

2、配置Nginx

修改/etc/nginx/conf.d下的nginx配置

server {
    #服务启动时监听的端口
	listen 80 default_server;
	listen [::]:80 default_server;
    #服务启动时文件加载的路径
	root /var/www/html/react-custom;
    #默认加载的第一个文件
	index index.php index.html index.htm index.nginx-debian.html;
    #页面访问域名,如果没有域名也可以填写_
	server_name www.xiexianbo.xin;

	location /fta-react-admin-demo {
        #页面加载失败后所跳转的页面
		try_files $uri $uri/ =404;
	}
    
      
    #以下配置只服务于php
	# 将PHP脚本传递给在127.0.0.1:9000上监听的FastCGI服务器
	location ~ .php$ {
		include snippets/fastcgi-php.conf;
		# With php7.0-cgi alone:
		#fastcgi_pass 127.0.0.1:9000;
		# With php7.0-fpm:
		fastcgi_pass unix:/run/php/php7.0-fpm.sock;
	}

	# 如果Apache的文档为root,则拒绝访问.htaccess文件
	location ~ /.ht {
		deny all;
	}
}

然后在服务器中执行打包脚本publish.sh

rm -rf node_module
yarn install
yarn build:prod
rm -rf /www/react-custom/
cp -r /home/react-custom/dist/. /var/www/html/react-custom
sudo service nginx restart