koa2 + react + node后台+热更新

技术栈

  • 代码 https://github.com/jinguohua/demo4
  • react+webpack 支持前台编写。node+mysql做后台维护
  • 使用ts,包括了tsc,bable7,antd
  • 使用mysql,koa-route 做路由

react+webpack配置

由于使用bable7 ,所以直接使用.babelrc 解析react、ts、es6高级语法
.babelrc相关配置如下,此文件位于项目根目录下。

//.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false
      }
    ],
    ["@babel/preset-react"],
    ["@babel/preset-typescript"]
  ],
  "plugins": [
    [
      "import",
      {
        "libraryName": "antd"
      }
    ],
    [
      "@babel/plugin-proposal-class-properties",
      {
        "loose": true
      }
    ],
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ],
    "@babel/plugin-syntax-dynamic-import"
  ]
}

同样需要配置tsconfig.json,此文件位于项目根目录下。内容如下

{
    "compilerOptions": {
        "outDir": "./dist",
        "module": "esnext",
        "target": "es5",
        "lib": [
            "es6",
            "dom"
        ],
        "sourceMap": true,
        "allowJs": true,
        "jsx": "react",
        "moduleResolution": "node",
        "forceConsistentCasingInFileNames": true,
        "noImplicitReturns": true,
        "noImplicitThis": true,
        "strictNullChecks": true,
        "suppressImplicitAnyIndexErrors": true,
        "noUnusedLocals": true,
        "allowSyntheticDefaultImports": true,
        "skipLibCheck": true,
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "allowUnreachableCode": true,
        "noImplicitAny": false
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules"
    ]
}

这是基础配置,接下来是配置webpack.config.js,此文件位于项目根目录下
区分了开发和线上环境

if (process.env.NODE_ENV === "development") {
  module.exports = require("./config/webpackconfig");
} else {
  module.exports = require("./config/webpackconfig");
}

根据webpack.config.js中文件的路径,新建生产和线上的webpack 配置,有几个小知识点,
1、使用了happypack ,直接打包了babel-loader
2.1、output.path是硬盘文件路径
2.2、output.publicPath配置的是打包文件输出的访问路径
3、const MiniCssExtractPlugin = require(“mini-css-extract-plugin”);这个可以将.tsx中引入的.less 文件拆分出来
4、const HtmlWebpackPlugin = require(“html-webpack-plugin”);可以根据你设置的路径在dist中生成.html模版,并且将3中拆分出来的less 引入到dist/index.html中
5、new webpack.optimize.OccurrenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoEmitOnErrorsPlugin(),
这三个是是用于koa-webpack-dev-middleware热更新的,随后配置后端的时候,会再提到
下面是webpack 代码

const path = require("path");
const webpack = require("webpack"); // 加载webpack 中的模块
const HappyPack = require("happypack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const os = require("os");
const happyThreadPool = HappyPack.ThreadPool({ size: os.cpus().length });
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const nodeEnv = "development";
module.exports = {
  mode: process.env.NODE_ENV,

  entry: {
    index: [
      "@babel/polyfill",
      "webpack-hot-middleware/client?noInfo=true",
      "./src/index.tsx"
    ]
  },
  output: {
    path: path.join(__dirname, "../dist"),
    publicPath: "/dist/",
    filename: "js/[name].js",
    chunkFilename: "js/[name].chunk.js"
  },

  module: {
    rules: [
      {
        test: /\.(js?|ts?|jsx?|tsx?)$/,
        exclude: /node_modules/,
        use: ["happypack/loader?id=babel"]
      },
      {
        test: /\.(c|le)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: process.env.NODE_ENV === "development"
              //reloadAll: true,
            }
          },
          "css-loader",
          "postcss-loader",
          "less-loader"
        ]
      }
    ]
  },

  plugins: [
    new webpack.optimize.OccurrenceOrderPlugin(),
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.DefinePlugin({
      "process.env": {
        NODE_ENV: JSON.stringify(nodeEnv),
        NODE_LOCATION: JSON.stringify(process.env.NODE_ENV)
      }
    }),
    new HappyPack({
      id: "babel",
      loaders: [
        {
          loader: "babel-loader"
        }
      ],
      threadPool: happyThreadPool
    }),
    new MiniCssExtractPlugin({
      filename: "css/[name].css",
      chunkFilename: "css/[name].css",
      ignoreOrder: false
    }),
    new HtmlWebpackPlugin({
      filename: "index.html",
      template: "index.html",
      inject: true
    })
  ],

  resolve: {
    //有些不同的模块会区分不同的代码这里决定使用哪份代码,例如es6 and es5
    mainFiles: ["index.web", "index"],
    modules: [path.resolve(__dirname, "src"), "node_modules"],
    // 添加让webpack 解析的后缀 例如 ./entry ,优先匹配entry.ts,entry.tsx,最后是entry.js
    extensions: [".ts", ".tsx", ".js", ".json", ".less", ".css"]
  },
  performance: {
    hints: false
  }
};

