最近在做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解决前进行预处理的hook
  • addPostProcessor: 增加后处理器,能够配置源码在通过less解决后进行后续加工的hook
  • addFileManager: 增加一个文件管理器,能够扩大文件解析,次要是解决@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-csscsswring的进行压缩解决;应用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等构建框架中,有其余的形式替换掉前置/后置处理器,如webpackless-loader前或者后增加其余loader就能够实现等同成果。而语法拜访器插件,只有在有本人的less语法糖/自动化解决流程在less原生无奈提供时,须要开发插件。对于css的一些语法糖或者自动化解决(如主动将px替换成rem)倡议应用postcss开发插件。