NPM出版自制车轮的方法和步骤
1.前言
自从Node.js出现以来,它的好朋友npm(node package manager)也是我们日常开发中必不可少的东西。Npm让js模块化,这样可以更方便的重用别人写的模块(搬砖),也可以让我们分享一些自己的作品给大家使用(做轮子)。今天和大家分享一个用命令行压缩图片的工具。其用法大致如下:
//全局安装后,在图片目录中运行这一行$ tinyhere,以便压缩文件夹中的图片。这里使用tinypng提供的接口进行压缩,压缩比大致为50%,基本可以压缩一半。以前写项目的时候,测试验收完成后总是要手动按图片。后来我想把这个枯燥重复的东西(懒)自动化,但是公司脚手架没有集成这个东西,所以我就想写一个轮子用。它的名字叫tinyhere,所以你可以试着安装和使用它。
$ npm i tinyhere -g2,npm简介
如果你想写一个模块并发布到npm,你必须首先了解npm的用法。
为这个模块创建一个文件夹,然后在目录中运行npm init来初始化它的package.json,这是这个包的描述
//个人比较喜欢遵循-yes,会生成一个带有默认参数的package . JSON $ npminit(-yes)package . JSON。详细信息:
{'name': 'pkgname ',//包名,默认文件夹名' version' :' 1.0.0 ',' description' :' mypackage ',' main' :' index.js ',//如果只用于全局安装,则不必编写' bin' :' CLI。//如果在命令行上使用,则必须使用此选项。该名称是命令名称“scripts”: {“test”3360“echo”错误:未指定测试\ Exit 1'//test对应于NPM运行测试}、“keywords”:[“CLI”、“images”、“compress”]、“author”:“croc-wend”、“license”:“MIT”,有关更多配置信息,请参考vue的package.json的https://github.com/vuejs/vue/blob/dev/package.json
初始化完成后,您可以手工编写这个包。当你认为你已经写了它,你可以发布到npm
Npm日志inn pm publish[email protected]//成功此时,你在n pm搜索你的包名,你在package.json中写的信息会被解析,然后你的包的页面介绍内容就是你的README.md
3.写这个包裹
在包初始化之后,我们可以开始编写这个包
对于这种压缩工具,只能使用两种材料。需要tinypng接口使用的api-key和压缩图片。因此,我对这两种材料需要的一些操作做了如下分析:
我的初衷是把这个命令写得尽可能简单,这样我就能想到压缩图片=简单,所以我决定整个包只能用一个字运行,就像这样:
$ tiny的其他操作放在子命令和选项上。
然后开始划分项目结构
大致来说,全局命令执行的tinyhere放在bin目录下,然后subCommand负责提供操作函数,再把可重用的函数(比如读写操作)提取出来放到util上,把比较复杂的函数提取到一个文件中,比如compress,然后把一个函数导出到subCommand。用于存储用户的api密钥存储在数据下的密钥中。
tinyhere的可执行文件负责解析用户的输入,然后执行subCommand给出的相应函数。
4.过程分析
压缩这组图片的过程如下:
1.解析当前目录下的所有图片文件。这里要根据二进制流和文件头得到文件类型mime-type,然后读取文件二进制的头信息得到其真实的文件类型,从而判断是否真的是图片文件,而不是那些后缀改成. png的假货。
2.如果用户要求将压缩的图片存储在指定的目录中,则需要创建一个文件夹来存储它们。然后,首先判断这个路径是否合法,然后生成这个目录
3.确定用户剩余的api键数量是否足以进行此图片压缩。如果这个键不够,切换到下一个键,直到遍历文件中的所有键并找到可用的键。
4.图片和密钥都有,可以压缩。用数组保存压缩失败,然后在每次压缩完成时输出提示。处理完所有图片后,如果有压缩失败,询问是否继续压缩失败的图片
5.这样,处理在一次压缩后完成。压缩的图片将覆盖原始图片或存储在指定的路径中
PS:这里的tiny深度压缩目录(包括子目录)中的所有图片。这个命令的流程和上面的主命令有点不同。目前已有线索,尚未开发。考虑到文件系统是一个树形结构,我目前的想法是通过深度遍历,以带有图片的文件夹为单位,然后递归执行压缩。
其他:
这里,吐槽一下tinypng的界面。真的很糟糕。查询键有效性的validate函数只接受报告错误的回调,但是它成功了,没有任何操作。我真的很感动。我曾经延迟判断用户密钥的有效性。最后,我实在受不了这种类似bug的写法,决定用Object.defineProperty来监控它的使用次数的变化。如果它的setter被调用,这意味着它是一个合法的密钥
5.摘要
在这里,我想告诉你,如果你做一些你认为很酷的东西,并想让更多的人使用它来做得更好,在NPM发布它是一个非常好的方法。看完以上内容,你会发现分享真的不难,你也有机会让世界看到你的风采!
如果你觉得我写错了什么或者写得不好,还有什么其他的建议(赞美),欢迎补充。希望大家能交换意见,互相学习,共同进步!我是一个19岁的新人,所以以上就是今天的分享。新手上路,我会跟进不定时的周班(或月班)。我会努力让自己变得更好,写出更好的文章。文章中有一些错误,请改正。如果你觉得这篇文章对你有帮助,请记得喜欢或者评论。
6.写在最后
欢迎大家提出问题或建议!地址是:
https://github.com/Croc-ye/tinyhere
https://www.npmjs.com/package/tinyhere
最后,粘贴一些代码,内容太长,可以跳过
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 ')。版本,'-v,-version ')。用法('[选项])。选项('-p,-path new path ',将压缩图片保存到指定路径(使用相对路径)。选项('-a,-addkey ',' add API-key ')。选项('-deletekey ','删除指定的API-key ')。选项('-l,-list ',显示保存的应用编程接口键')。选项('-empty ','空保存的api-key')//子命令command.command(' deep ')。描述('压缩目录(包括子目录)中的所有图片')。操作(()={/)。Console.log('尚未完成,敬请期待');})commander . parse(process . argv);//选择入口if (commander.path) {//将图片存储在另一个路径compress(commander.path)中;} else if(commander . add){//add API-key addKey(commander . add);} else if(commander . delete){//delete API-key delete key(commander。删除);} else if(commander . list){//display API-key list();} else if(commander . empty){//empty API-key empty key();} else {//main命令if(类型为commander . args[0]==' object '){//subcommand return;} if (commander.args.length!==0) {console.log('未知命令');返回;} 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 image info=require(' image info ');const inquirer=require(' inquirer ');const {checkApiKey,getKeys}=require(' ./util ');//对当前目录内的图片进行压缩const compress=(新路径=' ')={ const imageList=ReadR();if(imagelist。长度===0){控制台。日志('当前目录内无可用于压缩的图片');返回;} newPath=path.join(process.cwd(),NewPath);mkDir(新路径);findValidateKey(imagelist。长度);控制台。日志('=============开始压缩=========');if (newPath!==process.cwd()) { console.log('压缩到:' newPath.replace(/\)./g,"));} compressArray(imageList,新路径);};//生成目录路径const mkDir=(文件路径)={ if(文件路径dirExists(文件路径)==false) { fs.mkdirSync(文件路径);}}//判断目录是否存在const dirExists=(filePath)={ let RES=false;请尝试{ RES=fs。existssync(文件路径);} catch(错误){ console.log('非法路径');过程。exit();}返回RES };/** * 检查美国石油学会(American Petroleum Institute)密钥剩余次数是否大于500 * @param {*}计数本次需要压缩的图片数目*/const checkcompresscount=(计数=0)={ return(500-tinify。压缩计数-计数)0;}/** * 找到可用的api-key * @param {*} imageLength本次需要压缩的图片数目*/const FindValidateKey=async imageLength={//bug高发处const keys=GetKeys();用于(设I=0;长度;I){ await CheckApiKey(key[I]);RES=check compresscount(imageLength);如果(res)返回;} console.log('已存储的所有美国石油学会(American Petroleum Institute)密钥都超出了本月500张限制,如果要继续使用请添加新的API-key’);过程。exit();}//获取当前目录的所有巴布亚新几内亚/日本文件const ReadR=()={ const文件路径=进程。CWD()}常量arr=fs。ReaDirsync(文件路径).过滤器(项目={ //这里应该根据二进制流及文件头获取文件类型哑剧类型,然后读取文件二进制的头信息,获取其真实的文件类型,对与通过后缀名获得的文件类型进行比较if (/(\).png|\ .jpg|\ .jpeg)$/.测试(项目)){ //求不要出现奇奇怪怪的文件名。const fileInfo=fs.readFileSync(项);const info=image info(FileInfo);return /png|jpg|jpeg/.测试(信息。mime type);}返回false });返回逮捕;};/** * 对数组内的图片名进行压缩* @param {*} imageList存放图片名的数组* @param {*} newPath压缩后的图片的存放地址*/const compressArray=(imageList,新路径)={ const fail list=[];imagelist。foreach(item={ compressImg(item,imageList.length,failList,new path));});}/** * 压缩给定名称的图片* @param {*}名称文件名* @param {*} fullLen全部文件数量* @param {*}故障列表压缩失败的数组* @param {*}文件路径用来存放的新地址*/const compressImg=(name,fullLen,failsList,filePath)={ fs.readFile(name,function(err,源数据){ if(err)throw err;tinify.fromBuffer(sourceData).toBuffer(函数(err,resultData){ if(err)抛出err;filePath=path.join(文件路径,名称);const writerStream=fs。createwriterstream(文件路径);//标记文件末尾writerStream.write(resultData,‘binary’);writerstream。end();//处理流事件-数据、结束和错误writerStream.on('finish ',function(){ failslist。push(null);记录(名称、真、失败列表、长度、完整长度);if(失败列表。length===full len){ finish CB(失败列表,文件路径);} });writerStream.on('error ',function(err){ failslist。推送(名称);记录(名称,错误,失败列表长度,完整长度);if(失败列表。length===full len){ finish CB(失败列表,文件路径);} });});});}//生成日志const record=(name,success=true,currNum,fullLen)={ const status=success?'完成' : '失败;console.log(`${name}压缩${status}。
$ { CurrNum }/$ { full len } `);}/* * *回调以完成调用* @param {*} failList存储失败图片名称数组* @ param { * } file path的新地址以存储*/constfinishcb=(faillist,file path)={ construst=500-tinify。压缩性计数;Console.log('本月剩余时间:' rest ');const fail=fail list . filter(item=item!==null);如果(failed . length 0){//有失败的压缩项目(显示失败项目的名称)。询问是否继续压缩那些失败的压缩。是/否//选择否后,询问是否生成错误日志查询程序。提示({Type: '确认',Name3360 '再次压缩',Message 3360 '已压缩。default: true })。然后(RES={ if(RES){ compressArray(fail list,file path);} else {//询问是否生成错误日志} })} else {//压缩完成console . log('=======');} } module . exports={ compress } libs/subcommand . js
const inquirer=require(' inquirer ');const {compress}=require(' ./压缩。js’);const {checkApiKey,getKeys,addKeyToFile,list}=require(' ./util。js’);模块。出口。压缩=压缩;模块。出口。init=()={ inquirer。提示({键入: ' input ',名称: 'apiKey ',消息: '请输入api-key:“,validate :(APiKey)={//console。日志(' \ n正在检测,请稍候.');process.stdout.write('\n正在检测,请稍候.');返回新的承诺(异步(解析)={ const RES=wait CheckApiKey(ApiKey);决心;});} }).然后(async RES={ await addKeyToFile(RES . apikey);console.log('apikey已完成初始化,压缩工具可以使用了');})}模块。出口。add key=async key={ await CheckApiKey(key);const keys=wait GetKeys();if(键。包括(键)){控制台。日志('该美国石油学会(American Petroleum Institute)密钥已存在文件内');返回;} const content=keys.length===0?“:键。连接(" " ";等待添加密钥文件(密钥,内容);list();}模块。出口。delete key=async key={ const key=wait GetKeys();const index=keys.indexOf(键);如果(索引0) { console.log('该美国石油学会(American Petroleum Institute)密钥不存在');返回;} keys.splice(index,1);console.log(键);const content=keys.length===0?键。join(');等待addKeyToFile(",内容);list();}模块。出口。emptykey=async key={ inquirer。提示({键入: '确认',名称name: ' emptyConfirm ',消息: '确认清空所有已存储的美国石油学会(American Petroleum Institute)密钥?default: true }).然后(RES={ if(RES . emptyconfigirm){ addKeyToFile('));} else { console.log('已取消');} })}模块。出口。list=listlibs/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=(毫秒)={ 0返回新的承诺(函数(解析){ setTimeout(()={ resolve(true));},ms);});}//判定请求是否有效const CheckApiKey=async ApiKey={ 0返回新承诺(异步解析={让res=true/^\w{32}$/.测试(ApiKey);if(RES===false){ console。日志(' API-密钥格式不对');决心;返回;} RES=await CheckKeyValidate(ApiKey);决心;})}//检查美国石油学会(American Petroleum Institute)密钥是否存在const CheckKeyValidate=ApiKey={ 0返回新的Promise(异步(解析)={ tinify。key=ApiKeytinify。validate(function(err){ if(err){ console。日志('该美国石油学会(American Petroleum Institute)密钥不是有效值');解决(假);} });让计数=500;对象。define property(tinify,' compressionCount ',{ get :()={ return COunt;},设置: NewValue={ count=NewValue决心(真);},可枚举:真的,可配置: true });});};//获取文件内的关键,以数组的形式返回const GetKeys=()={ const Keys=fs。ReadFileSync(KEY _ FILE _ PATH,' utf-8 ').拆分("");返回键[0]===' '?[] :键;}//把美国石油学会(American Petroleum Institute)密钥写入到文件里const addKeyToFile=(apiKey,content=' ')={ 0返回新的承诺(异步解析={ const writerStream=fs。createwriterstream(KEY _ FILE _ PATH);//使用utf8编码写入数据writerStream.write(内容apiKey,' UTF8 ');//标记文件末尾writerstream。end();//处理流事件-数据、结束和错误writerStream.on('finish ',function() { console.log('=====已更新=====');决心(真);});writerStream.on('error ',函数(err){ console。日志(错误。堆栈);console.log('写入失败。');解决(假);});})}//显示文件内的API-键常量列表=()={常量键=GetKeys();if(键。长度===0){控制台。日志('没有存储API-key’);} else { keys。foreach((键)={ console。log(key));});}};module.exports={ sleep,checkApiKey,getKeys,addKeyToFile,list}以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
版权声明:NPM出版自制车轮的方法和步骤是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。