亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > JS > 正文

在NPM發布自己造的輪子的方法步驟

2024-05-06 16:48:53
字體:
來源:轉載
供稿:網友

1、前言

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

// 全局安裝后,在圖片目錄下,運行這行$ tinyhere

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

$ npm i tinyhere -g

2、npm簡介

如果要寫一個模塊發布到npm,那么首先要了解一下npm的用法。

給這個模塊建一個文件夾,然后在目錄內運行npm init來初始化它的package.json,就是這個包的描述

// 個人比較喜歡后面帶--yes,它會生成一個帶默認參數的package.json$ npm init (--yes)

package.json詳情:

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

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

初始化完成之后,你就可以著手寫這個包了,當你覺得你寫好了之后,就可以發布到npm上面

npm loginnpm publish+ pkgname@1.0.0 // 成功

這時,你在npm上面搜你的包名,你寫在package.json 的信息都會被解析,然后你的包的頁面介紹內容就是你的README.md

3、寫這個包

包初始化好了之后,我們就可以開始寫這個包了

對于這個壓縮工具來說,要用到的素材只有兩個,tinypng接口要用到的 api-key,需要壓縮的圖片,所以我對這兩個素材需要用到的一些操作進行了以下分析:

NPM,輪子

我的初衷是想把這個命令寫的盡量簡單,讓我可以聯想到壓縮圖片=簡單,所以我待定了整個包只有一個單詞就能跑,是這樣:

$ tinyhere

其他的操作都放在子命令和可選項上。

然后開始劃分項目結構

NPM,輪子

大致上是這樣,把全局命令執行的 tinyhere 放在bin目錄下,然后subCommand負責提供操作函數,然后把可復用的函數(比如讀寫操作)抽離出來放在util上,比較復雜的功能單獨抽離成一個文件,比如compress,然后導出一個函數給subCommand。至于存放用戶的api-key,就存放在data下面的key里。

tinyhere的執行文件就負責解析用戶的輸入,然后執行subCommand給出的對應函數。

4、過程解析

壓縮圖片的這個包的過程是這樣的:

1、解析當前目錄內的所有圖片文件,這里應該根據二進制流及文件頭獲取文件類型mime-type,然后讀取文件二進制的頭信息,獲取其真實的文件類型,來判斷它是否真的是圖片文件,而不是那些僅僅是后綴名改成.png的假貨

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

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

4、圖片和key都有了,這時可以進行壓縮了。用一個數組把壓縮失敗的存起來,然后每次壓縮完成都輸出提示,在所有圖片都處理完成后,如果存在壓縮失敗的,就詢問是否把壓縮失敗的圖繼續壓縮

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

ps:$ tinyhere deep >>> 把目錄內的所有圖片都進行壓縮(含子目錄)。這個命令和上述的主命令的流程有點不同,目前有點頭緒,還沒有開發完成,考慮到文件系統是樹形結構,我目前的想法是通過深度遍歷,把存在圖片的文件夾當作一個單位,然后遞歸執行壓縮。

其他:

這里吐槽一下tinypng 的接口寫的真的爛。。在查詢key的合法性的 validate 函數只接受報錯的回調,但是成功卻沒有任何動作。我真是服了,之前是做延時來判斷用戶的key的合法性,最后實在是受不了這個bug一樣的寫法了,決定用Object.defineProperty來監聽它的使用次數的變化。如果它的setter被調用則說明它是一個合法的key了

5、小結

在這里,我想跟大家說,如果你做了一個你覺得很酷的東西,也想給更多的人去使用,來讓它變得更好,選擇發布在NPM上面就是一個非常好的途徑,看了上面的內容你會發現分享其實真的不難,你也有機會讓世界看到屬于你的風采!

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

6、寫在最后

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

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

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

最后貼上部分代碼,內容過長,可以跳過哦

bin/tinyhere

