#25 Click! Serve! Plus

今天我们来为我们昨天做的「Click! Serve!」增加一些「设定」。

增加 pkg 设定

昨天我们用最简单的设定让 pkg 可以将程序打包成可执行档,今天我们让它不要乱放产生出来的档案,以及让产生出来的档案不要叫「index.exe」。

我们更新我们的 package.json 档:

{
    "name": "serv",
    "version": "1.0.0",
    "description": "",
    "main": "index.js",
    "bin": {
        "serv": "index.js"
    },
    "scripts": {
        "build": "pkg ."
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "dependencies": {
        "koa": "^2.13.3",
        "koa-static": "^5.0.0"
    },
    "devDependencies": {
        "pkg": "^5.3.3"
    },
    "pkg": {
        "outputPath": "dist"
    }
}

增加了一个 bin 的项目,告诉 pkg 我们的 entry 是 index.js。
然後把 script 中的 pkg index.js 改成 pkg . 代表读这个专案资料夹的 package.json 的设定。
最後加上 pkg 让 pkg 把产生的档案输出至 dist 资料夹。

重新整理专案

因为多了一些功能,所以将程序分档案放到 src 资料夹中。

然後把 index.js 改成:

require("./src/main");

main.js

main.js 是主程序,它把处里参数的工作跟服务器分开给两个档案,看起来很简单。

const config = require("./config");
const createServer = require("./server");

createServer(config);

流程看起来好舒服,不用管到底怎麽抓参数或建立服务器的。

config.js

这个档案用来处理参数,并将处里好的参数 exports 出去。

const process = require("process");
const path = require("path");

// 预设参数,如果用户没设定,就用它
const defaultConfig = {
    port: 80,
    folder: "www",
    log: false,
    logFile: "log.txt",
};

// 抓参数,第一个是 Node.js 位置,第二个是 js 档位置,虽然打包但不变
const argv = process.argv.slice(2);
const config = extractConfig(argv);

// 如果使用者想要 help 的话,给简单的 help
if (config["help"] || config["h"] || config["?"] || config["-help"] || config["-h"] || config["-?"] || config["--help"] || config["--h"] || config["--?"]) {
    console.log("Usage: serv [port=80] [folder=www] [log] [logFile=log.txt]");
    process.exit(0);
}

// 参数本来应为 key=val 或 key,这里把他们拆开
function extractConfig(argv) {
    let config = {};
    for (let i = 0; i < argv.length; i++) {
        let arg = argv[i];
        arg = arg.split("=");
        const key = arg.shift();
        let value = arg.join("=") || true;
        if (value[0] === '"' && value[value.length - 1] === '"') value = value.substring(1, value.length - 1);
        config[key] = value;
    }
    return check(config);
}

// 处里一些转型问题或相对绝对路径的转换
function check(config) {
    if (config.port) config.port = parseInt(config.port);
    if (config.log) config.log = config.log !== "false" && config.log !== "0";
    if (config.folder) config.folder = path.isAbsolute(config.folder) ? config.folder : path.join(process.cwd(), config.folder);
    if (config.logFile) config.logFile = path.isAbsolute(config.logFile) ? config.logFile : path.join(process.cwd(), config.logFile);
    return config;
}

// 合并预设与自订参数後汇出
module.exports = Object.assign({}, defaultConfig, config);

参数的抓取、合并以及型别路径转换等都在此完成。

server.js

这个档案会 exports 一个建立 server 用的函式,然後由 main.js 把处里好的参数丢进去用。

const fs = require("fs");
const Koa = require("koa");

function createServer({ port, folder, log, logFile }) {
    const app = new Koa();
    // 因为 log 会一直写入,所以用 stream
    let logFileStream;
    if (log) logFileStream = fs.createWriteStream(logFile, { flags: "a" });

    app.use(async (ctx, next) => {
        console.log(`Process ${ctx.request.method} ${ctx.request.url} from ${ctx.request.ip}`);
        if (log) logFileStream.write(`${new Date().toISOString()} ${ctx.request.method} ${ctx.request.url} from ${ctx.request.ip}\n`);
        await next();
    });

    app.use(require("koa-static")(folder));

    app.listen(port);

    console.log(`Server started at port ${port}`);
    console.log(`Serving static files from ${folder}`);
    console.log(`Visit http://localhost:${port}/ to see your website.`);
    if (log) console.log(`Log file: ${logFile}`);
    if (log) logFileStream.write(`=====\n${new Date().toISOString()} Server Started.\n`);
    if (log) logFileStream.write(`${new Date().toISOString()} Serving static files from ${folder}\n`);
}

module.exports = createServer;

基本上这个部分跟昨天的程序相似度非常高,只是多了些 log 的部分而已。

实测

来实测程序吧!


help 指令 OK!


不输入任何参数执行也 OK!(当然你可以 Click 点开)


带入参数执行也都 OK!!


同时在不同 port 执行两个 server 也 OK!

而且,log 有正常运作:

基本上都 OK 啦!(不会写测试)

接下来

赞赞!写出一个应该可以有用的程序了。
再来我们看看能不能用 Electron 给他个 GUI 吧!


每日铁人赛热门 Top 10 (1008)

以 10/08 20:00 ~ 10/09 20:00 文章观看数增加值排名

  1. +190 [访谈] APCS x 竞程选手 Colten
    • 作者: skyhong2002
    • 系列:深入高中程序设计能力指标 APCS
  2. +133 LeetCode 双刀流:62. Unique Paths
    • 作者: WeiYuan
    • 系列:LeetCode 双刀流:Python x JavaScript
  3. +117 【Day24】维持权限 — 隐藏後门(一)
    • 作者: Chilla
    • 系列:资安由浅入深
  4. +116 Day27 vue.js简易照片上传功能(base64)
    • 作者: 乔飞
    • 系列:用vue.js写出一个实用的科内分享网站
  5. +114 Angular 深入浅出三十天:表单与测试 Day24 - Reactive Forms 进阶技巧 - Auto-Complete Searching
    • 作者: Leo
    • 系列:Angular 深入浅出三十天:表单与测试
  6. +110 【在 iOS 开发路上的大小事-Day27】透过 Firebase 来管理资料 (Cloud Firestore 篇) Part1
    • 作者: leoho0722
    • 系列:在 iOS 开发路上的大小事
  7. +108 Day 24 快速启动个 JSON Server
    • 作者: smlpoints
    • 系列:以 Docker 为始的多种开源服务初探
  8. +108 Day24-按钮分身术(下)_我的分身想去哪
    • 作者: sweetyue9045
    • 系列:30天每天写网站
  9. +108 Day24 read-write lock
    • 作者: chengchen
    • 系列:当你凝视linux, linux也在凝视你
  10. +106 Day24 Let's ODOO: Discuss
    • 作者: 盖瑞
    • 系列:Let's ODOO 开发与应用30天挑战

恭喜 Sky Hong 同学封顶!!
今天访谈说好久喔,差点来不及写了 XD


<<:  Day-28 Breadth-First Search(BFS), 广度优先搜寻

>>:  Day27 - 部属到正式环境 (2)

Day01:铁人赛开场

一、主题内容 虽然知道全端工程师的路不好走,自己目前也还不是很称职,仍想以自己转职的角度、回顾的方式...

Day 7: Docker 介绍与安装

Docker 发展之路 过去什麽都没有的黑暗时代,所有的Application都是直接放在服务器上的...

xlsx档与json档转换

这篇接续上一篇,将电影名称爬取後转为json档,今天就要再转为xlsx档,使资料以表格方式呈现。废话...

软件职涯谈:拥有「分散式架构」能带来甚麽长期价值?

Youtube 连结:https://bit.ly/3FNg3KA 对於「 分散式架构 」主题,数...

Choosing the correct postcard size for print

Whenever a perfect ERP installation is expected, o...