主要是当收藏夹用_基础

nodessr搭建项目(面向对象版)

2019-06-15  本文已影响0人  zxhnext

1. 搭建项目文件

后端项目文件夹如下:

项目目录

assets --静态资源
config --项目配置项
controllers --路由
models --java等后台请求,模版
tests --测试
views --前端视图
app.js --项目启动文件
logs --日志
middleawares --容错处理
utils --存放一些公用的函数等

总项目文件如下:


image

dist --项目打包后的文件
docs --文档接口文件
gulpfile.js --gulp配置
config --webpack配置文件
scripts --shell命令文件
src --项目源文件

二、后端架构

  1. 首先配置app.js
import Koa from "koa";
import serve from 'koa-static'; // 静态文件引入
import config from "./config"; // 配置文件
import render from 'koa-swig'; // 采用koa模版
import co from 'co';
import log4js from 'log4js';
import errorHandler from "./middleawares/errorHandler"; // 容错机制
const app = new Koa();
//前端模板
//co的作用是把 *函数全部自动向下执行 next -> next -> done
//async await 语法糖版本 koa-swig 并为KOA2 升级 KOA1 
app.context.render = co.wrap(render({
    root: config.viewDir,
    autoescape: true,
    cache: false, // cache: 'memory', // disable, set to false 模版缓存,开发时设为false,上线时再这样设置
    varControls: ["[[", "]]"], // 换为[[]],防止和vue冲突
    ext: 'html',
    writeBody: false
}));
log4js.configure({ // log4配置
    appenders: {
        cheese: {
            type: 'file',
            filename: './logs/yd-log.log' // 日志路径
        }
    },
    categories: {
        default: {
            appenders: ['cheese'],
            level: 'error'
        }
    }
});
const logger = log4js.getLogger('cheese');
errorHandler.error(app, logger); // 若错处理
require("./controllers")(app); // 引入路由
//配置静态资源
app.use(serve(config.staticDir)); // 引入静态文件
app.listen(config.port, () => {
    console.log("服务已启动🍺");
});
  1. 设置config文件
import {
    join
} from "path";
import _ from "lodash";
console.log("取到的环境变量", process.env.NODE_ENV);
let config = {
    "viewDir": join(__dirname, "..", "views"), // 前端页面
    "staticDir": join(__dirname, "..", "assets") // 静态资源目录
}

if (process.env.NODE_ENV == "development") {
    const localConfig = {
        baseUrl: "http://localhost/index.php?r=", // 后端请求url
        port: 8080
    }
    config = _.extend(config, localConfig);
}
//留给大家 PM2启动
if (process.env.NODE_ENV == "production") {
    const prodConfig = {
        port: 8081
    }
    config = _.extend(config, prodConfig);
}
module.exports = config;
  1. 配置容错处理
const errorHandler = {
    error(app,logger){
        app.use(async(ctx,next)=>{ // 服务器内部出错情况
            try{
                await next(); 
            }catch(error){ // 或者统一返回出错页面
                logger.error(error);
                ctx.status = error.status || 500;
                ctx.body = error;
            }
        });
        app.use(async(ctx,next)=>{ // 404返回统一的404页面
            await next();
            if(404 != ctx.status){
                return;
            }
            //百度K权 
            //ctx.status = 200;
            ctx.status = 404;
            ctx.body = '<script type="text/javascript" src="//qzonestyle.gtimg.cn/qzone/hybrid/app/404/search_children.js" charset="utf-8" homePageUrl="/" homePageName="回到我的主页"></script>';
        })
    }
}
module.exports = errorHandler;
  1. 路由设置
const IndexController = require("./IndexController");
const BooksController = require("./BooksController");
const indexControll = new IndexController();
const bookControll = new BooksController();
const router = require('koa-simple-router')
// const init = (app) => {

