Giter Site home page Giter Site logo

linweiwei123 / multipages-generator Goto Github PK

View Code? Open in Web Editor NEW
357.0 12.0 39.0 2.75 MB

🥇 generator for multiple pages webpack application

Home Page: https://linweiwei123.github.io/multipages-generator/

JavaScript 77.19% HTML 0.61% Vue 22.20%
nodejs node-module express flexible optimization frontend webpack

multipages-generator's Introduction

English | 中文

multipages-generator NPM version

NPM

multipages-generator is a multiple pages application generator (or CLI) for mobile. It has the whole DevOps which includes development, build, publish and the deployment. It is One-stop solution for mobile H5.

Scene

Multipages-generator suite for multipages website whatever is mobile website or PC website, H5 in hybird app. For example: this, chiji game.

Feature

  1. One-stop mobile MPA solution with modern web technologys like Nodejs, webpack4, babel, Vue with server side rendering.
  2. Efficient commands like new, develop,build,upload,analysis,deploy.
  3. Best practices for architechure and organization.
  4. 🔥 (new) Support Vue SSR and no framework or any other framework you like.
  5. Support development,producton ENV.
  6. Support sass、less、postcss
  7. Hot code reload for CSS and JS
  8. Support upload to Ali OSS and Qiniu OSS
  9. Support mobile adaptation with taobao flexible layout solution,fit different screen size and DPI.
  10. Support pm2 deployment

Document

Global install ⚙️

Envirment requirement

NodeJS: >= 6.11.0

OS: MacOS,windows,centos

install

npm install multipages-generator -g  //now the latest is 1.6.x

Create a project 📽

init a project

meet init

