国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

在NPM發(fā)布自己造的輪子

binaryTree / 1270人閱讀

摘要:在發(fā)布自己造的輪子前言自從出現(xiàn),它的好基友也是我們?nèi)粘i_(kāi)發(fā)中必不可少的東西。

在NPM發(fā)布自己造的輪子 1、前言

自從Node.js出現(xiàn),它的好基友npm(node package manager)也是我們?nèi)粘i_(kāi)發(fā)中必不可少的東西。npm讓js實(shí)現(xiàn)了模塊化,使得復(fù)用其他人寫(xiě)好的模塊(搬磚)變得更加方便,也讓我們可以分享一些自己的作品給大家使用(造輪子),今天這里我就給大家分享一個(gè)用命令行壓縮圖片的工具,它的用法大致是這樣的:

// 全局安裝后,在圖片目錄下,運(yùn)行這行
$ tinyhere

這樣就把文件夾內(nèi)的圖片進(jìn)行壓縮。這里壓縮采用的是 tinypng 提供的接口,壓縮率大致上是50%,基本可以壓一半的大小。以前在寫(xiě)項(xiàng)目的時(shí)候,測(cè)試驗(yàn)收完成后總是要自己手動(dòng)去壓一次圖片,后來(lái)想把這個(gè)枯燥重復(fù)的事自動(dòng)化去完成(懶),但是公司腳手架又沒(méi)有集成這個(gè)東西,就想自己寫(xiě)一個(gè)輪子做出來(lái)用用就好了。它的名字叫做tinyhere,大家可以去安裝使用試一下

$ npm i tinyhere -g
2、npm簡(jiǎn)介

如果要寫(xiě)一個(gè)模塊發(fā)布到npm,那么首先要了解一下npm的用法。

給這個(gè)模塊建一個(gè)文件夾,然后在目錄內(nèi)運(yùn)行npm init來(lái)初始化它的package.json,就是這個(gè)包的描述

// 個(gè)人比較喜歡后面帶--yes,它會(huì)生成一個(gè)帶默認(rèn)參數(shù)的package.json
$ npm init (--yes)

package.json詳情:

{
  "name": "pkgname", // 包名,默認(rèn)文件夾的名字
  "version": "1.0.0",
  "description": "my package",
  "main": "index.js", // 如果只是用來(lái)全局安裝的話,可以不寫(xiě)
  "bin": "cli", // 如果是命令行使用的話,必須要這個(gè),名字就是命令名
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1" // npm run test對(duì)應(yīng)的test
  },
  "keywords": ["cli", "images", "compress"],
  "author": "croc-wend",
  "license": "MIT",
  ...
}

更多配置信息可以參考一下vue的package.json的https://github.com/vuejs/vue/blob/dev/package.json

初始化完成之后,你就可以著手寫(xiě)這個(gè)包了,當(dāng)你覺(jué)得你寫(xiě)好了之后,就可以發(fā)布到npm上面

npm login
npm publish
+ pkgname@1.0.0 // 成功

這時(shí),你在npm上面搜你的包名,你寫(xiě)在package.json 的信息都會(huì)被解析,然后你的包的頁(yè)面介紹內(nèi)容就是你的README.md

3、寫(xiě)這個(gè)包

包初始化好了之后,我們就可以開(kāi)始寫(xiě)這個(gè)包了

對(duì)于這個(gè)壓縮工具來(lái)說(shuō),要用到的素材只有兩個(gè),tinypng接口要用到的 api-key,需要壓縮的圖片,所以我對(duì)這兩個(gè)素材需要用到的一些操作進(jìn)行了以下分析:

我的初衷是想把這個(gè)命令寫(xiě)的盡量簡(jiǎn)單,讓我可以聯(lián)想到壓縮圖片=簡(jiǎn)單,所以我待定了整個(gè)包只有一個(gè)單詞就能跑,是這樣:

$ tinyhere

其他的操作都放在子命令和可選項(xiàng)上。

然后開(kāi)始劃分項(xiàng)目結(jié)構(gòu)