// }
//初始化所有的路由
module.exports = (app)=>{
    app.use(router(_ => {
        _.get('/', indexControll.actionIndex())
        // index.php?r=index/data
        _.get('/book', bookControll.actionIndex());
        _.get('/book/savedata', bookControll.actionSaveData());
        _.get('/book/add', bookControll.actionCreate());
    }));
}
  1. model设置
/**
 * @fileOverview 实现Index数据模型
 * @author yuanzhijia@yidengxuetang.com
 */
const SafeRequest = require("../utils/SafeRequest");
/**
 * Index类 获取后台有关于图书相关数据类
 * @class
 */
 class Index{
    /**
     * @constructor
     * @param {string} app KOA2执行的上下文环境 
     */
     constructor(app){}
     /**
      * 获取后台的全部图书数据方法
      * @param {*} options  配置项
      * @example
      * return new Promise
      * getData(url, options)
      */
     getData(options){
        const safeRequest = new SafeRequest("book");
        return safeRequest.fetch({});
     }
    /**
      * 获取后台的全部图书数据方法
      * @param {*} options  配置项
      * @example
      * return new Promise
      * saveData(url, options)
      */
     saveData(options){
        const safeRequest = new SafeRequest("books/create");
        return safeRequest.fetch({
            method: 'POST',
            params:options.params
        });
     }
 }
 module.exports = Index;
  1. 接下来我们来封装公共请求SafeRequest
const fetch = require('node-fetch');
const config = require("../config");
//统一代码 处理容错
class SafeRequest {
    constructor(url) {
        this.url = url;
        this.baseURL = config.baseUrl;
    }
    fetch(options) {
        return new Promise((resolve, reject) => {
            //失败
            let result = {
                code: 0, //正常请求状态
                message: "",
                data: []
            };
            let yfetch = fetch(this.baseURL + this.url);
            if (options.params) {
                yfetch = fetch(this.baseURL + this.url, {
                    method: options.method,
                    body:options.params
                })
            }
            yfetch
                .then(res => res.json())
                .then((json) => {
                    result.data = json;
                    resolve(result);
                }).catch((error) => {
                    result.code = 1;
                    //mail 服务器 直接打电话 发邮件
                    result.message = "node-fetch请求失败,后端报警!";
                    reject(result)
                })
        })
    }
}
module.exports = SafeRequest;
  1. 多页转单页
    以一个列表页为例:
const Index = require("../models/Index"); 
const {
    URLSearchParams // 获取参数
} = require('url');
const cheerio = require('cheerio')
class IndexController {
    constructor() {

    }
    actionIndex() {
        return async (ctx, next) => {
            // ctx.body = 'hello world'
            //Header由第一次ctx.body设置的
            //输出的内容已最后一次
            const index = new Index();
            const result = await index.getData();
            // console.log("整个node系统是否通了", result);
            const html = await ctx.render("books/pages/index", {
                data: result.data
            });
            if (ctx.request.header["x-pjax"]) {
                const $ = cheerio.load(html);
                //我只需要一小段html 基础的核心原理
                ctx.body = $("#js-hooks-data").html();
                // ctx.body = {
                //     html:$("#js-hooks-data").html(),
                //     css: <!-- injectcss -->,
                //     js: <!-- injectjs -->
                // } 
                //CSR方式
                //ctx.body = "<x-add></x-add>"
            } else {
                ctx.body = html;
            }
        }
    }
    actionCreate() {
        return async (ctx, next) => {
            const html = await ctx.render("books/pages/add");            
            if (ctx.request.header["x-pjax"]) {
                const $ = cheerio.load(html);
                let _result = "";
                $(".pjaxcontent").each(function() {
                    _result += $(this).html();
                });
                console.log("_result", _result)
                $(".lazyload-css").each(function() {
                    _result += $(this).html()
                });
                $(".lazyload-js").each(function() {
                    // _result += `<script src="${$(this).attr("src")}"></script>`
                    _result = `<script>${$(this).attr("src")}</script>`
                });
                ctx.body = _result;
            } else {
                ctx.body = html;
            }
            
        }
    }
    actionSaveData() {
        return async (ctx, next) => {
            const index = new Index();
            const params = new URLSearchParams();
            params.append("Books[title]", "测试的书名");
            params.append("Books[author]", "测试作者");
            params.append("Books[publisher]", "测试出版社");
            const result = await index.saveData({
                params
            });
            ctx.body = result;
        }
    }
}
module.exports = IndexController;

