发布网友 发布时间:2024-09-17 02:28
共1个回答
热心网友 时间:2024-09-17 20:57
公司的前端项目基本上都是用的scss预处理器,但是有两个Vue项目因为历史原因是stylus和scss混用的情况,所以有了将stylus转为scss的需求。经过一番搜索,我们找到了stylus-converter这个插件,但是仍然还存在一些需要手动机解决的问题。
发现问题首先按照教程操作一波目录的转译:
#下载stylus-converternpminstall-gstylus-converter#进入项目目录mvsrcsrc-temp#运行cli转换目录stylus-conver-dyes-isrc-temp-osrc很快就转译结束了,赶紧run一下,结果马上出问题:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js好家伙,原来在入口文件引入的一个styl文件被改成scss文件后找不到了,这个插件只编译了.stylus后缀的文件和.vue中的包含stylus字符串的<style>标签,源码中对应的正则是/\.styl$/,/\.vue$/和/<style(.*)>([\w\W]*?)<\/style>/g。
手动把import的stylus文件都改成对应的scss文件后,再次查看控制台,发现还是报错,有一堆的mixin找不到import,转译前后的import应该是不会变更的,为什么在转移后就找不到了呢?猜想是配置了stylus的全局导入,一看webpack的配置果然如此,项目用来style-resources-loader将stylus样式预导入了全局的mixin,将其改成对应的scss导入即可。
计算表达式转译错误再次运行,这回是新的报错:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?转译前是:flex:00(100/n)%转移后:flex:00,100/$n%;
很显然这在sass是一个语法错误,看了下这类数量不多,就手动改掉了,比如这个改成了:flex:00,(100/$n)*1%;
样式穿透错误再次运行,发现了新的报错:
SassError:expectedselector.?5│margin:20px00;/deep/.table{│^?转译前是:
.xxclassnamemargin:20px00>>>.table//...看了下源码是这样子替换深度选择器的:
result=result.replace(/(.*)>>>(.*)/g,`$1/deep/$2`)我看了下代码里的深度选择器,都是可以直接通过字符串替换解决的,也就是/deep/替换成::v-deep,于是一键替换了。
这里还有个注意点,就是可能有这样的代码:/deep/div{,如果简单的替换成::v-deepdiv{那肯定有问题,所以替换的::v-deep是包含一个空格符的。
Mixin签名差异再次运行,发现了新的报错:
SassError:Only0argumentsallowed,but2werepassed.┌──>src/views/trace/detail.scss6│@includefontHeight(30px,12px);│^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^invocation?┌──>/Users/grapevine/Documents/chan/cmm-wap/src/views/trace/detail.vue12│@mixinfontHeight(){│━━━━━━━━━━━━declaration?看下源码:
fontHeight()if(length(arguments)==1)height:arguments[0]line-height:arguments[0]font-size:arguments[0]elseheight:arguments[0]line-height:arguments[0]font-size:arguments[1]转移后:
@mixinfontHeight(){@iflength($arguments)==1{height:map-get(arguments,0);line-height:map-get(arguments,0);font-size:map-get(arguments,0);}@else{height:map-get(arguments,0);line-height:map-get(arguments,0);font-size:map-get(arguments,1);}}使用这个mxin的地方:
@includefontHeight(30px,12px);好家伙,原来是stylus的@mixin的签名里参数可以为空,实际使用时可以传入参数,通过arguments关键字去获取参数。而scss必须在mixin签名显示指定参数,于是乎查阅scss文档然后改造了一番:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js0scss可以使用剩余参数,然而比较不符合直觉的是这个剩余参数是index是从1开始的……
好了,到这里控制台就没有报错了,但是还不能高兴太早,run起来看看。
果然最担心的事情还是发生了,没有报错,但是界面上的样式明显是有问题的,经过一番定位,发现了几个问题。
CSS属性同名的Mixin问题首先发现的是css属性同名的Mixin问题,因为stylus全局定义了一些Mixin,并且用webpack插件进行全局导入,而stylus使用Mixin是不需要显示指定@include,所以会出现如下情况:
转译前:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js1Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js2而在转译时,检测到border-top是一个合法css属性,所以不会将其解析成mixin。真是令人头疼。
一个比较好的解决方法是在stylus语法分析时,将和我们定义的mixin同名的css属性标记成mixin而不是普通的属性,但是鉴于对stylus语法分析流程不熟悉,所以退而求其次:先遍历一次找到所有的mixin,收集到和css同名的mixin集合中;然后在遍历第二次,在遍历css属性时判断该属性是否在和css同名的mixin集合中,如果是则打印当前文件名和代码位置,然后手动去修改代码。
说干就干,首先将项目clone到本地,然后用vscode进行调试,编写launch.json:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js3通过debug很快就能找到stylus的解析器:node_modules/stylus/lib/parser.js,在Parser这个函数上定义了状态机针对不同词法的解析,我们首先需要找到mixin节点在解析后的节点类型是什么样的。
在lib/index.js中,我们可以找到这样一段代码:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js4取消这个注释,我们写一段简单的stylus代码看看ast是什么样的,定义一个mixin:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js5打印结果:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js6可以看到这个节点就是我们要的:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js7接下来去paser.js中寻找语句的解析:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js8打印结果:
Theserelativemoduleswerenotfound:*xxx/xxx.stylin./src/main.js9可以发现和整棵ast树中对应的节点是一致的。
我们也能发现mixin节点的特性,所以我们在每次statement解析后收集它:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?0一运行,结果都是空的,咋回事呢?通过debug我们发现,其实它的节点长这样:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?1仔细一番查看发现是这些节点都重写了原型上的toJSON方法,导致打印出来的节点有所差异,可以在node_modules/stylus/lib/nodes查看这些节点。
所以我们需要修改代码:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?2再次运行,可以发现其正确收集到了所有的mixin:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?3我们需要从这个集合中挑出和css属性同名的mixin。
接下来需要找到属性是否包含mixin,需要在parser的indent中的atrule情况判断:
case'atrule':constp=this.property();//检测所有的属性是否包含指定的MixinconstMixins=SassError:Expectedexpression.?78│flex:00,100/$n%;│^?3letcurrentMixin=[];if(Array.isArray(p.segments)&&p.segments.some(e=>{currentMixin=Mixins.filter(mixin=>mixin==e.name)returncurrentMixin.length})){//输出当前文件位置和currentMixin}}returnp那么问题来了,如何获取当前文件位置呢?在Parser遍历节点的过程中并没有上下文,无法自上而下传递信息,我想到的一个方法是用全局属性,在读取文件的时候把文件名挂载到global.DIR属性上,但是这样子必须确保文件的IO操作是同步的,所以我将源码中的文件API从异步改成了同步,比如fs.readFile改成fs.readFileSync等。
在文件读取的入口bin/convertStylus.js中的functionconvertStylus中fs.readFileSync后记录当前文件的位置:global.DIR=input;。
回到我们节点属性的判断:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?5运行之后,成功获取到每个和css属性同名mixin的引用位置,可以手动去修改它。
插件去括号的问题在一桶猛如虎的操作后,接着又发现了一个样式问题,在浏览器元素审查后发现这个报错:
transform:translate(-50%,-50%)scale(10/12,10/12);
除法符号没有计算成功,查看代码:
转译前:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?6转移后:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?7唉我括号呢?
这回我们在插件的代码中去寻找解决方法,因为我发现lib/index.js中的visitNode()会遍历每个节点,在这里寻找stylusAST中有/计算符号的节点:
SassError:Expectedexpression.?78│flex:00,100/$n%;│^?8然后手动去把括号加上。
总结总结一下遇到的3个问题:样式穿透选择器只能转译成/deep/是有问题的,应该提供一个可选项让用户选择什么样的选择器;Mixin签名差异、计算表达式和去括号的操作有明显的bug;而CSS属性同名的Mixin问题我觉得比较特殊,因为我们的开发小伙伴当初应该是为了解决1px的问题而做的,虽然这种做法极其不推荐,但是插件在这方面还是有优化空间的。
一开始没有想到会踩这些坑,早知道如此,就在一开始从AST的角度去解决问题,而不是这种半自动化的操作。如果时间充足,倒是想给作者提一下PR,但是限于时间关系,只能止步于此。
最后还是感谢sylus-convert这个插件给我们的业务带来的便利。
原文:https://juejin.cn/post/7097491392854753287