扩展新端编译插件
参考cml-demo-plugin/index.js
文件 实现编译类。对项目中的每一个文件或者部分进行编译处理。处理节点的source
字段,编译后结果放入output
字段。
1 模板编译
监听compile-template
事件
参数列表:(currentNode,parentNodeType)
- currentNode 当前处理的节点
- parentNodeType 父节点的nodeType,如果是app/page/component节点的子节点会有值,否则为undefined
说明:
这个钩子用于处理cml
文件的模板部分,如果模板是类vue语法,内部已经将其转为标准的cml语法,这个阶段用于对模板语法进行编译,生成目标代码。
compiler.hook('compile-template', function(currentNode, parentNodeType) {
currentNode.output = templateParser(currentNode.source)
})
mvvm-template-parser
这个npm包提供了模板编译的方法。
const {cmlparse, generator, types: t, traverse} = require('mvvm-template-parser');
- cmlparse 将字符串转为ast语法树
- traverse 对语法树进行遍历
- types 语法树节点类型判断
- generator 语法树生成字符串
例如模板编译方法如下:
const {cmlparse, generator, types: t, traverse} = require('mvvm-template-parser');
module.exports = function(content) {
let ast = cmlparse(content);
traverse(ast, {
enter(path) {
let node = path.node;
if (t.isJSXElement(node)) {
let attributes = node.openingElement.attributes;
attributes.forEach(attr=>{
if(t.isJSXIdentifier(attr.name) && attr.name.name === 'c-for') {
attr.name.name = 'wx:for'
}
if(t.isJSXIdentifier(attr.name) && attr.name.name === 'c-if') {
attr.name.name = 'wx:if'
}
})
let tagName = node.openingElement.name.name;
if(/^origin\-/.test(tagName)) {
let newtagName = tagName.replace(/^origin\-/,'');
node.openingElement.name.name = newtagName;
node.closingElement.name.name = newtagName;
}
}
}
});
return generator(ast).code;
}
上面的方法就可以将 模板
<view>
<view c-for="{{array}}">
</view>
<view c-if="{{condition}}"> True </view>
<origin-button></origin-button>
</view>
编译为
<view>
<view wx:for="{{array}}">
</view>
<view wx:if="{{condition}}"> True </view>
<button></button>
</view>
对模板ast的编译本质上是对目标节点的增删改,通过类型判断确定目标节点.
可以使用网站https://astexplorer.net/
方便我们确定节点类型。该网站是将ast中的节点图形化展示出来,注意选择javascript
,babylon7
jsx
。
增删改的api参考babel插件编写文档https://github.com/jamiebuilds/babel-handbook/blob/master/translations/zh-Hans/plugin-handbook.md#toc-replacing-a-node-with-multiple-nodes
。
我们没有直接让用户采用babel系列 是因为对generator和parser
内部都有做针对cml的改造。
原生组件的处理
chameleon中规定,在模板中使用origin-组件名称
作为组件名称,代表使用各端原生组件。例如
<template>
<origin-button></origin-button>
</tempalte>
代表使用原生的<button></button>
组件,chameleon对于模板的标准编译没有处理origin-组件名称
这种标签名称,原因是能够让用户根据组件的名称区别组件是否是原生组件而做不同的处理,例如原生组件的事件不做代理。所以最后用户的模板编译中应该对origin-组件名称
这种组件名称进行替换。替换方法如下:
traverse(ast, {
enter(path) {
let node = path.node;
if (t.isJSXElement(node)) {
let tagName = node.openingElement.name.name;
if(/^origin\-/.test(tagName)) {
let newtagName = tagName.replace(/^origin\-/,'');
node.openingElement.name.name = newtagName;
node.closingElement.name.name = newtagName;
}
}
}
});
注
模板编译总体上是要将所有的CML的语法编译成目标端的语法,可以参考chameleon-template-parse中的编译实现。 比如微信端包括如下几方面的实现:
- 标签的替换,比如
slider-item
标签替换成swiper-item
。 - 属性的替换,c-if c-else c-else-if c-show c-for c-text c-key。
- 动态组件,component is 动态组件的支持。
- style与class的编译处理
- c-model的实现 配合运行时mixins代理事件与处理事件对象
- c-animation的处理
- c-bind 事件绑定 配合运行时mixins代理事件与处理事件对象
2 样式编译
监听compile-style
事件
参数列表:(currentNode,parentNodeType)
- currentNode 当前处理的节点
- parentNodeType 父节点的nodeType,如果是app/page/component节点的子节点会有值,否则为undefined
说明:
这个钩子用于处理所有的style
节点,内部已经对less stylus等语法进行编译处理,这里得到的已经是标准的css格式,可以转成对应端的样式,比如对尺寸单位cpx的转换,将css转成对象形式等。
compiler.hook('compile-style', function(currentNode, parentNodeType) {
currentNode.output = styleParser(currentNode.source);
})
推荐用户使用postcss
或者rework
进行编译,参考cml-demo-plugin/styleParser.js
的实现。利用了chameleon-css-loader
中的postcss插件进行编译。例如:
const postcss = require('postcss');
const cpx = require('chameleon-css-loader/postcss/cpx.js')
const weexPlus = require('chameleon-css-loader/postcss/weex-plus.js')
module.exports = function(source) {
let options = {
cpxType: 'rpx'
}
return postcss([cpx(options), weexPlus()]).process(source).css;
}
上面方法可以将 内容
.test {
font-size: 24cpx;
lines: 1;
}
编译为:
.test {
font-size: 24rpx;
lines: 1;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
3.3 script编译
参数列表:(currentNode,parentNodeType)
- currentNode 当前处理的节点
- parentNodeType 父节点的nodeType,如果是app/page/component节点的子节点会有值,否则为undefined
说明:
这个钩子用于处理script
节点,内部已经对js文件进行了babel处理,这个阶段用于做模块的包装,compiler.amd
对象提供了amd模块的包装方法,模块id使用节点的modId
字段。例如:
compiler.hook('compile-script', function(currentNode, parentNodeType) {
currentNode.output = compiler.amd.amdWrapModule(currentNode.source, currentNode.modId);
})
例如一个节点source如下,modId为'./component/test.js':
module.exports = function() {
return 'test';
}
经过compiler.amd.amdWrapModule(currentNode.source, currentNode.modId)
处理后成为
cmldefine('./component/test.js', function(require, exports, module) {
module.exports = function() {
return 'test';
}
})
3.4 asset编译
静态资源的编译和script
节点的编译相同,因为cml内部已经将静态资源节点变成了资源的publicPath字符串。
例如src/assets/img/chameleon.png
这个节点的source为
module.exports = 'http://168.1.1.1:8000/static/img/chameleon.png'
经过compiler.amd.amdWrapModule(currentNode.source, currentNode.modId)
处理后成为
cmldefine('./component/test.js', function(require, exports, module) {
module.exports = 'http://168.1.1.1:8000/static/img/chameleon.png';
})
更多编译事件参考 扩展新端手册。
3.5 编译打包与文件输出
参考cml-demo-plugin/index.js
中对compiler.hook('pack', function(projectGraph) {})
pack
事件的实现,编译编译图,拼接目标文件,调用compiler.writeFile
输出文件。