node服务端衬着(完全demo)

简介

nodejs搭建多页面效劳端衬着

  • 手艺点

    1. koa 搭建效劳
    2. koa-router 建立页面路由
    3. nunjucks 模板引擎组合html
    4. webpack打包多页面
    5. node端异步要求
    6. 效劳端日记打印

    项目源码 git clone
    https://gitee.com/wjj0720/nod…

  • 运转

    • npm i
    • npm start

一、 当代效劳端衬着的由来

效劳端衬着观点: 是指,浏览器向效劳器发出要求页面,效劳端将准备好的模板和数据组装成完全的HTML返回给浏览器展现

  • 1、前端后端星散

    早在七八年前,险些一切网站都运用 ASP、Java、PHP做后端衬着,跟着收集的加速,客户端机能进步以及js自身的机能进步,我们最先往客户端增添更多的功用逻辑和交互,前端不再是简朴的html+css更多的是交互,前端页在这是从后端星散出来「前后端正式分爨」

  • 2、客户端衬着

    跟着ajax手艺的提高以及前端框架的兴起(jq、Angular、React、Vue) 框架的兴起,最先转向了前端衬着,运用 JS 来衬着页面大部份内容到达部份革新的作用

    • 上风

      • 部份革新,用户体验优
      • 富交互
      • 勤俭效劳器本钱
    • 瑕玷

      • 不利于SEO(爬虫没法爬取ajax)要求返来的数据
      • 受浏览器机能限定、增添手机端的耗电
      • 首屏衬着须要等js运转才展现数据
  • 3、如今效劳端衬着

    为了处置惩罚上面客户端衬着的瑕玷,然前后端星散后必不能合,假如要把前后端部门兼并,拆掉的肯定是前端部门

    • 如今效劳端衬着的特性

      • 前端开发人员编写html+css模板
      • node中间效劳担任前端模板和背景数据的组合
      • 数据依旧由java等前效劳端言语供应
    • 上风

      • 前后端分工明白
      • SEO问题处置惩罚
  • 4、前、后端衬着相干议论参考

二、 项目最先

确保你装置node

第一步 让效劳跑起来

目的: 建立node效劳,经由过程浏览器接见,返回’hello node!'(html页面实在就是一串字符串)

  /** 建立项目目次构造以下 */
    │─ package-lock.json
    │─ package.json
    │─ README.md
    ├─bin
      │─ www.js

  // 1. 装置依靠 npm i koa 
  // 2. 修正package.json文件中 scripts 属性以下
    "scripts": {
      "start": "node bin/www.js"
    }

  // 3. www.js写入以下代码
    const Koa = require('koa');
    let app = new Koa();
    app.use(ctx => {
      ctx.body = 'hello node!'
    });
    app.listen(3000, () => {
      console.log('效劳器启动 http://127.0.0.1:3000');
    });

  // 4 npm start 浏览器接见 http://127.0.0.1:3000 检察结果

第二步 路由的运用

目的:运用koa-router依据差别url返回差别页面内容

  • 依靠 npm i koa-router
    koa-router 更多细节 请至npm检察

      /** 新增routers文件夹   目次构造以下 
        │─.gitignore
        │─package.json
        │─README.md
        ├─bin
        │   │─www.js
        ├─node_modules
        └─routers
            │─home.js
            │─index.js
            │─user.js 
      */
      //项目中应按照模块对路由举行分别,示例简朴将路由分别为首页(/)和用户页(/user) 在index中将路由集合治理导, 出并在app实例后挂载到app上
      /** router/home.js 文件 */
      // 引包
      const homeRouter = require('koa-router')()
      //建立路由划定规矩
      homeRouter.get(['/', '/index.html', '/index', '/home.html', '/home'], (ctx, next) => {
        ctx.body = 'home'
      });
      // 导出路由备用
      module.exports = homeRouter
    
      /** router/user.js 文件 */
      const userRouter = require('koa-router')()
      userRouter.get('/user', (ctx, next) => {
        ctx.body = 'user'
      });
      module.exports = userRouter
    
      /** router/index.js 文件 */
      // 路由集合点
      const routers = [
        require('./home.js'),
        require('./user.js')
      ]
      // 简朴封装 
      module.exports = function (app) {
        routers.forEach(router => {
          app.use(router.routes())
        })
        return routers[0]
      }
  /** www.js 文件改写 */
  // 引入koa
  const Koa = require('koa')
  const Routers = require('../routers/index.js')
  // 实例化koa对象
  let app = new Koa()

  // 挂载路由
  app.use((new Routers(app)).allowedMethods())

  // 监听3000端口
  app.listen(3000, () => {
    console.log('效劳器启动 http://127.0.0.1:3000')
  })