多页转单页,同时我们需要设置

$(document).pjax("a", "#app");

这样就可以根据请求头判断请求

  1. 模版页面
<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <title>{% block title %}{% endblock %}</title>
    {% block head %}
    <link rel="stylesheet" href="/styles/index.css">
    {% endblock %}
</head>

<body>
    {% block content %}{% endblock %}
    {% block scripts %}{% endblock %}
</body>

</html>

三、前端架构

  1. 来使用下xtag
// add.html
div class="add pjaxcontent">
  <x-add></x-add>
</div>
// add.js
const add = {
  init(){
    xtag.create('x-add', class extends XTagElement {
      constructor() {
        super();
        this.datas = {

        }
      }
      '::template(true)' (){
        return `<h3>添加新闻</h3>
                <form>
                  <div class="form-group">
                    <label for="exampleInputEmail1">书名</label>
                    <input type="text" class="form-control" id="exampleInputText1" placeholder="请输入书名">
                  </div>
                  <div class="form-group">
                    <label for="exampleInputEmail1">作者</label>
                    <input type="text" class="form-control" id="exampleInputText2" placeholder="请输入作者">
                  </div>
                  <div class="form-group">
                    <label for="exampleInputEmail1">出版社</label>
                    <input type="text" class="form-control" id="exampleInputText3" placeholder="请输入出版社">
                  </div>    
                  <button id="add-btn" type="button" class="btn btn-info">提交</button>
                </form>`
      }
      "click::event"(e){
        if(e.target.id == "add-btn") {
          alert("qq")
        }
      }
    });
  }
}
export default add;
  1. 在组件中使用js,css
// banner.html
<div class="components-banner">
  <div class="container">
    <div class="nav">
      <a href="/book/">新闻列表</a>
      <a href="/book/add">添加新闻</a>
    </div>
  </div> 
</div>
// banner.css
:root{
  --heightUnit: 70px;
}
.components-banner{
    height: var(--heightUnit);
    line-height: var(--heightUnit);
    margin-bottom: 10px;
    box-shadow: 0 0 10px gray;
    & .nav{
      color: purple;
    }
}
// banner.js
import("./banner.css");
const banner = {
    init(){
        console.log("banner");
    }
}
export default banner;

在页面中引入以上组件

{% extends 'common:layout.html' %}
{% block title %} 新闻列表 {% endblock %}
{% block styles %} 
    <!-- injectcss --> // 占位符
{% endblock %}
{% block header %}
    {% include "components:banner/banner.html"%}
{% endblock %}
{% block content %}
    {% include "components:list/list.html"%}
{% endblock %}
{% block scripts %} 
    <!-- injectjs -->
{% endblock %}

页面启动文件

// books-index.entry.js
import banner from "../../components/banner/banner.js";
import list from "../../components/list/list.js";
list.init();
banner.init();

四、对前后端项目打包

  1. 前端项目打包
const merge = require("webpack-merge"); // 合并插件
const argv = require("yargs-parser")(process.argv.slice(2)); // 获取当前环境
const _mode = argv.mode || "development";
const _modeflag = (_mode == "production" ? true : false);
const _mergeConfig = require(`./config/webpack.${_mode}.js`); // 引入开发/线上环境配置
const glob = require("glob"); // node的glob模块允许你使用 *等符号, 来写一个glob规则
const HtmlWebpackPlugin = require('html-webpack-plugin');
const LiveReloadPlugin = require('webpack-livereload-plugin'); // 刷新页面
const HtmlAfterWebpackPlugin = require('./config/HtmlAfterWebpackPlugin');
const ManifestPlugin = require("webpack-manifest-plugin"); // 生成映射文件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const {
    join
} = require("path");