#!/usr/bin/env nodeconst 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 <newPath>', '壓縮后的圖片存放到指定路徑(使用相對路徑)').option('-a, --add <key>', '添加api-key').option('--delete <key>', '刪除指定api-key').option('-l, --list', '顯示已儲存的api-key').option('--empty', '清空已儲存的api-key')// 子命令commander.command('deep').description('把該目錄內的所有圖片(含子目錄)的圖片都進行壓縮').action(()=> {  // deepCompress();  console.log('尚未完成,敬請期待');})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('請初始化你的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');// 對當前目錄內的圖片進行壓縮const compress = (newPath = '')=> {  const imageList = readDir();  if (imageList.length === 0) {    console.log('當前目錄內無可用于壓縮的圖片');    return;  }  newPath = path.join(process.cwd(), newPath);  mkDir(newPath);  findValidateKey(imageList.length);  console.log('===========開始壓縮=========');  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剩余次數是否大于500 * @param {*} count 本次需要壓縮的圖片數目 */const checkCompressionCount = (count = 0)=> {  return (500 - tinify.compressionCount - count) >> 0;}/** * 找到可用的api-key * @param {*} imageLength 本次需要壓縮的圖片數目 */const findValidateKey = async imageLength=> { // bug高發處  const keys = getKeys();  for (let i = 0; i < keys.length; i++) {    await checkApiKey(keys[i]);    res = checkCompressionCount(imageLength);    if (res) return;  }  console.log('已存儲的所有api-key都超出了本月500張限制,如果要繼續使用請添加新的api-key');  process.exit();}// 獲取當前目錄的所有png/jpg文件const readDir = ()=> {  const filePath = process.cwd()  const arr = fs.readdirSync(filePath).filter(item=> {    // 這里應該根據二進制流及文件頭獲取文件類型mime-type,然后讀取文件二進制的頭信息,獲取其真實的文件類型,對與通過后綴名獲得的文件類型進行比較。    if (/(/.png|/.jpg|/.jpeg)$/.test(item)) { // 求不要出現奇奇怪怪的文件名。。      const fileInfo = fs.readFileSync(item);      const info = imageinfo(fileInfo);      return /png|jpg|jpeg/.test(info.mimeType);    }    return false;  });  return arr;};/** * 對數組內的圖片名進行壓縮 * @param {*} imageList 存放圖片名的數組 * @param {*} newPath 壓縮后的圖片的存放地址 */const compressArray = (imageList, newPath)=> {  const failList = [];  imageList.forEach(item=> {    compressImg(item, imageList.length, failList, newPath);  });}/** * 壓縮給定名稱的圖片 * @param {*} name 文件名 * @param {*} fullLen 全部文件數量 * @param {*} failsList 壓縮失敗的數組 * @param {*} filePath 用來存放的新地址 */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);     // 標記文件末尾     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}`);}/** * 完成調用的回調 * @param {*} failList 存儲壓縮失敗圖片名的數組 * @param {*} filePath 用來存放的新地址 */const finishcb = (failList, filePath)=> {  const rest = 500 - tinify.compressionCount;  console.log('本月剩余次數:' + rest);  const fails = failList.filter(item=> item !== null);  if (fails.length > 0) {    // 存在壓縮失敗的項目(展示失敗的項目名),詢問是否把壓縮失敗的繼續壓縮 y/n    // 選擇否之后,詢問是否生成錯誤日志    inquirer.prompt({      type: 'confirm',      name: 'compressAgain',      message: '存在壓縮失敗的圖片,是否將失敗的圖片繼續壓縮?',      default: true    }).then(res=> {      if (res) {        compressArray(failList, filePath);      } else {        // 詢問是否生成錯誤日志      }    })  } 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: '請輸入api-key:',    validate: (apiKey)=> {      // console.log('/n正在檢測,請稍候...');      process.stdout.write('/n正在檢測,請稍候...');      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已存在文件內');    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: '確認清空所有已存儲的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格式不對');      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    });  });};// 獲取文件內的key,以數組的形式返回const getKeys = ()=> {  const keys = fs.readFileSync(KEY_FILE_PATH, 'utf-8').split(' ');  return keys[0] === '' ? [] : keys;}// 把api-key寫入到文件里const addKeyToFile = (apiKey, content = '')=> {  return new Promise(async resolve=> {    const writerStream = fs.createWriteStream(KEY_FILE_PATH);    // 使用 utf8 編碼寫入數據    writerStream.write(content + apiKey,'UTF8');    // 標記文件末尾    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('寫入失敗。');      resolve(false);    });  })}// 顯示文件內的api-keyconst list = ()=> {  const keys = getKeys();  if (keys.length === 0) {    console.log('沒有存儲api-key');  } else {    keys.forEach((key)=> {      console.log(key);    });  }};module.exports = {  sleep,  checkApiKey,  getKeys,  addKeyToFile,  list}

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JavaScript/Ajax教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
51视频国产精品一区二区| 国产精品狠色婷| 日本一区二区三区在线播放| 日韩欧美亚洲范冰冰与中字| 亚洲一区二区久久久久久久| 欧洲成人性视频| 久久香蕉频线观| 91精品国产色综合久久不卡98口| 色婷婷综合久久久久中文字幕1| 国产精品丝袜一区二区三区| 日韩亚洲综合在线| 国产精品久久久久久久久久免费| 亚洲欧美另类中文字幕| 高清亚洲成在人网站天堂| 亚洲精品自拍第一页| wwwwwwww亚洲| 久久精品国产亚洲| 亚洲精品国产精品国产自| 欧美激情久久久久| 亚洲第一在线视频| 日韩精品在线观看网站| 国产福利精品av综合导导航| 亚洲一区二区三区四区视频| 亚洲午夜未满十八勿入免费观看全集| 国产精品亚洲欧美导航| 这里只有视频精品| 日韩av免费在线观看| 欧美与黑人午夜性猛交久久久| 在线看片第一页欧美| 亚洲www视频| 精品国产网站地址| 亚洲欧美国产高清va在线播| 欧美精品aaa| 日韩精品视频在线观看免费| 欧美成人免费在线视频| 亚洲国产精品女人久久久| 91中文字幕一区| 欧美激情免费看| 日韩视频在线观看免费| 2019中文在线观看| 国产精品视频xxx| 91精品国产91久久久久久久久| 久久久久久久久久久成人| xxxxx91麻豆| 热久久美女精品天天吊色| 欧美性猛交xxx| 国产精品xxxxx| 欧美亚洲伦理www| 伊人久久精品视频| 欧美日韩中文字幕在线视频| 欧美电影免费观看电视剧大全| 国产亚洲精品高潮| 亚洲国产精彩中文乱码av| 亚洲jizzjizz日本少妇| 欧美日产国产成人免费图片| 亚洲精品一区二区在线| 91成人天堂久久成人| 久久精品国产久精国产思思| 久久精品国产96久久久香蕉| 久久亚洲精品一区| 欧美性黄网官网| 国产精品第100页| 久久久国产一区二区三区| 国产一区红桃视频| 91免费电影网站| 日韩一区二区三区xxxx| 欧美极品美女视频网站在线观看免费| 欧美高清第一页| 国产精品久久久久久久一区探花| 综合av色偷偷网| 日韩一二三在线视频播| 亚洲国产精彩中文乱码av在线播放| 亚洲精品98久久久久久中文字幕| 国内揄拍国内精品少妇国语| 26uuu亚洲国产精品| 中文字幕欧美专区| 成人av电影天堂| 日韩在线视频观看正片免费网站| 亚洲影院污污.| 日韩欧美第一页| 欧美高清在线视频观看不卡| 日韩在线观看免费全集电视剧网站| 国产精品狠色婷| 国产日韩专区在线| 在线电影欧美日韩一区二区私密| 日韩成人激情在线| 日韩亚洲成人av在线| 欧美精品成人在线| 成人激情电影一区二区| 欧美性极品少妇精品网站| 精品动漫一区二区三区| 亚洲女人天堂av| 国产欧美一区二区三区视频| 亚洲性线免费观看视频成熟| 久久久久久久久久久国产| 国产在线精品自拍| 欧美精品videosex性欧美| 97在线视频免费播放| 久久在精品线影院精品国产| 欧美日韩国产二区| 日韩在线视频线视频免费网站| 国产精品xxxxx| 欧美激情在线狂野欧美精品| 亚洲精品白浆高清久久久久久| 欧美电影院免费观看| 欧美精品久久久久久久久| 国产盗摄xxxx视频xxx69| 最近2019年好看中文字幕视频| 少妇高潮久久77777| 欧美一区二区影院| 这里只有精品视频在线| 欧美国产高跟鞋裸体秀xxxhd| 日韩成人在线免费观看| 国产日韩在线看| 69av在线播放| 亚洲精品综合精品自拍| 欧美在线视频a| 日韩中文字幕在线观看| 久久久中文字幕| 永久免费看mv网站入口亚洲| 精品色蜜蜜精品视频在线观看| 成人精品一区二区三区电影免费| 欧美高清性猛交| 精品自拍视频在线观看| 欧美日韩国产页| 久久久91精品国产一区不卡| 欧美精品亚州精品| 国产mv免费观看入口亚洲| 久久国产精品久久国产精品| 亚洲精品电影久久久| 国产精品一区二区在线| 性夜试看影院91社区| 欧美性生活大片免费观看网址| 国产精品视频99| 国内精品伊人久久| 精品国偷自产在线视频99| 欧美视频免费在线观看| 亚洲第一精品夜夜躁人人爽| 国产精品劲爆视频| 亚洲综合大片69999| 国产91精品高潮白浆喷水| 日韩av日韩在线观看| 亚洲国产精久久久久久久| 欧美疯狂性受xxxxx另类| 亚洲国产小视频在线观看| 亚洲一区二区中文| 久操成人在线视频| 成人免费看黄网站| 久久综合久中文字幕青草| 国产婷婷成人久久av免费高清| 日日狠狠久久偷偷四色综合免费| 国产91精品久久久久久| 26uuu另类亚洲欧美日本一| 国产成人精品综合久久久| 亚洲精品福利免费在线观看| 成人黄色在线播放| 欧美国产精品日韩| 欧美精品精品精品精品免费| 亚洲欧美日韩图片| 亚洲аv电影天堂网| 这里只有精品视频在线| 国产主播喷水一区二区| 中文字幕久热精品在线视频| 日韩欧美亚洲综合|