本文原文出处:http://devework.com/postcss-p...。转载请提供原始出处,谢谢!前阵子为了工作需要开发了一个PostCSS插件,后来把这个插件提交给了PostCSS官方,得到了认可。在本文中,笔者将记录开发过程中遇到的一些问题,并大胆称之为“最佳实践”,希望对有兴趣尝试PostCSS插件开发的你有所帮助。介绍文章开发成果先展示成果:https://github.com/Jeff2Ma/postcss-lazyimagecss(欢迎打个star~)postcss-lazyimagecss插件的作用是自动转换背景对应的图片-CSS中的图像添加宽度和高度属性。简单的视觉效果如下:/*Input./src/index.css*/.icon-close{background-image:url(../slice/icon-close.png);//icon-close.png-16x16}.icon-new{background-image:url(../slice/icon-new@2x.png);//icon-new@2x.png-16x16}/*输出./dist/index.css*/.icon-close{background-image:url(../slice/icon-close.png);宽度:16px;height:16px;}.icon-new{background-image:url(../slice/icon-new@2x.png);宽度:8px;高度:8px;background-size:8px8px;}重复一个轮子开发这个PostCSS插件的原因是原工作流中使用的gulp-lazyimagecss插件被添加到SourceMap后功能不能正常运行,多次尝试修复都失败了.后来笔者想,PostCSS本身就天然支持SourceMap,那么如果把这个功能开发成PostCSS插件,不也可以完美支持SourceMap吗?于是作者在gulp-lazyimagecss的基础上开发了这样一个wheel。还要感谢原开发者hzlzh和littledu的大力帮助和支持。对于笔者来说,开发这个插件更像是站在巨人的肩膀上。PreparingPrinciples关于PostCSS的原理,官方有这么一张图:简单的解释,PostCSS会根据样式规则(rules)解析上一步传入的CSS,得到一个节点树;然后使用一系列插件对节点树进行转换操作,最后通过Stringifier进行拼接。sourcemap记录了前后的对应关系。当然,在实际开发中,没必要深究原理,最重要的是看它提供的API来调用。工欲善其事,必先利其器。开发一个PostCSS插件也是在开发一个Node模块。考虑到后面会发布到NPM和PostCSS官方,开源项目的可维护性和可扩展性也很重要。因此,在进入正式开发之前,笔者做了如下工作:1、配置editorconfig作为统一代码格式的解决方案,团队的很多项目中都使用了editorconfig。代码编辑者和不同编码习惯带来的潜在风险。这是最终的配置文件。2.基本开发流程在整个插件开发流程之前,作者根据需求配置了一个基于Gulp的开发流程,主要配备了以下功能(任务):代码质量监控ESlint的优秀开源代码必须有规范的JavaScript代码Style,所以使用ESlint在整个开发过程中严格控制你的代码质量。这是这个项目的ESlint配置文件。vareslint=require('gulp-eslint');gulp.task('lint',function(){returngulp.src(files).pipe(eslint()).pipe(eslint.format()).pipe(eslint.failAfterError());});基本CSS转换的任务其实就是这个PostCSS插件实现的功能。之所以需要在开发过程中配置,是为了调用下面的单元测试任务。单元测试秉承TDD(TestDrivenDevelopment)的开发理念,单元测试的任务必不可少。gulp.task('test',function(){returngulp.src('test/*.js',{read:false}).pipe(mocha({timeout:1000000}));});watchtaskgulpwatch任务是以上任务的统称,其作用是在开发过程中每当按下保存按钮时自动运行ESlint代码质量监控和单元测试任务。有效保证整个开发过程的质量。3.托管到Github并配置Travis-ci以进行持续集成。整个开发过程使用Github托管源码,并通过Travis-ci持续集成。PostCSS官方建议至少支持Node.js0.12,所以整个Travis-ci配置文件如下:sudo:falselanguage:node_jsnode_js:-"0.12"-"4"-"5"-"6"-"stable"before_script:-npminstall-gmocha对应的在Travis-ci管理后台配置push操作为actionhook,这样每次有commitpush都会自动测试并在log上显示结果:开发篇开始与一个PostCSS插件最基本的组成如下:varpostcss=require('postcss');module.exports=postcss.plugin('PLUGIN_NAME',function(opts){opts=opts||{};//传入配置相关代码returnfunction(root,result){//转换CSS的函数代码};});然后是不同的需求决定是否引入第三方模块,是否有额外的配置项,然后在包含root和result的匿名函数中进行最核心的转换代码函数的编写。root(css),rule,nodes,decl,prop,value根据本文开头的PostCSS原理进行分析,Parser转换后的CSS文件的递归个体子单元可以分类如下:root(css):也是整个CSS代码段,包含多个规则。规则:包含一个CSS类范围内的代码段。icon-close{background-image:url(../slice/icon-close.png);font-size:14px;}nodes:指规则Multipledeclsectionsfor中{}的中间。decl:单行CSS,即有属性和值的部分background-image:url(../slice/icon-close.png);prop,value对应的CSS属性和值,比如上面的prop是background-image,value是url(../slice/icon-close.png)伪代码根据postcss-lazyimagecss插件实现要实现的内容-在。CSS转换涉及以下场景:增加width属性,获取真实值;增加height属性,获取真实值在双值图的情况下,添加background-size属性,计算值结合上一节,可以先写出如下简洁的伪代码:css.walkRules(function(rule){//遍历所有CSSrule.walkDecls(/^background(-image)?$/,function(decl){//遍历每条CSS规则找到目标规则//参数传递等一些代码nodes.forEach(function(node){//遍历其他规则...});...//其他代码实现,比如找出图片的真实宽度等rule.append({prop:'width',value:valueWidth});//将宽度属性添加到声明中});});下一步细化代码是考虑不同的情况,增加一些逻辑判断:判断url是网络地址还是Base64的数据形式:imageRegex.exec(value).indexOf('data:')来判断规则下是否已经有宽度等属性,在nodes循环中:if(node.prop==='width'){CSSWidth=true;}判断2x图片的宽高是否偶数:value.indexOf('@2x')>-1&&(info.width%2!==0||info.height%2!==0不再赘述,完整的代码实现可以看这里。难点解决postcss-lazyimagecss插件使用第三方模块fast-image-size获取图片数据(文件类型、宽高),大大提高了开发效率。但是在实现寻找图片的绝对路径的过程中还是走了不少弯路。插件的思路是在css中的background-image属性对应的值中获取url()的图片相对路径,从而找到图片的绝对路径,然后使用fast-image-size模块获取相应的数据。但在某些特殊情况下,无法准确找到绝对路径。在CSS预处理器(如Less或Sass)中,@import经常用于组件化CSS代码,但在@import的层下可能已经更改了路径。比如有如下结构:├──css├──html├──img│└──icon.png└──scss├──index.scss└──second└──_import.scss的文件树中的scss/index.scss@imports二级目录下的_import.scss,_import.scss中有一个class需要用到img/icon.png。因为同时配置了本地服务器(以上面的./目录作为服务器的根目录),可以写成../../img/icon.png或者../img/icon.pngintheurl,orevenas../../../../../img/icon.png(N../)——在这些情况下,Sass编译的index.css可以正常阅读。原因相信是知道的,因为根url的存在,上面的路径写法相当于/img/icon.png。在这种情况下,用户感觉不到错误,但在插件中找不到真正的绝对路径。笔者采用如下方法解决这种情况:利用Node.js中的fs.existsSync函数检测绝对路径对应的文件是否存在。第一次是正常的fs.existsSync,如果找到就会跳出;如果不是,先执行replace('../','');然后再次执行fs.existsSync。如果两次都没有找到,会在终端提示,但这种情况下不会报错,进程的运行也会被销毁。functionfixAbsolutePath(dir,relative){//第一次查找varabsolute=path.resolve(dir,relative);//检查是否是图像文件varreg=/\.(jpg|jpeg|png|gif|svg|bmp)\b/i;if(!reg.test(absolute)){pluginLog('不是图像文件:',absolute);返回;}if(!fs.existsSync(absolute)&&(relative.indexOf('../')>-1)){relative=relative.replace('../','');//找到第二次absolute=path.resolve(dir,relative);}returnabsolute;}我不敢说这是最好的处理方式,但至少是一种可行的处理方式。单元测试使用mocha测试工具进行单元测试,should.js作为断言库。在笔者看来,结合TDD进行开发,单元测试只是作为开发的辅助手段,避免在开发过程中出现一些致命的错误。本文不展开如何编写单元测试,具体实现可以点这里。优化文章在Postcss的官方GithubRepo中,有一个PluginGuidelines。深为认同其所提倡的“做好一件事,做好”,所以在基本完成插件功能后,笔者做了以下优化工作。更友好的日志提示官方其实是推荐使用内置的result.warn来代替console.log或console.warn来显示日志信息(原因是说有些PostCSS处理器会忽略这样的控制台日志输出)。不过笔者尝试后发现,官方功能下提示的信息会很长。后来他借助chalk模块,采用封装console.log的形式,增加了高亮信息的展示。锦上添花当用户在编写CSS代码时,background-image的url可能会出现以下情况:输入的是一个目录,输入到一半后保存非图片路径。这是一个盲输入场景。很多,但对于插件来说,这只是能不能找到的问题。在处理这些错误场景时,也细分为“文件不存在”或“不是图片文件”,让这类错误提示更加友好。提示双图不对如果用户引用的双图(类似xxx@2x.png)宽高不均匀,会有相应提示。以上错误提示的实际运行效果如下:READMEPostCSS英文版官方推荐README.md要用英文写,其他语言的写法和README.zh类似。医学博士。维护一个changelog根据建议,更新历史和其他数据也放在一个名为CHANGELOG.md的文件中,并采用语义版本号。根据自己的开发习惯,在Github上的Repo中也放置了一个LICENSE文件。发布文章发布到npm官方发布到npm官方的步骤这里不再详述。只是分享一个增加版本号的好方法(告别手动更改packup.json的版本号)。npmversionpatch=>z+1npmversionminor=>y+1&&z=0npmversionmajor=>x+1&&y=0&&z=0和上面提到的语义版本号有关,vX.Y.Z(Majorversionnumber.Minorversionnumber.Revisionnumber)三个选项分别对应版本号的三部分。每次运行该命令时,相应的版本号将递增1,而subversion号将重置为零。请记住在运行上述命令之前将文件更改提交给git。然后运行??npm发布命令。发布到PostCSS官方Postcss官方主页有一个插件列表文件,显示所有第三方插件。如果提交的话,Fork一份,在文件中添加自己的插件详情,然后提交合并,等待作者的许可。发布到postcss.partpostcss.parts是一个非官方的PostCSS插件搜索平台。按照这些说明提交您自己的插件。其实本质也是Fork然后在Pullrequest中添加信息的方法,这里不再赘述。结束效果作者开发了postcss-lazyimagecss插件后,按照上述发布方式提交给了官方。后面的效果还不错,PostCSS的作者还提了一个star和一个issue。PostCSS官方推特上的推荐也带来了第一批观星者。为此,在第三届中国CSS大会上,我也有幸结识了PostCSS作者艾大神,并得到了他赠送的俄罗斯巧克力。思考在笔者看来,PostCSS是一个CSS转换引擎,其不参与细分功能实现而交给第三方插件的设计理念使其成为一个非常开放的生态。但是笔者并不认同这种开放机制下的一些情况,比如一些插件把CSS写成中文(当然这样更多是为了好玩),一些自定义的CSS属性比如size:10px2px等。insteadofwidth/heightPlug-ins-在我看来,PostCSS插件应该在符合CSS标准语法的基础上进行更多扩展。但无论如何,我还是很佩服作者开发了这样一个造福前端社区的工具;也因为同意作者的观点,所以写这篇文章是为了做一点推广PostCSS的工作;也希望看到文末的你能有所帮助,积极投身于开源创作事业。参考文章:http://ai.github.io/postcss-way/https://github.com/postcss/po...https://css-tricks.com/want-m...