第三步 到场模板

目的:

1.运用nunjucks剖析html模板返回页面

2.相识koa中间件的运用

  • 依靠 npm i nunjucks

nunjucks中文文档

  /*
    *我向项目目次下到场两个准备好的html文件 目次构造以下
    │─.gitignore
    │─package.json
    │─README.md
    ├─bin
    │   │─www.js
    │─middlewares  //新增中间件目次  
    │   ├─nunjucksMiddleware.js  //nunjucks模板中间件
    ├─node_modules
    │─routers
    │   │─home.js
    │   │─index.js
    │   │─user.js 
    │─views  //新增目次 作为视图层
        ├─home
        │   ├─home.html 
        ├─user
            ├─user.html
   */
  /* nunjucksMiddleware.js 中间件的编写 
    *什么是中间件: 中间件就是在顺序实行过程当中增添辅佐功用
    *nunjucksMiddleware作用: 给要求上下文加上render要领 将来在路由中运用 
  */
  const nunjucks = require('nunjucks')
  const path = require('path')
  const moment = require('moment')
  let nunjucksEVN = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'))
  // 为nkj到场一个过滤器
  nunjucksEVN.addFilter('timeFormate',  (time, formate) => moment(time).format( formate || 'YYYY-MM-DD HH:mm:ss'))

  // 推断文件是不是有html后缀
  let isHtmlReg = /\.html$/
  let resolvePath = (params = {}, filePath) => {
    filePath = isHtmlReg.test(filePath) ? filePath : filePath + (params.suffix || '.html')
    return path.resolve(params.path || '', filePath)
  }

  /** 
  * @description nunjucks中间件 增加render到要求上下文
  * @param params {}
  */
  module.exports = (params) => {
    return (ctx, next) => {
      ctx.render = (filePath, renderData = {}) => {
        ctx.type = 'text/html'
        ctx.body = nunjucksEVN.render(resolvePath(params, filePath), Object.assign({}, ctx.state, renderData))
      }
      // 中间件自身实行完成 须要挪用next去实行下一步设计
      return next()
    }
  }
  /* 中间件挂载 www.js中增添部份代码 */

  // 头部引入文件 
  const nunjucksMiddleware = require('../middlewares/nunjucksMiddleware.js')
  //在路由之前挪用 由于我们的中间件是在路由中运用的 故应该在路由前加到要求上下文ctx中
  app.use(nunjucksMiddleware({
    // 指定模板文件夹
    path: path.resolve(__dirname, '../views')
  })
  /* 路由中挪用 以routers/home.js 为例 修正代码以下*/
  const homeRouter = require('koa-router')()
  homeRouter.get(['/', '/index.html', '/index', '/home.html', '/home'], (ctx, next) => {
    // 衬着页面的数据
    ctx.state.todoList = [
      {name: '用饭', time: '2019.1.4 12:00'},
      {name: '下午茶', time: '2019.1.4 15:10'},
      {name: '放工', time: '2019.1.4 18:30'}
    ]
    // 这里的ctx.render要领就是我们经由过程nunjucksMiddleware中间件增加的
    ctx.render('home/home', {
      title: '首页'
    })
  })
  module.exports = homeRouter

第四步 抽取大众模板

目的: 抽取页面的公用部份 如导航/底部/html模板等

  /**views目次下增添两个文件夹_layout(公用模板) _component(大众组件) 目次构造以下
    │─.gitignore
    │─package.json
    │─README.md
    ├─bin
    │   │─www.js  /koa效劳
    │─middlewares  //中间件目次  
    │   ├─nunjucksMiddleware.js  //nunjucks模板中间件
    ├─node_modules
    │─routers  //效劳路由目次
    │   │─home.js
    │   │─index.js
    │   │─user.js 
    │─views  //页面视图层
        │─_component
        │   │─nav.html (公用导航)
        │─_layout
        │   │─layout.html  (公用html框架)
        ├─home
        │   ├─home.html 
        ├─user
            ├─user.html
  */
  <!-- layout.html 文件代码 -->
  <!DOCTYPE html>
  <html>
  <head>
    <meta charset="UTF-8">
    <title>{{ title }}</title>
  </head>
  <body>
    <!-- 占位 称号为content的block将放在此处 -->
    {% block content %}
    {% endblock %}
  </body>
  </html>


  <!-- nav.html  公用导航  -->
  <ul>
    <li><a href="/">首页</a></li>
    <li><a href="/user">用户页</a></li>
  </ul>
  <!-- home.html 改写 -->
  <!-- njk继续模板 -->
  {% extends "../_layout/layout.html" %}
  {% block content %}
    <!-- njk引入大众模块 -->
    {% include "../_component/nav.html" %}
    <h1>待办事项</h1>
    <ul>
      <!-- 过滤器的挪用 timeFormate即我们在中间件中给njk加的过滤器 -->
      {% for item in todoList %}
        <li>{{item.name}} ---> {{item.time | timeFormate}}</li>
      {% endfor %}
    </ul>
  {% endblock %}


  <!-- user.html -->
  {% extends "../_layout/layout.html" %}
  {% block content %}
    {% include "../_component/nav.html" %}
    用户中间
  {% endblock %}

第五步 静态资本处置惩罚

目的: 处置惩罚页面jscssimg等资本引入

  • 依靠

    1. 用webpack打包静态资本 npm i webpack webpack-cli -D
    2. 处置惩罚js npm i @babel/core @babel/preset-env babel-loader -D
    3. 处置惩罚less npm i css-loader less-loader less mini-css-extract-plugin -D
    4. 处置惩罚文件 npm i file-loader copy-webpack-plugin -D
    5. 处置惩罚html npm i html-webpack-plugin -D
    6. 清算打包文件 npm i clean-webpack-plugin -D
> *相干插件运用 检察npm相干文档*
  /* 项目目次 更改 
  │  .gitignore
  │  package.json
  │  README.md
  ├─bin
  │  www.js
  ├─config  //增添webpack设置目次
  │  webpack.config.js
  ├─middlewares
  │  nunjucksMiddleware.js
  ├─routers
  │  home.js
  │  index.js
  │  user.js
  ├─src
  │  │─template.html  // + html模板 以此模板为每一个进口天生 引入对应js的模板
  │  ├─images // +图资本目次
  │  │  ww.jpg
  │  ├─js // + js目次 
  │  │  ├─home
  │  │  │   home.js
  │  │  └─user
  │  │      user.js
  │  └─less // + css目次
  │      ├─common
  │      │   common.less
  │      │   nav.less
  │      ├─home
  │      │   home.less
  │      └─user
  │          user.less
  └─views
      ├─home
      │  home.html
      ├─user
      │  user.html
      ├─_component
      │      nav.html
      └─_layout  // webpac打包后的html模板
          ├─home
          │   home.html
          └─user
              user.html
  */
  <!--  template.html 内容-->
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{title}}</title>
  </head>
  <body>
    <!-- njk模板继续后添补 -->
    {% block content %}
    {% endblock %}
  </body>
  </html>
  /* src/js/home/home.js 一个进口文件*/
  
  import '../../less/home/home.less' //引入css
  import img from '../../images/ww.jpg' //引入图片
  console.log(111);
  let add = (a, b) => a + b; //箭头函数
  let a = 3, b = 4;
  let c = add(a, b);
  console.log(c);
  // 这里只做打包演示代码 不具任何意义
  <!-- less/home/home.less 内容 -->
  // 引入大众款式
  @import '../common/common.less';
  @import '../common/nav.less';

  .list {
    li {
      color: rebeccapurple;
    }
  }
  .bg-img {
    width: 200px;
    height: 200px;
    background: url(../../images/ww.jpg); // 背景图片
    margin: 10px 0;
  }
  /* webpack设置  webpack.config.js */
  const path = require('path');
  const CleanWebpackPlugin = require('clean-webpack-plugin');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const MiniCssExtractPlugin = require("mini-css-extract-plugin");
  const CopyWebpackPlugin = require('copy-webpack-plugin');

  // 多进口
  let entry = {
    home: 'src/js/home/home.js',
    user: 'src/js/user/user.js'
  }

  module.exports = evn => ({
    mode: evn.production ? 'production' : 'development',
    // 给每一个进口 path.reslove 
    entry: Object.keys(entry).reduce((obj, item) => (obj[item] = path.resolve(entry[item])) && obj, {}),
    output: {
      publicPath: '/',
      filename: 'js/[name].js',
      path: path.resolve('dist')
    },
    module: {
      rules: [
        { // bable 依据须要转换到对应版本 
          test: /\.js$/,
          exclude: /node_modules/,
          use: {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-env']
            }
          }
        },
        { // 转换less 并交给MiniCssExtractPlug插件提取到零丁文件
          test: /\.less$/,
          loader: [MiniCssExtractPlugin.loader,  'css-loader', 'less-loader'],
          exclude: /node_modules/
        },
        { //将css、js引入的图片目次指到dist目次下的images 坚持与页面引入的一致
          test: /\.(png|svg|jpg|gif)$/,
          use: [{
            loader: 'file-loader',
            options: {
              name: '[name].[ext]',
              outputPath: './images',
          }
          }]
        },
        {
          test: /\.(woff|woff2|eot|ttf|otf)$/,
          use: [{
            loader: 'file-loader',
            options: {
              name: '[name].[ext]',
              outputPath: './font',
          }
          }]
        }
      ]
    },
    plugins: [
      // 删除上一次打包目次(一般来说删除本身输出过的目次 )
      new CleanWebpackPlugin(['dist', 'views/_layout'], {
        // 当设置文件与package.json不再统一目次时刻须要指定根目次
        root: path.resolve() 
      }),
      new MiniCssExtractPlugin({
        filename: "css/[name].css",
        chunkFilename: "[id].css"
      }),
      // 将src下的图片资本平移到dist目次
      new CopyWebpackPlugin(
        [{
          from: path.resolve('src/images'),
          to: path.resolve('dist/images')
        }
      ]),
      // HtmlWebpackPlugin 每一个进口天生一个html 并引入对应打包临盆好的js
      ...Object.keys(entry).map(item => new HtmlWebpackPlugin({
        // 模块名对应进口称号
        chunks: [item], 
        // 输入目次 (可自行定义 这边输入到views下面的_layout)
        filename: path.resolve('views/_layout/' + entry[item].split('/').slice(-2).join('/').replace('js', 'html')),
        // 基准模板
        template: path.resolve('src/template.html')
      }))
    ]
  });

  <!-- package.json中增加 -->
  "scripts": {
    "start": "node bin/www.js",
    "build": "webpack --env.production --config config/webpack.config.js"
  }

  运转 npm run build 后天生 dist views/_layout 两个目次
  <!-- 检察打包后天生的模板 views/_layout/home/home.html-->
  <!DOCTYPE html>
  <html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>{{title}}</title>
    <!-- 引入了css文件 -->
  <link href="/css/home.css" rel="stylesheet"></head>
  <body>
    {% block content %}
    {% endblock %}
    <!-- 引入了js文件 此时打包后的js/css在dist目次下面 -->
  <script type="text/javascript" src="/js/home.js"></script></body>
  </html>
  <!-- view/home/home.html 页面改写 -->
  <!-- njk继续模板 继续的目的来自webpack打包天生 -->
  {% extends "../_layout/home/home.html" %}
  {% block content %}
    <!-- njk引入大众模块 -->
    {% include "../_component/nav.html" %}
    <h1>待办事项</h1>
    <ul class="list">
      <!-- 过滤器的挪用 timeFormate即我们在中间件中给njk加的过滤器 -->
      {% for item in todoList %}
        <li>{{item.name}} ---> {{item.time | timeFormate}}</li>
      {% endfor %}
    </ul>
    <div class="bg-img"> 背景图</div>
    <!-- 页面图片引入体式格局 -->
    <img src="/images/ww.jpg"/>
  {% endblock %}
  /**koa处置惩罚静态资本 
   * 依靠 npm i 'koa-static
  */

  // www.js 增添 将静态资本目次指向 打包后的dist目次
  app.use(require('koa-static')(path.resolve('dist')))