大致上是這樣,把全局命令執(zhí)行的 tinyhere 放在bin目錄下,然后subCommand負(fù)責(zé)提供操作函數(shù),然后把可復(fù)用的函數(shù)(比如讀寫(xiě)操作)抽離出來(lái)放在util上,比較復(fù)雜的功能多帶帶抽離成一個(gè)文件,比如compress,然后導(dǎo)出一個(gè)函數(shù)給subCommand。至于存放用戶的api-key,就存放在data下面的key里。

tinyhere的執(zhí)行文件就負(fù)責(zé)解析用戶的輸入,然后執(zhí)行subCommand給出的對(duì)應(yīng)函數(shù)。

4、過(guò)程解析

壓縮圖片的這個(gè)包的過(guò)程是這樣的:

1、解析當(dāng)前目錄內(nèi)的所有圖片文件,這里應(yīng)該根據(jù)二進(jìn)制流及文件頭獲取文件類型mime-type,然后讀取文件二進(jìn)制的頭信息,獲取其真實(shí)的文件類型,來(lái)判斷它是否真的是圖片文件,而不是那些僅僅是后綴名改成.png的假貨

2、 如果用戶有要求把壓縮的圖片存放到指定目錄,那就需要生成一個(gè)文件夾來(lái)存放它們。那么,首先要判斷這個(gè)路徑是否合法,然后再去生成這個(gè)目錄

3、判斷用戶的api-key的剩余次數(shù)是否足夠這次的圖片壓縮,如果這個(gè)key不夠,就換到下一個(gè)key,知道遍歷文件內(nèi)所有的key找到有可用的key為止。

4、圖片和key都有了,這時(shí)可以進(jìn)行壓縮了。用一個(gè)數(shù)組把壓縮失敗的存起來(lái),然后每次壓縮完成都輸出提示,在所有圖片都處理完成后,如果存在壓縮失敗的,就詢問(wèn)是否把壓縮失敗的圖繼續(xù)壓縮

5、這樣,一次壓縮就處理完成了。壓縮過(guò)的圖片會(huì)覆蓋原有的圖片,或者是存放到指定的路徑里

ps:$ tinyhere deep >>> 把目錄內(nèi)的所有圖片都進(jìn)行壓縮(含子目錄)。這個(gè)命令和上述的主命令的流程有點(diǎn)不同,目前有點(diǎn)頭緒,還沒(méi)有開(kāi)發(fā)完成,考慮到文件系統(tǒng)是樹(shù)形結(jié)構(gòu),我目前的想法是通過(guò)深度遍歷,把存在圖片的文件夾當(dāng)作一個(gè)單位,然后遞歸執(zhí)行壓縮。

其他:

這里吐槽一下tinypng 的接口寫(xiě)的真的爛。。在查詢key的合法性的 validate 函數(shù)只接受報(bào)錯(cuò)的回調(diào),但是成功卻沒(méi)有任何動(dòng)作。我真是服了,之前是做延時(shí)來(lái)判斷用戶的key的合法性,最后實(shí)在是受不了這個(gè)bug一樣的寫(xiě)法了,決定用Object.defineProperty來(lái)監(jiān)聽(tīng)它的使用次數(shù)的變化。如果它的setter被調(diào)用則說(shuō)明它是一個(gè)合法的key了

5、小結(jié)

在這里,我想跟大家說(shuō),如果你做了一個(gè)你覺(jué)得很酷的東西,也想給更多的人去使用,來(lái)讓它變得更好,選擇發(fā)布在NPM上面就是一個(gè)非常好的途徑,看了上面的內(nèi)容你會(huì)發(fā)現(xiàn)分享其實(shí)真的不難,你也有機(jī)會(huì)讓世界看到屬于你的風(fēng)采!

如果大家覺(jué)得我有哪里寫(xiě)錯(cuò)了,寫(xiě)得不好,有其它什么建議(夸獎(jiǎng)),非常歡迎大家補(bǔ)充。希望能讓大家交流意見(jiàn),相互學(xué)習(xí),一起進(jìn)步! 我是一名 19 的應(yīng)屆新人,以上就是今天的分享,新手上路中,后續(xù)不定期周更(或者是月更哈哈),我會(huì)努力讓自己變得更優(yōu)秀、寫(xiě)出更好的文章,文章中有不對(duì)之處,煩請(qǐng)各位大神斧正。如果你覺(jué)得這篇文章對(duì)你有所幫助,請(qǐng)記得點(diǎn)贊或者品論留言哦~。

