项目重构记录

1,729 阅读5分钟

项目最新一期需求变动比较大,刚好有时间做项目重构

项目基本结构

原始项目结构为

-- config
-- pm2
-- app
 -- utils
 -- routes
 -- views
 -- src
 -- webpack
文件夹 含义
config 基本信息配置,比如连接数据库等
pm2 项目部署到服务器
app 项目前端 + server

核心框架 koa react webpack3

后期重构之后,其目录为

-- dist
-- server
  -- config
  -- utils
  -- router
  -- app.ts
-- pm2
-- src
  -- project
  -- webpack
  -- utils
文件夹 含义
server server
dist server编译之后的js代码
pm2 项目部署到服务器
src 项目前端代码

核心框架 koa routing-controllers sequelize webpack4

server 端

之前搭建一个简单的koa项目

const koa = require('koa')
const Router = require('koa-router')
const app = new koa()
const bodyParser = require('koa-bodyparser')

// formData数据解析到ctx.request.body中
app.use(bodyParser())
// 处理静态资源
app.use(require('koa-static')(staticPath, { gzip: true }))

let user = new Router()
user.get('/users', async (ctx) => {
  ctx.body = 'get all users'
})

app.use(user.routes()).use(user.allowedMethods())

app.listen(3200, () => {
  console.log('3200')
})

加入 routing-controller 库

routing-controllers

routing-controllers --- 使用ts构建的一个帮助很方便处理router的框架

createKoaServer

创建服务

import "reflect-metadata"; 
import { createKoaServer } from "routing-controllers";
import {UserController} from "./UserController";

const app = createKoaServer({
   controllers: [UserController] 
})

app.listen(3000, () => {
  console.log('the port is 3000')
})

简单可以理解为创建了一个 koa 实例

源码

function createKoaServer(options) {
    var driver = new KoaDriver_1.KoaDriver();
    return createServer(driver, options);
}
useKoaServer

这也是一个常用的方法

当已经创建了 koa 实例, 可以直接使用 useKoaServer 进行router注入

import "reflect-metadata"; 
import Koa from 'koa'
import { useKoaServer } from 'routing-controllers'
const app = new Koa()

useKoaServer(app, {
  controllers: [path.join(__dirname, '/controllers/*{.js,.ts}')],
})
console.log(app, 'app')
app.listen(3000, (e) => {
  console.log('the port is 3000', e)
})

源码

function useKoaServer(koaApp, options) {
    var driver = new KoaDriver_1.KoaDriver(koaApp);
    return createServer(driver, options);
}

可以看到两者中都用创建了一个 KoaDriver 的实例,只不过是传参不同,后者将自己创建的koa实例作为参数传入函数内部了。

KoaDriver

核心代码

class KoaDriver {
    constructor (koa, router) {
        super();
        this.loadKoa();
        this.loadRouter();
        this.app = this.koa;
    }
    
    // 在路由和中间件注册之前 调用
    -- initialize ---
    initialize() {
        const bodyParser = require("koa-bodyparser");
        this.koa.use(bodyParser());
        if (this.cors) {
            const cors = require("kcors");
            if (this.cors === true) {
                this.koa.use(cors());
            } else {
                this.koa.use(cors(this.cors));
            }
        }
    }

    --- 注册 routes ---
    registerRoutes() {
        this.koa.use(this.router.routes());
        this.koa.use(this.router.allowedMethods());
    }
    
    --- 生成 koa 实例 --- 
    loadKoa () {
        if (!this.koa) this.koa = new (require('koa'))()
    }
    
    --- router  --
    loadRouter () {
        if (!this.router) {
            this.router = new (require("koa-router"))()
        }
    }
    
    -- 注册 Action --- 
    registerAction() {
        // -- something 没看懂---
        
        const multer = this.loadMulter()
        
        
    }
} 
createExecutor
  • createServer
function createServer(driver, options) {
    createExecutor(driver, options);
    return driver.app;
}
  • createExecutor
function createExecutor (river, options) {
    -- something ----
    new RoutingControllers_1.RoutingControllers(driver, options)
        .initialize()
        .registerInterceptors(interceptorClasses)
        .registerMiddlewares("before", middlewareClasses)
        .registerControllers(controllerClasses)
        .registerMiddlewares("after", middlewareClasses);
}
  • RoutingControllers
class RoutingControllers {
    -- something ----
    initialize() {
     // -- 调用了上面的 initialize 方法 -- 添加 koa-bodyparser 中间件
        this.driver.initialize(); 
        return this;
    }
    
    -- something ----
    registerControllers () {
        this.driver.registerAction(...)
    }
}
other
  • 关于 render 页面 --- 不能使用 @render
  @Get('/')
  @Render('index')
  async index (
    @HeaderParam('device') device: string
  ) {
    console.log('device')
    return {
      title: 'i am title'
    }
  }

这样写了之后 后面的代码就无法运行了

同事有提出这个问题

处理方案

    const body = fs.readFileSync(path.resolve('dist', 'index.html'), 'utf-8') 
    return body
  
    // --- 或者是 ----
    const htmlPath = path.resolve(process.cwd(), 'src/public/index.html')
    // 模板殷切使用 handlebars 
    return cons.swig(htmlPath, {
      title: 'Blued',
      config
    })
  • 关于处理 post 请求