运转

npm run build
npm start
浏览器接见127.0.0.1:3000 检察页面 js css img 结果

第六步 监听编译

目的: 文件发作改及时编译打包

  • 依靠 npm i pm2 concurrently

      /**项目中文件发作更改 须要重启效劳才看到结果是一件蛋疼的事,故须要及时监听更改 */
      <!-- 我们要监听的有两点 一是node效劳 而是webpack打包 package.json更改以下 -->
        "scripts": {
          // concurrently 监听同时监听两条敕令
          "start": "concurrently \"npm run build:dev\" \"npm run server:dev\"",
          "dev": "npm start",
          // 临盆环境 实行两条敕令即可 无监听
          "product": "npm run build:pro && npm run server:pro",
          // pm2 --watch参数监听效劳的代码更改
          "server:dev": "pm2 start bin/www.js --watch",
          // 临盆不须要用监听
          "server:pro": "pm2 start bin/www.js",
          // webpack --watch 对打包文件监听
          "build:dev": "webpack --watch --env.production --config config/webpack.config.js",
          "build:pro": "webpack --env.production --config config/webpack.config.js"
        }

第七步 数据要求

目的: node要求接口数据 添补模板

  • 依靠 npm i node-fetch

      /*上面的代码中routers/home.js首页路由中我们向页面衬着了下面的一组数据 */
      ctx.state.todoList = [
        {name: '用饭', time: '2019.1.4 12:00'},
        {name: '下午茶', time: '2019.1.4 15:10'},
        {name: '放工1', time: '2019.1.4 18:30'}
      ]
      /*但 数据是同步的 项目中我们必然会向java猎取其他背景拿到衬着数据再添补页面 我们来看看怎么做*/
        /*我们在根目次下建立一个util的目次作为东西库 并简朴封装fetch.js要求数据*/
      const nodeFetch = require('node-fetch')
      module.exports = ({url, method, data = {}}) => {
        // get要求 将参数拼到url
        url = method === 'get' || !method ? "?" + Object.keys(data).map(item => `${item}=${data[item]}`).join('&') : url;
        return nodeFetch(url, {
              method: method || 'get',
              body:  JSON.stringify(data),
              headers: { 'Content-Type': 'application/json' },
          }).then(res => res.json())
      }
      /*在根目次下建立一个service的目次作为数据层 并建立一个exampleService.js 作为示例*/
      //引入封装的 要求东西
      const fetch = require('../util/fetch.js')
      module.exports = {
        getTodoList (params = {}) {
          return fetch({
            url: 'https://www.easy-mock.com/mock/5c35a2a2ce7b4303bd93fbda/example/todolist',
            method: 'post',
            data: params
          })
        },
        //...
      }
      /* 将要求到场到路由中 routers/home.js 改写 */
      const homeRouter = require('koa-router')()
      let exampleService = require('../service/exampleService.js') // 引入service api
      //将路由婚配回调 改成async函数 并在请时刻 await数据返来 再挪用render
      homeRouter.get(['/', '/index.html', '/index', '/home.html', '/home'], async (ctx, next) => {
        // 要求数据
        let todoList = await exampleService.getTodoList({name: 'ott'})
        // 替代本来的静态数据
        ctx.state.todoList = todoList.data
        ctx.render('home/home', {
          title: '首页'
        })
      })
      // 导出路由备用
      module.exports = homeRouter