6、寫(xiě)在最后

歡迎大家提issue或者建議!地址在這:

https://github.com/Croc-ye/ti...

https://www.npmjs.com/package...

最后貼上部分代碼,內(nèi)容過(guò)長(zhǎng),可以跳過(guò)哦

bin/tinyhere

#!/usr/bin/env node

const commander = require("commander");
const {init, addKey, deleteKey, emptyKey, list, compress} = require("../libs/subCommand.js");
const {getKeys} = require("../libs/util.js");

// 主命令
commander
.version(require("../package").version, "-v, --version")
.usage("[options]")
.option("-p, --path ", "壓縮后的圖片存放到指定路徑(使用相對(duì)路徑)")
.option("-a, --add ", "添加api-key")
.option("--delete ", "刪除指定api-key")
.option("-l, --list", "顯示已儲(chǔ)存的api-key")
.option("--empty", "清空已儲(chǔ)存的api-key")

// 子命令
commander
.command("deep")
.description("把該目錄內(nèi)的所有圖片(含子目錄)的圖片都進(jìn)行壓縮")
.action(()=> {
    // deepCompress();
    console.log("尚未完成,敬請(qǐng)期待");
})

commander.parse(process.argv);


// 選擇入口
if (commander.path) {
    // 把圖片存放到其他路徑
    compress(commander.path);
} else if (commander.add) {
    // 添加api-key
    addKey(commander.add);
} else if (commander.delete) {
    // 刪除api-key
    deleteKey(commander.delete);
} else if (commander.list) {
    // 顯示api-key
    list();
} else if (commander.empty) {
    // 清空api-key
    emptyKey();
} else {
    // 主命令
    if (typeof commander.args[0] === "object") {
        // 子命令
        return;
    }
    if (commander.args.length !== 0) {
        console.log("未知命令");
        return;
    }
    if (getKeys().length === 0) {
        console.log("請(qǐng)初始化你的api-key")
        init();
    } else {
        compress();
    }
};

libs/compress.js

const tinify = require("tinify");
const fs = require("fs");
const path = require("path");
const imageinfo = require("imageinfo");
const inquirer = require("inquirer");
const {checkApiKey, getKeys} = require("./util");

// 對(duì)當(dāng)前目錄內(nèi)的圖片進(jìn)行壓縮
const compress = (newPath = "")=> {
    const imageList = readDir();
    if (imageList.length === 0) {
        console.log("當(dāng)前目錄內(nèi)無(wú)可用于壓縮的圖片");
        return;
    }
    newPath = path.join(process.cwd(), newPath);
    mkDir(newPath);

    findValidateKey(imageList.length);
    console.log("===========開(kāi)始?jí)嚎s=========");
    if (newPath !== process.cwd()) {
        console.log("壓縮到:  " + newPath.replace(/./g, ""));
    }
    compressArray(imageList, newPath);
};

// 生成目錄路徑
const mkDir = (filePath)=> {
    if (filePath && dirExists(filePath) === false) {
        fs.mkdirSync(filePath);
    }
}

// 判斷目錄是否存在
const dirExists = (filePath)=> {
    let res = false;
    try {
        res = fs.existsSync(filePath);
    } catch (error) {
        console.log("非法路徑");
        process.exit();
    }
    return res;
};


/**
 * 檢查api-key剩余次數(shù)是否大于500
 * @param {*} count 本次需要壓縮的圖片數(shù)目
 */
const checkCompressionCount = (count = 0)=> {
    return (500 - tinify.compressionCount - count) >> 0;
}

/**
 * 找到可用的api-key
 * @param {*} imageLength 本次需要壓縮的圖片數(shù)目
 */
const findValidateKey = async imageLength=> { // bug高發(fā)處
    const keys = getKeys();
    for (let i = 0; i < keys.length; i++) {
        await checkApiKey(keys[i]);
        res = checkCompressionCount(imageLength);
        if (res) return;
    }
    console.log("已存儲(chǔ)的所有api-key都超出了本月500張限制,如果要繼續(xù)使用請(qǐng)?zhí)砑有碌腶pi-key");
    process.exit();
}

