教你如何从零搭建一个Node.js 的 MVC 项目(一)

2,166 阅读5分钟

一、准备工作

安装 koa、热更新用的 supervisor

PS:安装 Node.js 就不介绍了,最新版本就 ok

npm init -y
cnpm i koa --save
cnpm i supervisor --save-dev

建立入口文件 app.js

./app.js

const Koa = require("koa");
const app = new Koa();

app.listen(3000,()=>{
    console.log("server is running..")
});

./package.json

···
  "scripts": {
    "start": "npm run dev",
    "dev": "supervisor -i ./node_modules ./app.js"
  },
···
注意 supervisor -i ./node_modules ./app.js 这条命令! 要是不把 ./node_modules 排除掉的话,supervisor 会吃光你的内存,风扇狂转!

二、建立 controller

建立如图所示目录结构

controller 么,先对路由下手就完了 ~

所以,继续下和路由有关的包吧~

 cnpm i koa-simple-router --save

ok,我们可以愉快的写 controller 的父类了

./controllers/indexControllers.js

class IndexControllers {
    constructor() {

    }

    actionIndex(ctx, next) {
        ctx.body = "koa2 is running~~";
    }
}

module.exports = IndexControllers;

继承父类的 controller

./controllers/index.js

const router = require("koa-simple-router");
const IndexController = require("./indexControllers");
const indexController = new IndexController();

module.exports = app => {
    app.use(router(_ => {
        _.get("/", indexController.actionIndex);
        _.get('/index.html', indexController.actionIndex);
    }))
};

_.get("/", indexController.actionIndex);
_.get('/index.html', indexController.actionIndex); 伪静态

回到入口文件 app.js 注册路由

const Koa = require("koa");
const app = new Koa();
// 注册路由
require("./controllers/index")(app);

app.listen(3000,()=>{
    console.log("server is running..")
});

OK! 我们现在应可以启动服务了,npm start 之后访问本地服务,就可以看到效果了,是不是还有点儿小激动?


三、建立配置文件优化项目

app.js 里面的app.listen(3000,()=>{console.log("server is running..")});3000扔到入口文件不伦不类,我们可以把它放到另外的位置

项目根目录建立 config 相关文件,如图所示

./config/index.js

const config={
    port:3000
};

module.exports(config);

./app.js

const Koa = require("koa");
const app = new Koa();
const config = require("./config");
// 注册路由
require("./controllers/index")(app);

app.listen(config.port, () => {
    console.log("server is running..")
});

既然要优化端口,不如把生产环境的 80 端口开发环境的 3000 端口分开吧

安装 cross-env

cnpm i cross-env --save-dev

改造 package.json 和 config.js

./package.json

···
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development supervisor -i ./node_modules ./app.js"
  },
···

./config/index.js

const config = {};

if (process.env.NODE_ENV === "development") {
    config.port = 3000;
} else if (process.env.NODE_ENV === "production") {
    config.port = 80;
}

module.exports = config;

四、实现 view 层

老规矩,先建立目录吧

那么问题来了,这些静态文件我们得把它们加载进来呀

安装 koa-static

cnpm i koa-static --save

继续改造 config.js

./config/index.js

const path = require("path");

const config = {
    staticDir: path.join(__dirname, "..", "assets")
};

if (process.env.NODE_ENV === "development") {
    config.port = 3000;
} else if (process.env.NODE_ENV === "production") {
    config.port = 80;
}

module.exports = config;

把静态文件加载进来

./app.js

const Koa = require("koa");
const app = new Koa();
const config = require("./config");
const serve = require("koa-static");
// 注册路由
require("./controllers/index")(app);
// 加载静态文件
app.use(serve(config.staticDir));

app.listen(config.port, () => {
    console.log("server is running..")
});

五、实现 Model 层

在这里,我们的模板引擎用的是 koa-swig.js

安装模板引擎

cnpm i koa-swig --save
cnpm i co --save

注:co是历史遗留问题,用来把koa1编译成koa2

完善 view 层

./views/layouts/layout.html

这个将会是所有模板的“祖宗”

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>

    {% block head %}
    {% endblock %}

    {% block link %}
    {% endblock %}
</head>
<body>
{% block content %}
{% endblock %}

{% block script %}
{% endblock %}
</body>
</html>

./views/index.html

继承了 layout.html

{% extends './layout.html' %}
{% block title %} 图书管理首页 {% endblock %}

{% block link %}
<link rel="stylesheet" href="/styles/index.css">
{% endblock %}

{% block content %}
<div>
    <h1>{{content}}</h1>
</div>
{% endblock %}

{% block script %}
<script src="/scripts/index.js"></script>
{% endblock %}

./assets/styles/index.css

css

body{
    background: #000;
}
h1{
    color:greenyellow;
}
p{
    color:yellow;
}

./assets/scripts/index.js

js

const content = "你好koa~~";
console.log(content);

完善 controller 层

./controllers/index.js

class IndexControllers {
    constructor() {

    }