之前的项目使用了 koa-bodyparser 来解析post请求的数据将其挂载在 ctx.body

如果涉及到文件上传,还需要添加中间件 koa-multer 做处理

可以考虑使用 Koa-body

参考文章

而routing-controllers 内部已经封装了 koa-bodyparser 和 koa-multer 可以直接使用

比如如果上传文件

upload

以form-data格式进行上传

@Controller()
export class UserController {
    @Post('/upload')
    test (
      @Body() body
      @UploadedFile('file') file
    ) {
        console.log('body', body)
        return {
          code: 200,
          data: body,
        }
    }
}

如果只是post发送普通数据

post

@Post('/test')
    test (
      @Body() body
    ) {
        return {
          code: 200,
          data: body,
        }
    }

如果以form-date格式发送普通数据,没有配合 @UploadedFile 则在Body上就无法拿到数据

sequelize

sequelize入门篇

前端

可以直接运行.ts文件

webpack

webpack + ts

ts版本的webpack配置基本安装官网搭建

server

需要安装 @types/webpack-dev-server

webpack -> v4

v3升级到v4,比较明显的变化是一些插件已被废弃或者已经变更了使用方式

  • hot属性

在webpack4中 webpack-dev-server的 hot 属性已经被废弃了

 devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    host: 'localhost',
    hot: true,
    inline: true,
    port: 7777,
    before: function (app) {
      console.log('devserver')
    }
  },

将 hot 属性删除

可以直接配置在命令中

"dev": "webpack-dev-server --open --hot --config webpack.dev.js --mode development"
  • 模块找不到

解决方案 webpack - reaolve

  extensions: ['.ts', '.tsx', '.json'], 

调整为

  extensions: ['.ts', '.tsx', '.json', 'js'], 
  • extract-text-webpack-plugin

使用 extract-text-webpack-plugin 来拆分css

但是一直在报错

参考 issue

安装

npm install --save-dev extract-text-webpack-plugin@next

或者更换为 mini-css-extract-plugin

这个使用起来更加方便

{
  test: /\.s?css$/,
  use: [MiniCssExtractPlugin.loader,'css-loader', 'postcss-loader', 'sass-loader']
}

------

new MiniCssExtractPlugin({
  filename: "[name].css",
  chunkFilename: "[id].css"
})

  • UglifyjsWebpackPlugin

V4 中将 压缩代码的逻辑放到 optimization中了

optimization{
    minimize: isPro, // 是否进行代码压缩
    minimizer: [
      new UglifyjsWebpackPlugin()
    ]
}

将代码进行压缩,但是在控制台会报错

optimization

是因为 UglifyjsWebpackPlugin 无法处理es6 语法

看下 tsx文件编译使用的是 ts-loader,加入 babel-loader 处理

     {
        test: /\.tsx?$/,
        include: /src/,
        // use: ['babel-loader'],
        use: ['babel-loader','ts-loader'],
        // use: ['ts-loader'],
        exclude: /node_modules/
      },

或者更换压缩代码的库 terser-webpack-plugin

optimization {
    minimize: true, // 是否进行代码压缩
    minimizer: [
      new TerserPlugin({....})
    }
}
  • 使用 image-webpack-loader 导致项目中雪碧图在iOS中加载失败

项目测试中发现,在iOS手机中,编译的雪碧图一直读取失败。在Safari中打开页面,也是有问题的。

img

这个资源是存在的

error

但是一个非常奇怪的问题,就是本地无法打开

local

右键使用Safari直接打开 也无法展示图片

可以看到直接从磁盘中打开都无法读取图片信息

info

所以肯定是讲过webpack编译之后的图片出现了问题

然后尝试关闭雪碧图,直接使用base64格式展示,仍然失败。

后来查看webpack关于图片的配置,有一个处理图片的loader -》image-webpack-loader

这个是用来减少图片大小的,

插件的issure中有提到这个问题 click me

issure

这个插件使用示例有一个 webp 参数

    webp: {
        quality: 75,
    }

这个参数一旦开启,会把所有的图片先转为 webp 的格式 格式转化会导致在iOS中展示失败 所以要关闭这个参数

  • dom7??

dom7

rules: [
    {
      test: /\.js$/, // Check for all js files
      exclude: /node_modules\/(?!(dom7|swiper)\/).*/,
      loader: 'babel-loader'
    }
]
  • babel

babel配置变动 @babel/xxx

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

babel && babel-polyfill

项目上线之后,有用户反馈在安卓低版本(安卓 4.x 左右) 中出现白屏问题

发现是代码中的一些API没有被支持,查阅问题后,解决方案是引入 babel-polyfill

1 在页面头部直接引入

import 'babel-polyfill'

2 调整webpack的入口文件

    entry: {
      Vendor,
      index: ['babel-polyfill', `./project/${project}/index.tsx`]
    },

3 调整 babel 配置

plugin: ["@babel/plugin-transform-runtime"]

本来以为 babel 本身是可以处理各种兼容问题,原来不是这样的, babel只是会编译语法,对于代码中使用的API是不会做处理的

let newAry = [...1, 2,  3.2, 3],map((it) => Number.isFinite(it))

babel

如果所示,babel 并没有将es6的 isFinite 这个API转为ES5的方法实现

然而在低版本中 有可能是不支持 isFinite 这个方法的,可能导致问题,所以需要借助 polyfill 来处理

参考文章