第八步 日记打印

目的: 使顺序运转可视

  • 依靠 npm i log4js

      /* 在util目次下建立 logger.js 代码以下 作简朴的logger封装 */
      const log4js = require('log4js');
      const path = require('path')
      // 定义log config
      log4js.configure({
        appenders: { 
          // 定义两个输出源
          info: { type: 'file', filename: path.resolve('log/info.log') },
          error: { type: 'file', filename: path.resolve('log/error.log') }
        },
        categories: { 
          // 为info/warn/debug 范例log挪用info输出源   error/fatal 挪用error输出源
          default: { appenders: ['info'], level: 'info' },
          info: { appenders: ['info'], level: 'info' },
          warn: { appenders: ['info'], level: 'warn' },
          debug: { appenders: ['info'], level: 'debug' },
          error: { appenders: ['error'], level: 'error' },
          fatal: { appenders: ['error'], level: 'fatal' },
        }
      });
      // 导出5种范例的 logger
      module.exports = {
        debug: (...params) => log4js.getLogger('debug').debug(...params),
        info: (...params) => log4js.getLogger('info').info(...params),
        warn: (...params) => log4js.getLogger('warn').warn(...params),
        error: (...params) => log4js.getLogger('error').error(...params),
        fatal: (...params) => log4js.getLogger('fatal').fatal(...params),
      }
      /* 在fetch.js中是哟logger */
      const nodeFetch = require('node-fetch')
      const logger = require('./logger.js')
    
      module.exports = ({url, method, data = {}}) => {
        // 到场要求日记
        logger.info('要求url:', url , method||'get', JSON.stringify(data))
    
        // get要求 将参数拼到url
        url = method === 'get' || !method ? "?" + Object.keys(data).map(item => `${item}=${data[item]}`).join('&') : url;
    
        return nodeFetch(url, {
          method: method || 'get',
            body:  JSON.stringify(data),
            headers: { 'Content-Type': 'application/json' },
        }).then(res => res.json())
      }
    
      <!-- 日记打印 -->
      [2019-01-09T17:34:11.404] [INFO] info - 要求url: https://www.easy-mock.com/mock/5c35a2a2ce7b4303bd93fbda/example/todolist post {"name":"ott"}
    

注: 仅共进修参考,临盆设置自行推敲!转载请备注泉源!

    原文作者:Random
    原文地址: https://segmentfault.com/a/1190000017832572
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