//需要处理的入口文件
let _entry = {};
//插件系统
let _plugins = [];
const files = glob.sync("./src/webapp/views/**/*.entry.js"); // 寻找入口js
for (let item of files) {
    if (/.+\/([a-zA-Z]+-[a-zA-Z]+)(\.entry\.js$)/g.test(item) == true) {
        const entryKey = RegExp.$1;
        _entry[entryKey] = item;
        const [dist, template] = entryKey.split("-");
        _plugins.push(new HtmlWebpackPlugin({
            filename: `../views/${dist}/pages/${template}.html`,
            template: `src/webapp/views/${dist}/pages/${template}.html`,
            inject: false,
            chunks: ["runtime", "commons", entryKey], // 页面注入chunks
            minify: {
                collapseWhitespace: _modeflag, // 去空格
                // removeComments: _modeflag // 注释不能删
            }
        }))
    }
}
let cssLoaders = [MiniCssExtractPlugin.loader, {
        loader: "css-loader"
    },{
        loader: "postcss-loader"
    }  
]
// 开发环境
!_modeflag && cssLoaders.unshift("css-hot-loader")
const webpackConfig = {
    module: {
        rules: [{
            test: /\.css$/,
            use: cssLoaders
        }]
    },
    entry: _entry,
    plugins: _plugins,
    // watch: !_modeflag,
    output: {
        path: join(__dirname, "./dist/assets"),
        publicPath: "/",
        filename: "scripts/[name].bundule.js"
    },
    optimization: {
        splitChunks:{
            cacheGroups:{
                commons:{
                    chunks:"initial",
                    name:"commons",
                    minChunks:3,
                    minSize:0
                }
            }
        },
        runtimeChunk:{
            name: "runtime"
        }
    },
    plugins: [
        ..._plugins,
        new MiniCssExtractPlugin({
          filename: _modeflag ? "styles/[name].[contenthash:5].css" : "styles/[name].css",
          chunkFilename: _modeflag ? "styles/[name].[contenthash:5].css" : "styles/[name].css"
        }),
        new HtmlAfterWebpackPlugin(),
        new ManifestPlugin(),
        new LiveReloadPlugin()
    ]
};
module.exports = merge(webpackConfig, _mergeConfig);

// HtmlAfterWebpackPlugin

const pluginName = 'HtmlAfterWebpackPlugin';
const assetsHelp = (data) => {
  let css = [];
  let js = [];
  const dir = {
    js: item => `<script class="lazyload-js" type="text/javascript" src="${item}"></script>`,
    css: item => `<link class="lazyload-css" rel="stylesheet" type="text/css" href="${item}">`
  }
  for (let jsitem of data.js) {
    js.push(dir.js(jsitem))
  }
  for (let cssitem of data.css) {
    css.push(dir.css(cssitem))
  }  
  return {
    js,
    css
  }
}

class HtmlAfterWebpackPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap(pluginName, compilation => {
      compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tap(pluginName, htmlPluginData => { // htmlWebpackPluginAfterHtmlProcessing webpack4钩子
        let _html = htmlPluginData.html;
        _html = _html.replace(/components:/g, '../../../components/'); // 替换components:
        _html = _html.replace(/common:/g, '../../common/');
        const result = assetsHelp(htmlPluginData.assets);
        _html = _html.replace("<!-- injectcss -->", result.css.join(""));
        _html = _html.replace("<!-- injectjs -->", result.js.join(""));        
        htmlPluginData.html = _html;
      })
    });
  }
}

module.exports = HtmlAfterWebpackPlugin;

// webpack.development.js

