项目最新一期需求变动比较大,刚好有时间做项目重构
项目基本结构
原始项目结构为
-- 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 可以直接使用
比如如果上传文件
以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('/test')
test (
@Body() body
) {
return {
code: 200,
data: body,
}
}
如果以form-date格式发送普通数据,没有配合 @UploadedFile 则在Body上就无法拿到数据
sequelize
前端
可以直接运行.ts文件
webpack
webpack + ts
ts版本的webpack配置基本安装官网搭建
-
无法使用webpack-dev-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()
]
}
将代码进行压缩,但是在控制台会报错
是因为 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中打开页面,也是有问题的。
这个资源是存在的
但是一个非常奇怪的问题,就是本地无法打开
右键使用Safari直接打开 也无法展示图片
可以看到直接从磁盘中打开都无法读取图片信息
所以肯定是讲过webpack编译之后的图片出现了问题
然后尝试关闭雪碧图,直接使用base64格式展示,仍然失败。
后来查看webpack关于图片的配置,有一个处理图片的loader -》image-webpack-loader
这个是用来减少图片大小的,
插件的issure中有提到这个问题 click me
这个插件使用示例有一个 webp 参数
webp: {
quality: 75,
}
这个参数一旦开启,会把所有的图片先转为 webp 的格式 格式转化会导致在iOS中展示失败 所以要关闭这个参数
- 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 并没有将es6的 isFinite 这个API转为ES5的方法实现
然而在低版本中 有可能是不支持 isFinite 这个方法的,可能导致问题,所以需要借助 polyfill 来处理