乐趣区

关于less:less预处理插件开发

最近在做 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 开发插件。

退出移动版