const CopyWebpackPlugin = require('copy-webpack-plugin');
const {
    join
} = require("path");
module.exports = {
    plugins: [
        new CopyWebpackPlugin([{
            from: join(__dirname, "../", "/src/webapp/views/common/layout.html"),
            to: '../views/common/layout.html'
        }]),
        new CopyWebpackPlugin([{
            from: join(__dirname, "../", "/src/webapp/components"),
            to: '../components'
        }], {
            copyUnmodified: true,
            ignore: ['*.js', '*.css']
        })
    ]
}

// webpack.production.js

const CopyWebpackPlugin = require('copy-webpack-plugin');
const {
    join
} = require("path");
const minify = require("html-minifier").minify; // 压缩html/css/js
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin'); // 压缩css
// shash chunkhash contenthash
module.exports = {
    output: {
        filename: "scripts/[name].[contenthash:5].bundule.js"
    },    
    plugins: [
        new OptimizeCssAssetsPlugin({  // 压缩css
          assetNameRegExp: /\.css$/g,
          cssProcessor: require('cssnano'),
          cssProcessorPluginOptions: {
            preset: ['default', { discardComments: { removeAll: true } }],
          },
          canPrint: true
        }),
        new CopyWebpackPlugin([{
            from: join(__dirname, "../", "/src/webapp/views/common/layout.html"),
            to: '../views/common/layout.html'
        }]),
        new CopyWebpackPlugin([{
            from: join(__dirname, "../", "/src/webapp/components"),
            to: '../components',
            transform(content){
                //html hint 优化
                return minify(content.toString("utf-8"), { // 压缩html
                  collapseWhitespace: true // 去空格
                });
            }
        }], {
            ignore: ['*.js', '*.css']
        })
    ]
}
  1. 后端项目打包
    将node中的require改为import写法
//1.简单 快
//2.webpack 前端打包工具 
const gulp = require("gulp");
const babel = require("gulp-babel");
const watch = require('gulp-watch'); // 监听文件改变
const rollup = require('gulp-rollup');
const replace = require('rollup-plugin-replace');
const entry = 'src/nodeuii/**/*.js';
//并行工具gulp-sequence
//开发环境
function builddev() {
    return watch(entry, {
        ignoreInitial: false
    }, function () {
        gulp.src(entry)
            .pipe(babel({
                babelrc: false,
                "plugins": [
                    ["@babel/plugin-proposal-decorators", { "legacy": true }],
                    ["@babel/plugin-proposal-class-properties", { "loose" : true }],
                    "transform-es2015-modules-commonjs"
                ]
            }))
            .pipe(gulp.dest('dist'))
    });
}
// //上线环境
// gulp.task("buildprod", () => {
function buildprod() {
    return gulp.src(entry)
        .pipe(babel({
            babelrc: false,
            ignore: ["./src/nodeuii/config/*.js"],
            "plugins": [
                ["@babel/plugin-proposal-decorators", { "legacy": true }],
                ["@babel/plugin-proposal-class-properties", { "loose" : true }],
                "transform-es2015-modules-commonjs"
            ]
        }))
        .pipe(gulp.dest('dist'));
}

function buildconfig() { // 流清洗
    return gulp.src(entry)
        .pipe(rollup({
            output: {
                format: "cjs"
            },
            input: "./src/nodeuii/config/index.js",
            plugins: [
                replace({ // 删除死代码
                    "process.env.NODE_ENV": JSON.stringify('production')
                })
            ]
        }))
        .pipe(gulp.dest('./dist'));
}
let build = gulp.series(builddev); // gulp.series 串行执行
if (process.env.NODE_ENV == "production") {
    //这里出现了问题
    build = gulp.series(buildprod,buildconfig);
}
if (process.env.NODE_ENV == "lint") {
    build = gulp.series(buildlint);
}
gulp.task("default", build);
上一篇下一篇

猜你喜欢

热点阅读