    async actionIndex(ctx, next) {
        ctx.body = await ctx.render('index.html',{
            content:"hello node!"
        });
    }
}

module.exports = IndexControllers;

在入口文件注册模板引擎

./config/index.js

const path = require("path");

const config = {
    viewDir: path.join(__dirname, "..", "views"), // 把视图层加载引来
    staticDir: path.join(__dirname, "..", "assets")
};

if (process.env.NODE_ENV === "development") {
    config.port = 3000;
} else if (process.env.NODE_ENV === "production") {
    config.port = 80;
}

module.exports = config;

./app.js

const Koa = require("koa");
const app = new Koa();
const config = require("./config");
const serve = require("koa-static");
const render = require("koa-swig");
const co = require("co");
// 注册路由
require("./controllers/index")(app);
// 加载静态文件
app.use(serve(config.staticDir));
// 配置模板引擎
app.context.render = co.wrap(render({
    root: config.viewDir, // 把视图层加载引来
    autoescape: true,
    cache: false, // 缓存
    ext: 'html',
    writeBody: false
}));
app.listen(config.port, () => {
    console.log("server is running..")
});

OK,我们现在再看下

如果你做到这步了,那么胜利的曙光就要降了!

六、安装 babel

有人会问,现在的代码有什么问题吗?,有,而且不少。

大家回到 ./assets/scripts/index.js文件,如果我新增了一行如下代码,那会怎样呢?

const content = "你好koa~~";
console.log(content);

export default content;

很正常么,因为它不认识啊,另外如果浏览器不支持es6语法怎么办?

安装 babel

cnpm i babel @babel-cli --save-dev
cnpm i babel @babel-core --save-dev
cnpm i babel @babel-preset-env --save-dev

项目根目录新建 .babelrc 文件

./.babelrc

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

完善 package.json

···
  "scripts": {
    "start": "npm run dev",
    "dev": "cross-env NODE_ENV=development supervisor -i ./node_modules ./app.js",
    "build": "babel ./assets/scripts/index.js -o ./assets/scripts/index-bundle.js"
  },
···

npm run build 一下

完善 views/index.html

{% extends './layouts/layout.html' %}
{% block title %} 图书管理首页 {% endblock %}

{% block link %}
<link rel="stylesheet" href="/styles/index.css">
{% endblock %}

{% block content %}
<div>
    <h1>{{content}}</h1>
</div>
{% endblock %}

{% block script %}
<script type="module">
    import ("/scripts/index.js").then((_) => {
        console.log(_.default);
    })
</script>
<script type="nomodule" src="https://cdn.bootcss.com/systemjs/6.2.5/system.min.js"></script>
<script type="nomodule">
    System.import("/scripts/index-bundle.js").then((_) => {
        console.log(_.default);
    })
</script>
{% endblock %}

在这里我们引入了万能木块加载器 systemjs

OK,现在我们的第一阶段学习完成了~

七、CSR + SSR

比如我们可以把vuejQuery

./views/layouts/layout.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>

    {% block head %}
    {% endblock %}

    {% block link %}
    {% endblock %}
</head>
<body>
{% block content %}
{% endblock %}
<script src="https://cdn.bootcss.com/vue/2.6.11/vue.min.js"></script>
{% block script %}
{% endblock %}
</body>
</html>

./assets/scripts/index.js

const content = "你好koa~~";
console.log(content);
const app6 = new Vue({
    el: '#app-6',
    data: {
        message: 'Hello Vue!'
    }
});
export default content;

./views/index.html

{% extends './layouts/layout.html' %}
{% block title %} 图书管理首页 {% endblock %}

{% block link %}
<link rel="stylesheet" href="/styles/index.css">
{% endblock %}

{% block content %}
<div>
    <h1>[[content]]</h1> // 注意!
</div>
<div id="app-6">
    <p>{{ message }}</p>
    <input v-model="message">
</div>
{% endblock %}

{% block script %}
<script type="module">
    import ("/scripts/index.js").then((_) => {
        console.log(_.default);
    })
</script>
<script type="nomodule" src="https://cdn.bootcss.com/systemjs/6.2.5/system.min.js"></script>
<script type="nomodule">
    System.import("/scripts/index-bundle.js").then((_) => {
        console.log(_.default);
    })
</script>
{% endblock %}

注意!vue 和 swig 的模板引擎的标识符不能冲突

所以我们还要改下配置文件

./app.js

const Koa = require("koa");
const serve = require("koa-static");
const render = require("koa-swig");
const config = require("./config");
const co = require("co");
const app = new Koa();
app.use(serve(config.staticDir));
app.context.render = co.wrap(render({
    varControls:["[[","]]"],// 自定义模板语法
    root: config.viewDir,
    autoescape: true,
    cache: false, // 缓存
    ext: 'html',
    writeBody: false
}));
require("./controllers")(app);
app.listen(config.port, () => {
    console.log("图书管理平台启动成功...");
});

大功告成!