// 獲取當(dāng)前目錄的所有png/jpg文件
const readDir = ()=> {
    const filePath = process.cwd()
    const arr = fs.readdirSync(filePath).filter(item=> {
        // 這里應(yīng)該根據(jù)二進(jìn)制流及文件頭獲取文件類型mime-type,然后讀取文件二進(jìn)制的頭信息,獲取其真實(shí)的文件類型,對(duì)與通過(guò)后綴名獲得的文件類型進(jìn)行比較。
        if (/(.png|.jpg|.jpeg)$/.test(item)) { // 求不要出現(xiàn)奇奇怪怪的文件名。。
            const fileInfo = fs.readFileSync(item);
            const info = imageinfo(fileInfo);
            return /png|jpg|jpeg/.test(info.mimeType);
        }
        return false;
    });
    return arr;
};

/**
 * 對(duì)數(shù)組內(nèi)的圖片名進(jìn)行壓縮
 * @param {*} imageList 存放圖片名的數(shù)組
 * @param {*} newPath 壓縮后的圖片的存放地址
 */
const compressArray = (imageList, newPath)=> {
    const failList = [];
    imageList.forEach(item=> {
        compressImg(item, imageList.length, failList, newPath);
    });
}

/**
 * 壓縮給定名稱的圖片
 * @param {*} name 文件名
 * @param {*} fullLen 全部文件數(shù)量
 * @param {*} failsList 壓縮失敗的數(shù)組
 * @param {*} filePath 用來(lái)存放的新地址
 */
const compressImg = (name, fullLen, failsList, filePath)=> {
    fs.readFile(name, function(err, sourceData) {
        if (err) throw err;
        tinify.fromBuffer(sourceData).toBuffer(function(err, resultData) {
          if (err) throw err;
          filePath = path.join(filePath, name);
          const writerStream = fs.createWriteStream(filePath);
          // 標(biāo)記文件末尾
          writerStream.write(resultData,"binary");
          writerStream.end();
      
          // 處理流事件 --> data, end, and error
          writerStream.on("finish", function() {
            failsList.push(null);
            record(name, true, failsList.length, fullLen);
            if (failsList.length === fullLen) {
                finishcb(failsList, filePath);
            }
          });

          writerStream.on("error", function(err){
            failsList.push(name);
            record(name, false, failsList.length, fullLen);
            if (failsList.length === fullLen) {
                finishcb(failsList, filePath);
            }
          });
        });
    });
}

// 生成日志
const record = (name, success = true, currNum, fullLen)=> {
    const status = success ? "完成" : "失敗";
    console.log(`${name} 壓縮${status}。 ${currNum}/${fullLen}`);
}

/**
 * 完成調(diào)用的回調(diào)
 * @param {*} failList 存儲(chǔ)壓縮失敗圖片名的數(shù)組
 * @param {*} filePath 用來(lái)存放的新地址
 */
const finishcb = (failList, filePath)=> {
    const rest = 500 - tinify.compressionCount;
    console.log("本月剩余次數(shù):" + rest);
    const fails = failList.filter(item=> item !== null);
    if (fails.length > 0) {
        // 存在壓縮失敗的項(xiàng)目(展示失敗的項(xiàng)目名),詢問(wèn)是否把壓縮失敗的繼續(xù)壓縮 y/n
        // 選擇否之后,詢問(wèn)是否生成錯(cuò)誤日志
        inquirer.prompt({
            type: "confirm",
            name: "compressAgain",
            message: "存在壓縮失敗的圖片,是否將失敗的圖片繼續(xù)壓縮?",
            default: true
        }).then(res=> {
            if (res) {
                compressArray(failList, filePath);
            } else {
               // 詢問(wèn)是否生成錯(cuò)誤日志
            }
        })
    } else {
        // 壓縮完成
        console.log("======圖片已全部壓縮完成======");
    }
}

module.exports = {
    compress
}

libs/subCommand.js