总的来说,react+webpack 前半部分基本上就是这样了
package.json 启动脚本是这样的

   "start": "cross-env NODE_ENV=development  nodemon --watch 'server_koa.js' -e ts,tsx --exec 'ts-node' ./server_koa.js ",

koa_server.js,在后半部分详细介绍,启动http 服务,做接口请求。
nodemon 检测server_koa.js,支持检测路径’server/**/*’,当使用ts 编写后台时,可以直接重新编译,然后重启ts-node’ ./server_koa.js 服务

======================手动分割线=

现在是重中之重koa2+node +koaroute路由做接口

koa2 是轻量级的服务器接口

  • koa2 中没有像express中可以直接使用use,或者get 拦截处理路由,需要使用const Router = require(“koa-router”);做路由分发,也可以自己做这一步,不过麻烦

  • koa2 中同样没有静态文件分发,需要使用const send = require(“koa-send”);进行react打包的js 和less 进行模版发送,就是webpack 中从.tsx中抽离出来的js 和less 。也有资源说可以使用koa-static,但是,但是我这里加在不出来js和less,报错404.所以最后问同事才了解到koa-send。koa-static是继承实现koa-send,所以问题不大。

  • 下面就是这一块代码,/dist/是webpack 中 output.publicpath 路径,检测系统访问的时候分发文件资源,
    /test/是项目文件,就是react route 的路径,也就是浏览器地址栏的路径
    这里配置中间件的格式,返回我们配置的模版,所有的访问路径都会经过这个中间件

router.get("/dist/*", async (ctx, next) => {
  await send(ctx, ctx.path);
});
// app.use(static(__dirname + "/dist/", { extensions: ["html"] }));

router.get("/test/*", async (ctx, next) => {
  ctx.type = "html";
  ctx.body = fs.createReadStream("dist/index.html");
  await next();
});

nodemon 是检测server,也监控node后台代码。检测到改变就重新编译

  • 配置到现在,仍然不能完美的使用koa2 替换express ,现在还没有热更新,在js .tsx修改的代码仍然不能编译,下面就是需要koa的热更新
const  webpackDevMiddleware = require("koa-webpack-dev-middleware");
const webpackHotMiddleware = require("koa-webpack-hot-middleware");

const wdm = webpackDevMiddleware(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath
});
app.use(wdm);
app.use(webpackHotMiddleware(compiler));

这两块内容来对.tsx 文件进行热更新。webpackDevMiddleware这个板块中的必须注明 publicPath: config.output.publicPath这个路径,否则不能进行文件热更新,因为找不到更改的文件对应的板块,出现的问题就是hot-update.json找不到,这个链接中注明了publicPath必须写明的原因。
还有就是config/webpackconfig.js 中使用的那三个插件

new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin(),

注意npm 官网上的这三个插件名称不正确,会直接在webpack 编译中直接报错,拿着报错信息自行百度一下就会有结果。
下面的是server_koa.js完整代码

const Koa = require("koa");
const app = new Koa();
const Router = require("koa-router");
// const koaBody = require("koa-body");
// const static = require("koa-static");
// const path = require("path");
const send = require("koa-send");
const fs = require("fs");

const webpack = require("webpack");
const webpackDevMiddleware = require("koa-webpack-dev-middleware");
const webpackHotMiddleware = require("koa-webpack-hot-middleware");
const config = require("./webpack.config");

const compiler = webpack(config);
const router = new Router();

router.get("/dist/*", async (ctx, next) => {
  await send(ctx, ctx.path);
});
// app.use(static(__dirname + "/dist/", { extensions: ["html"] }));

router.get("/test/*", async (ctx, next) => {
  ctx.type = "html";
  ctx.body = fs.createReadStream("dist/index.html");
  await next();
});

const wdm = webpackDevMiddleware(compiler, {
  noInfo: true,
  publicPath: config.output.publicPath
});
app.use(wdm);
app.use(webpackHotMiddleware(compiler));

app.use(router.routes());

app.listen(8081);

console.log("Server running on port 8081");

或者把webpack打包的这一块抽成中间件

const Koa = require("koa");
const app = new Koa();
const Router = require("koa-router");
const send = require("koa-send");
const fs = require("fs");

const webpackMiddleware = require("./config/middleware/webpack-dev-hot-middleware");
app.use(webpackMiddleware.wdm());
app.use(webpackMiddleware.whm());

const router = new Router();
router.get("/dist/*", async (ctx, next) => {
  await send(ctx, ctx.path);
});
router.get("/test/*", async (ctx, next) => {
  ctx.type = "html";
  ctx.body = fs.createReadStream("dist/index.html");
  await next();
});

