美文网首页主要是当收藏夹用_基础
nodessr搭建项目(面向对象版)

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

作者: zxhnext | 来源:发表于2019-06-15 23:13 被阅读0次

    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);
    

    相关文章

      网友评论

        本文标题:nodessr搭建项目(面向对象版)

        本文链接:https://www.haomeiwen.com/subject/wsbetctx.html