const inquirer = require("inquirer");
const {compress} = require("./compress.js");
const {checkApiKey, getKeys, addKeyToFile, list} = require("./util.js");

module.exports.compress = compress;
module.exports.init = ()=> {
    inquirer.prompt({
        type: "input",
        name: "apiKey",
        message: "請(qǐng)輸入api-key:",
        validate: (apiKey)=> {
            // console.log("
正在檢測(cè),請(qǐng)稍候...");
            process.stdout.write("
正在檢測(cè),請(qǐng)稍候...");
            return new Promise(async (resolve)=> {
                const res = await checkApiKey(apiKey);
                resolve(res);
            });
        }
    }).then(async res=> {
        await addKeyToFile(res.apiKey);
        console.log("apikey 已完成初始化,壓縮工具可以使用了");
    })
}

module.exports.addKey = async key=> {
    await checkApiKey(key);
    const keys = await getKeys();
    if (keys.includes(key)) {
        console.log("該api-key已存在文件內(nèi)");
        return;
    }
    const content = keys.length === 0 ? "" : keys.join(" ") + " ";
    await addKeyToFile(key, content);
    list();
}

module.exports.deleteKey = async key=> {
    const keys = await getKeys();
    const index = keys.indexOf(key);
    if (index < 0) {
        console.log("該api-key不存在");
        return;
    }
    keys.splice(index, 1);
    console.log(keys);
    const content = keys.length === 0 ? "" : keys.join(" ");
    await addKeyToFile("", content);
    list();
}

module.exports.emptyKey = async key=> {
    inquirer.prompt({
        type: "confirm",
        name: "emptyConfirm",
        message: "確認(rèn)清空所有已存儲(chǔ)的api-key?",
        default: true
    }).then(res=> {
        if (res.emptyConfirm) {
            addKeyToFile("");
        } else {
            console.log("已取消");
        }
    })
}

module.exports.list = list;

libs/util.js

const fs = require("fs");
const path = require("path");
const tinify = require("tinify");
const KEY_FILE_PATH = path.join(__dirname, "./data/key");

// 睡眠
const sleep = (ms)=> {
    return new Promise(function(resolve) {
        setTimeout(()=> {
            resolve(true);
        }, ms);
    });
}
// 判定apikey是否有效
const checkApiKey = async apiKey=> {
    return new Promise(async resolve=> {
        let res = true;
        res = /^w{32}$/.test(apiKey);
        if (res === false) {
            console.log("api-key格式不對(duì)");
            resolve(res);
            return;
        }
        res = await checkKeyValidate(apiKey);
        resolve(res);
    })
}
// 檢查api-key是否存在
const checkKeyValidate = apiKey=> {
    return new Promise(async (resolve)=> {
        tinify.key = apiKey;
        tinify.validate(function(err) {
            if (err) {
                console.log("該api-key不是有效值");
                resolve(false);
            }
        });
        let count = 500;
        Object.defineProperty(tinify, "compressionCount", {
            get: ()=> {
                return count;
            },
            set: newValue => {
                count = newValue;
                resolve(true);
            },
            enumerable : true,
            configurable : true
        });
    });
};

// 獲取文件內(nèi)的key,以數(shù)組的形式返回
const getKeys = ()=> {
    const keys =  fs.readFileSync(KEY_FILE_PATH, "utf-8").split(" ");
    return keys[0] === "" ? [] : keys;
}

// 把a(bǔ)pi-key寫(xiě)入到文件里
const addKeyToFile = (apiKey, content = "")=> {
    return new Promise(async resolve=> {
        const writerStream = fs.createWriteStream(KEY_FILE_PATH);
        // 使用 utf8 編碼寫(xiě)入數(shù)據(jù)
        writerStream.write(content + apiKey,"UTF8");

        // 標(biāo)記文件末尾
        writerStream.end();

        // 處理流事件 --> data, end, and error
        writerStream.on("finish", function() {
            console.log("=====已更新=====");
            resolve(true);
        });

        writerStream.on("error", function(err){
            console.log(err.stack);
            console.log("寫(xiě)入失敗。");
            resolve(false);
        });
    })
}

// 顯示文件內(nèi)的api-key
const list = ()=> {
    const keys = getKeys();
    if (keys.length === 0) {
        console.log("沒(méi)有存儲(chǔ)api-key");
    } else {
        keys.forEach((key)=> {
            console.log(key);
        });
    }
};
module.exports = {
    sleep,
    checkApiKey,
    getKeys,
    addKeyToFile,
    list
}

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/109039.html

相關(guān)文章

  • 結(jié)合自己造的輪子實(shí)踐按需加載

    摘要:原文地址為了探究按需加載的本質(zhì),選擇了對(duì)先前造的輪子進(jìn)行實(shí)驗(yàn)。下文就來(lái)揭開(kāi)面紗,并動(dòng)手改造項(xiàng)目,最終目標(biāo)是用第二種寫(xiě)法實(shí)現(xiàn)按需加載,減小打包體積。下面給出種可以按需加載的方案。 原文地址 為了探究按需加載的本質(zhì),選擇了對(duì)先前造的輪子 diana 進(jìn)行實(shí)驗(yàn)。 實(shí)驗(yàn)一:全量引用 import * as _ from diana 打包體積結(jié)果如下: showImg(http://oqhtsc...

    Alfred 評(píng)論0 收藏0
  • 一年前端造的輪子是什么樣子?

    摘要:起因工作也差不多滿一年了,對(duì)于基本的業(yè)務(wù)開(kāi)發(fā)有了一些自己的想法剛開(kāi)始工作的前個(gè)月,每天都可以接觸到新東西,接觸新業(yè)務(wù)個(gè)月之后業(yè)務(wù)開(kāi)發(fā)熟悉了對(duì)于自己的技術(shù)成長(zhǎng)就感覺(jué)受到了局限如果一直沒(méi)有作出改變,那么等于是個(gè)月的經(jīng)驗(yàn)要用一年我的學(xué)習(xí)方式就是多 起因 工作也差不多滿一年了,對(duì)于基本的業(yè)務(wù)開(kāi)發(fā)有了一些自己的想法 剛開(kāi)始工作的前3個(gè)月,每天都可以接觸到新東西,接觸新業(yè)務(wù) 3個(gè)月之后業(yè)務(wù)開(kāi)發(fā)熟...

    szysky 評(píng)論0 收藏0
  • 那些年造的輪子,我們?cè)摓檎l(shuí)樹(shù)墓碑?

    摘要:為此,玉伯當(dāng)時(shí)還特意發(fā)了一條微博,說(shuō)是應(yīng)該給和也樹(shù)一塊墓碑了。這里,閏土所說(shuō)的過(guò)時(shí),并不是指它現(xiàn)在就不能用了,而是說(shuō)出現(xiàn)了明顯更加先進(jìn)的理念或者標(biāo)準(zhǔn),這會(huì)導(dǎo)致未來(lái)它的使用場(chǎng)景大為減少,整體趨勢(shì)已經(jīng)步入衰落。 showImg(https://segmentfault.com/img/bVYQLf?w=700&h=392); 前言 都已經(jīng)2017年的11月份了,我們項(xiàng)目還打算用seajs?...

    vspiders 評(píng)論0 收藏0
  • 輪子 - EGGJS的MySQL操作庫(kù)

    摘要:最近學(xué)習(xí),學(xué)習(xí)過(guò)程中使用官方推薦的庫(kù),感覺(jué)官方庫(kù)不太好用,基礎(chǔ)的沒(méi)問(wèn)題。介紹這個(gè)輪子其實(shí)是很早以前就造好的,主要參考的數(shù)據(jù)庫(kù)操作方式。將設(shè)置表名設(shè)置查詢字段聯(lián)表等操作進(jìn)行鏈?zhǔn)讲僮?,給人一種語(yǔ)義化操作數(shù)據(jù)庫(kù)的感覺(jué)。 最近學(xué)習(xí)eggjs,學(xué)習(xí)過(guò)程中使用官方推薦的MySQL庫(kù),感覺(jué)官方庫(kù)不太好用,基礎(chǔ)的CURD沒(méi)問(wèn)題。但是復(fù)雜點(diǎn)的操作就不行了,雖然官方還有一個(gè)egg-sequelize,但是...

    Alex 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<