iT邦幫忙

2021 iThome 鐵人賽

DAY 25
2
Modern Web

JavaScript Easy Go!系列 第 25

#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


上一篇
#24 可攜式靜態伺服器
下一篇
#26 初探 Electron
系列文
JavaScript Easy Go!31

尚未有邦友留言

立即登入留言