app.use(router.routes());

app.listen(8081);

console.log("Server running on port 8081");

对应的webpack 中间件./config/middleware/webpack-dev-hot-middleware

const webpack = require("webpack");
const webpackDevMiddleware = require("koa-webpack-dev-middleware");
const webpackHotMiddleware = require("koa-webpack-hot-middleware");
const config = require("../../../webpack.config");
const compiler = webpack(config);

const webpackMiddleware = {
  wdm() {
    return webpackDevMiddleware(compiler, {
      noInfo: true,
      publicPath: config.output.publicPath
    });
  },
  whm() {
    return webpackHotMiddleware(compiler);
  }
};

module.exports = webpackMiddleware;

接下来,就是配置koat-route ,与之对应的就是ajax 访问的接口,然后数据传输
在server_koa.ts中封装route ,

// 接口访问
let controller = fs.readdirSync(__dirname + '/controller');
controller.forEach((element) => {
  let module = require(__dirname + '/controller/' + element);
  router.use('/ajax', module.routes(), module.allowedMethods());
  console.log(router, 'router')
})

koa2 + react + node后台+热更新_第1张图片
这里就是在server.koa.ts 中读取controller 下定义的全部模块,还有一层dao 文件,这里是将数据库操作和业务层代码分开,在controller 中解析request ,读取dao 层中的数据库操作语句,返回结果。封装response。

这里是controller 中login 下的index.ts 文件
这两部极易遇到404,检查是否写了 next();检查是否在context 返回了没有->

ctx.set("Content-Type", "application/json")
        ctx.body = JSON.stringify({
            code: 200,
            data: { username: response[0].username },
            message: '登录成功',
        })
const login = require('koa-router')();
//这个引入是将同为login 目录下的其他路由引入到index.ts。因为在server_koa.ts 中读取的只是index.ts中的路由
const loginout = require('./loginout');
// 读取数据库操作,将结果返回过来
const daoLogin = require('../../dao/login');
// login
login.post('/login', async (ctx, next) => {
    const user = ctx.request.body;
    const response = await daoLogin.loginCheck(user, next);
    if (response && response.length > 0) {
        ctx.set("Content-Type", "application/json")
        ctx.body = JSON.stringify({
            code: 200,
            data: { username: response[0].username },
            message: '登录成功',
        })
    } else {
        ctx.set("Content-Type", "application/json")
        ctx.body = JSON.stringify({
            code: 202,
            message: '密码错误',
        })
    }
})

//可以引入其他文件的但是属于本模块的路由,类似于logout 这样的功能
login.use('/loginout', loginout.routes());

module.exports = login;
export { };

这是dao 层的login

const db = require('../../../config/middleware/database');

const loginCheck = (param, next) => {
    let sql = 'select username, userid from users where username= ? and password = ?',
        value = [param.username, param.password];
    return db.query(sql, value);
}
module.exports = {
    loginCheck
}

可以看到这里就是操作数据库了,具体的数据库封装是这样的
koa2 + react + node后台+热更新_第2张图片

//index.ts
let mysql = require('mysql');

let pool = mysql.createPool(require('./config'));

let connect = () => pool.getConnection(function (err, connection) {
    if (err) {
        console.error('error connecting: ' + err.stack);
        return;
    }
    console.log('connected as id ' + connection.threadId);
});

let query = (sql, values) => {
    return new Promise((resolve, reject) => {
        pool.getConnection((err, connection) => {
            if (err) {
                reject(err)
            } else {
                connection.query(sql, values, (err, rows) => {
                    if (err) {
                        reject(err)
                    } else {
                        resolve(rows)
                    }
                    connection.end()
                })
            }
        })
    })
};
module.exports = {
    connect,
    query
}

//confg.ts
let dbConfig = {
    host: 'localhost',
    user: '*******',
    password: '**********',
    database: '*****',
    port: 3306,
    multipleStatements: true//允许多条sql同时执行
};
module.exports = dbConfig;

如果已经装好mysql 了,这里稍微改一下就可以了。

配置数mysql据库

下载好,直接装就行了,连接过程总可能会遇到这个错误:Client does not support authentication protocol requested by server; consider upgrading MySQL client。
这个是解决办法:https://www.cnblogs.com/zichuan/p/9203129.html
一些基本的数据库操作

  • 控制台操作登录报错
    mac 安装mysql :mysql: command not found
    https://www.jianshu.com/p/f910c5294588

PATH="$PATH":/usr/local/mysql/bin

  • 命令行操作数据库基本操作
    https://www.jianshu.com/p/d0e1e3b26ca4
    https://www.cnblogs.com/sujulin/p/9021355.html

你可能感兴趣的:(node)