最近在做 less 的一些语法上的解决,想把 less 的变量在编译时主动转为 css 的变量应用形式,进而学习了一下 less 的预处理插件开发。
less 反对的插件分为两种模式,一种是一般插件,次要是扩大 less 的运行时语法 (less 默认函数) 插件,通过 @plugin
语法应用。这种形式的次要应用请参考官网文档,本文不做探讨。
预处理插件能够对编译的源码在编译前,编译后和编译中进行一些代码调整。也能够增加一些对 less 进行扩大,如文件系统的扩大等。因为官网还没有对这一块有对应的文档形容,所以本文用于在官网文档进去之前,依据本人的应用教训记录的一些最佳实际。
预处理插件代码构造
一个简略的预处理插件的格局为:
class LessPlugin {constructor(options) {this.minVersion = [3, 0, 0];
this._options = options;
}
install(less, pluginManager, functions) {// 插件初始化逻辑}
setOptions() {}
printUsage() {return '';}
}
插件须要是一个领有 install
办法的对象,会被注入三个参数less
, pluginManager
, functions
:
less
: 以后 less 对象;pluginManager
:插件管理器,用于注册具体的插件;functions
:外部办法管理器,@plugin
应用的一般插件就是通过functions.add
增加下来的
预处理插件次要是通过 pluginManager
对象增加,它的要害 API 有:
addVisitor
:增加 ast 语法拜访器,能够在编译过程中批改或者查看语法操作;addPreProcessor
:增加预处理器,能够配置源码在通过 less 解决前进行预处理的 hookaddPostProcessor
: 增加后处理器,能够配置源码在通过 less 解决后进行后续加工的 hookaddFileManager
: 增加一个文件管理器,能够扩大文件解析,次要是解决@import
的资源导入解析问题
能够查看官网举例的插件列表.
另外插件对象中请尽量设置 minVersion
属性,用来表明该插件兼容的最低的 less 版本;setOptions
是用于承受 less
命令行形式执行时传入的参数,在构造函数中承受的选项是因为创立插件对象采取类的形式,所以能够在构建函数中承受这个对象创立的前置参数,也就是能够用于应用代码的形式配置插件时的传入的选项;printUsage
也是在应用命令行形式注入插件(--plugin=xxxx
),打印帮忙的时候输入的提示信息。
构建过程中批改源码的插件
addVisitor
能够加一个 less 的 ast 语法处理器,在编译过程中对语法进行解决(如将 less 变量的应用改为 css 变量的应用)。一个简略的语法拜访器的插件创立为:
class LessPlugin {
// ...
install(less, pluginManager, functions) {
// 注册拜访器,拜访器为一个对象
pluginManager.addVisitor(new ExampleVisitor(less, this._options));
}
// ...
}
class ExampleVisitor {constructor(less, options) {
this._less = less;
this._options = options;
this._visitor = new less.visitors.Visitor(this);
}
run(root) {return this._visitor.visit(root);
}
// ... 其余代码
}
对于拜访器对象,咱们同样采取 class 形式创立。个别状况下,都须要将 less
对象传入,用于拜访 less
相干 api。
在拜访器对象中,最要害的就是 run
函数了,在语法被解析后,生成了 ast 构造后,就会执行 run
函数。此时传入的 root
就是整个代码的 ast 语法树,run
函数执行结束后须要返回一颗新的 ast 语法树。能够间接在 run
函数中对整个 root
进行遍历和批改,但更加举荐应用 less
自带的语法树拜访工具(new less.visitors.Visitor(this)
), 在 run
函数中启动拜访语法树this._visitor.visit(root);
, 这样就能够像 babel 一样,写一些节点拜访函数更加优雅的遍历节点树:
class ExampleVisitor {constructor(less, options) {
this._less = less;
this._options = options;
this._visitor = new less.visitors.Visitor(this);
}
run(root) {return this._visitor.visit(root);
}
visitRuleset(node, visitArgs) {
// 批改逻辑
return node;
}
}
visitRuleset
就是在遍历节点树时,碰到的每一个申明集 ({}
包裹的代码,整个文件尽管没有被 {}
包裹也属于一个申明集下)都会被调用。如果你想要在拜访这个节点外部节点之后,也就是行将来到的时候函数被触发 (冒泡的概念),只须要在函数后加Out
即可:
visitRulesetOut(node, visitArgs) {
// 批改逻辑
return node;
}
less 中领有的节点类型次要有:AtRule
, RuleSet
, Declaration
,所有的类型能够查看源码中对于节点的定义目录下的所有节点和每个节点的属性定义。
因为 css 这一块的 ast 标准没有找到,并且看了下 postcss 跟 less 差别很大,可能没有规范的,所以就不去定义每一个节点的名称,这里能够阐明下
AtRule
为@
像的解析,在 less 中不蕴含变量外的一些配置,如@charset
;RuleSet
为申明集,Declaraion
为一个申明,如font-size: 200px
。
咱们能够在拜访节点函数中,对节点进行革新:
visitDeclaration(node) {const { name, value: valueNode} = node;
const {value} = valueNode;
if (name === 'font-size' && value.endsWith('px')) {valueNode.value = `${Number.parseFloat(value) / fontBase}rem`;
}
return node;
}
对 node
对象的属性进行革新,就是调整了代码,如下面的代码中,就是将设置为 px
单位字体大小主动调整为反对响应式的 rem
单位。
在革新时,须要留神,less 的 ast 中,值会被多层的 Value
节点包含。开发时,能够 debugger 一下,看一下这些 value
节点的构造。
less 中会对申明的值进行解析,这点跟 postcss
是有区别的,并且值的解析进去的 ast 构造十分丰盛。
下面的案例都是对 node 进行小小的革新,如果是须要替换节点类型(替换整个节点),能够这样:
visitDeclaration(node) {const { name, value: valueNode} = node;
const {value} = valueNode;
if (value === 'red') {
return new this._less.tree.Call(
'var',
[new this._less.tree.Keyword('--red')],
);
}
return node;
}
上文代码含意为如果一个款式的值是red
, 将其替换成 css 变量var(--red)
。
如果须要替换节点,间接依据已有节点的一些值创立一个新的 less.tree.*
节点对象返回即可。创立新的节点类型须要传入的参数和旧节点类型(能够通过 type
属性查看每个节点的类型)蕴含的值,能够查看源码中节点的定义目录下的节点列表和每个节点的属性定义。
最初,一个主动将 font-size
的值转为反对响应式 rem
的插件简略实现残缺代码为:
class ExampleVisitor {constructor(less, options) {
this._less = less;
this._options = options;
this._visitor = new less.visitors.Visitor(this);
}
run(root) {return this._visitor.visit(root);
}
visitDeclaration(node) {const { name, value: valueNode} = node;
const {value} = valueNode;
if (name === 'font-size' && value.endsWith('px')) {valueNode.value = `${Number.parseFloat(value) / fontBase}rem`;
}
return node;
}
}
class LessPlugin {constructor(options) {this.minVersion = [3, 0, 0];
this._options = options;
}
install(less, pluginManager, functions) {pluginManager.addVisitor(new ExampleVisitor(less, this._options));
}
setOptions() {}
printUsage() {return '';}
}
更多的 visitor 的写法,能够参考 less 中的内置拜访器中代码的写法和一个官网实现的插件中非用法。
前置解决插件
addPreProcessor
能够增加一个前置解决插件。预处理插件个别在 less 构建之前执行,可用于代码的预处理和其余类型插件的前置筹备工作。
一个简略的前置解决插件为:
class LessPlugin {
// ...
install(less, pluginManager, functions) {
// 注册拜访器,拜访器为一个对象
pluginManager.addPreProcessor(new ExamplePreProcessor());
}
// ...
}
class ExamplePreProcessor {process(src, processArgs) {//}
}
前置处理器为一个带 process
函数的对象。process
函数会被注入一个源码内容和上下文对象,须要返回一个新的字符串。在上下文对象中,能够拿到如以后解决的文件信息等。
因为前置处理器插件需要场景特地少,这里就不写案例。能够参考 less-plugin-sass2less。
后置处理器
能够通过 addPostProcessor
增加后置处理器,用于对 less 生成的 css 代码做后续解决,如压缩等。less 的后置处理器为一个携带 process
办法的对象,能够应用类创立。一个简略的后置处理器代码示意:
class LessPlugin {
// ...
install(less, pluginManager, functions) {
// 注册拜访器,拜访器为一个对象
pluginManager.addPostProcessor(new ExamplePreProcessor());
}
// ...
}
class ExamplePreProcessor {process(src, processArgs) {//}
}
process
函数会被注入一个源码内容和上下文对象,须要返回一个新的字符串。在上下文对象中,能够拿到如以后解决的文件信息等。
后置处理器跟前置处理器相似,但比前置处理器应用场景更广,官网提供的插件也基本上为后置处理器。后置处理器中,能够集成其余工具,进行一些 css 代码后续操作:比方集成 clean-css
或csswring
的进行压缩解决;应用 csscomb
进行格式化解决;还能够集成 postcss
应用整个 postcss
生态,如Autoprefixer
。
因为处理器的 process
只承受同步解决,所以在集成第三方框架的时候,须要应用他们的同步形式。但 postcss
官网都是异步应用案例,能够参照下文这样同步应用:
class ExamplePreProcessor {process(src, processArgs) {const result = postcss([...postcssPlugins]).process(src, {
from: filename,
// ....
});
return result.css;
}
}
文件管理器
能够通过 addFileManager
增加文件管理器,用于扩大 @import
的解决。一个规范的构造能够为:
class LessPlugin {
// ...
install(less, pluginManager, functions) {
// 注册拜访器,拜访器为一个对象
pluginManager.addFileManager(buildExampleFileManager(less));
}
// ...
}
function buildExampleFileManager(less) {
class ExampleFileManager extends less.FileManager {supports(filename, currentDirectory, options, environment) {// ...}
supportsSync(filename, currentDirectory, options, environment) {// ...}
resolve(filename, currentDirectory) { },
loadFile(filename, currentDirectory, options, environment){ },
loadFileSync(filename, currentDirectory, options, environment){ },
tryAppendExtension(path, ext) { },
tryAppendLessExtension(path){},}
return new ExampleFileManager();}
两头的要害函数接口的实现,能够参照内置的 less.FileManager
实现,或者官网提供的 less-plugin-npm-import 实现。
写在最初
尽管 less 提供了多种预处理插件,然而如果是在类 webpack
等构建框架中,有其余的形式替换掉前置 / 后置处理器,如 webpack
在less-loader
前或者后增加其余 loader
就能够实现等同成果。而语法拜访器插件,只有在有本人的 less 语法糖 / 自动化解决流程在 less 原生无奈提供时,须要开发插件。对于 css 的一些语法糖或者自动化解决 (如主动将px
替换成 rem
) 倡议应用 postcss 开发插件。