Choose a template:

  • No JavaScript framework (You can add your framework like jQuery,zepto,vue,react and so on.)
  • Vue width SSR (It's add SSR default for now)
? Select your JavaScript framework (Use arrow keys)
❯ No JavaScript framework 
  Vue width SSR 

start

When initialized, install the dependencis and start the demo

C:\xxx\workspace>meet init
? Project name: h5-project
  __  __           _      ____ _     ___
 |  \/  | ___  ___| |_   / ___| |   |_ _|
 | |\/| |/ _ \/ _ \ __| | |   | |    | |
 | |  | |  __/  __/ |_  | |___| |___ | |
 |_|  |_|\___|\___|\__|  \____|_____|___|

   [Success] Project h5-project init finished, be pleasure to use 😊!

   Install dependencies:
     cd h5-project && npm install

   Run the app:
     meet start demo
   Or:
     pm2 start process.json

Commands

Use meet -help to show all the commands.

C:\xxx\workspace>meet -help

  Usage: meet [command]

  Options:

    -v, --version                 output the version number
    -h, --help                    output usage information

  Commands:

    init                          initialize your project
    new [module]/[module]-[page]  generate_native a new module
    start [module]                start application in development mode
    build [module]                build a module using webpack
    upload                        upload dist files to CDN
    analyse                       analysis dist files size and percent
    git                           auto git commit and push

Create a new module

meet new [module]/[module]-[page]

Description

Attention, create a new module use like this

meet new [module]

When you need to create a new page in the existed module, use this command:

meet new [module]-[page]

For a example, create a game H5(module)

meet new game  // create a game with default page index.html

Because it's so called multiple pages generator, so create another page use this:

meet new game-detail // create the game detail.html in the game module

And you got a list files like this:

game
 ├─images // this is no images, just a dictory
 ├─js
 | ├─index
 | | ├─business.js  // the business js(Expand as you wish)
 | | ├─service.js   // http service code
 | | └─util.js      // utils code
 | └─index.js       // the main js file
 ├─styles
 | └─index.css      // css code
 └─views
   └─index.html     // html code

Develop a module

meet start [module]

meet start demo

It started with this followed, you can choose a link to open in browser.

 √ Build done

[Tips] visit: http://localhost:8080/demo/
            : http://192.168.50.194:8080/demo/

Attention: Vue CSR: http://localhost:8080/demo/?csr=true Vue SSR: http://localhost:8080/demo/

Hot reload

JS、CSS support hot code reload,HTML changes need man to refresh the browser.

image

Html generated contain two marker, you don't need to worry about this. It's for better development and will removed when in build.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <% include ../head.html %>
  <title>demo</title>
  <!--@hot-reload, will auto remove after compiled-->
  <link rel="stylesheet" data-hr="hot-reload" href="/demo/styles/index.css">
</head>
<body>
  <div>you content...</div>
  <!--@hot-reload, will auto remove after compiled-->
  <script type="text/javascript" data-hr="hot-reload" src="/common/js/hot-reload.js"></script>
</body>
</html>

Build a module

meet build [demo]

meet build demo
C:xxx\workspace\h5>meet build demo

> [email protected] build C:\meetyou\workspace\test\mg-workspace\h5
> cross-env NODE_ENV=production node build/commands/build.js "demo"

Delete dist directory!
  ⣾ Building...
  ⣽ lasted 1 seconds. HTML去除开发环境hotReload代码: ..\server\views\prod\demo\index.html
Hash: 2a217fb45f03fb354254
Version: webpack 4.17.2
Time: 1687ms
Built at: 2018-09-06 19:50:40
                               Asset      Size  Chunks             Chunk Names
                  index.12969e6e.css  4.71 KiB       0  [emitted]  index
                   index.080a1e3d.js  1.01 KiB       0  [emitted]  index
..\server\views\prod\demo\index.html  3.74 KiB          [emitted]
Entrypoint index = index.12969e6e.css index.080a1e3d.js

Upload dist files to Qiniu CDN:
Webpack Bundle Analyzer is started at http://127.0.0.1:8888
Use Ctrl+C to close it
[Success]: 上传文件至七牛云CDN成功!文件地址:http://cnd.yintage.com/index.080a1e3d.js
[Success]: 上传文件至七牛云CDN成功!文件地址:http://cnd.yintage.com/index.12969e6e.css
[Success]: 上传完毕 😊!
Use Ctrl+C to close it

After analysis powerd by webpack plugin, the page will show the code proportion.

image

meet analyse

Use this command after builded.

meet analyse

image

Upload

meet upload

Upload the files which in the dist dictory to OSS server. Config the Ali OSS or Qiniu OSS configs in mg.config.js.

meet upload

Config

mg.config.js

mg.config.js is look like:

module.exports = {

    // the client server (use for hot reload ) port
    clientPort: '8080',

    // the server(for deployment) port
    server: {
        port: '8090',
    },

    // upload config
    upload: {
        cdn: '//oflt40zxf.bkt.clouddn.com/',
        projectPrefix: 'nodejs-common',

        // if use Ali OSS,set aliconfig a empty object, now it support Ali CLI for upload, 
        // aliconfig: {
        //
        // },
       
        // Qiniu OSS
        qconfig: {
            ACCESS_KEY: 'ei1uOdGpVLliA7kb50sLcV9i4wfYLPwt5v0shU10',
            SECRET_KEY: '-pFFIY-ew35Exyfcd67Sbaw40k15ah3UfZTFWFKF',
            bucket:'hotshots-image',
            origin:'http://cnd.yintage.com'
        },

        // is auto upload after build
        autoUpload: true

    }
};

Ali OSS upload

Todo List

  1. Better Vue SSR solution
  2. Support react, react-ssr

deployment

deploy to server in 30 minutes

License

The MIT License

multipages-generator's People

Contributors

linweiwei123 avatar wujunchuan avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

multipages-generator's Issues

运行有问题

在我的环境下,运行出现了问题。

现象 打开 localhost:2000 之后,得不到响应,就一直在那里转圈。

$ node --version
v8.9.4
$ yarn run watch:viewport
yarn run v1.5.1
$ rimraf public &&cross-env ENV=dev PROJECT_NAME=viewport node ./tools/webpack.watch.js
Build completed in 10.644s

[webpack]: build done!


[Browsersync] Proxying: http://localhost:4000
[Browsersync] Access URLs:
 -------------------------------------
       Local: http://localhost:2000
    External: http://192.168.1.33:2000
 -------------------------------------
          UI: http://localhost:3001
 UI External: http://192.168.1.33:3001
 -------------------------------------
[Browsersync] Watching files...

$ yarn run start
yarn run v1.5.1
$ nodemon ./bin/www
[nodemon] 1.17.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: *.*
[nodemon] starting `node ./bin/www`

NodeJS日志解决方案

NodeJS在web领域常常作为服务器运行。NodeJS可以像其他服务器一样进行数据库操作、逻辑运算、HTTP和websocket通信、文件读写、模板渲染等等许多功能。在美柚NodeJS更多的用来做中间层,用来做API的请求结合模板引擎进行服务端渲染,还有一些轻微的逻辑处理。本文对比最佳实践例子与反面例子,系统阐述NodeJS日志的解决方案。

1. 划分不同的错误类型

要用使用NodeJS环境自带的Error对象进行抛错,它含有错误的堆栈信息和其他有用属性。根据业务需要设计不同类型的Error,根据场景选择正确的Error输出。

正确使用

app.get('/api', (req, res, next) => {
    Http.fetch(url)
    .then(resp => {
        throw new Error('错误信息')
    })
    .catch(err => next(err))
})

http://oflt40zxf.bkt.clouddn.com/log_1.png

堆栈信息将被收集,方便开发调试,也用于记录生产错误信息。

反面例子

app.get('/api', (req, res, next) => {
    Http.fetch(url)
    .then(resp => {
        throw '错误信息' // 或者抛一个对象也是不佳的
    })
    .catch(err => next(err))
})

最佳的实践

在美柚的业务场景中我们设计了这些错误类型:

  • MeetError - 原型继承Error,并具有自身属性和方法
  • CommonError - 程序错误
  • DataNotFoundError - API返回的404错误
  • RespDataError - API 返回的数据格式错误
  • JSONParseError - API的repsonse json解析错误
  • EJSRenderError - EJS模板解析错误
  • RedisError - redis数据库连接错误
  • HttpTimeOutError - http超时错误

这些CustomError都继承Error,具有Error的属性和方法,还自定义了自己属性如code、name等

http://ovn18u9yn.bkt.clouddn.com/WX20180928-073534.png

MeetError 原型继承Error

function MeetError(message){
    this.message = message;
    Error.call(this);
    Error.captureStackTrace(this,this.constructor);
}

MeetError.prototype = Object.create(Error.prototype);
MeetError.prototype.constructor = MeetError;

module.exports = MeetError;

自定义业务Error原型继承MeetError

const MeetError = require('./MeetError');
const { errorConstants } = require('./codeConstants');

function JSONParseError(message){
    MeetError.call(this, message);
    this.name = 'JSONParseError';
    this.code = errorConstants.JSONParseError
}

JSONParseError.prototype = Object.create(MeetError.prototype);
JSONParseError.prototype.constructor = JSONParseError;

这样自定义错误信息不仅有Error的属性和方法,还有自身的code,name,在统一处理错误的中间件errorHandler中可以根据错误类型进行分类处理。业务开发时只需要根据业务场景抛相应的错误即可。

2.日志分级打印

不同的环境对日志有不同的需求,比如开发环境喜欢看到更多的调试信息(debug level),测试、预发环境喜欢看到更多的警告信息(info level),线上环境由于量特别大,像美柚网页日pv超过1000万,随便一个日志信息的打印可能导致大量的日志产生,所以线上只用于打印有用的错误信息(error level)

正确使用

使用成熟的日志模块(这里举例winston)

const winston = require('winston');

var level = 'debug';
var ENV = process.env.NODE_ENV;

switch (ENV) {
    case 'development' : level = 'debug';break;
    case 'development' : level = 'info';break;
    case 'development' : level = 'info';break;
    case 'production' : level = 'error';break;
    default : level = 'debug';break;
}

const logger = new winston.Logger({
    transports: [
        new winston.transports.Console({
            level: level,
            timestamp: function () {
                return (new Date()).toISOString();
            }
            // 开发环境可以输出文件,生产环境用pm2管理日志
        })
    ]
});

module.exports = logger;

日志分级打印

logger.debug(err)  // 调试
logger.info(err)   // 提示或者警告 
logger.error(err)  // 错误

分级之后根据不同环境会有不同的输出。

反面例子

    app.get('/api', (req, res, next) => {
    Http.fetch(url)
    .then(resp => {
        console.log(resp); // 随便输出打印调试日志 ❌。
        throw new Error('错误信息') 
    })
    .catch(err => {
    })
})

3. 错误统一处理,业务代码不消化错误

错误放在errorHandler中间件统一处理,业务代码无需根据错误类型进行消化来判断跳错误页面还是提示内容不存在页面(除非业务需要个性化的这些页面)

errorHandler

const logger = require('../config/logger');
const { infoLevelArray } = require('../errors/codeConstants');
const ENV = process.env.NODE_ENV;

module.exports = (err, req, res, next) => {

    // 如果是router 404错误,则直接进入404页面
    if(err.status === 404){
        return next();
    }

    // 判断错误类型,分级记录日志
    LoggerError(err);

    // render the error page
    res.status(err.status || 500);
    res.render('error', {
        message: err.message,
        error: ENV !== 'production' ? err : {}
    });
};


 // 判断error级别记录
function LoggerError(err){
   if(err.name === undefined || err.name === 'Error'){
       logger.error('捕获到未预期的错误');
       logger.error(err)
   }
   else if(infoLevelArray.indexOf(err.name)>-1){  // 某些错误只在info级别,如API返回的数据不存在。
       logger.info(err);
   }
   else {
       logger.error(err);
   }

}

根据err的类型进行统一处理,如果时router 404 则直接进入404页面。而后根据错误类型进行不同的记录操作。如日志记录,500页面,数据不存在而页面等。

infoLevelArray存放了一些info级别的错误类型,如服务端数据不存在,已删除等,这种业务上大量存在,如果记录导致大量无用信息记录

router404

const ENV = process.env.NODE_ENV;

module.exports = (req, res, next) => {
    const err = new Error('哎哟喂!页面被小柚子给弄丢了!');
    res.status(404).render('404', {
        message: err.message,
        error: ENV !== 'production' ? err : {}
    });
}
// error handler
app.use(errorHandler);
app.use(router404);

统一的errorHandler可以让业务代码与错误处理代码剥离、业务代码只需关心业务,也不用每个router去render错误页面。层级分明,分工明确。

反面例子

业务router 消化error,render error页面

exports.index = (req, res) => {

  // ...

  http.fetch(`${base}?${search}`, {headers:{_headers:''}})
    .then(response => response.json())
    .then(json => res.render('topic-templete/index', medalData(json, req, pagesize)))
    .catch(error => res.render('error', tools.failData(error, req)));
};

业务代码消化error,error页面放业务的html代码中,根据数据情况显示

  http.fetch(`${base}?${search}`, { headers:{_headers:''}})
    .then(response => {
      return response.json();
    })
    .then(json => {
      return res.render('xiaoyou/circle-detail', process(json, req))
    })
    .catch(error => res.render('xiaoyou/circle-detail', tools.failData(error, req)));
exports.failData = (error, req) => {
  // log(error.stack);
  errLog.log(error, req);
  const query = req.query;
  const qs = exports.stringifyQs(query, qsArray);
  const myclient = req.headers['myclient'] || query.myclient;
  const host = '';
  return { status: 'error', data: {}, query, qs, host, message: error.message, appid: req._appid, myclient: myclient };
};
<!doctype html>

<html lang="zh-CN">
<% if(data.code == 0 && data.data) { %>
  <head...>
  <body class="share-body share-body<%= appid %>"...>
<% } else { %>
  <% include ../error.html %>
<% } %>
</html>

上面可以看出业务代码写了很多错误处理的内容,代码冗余。
正确做法应该是catch的error中解析错误内容,next(error),交给errorHander来统一处理

4.使用举例

const errors = require('../errors/errors');

module.exports.index = function(req, res){
  
    global.fetch(api, { headers })
        .then(response => {    // 这里比较繁琐,而可以封装
            let respJson;
            try{ 
                respJson = response.json()
            }
            catch(e){
                throw new errors.JSONParseError(e)  //解析出错 ✔
            }
            return respJson
        ) 
        .then(data => {
          if (data && data.data && data.code == 0) { // 数据正常
            res.render('index', data, function(error, html) {
                if (error) {
                  throw new errors.RenderError(error); // 渲染出错 ✔
                } else {
                  res.send(html); // 正常返回页面
                }
           )
          } else { //API给的数据不对,会导致页面无法正常渲染,可能是404,403
            next(errors.RespDataError('数据错误')) // 数据异常 ✔
          }
        })
        .catch(error => {
          next(error)
        });
}
  • 异步的使用promise捕获异常,同步的使用你try catch捕获
  • 区分了错误类型,方面errorHandler进行区分记录,有类型信息方便排查
  • 业务代码不消化错误,不做render error页面
  • 全面的考虑了可能存在的错误
  • 未考虑的异常跟这些错误一起在catch中传递给了errorHandler做处理

注意:还应该考虑API返回的一切可能情况,确保无懈可击

5. 调试日志的打印

如果是用于调试的日志引用日志区分环境进行输出

const logger = require('../../config/logger');
 
...     
.then(response => {
  logger.info('微信返回data',response); // 获取debug级别
  ...
})

有些必须在外网调试的,比如微信认证授权等的,用日志调试至关重要。

IOS animation-play-state 解决方案

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=375, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .spin {
            position: absolute;
            top:50%;
            left: 50%;
            transform: translate(-50%, -50%);
            animation: rotate-music 10s infinite linear;
        }
        .no-animation {
            -webkit-animation: none !important;
            -moz-animation: none !important;
            animation: none !important;
        }
        button {
            font-size: 32px;
        }
        @-webkit-keyframes rotate-music{
            0% {
                transform: rotate(0deg);
            }
            100% {transform: rotate(360deg);}
        }
    </style>
</head>
<body>
<h1 id="text" class="spin">text...</h1>
<button onclick="stop()">stop</button>
<button onclick="restart()">restart</button>

<script>
    function stop(){
        var textEl = document.querySelector('#text');
        document.querySelector('#text').style.transform = 'rotate('+ getRotate(document.getElementById('text')) +'deg)';
        change();
        textEl.classList.add('no-animation');
    }

    function restart(){
        document.querySelector('#text').classList.remove('no-animation');
    }

    function getRotate(el){
        var st = window.getComputedStyle(el, null);
        var tr = st.getPropertyValue("-webkit-transform") ||
            st.getPropertyValue("-moz-transform") ||
            st.getPropertyValue("-ms-transform") ||
            st.getPropertyValue("-o-transform") ||
            st.getPropertyValue("transform") ||
            "FAIL";
        var values = tr.split('(')[1].split(')')[0].split(',');
        var a = values[0];
        var b = values[1];
        var angle = Math.round(Math.atan2(b, a) * (180/Math.PI));
        return angle;
    }

    function change(){
        var rule = document.styleSheets[0].cssRules[3];
        var deg = getRotate(document.getElementById('text'));
        var end = deg + 360;
        rule.deleteRule("0%");
        rule.deleteRule("100%");
        rule.appendRule("0% {  transform: rotate("+ deg + "deg);}");
        rule.appendRule("100% { transform: rotate("+ end + "deg);}");
    }
</script>
</body>
</html>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.