# qml 文件编译器 qcc

qss 编译器 qcsc，可以参考<https://git.code.oa.com/QQMiniApp/qcsc>

目标：

- 实现`qml`文件编译器，对齐微信的`wcc`编译器
- 完善的测试用例支持
- 完善的性能测试
- 客观的编译性能
- 支持 windows\linux\macos

## 项目如何使用 qcc

tnpm 安装 qcc 依赖：

```sh
tnpm install @tencent/qcc --save
```

代码示例：

```nodejs
let QCC = require('@tencent/qcc');
let qccCompileConfig = {
  cmd: ['-d', '-cc', '-gn', '$gwx'], //指定运行的参数
  FILESBASE: '/Users/xxx/proj/miniprogram/proj1/' //项目文件的目录,
  FILES: [
    "./pages/index/index.wxml", // 项目文件列表
  ]
};
// 异步调用
QCC(qccCompileConfig).then(function (qccRes) {
  /*qccRes: {code: 'output code', templatesObjs: {}}*/
  let code = qccRes.code; // 编译后的代码
}).catch(function (err) {
  /*err: {code: -1, message: 'error message'}*/
  console.error(err);
});
// 也可以使用同步调用
let qccResSync = QCC.QCCSync(qccCompileConfig)
let codeSync = qccResSync.code;
```

## 本地开发 qcc

### 下载代码

```sh
git clone git@git.code.oa.com:QQMiniApp/qcc.git
cd qcc
tnpm install
```

### 代码提交前的自动化测试：**_已经累计上千个测试用例_**

样例只支持`macOs`和`windows`，不支持`linux`

1 正向用例（qcc 和 wcc 的正常运行且运行结果必须完全一致）

```sh
npm run test
```

2 反向用例（qcc 和 wcc 都必须运行报错，提示开发者报错信息，报错信息不要求完全一致）

```sh
npm run test:fail
```

### 代码提交前的性能测试

样例只支持`macOs`和`windows`，不支持`linux`

```sh
npm run benchmark
```

## 关于微信的 wcc 工具

微信开发者工具中的二进制编译器，用来将`wxml`和`wxs`文件，编译成`js`文件，

`js`文件在`jsCore`中执行后，可以得到`$gwx`函数，`$gwx`函数用来生成渲染页面

需要的虚拟`dom`节点的原始数据。

`mac`版本`wcc`文件地址: <https://git.code.oa.com/QQMiniApp/qcc/blob/master/test/wcc>
`windows`版本`wcc`文件地址: <https://git.code.oa.com/QQMiniApp/qcc/blob/master/test/wcc.exe>

如何获得`wcc`？ [下载 mac 版本微信开发者工具](https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html)，安装后，打开`Applications`目录，找到微信开发者工具，右键`Show Package Contents`，在`Contents/Resources/package.nw/js/vender/wcc`（该目录可能会被调整）。

### wcc 编译用法

```sh
wcc [-d] [-o OUTPUT] [-xc XComponentDefine] [-om XComponentDefine] [-cb [callback.js...]] <FILES... | -s <SINGLE_FILE>
```

## 已经完成的功能

### 编译参数支持

#### [-d] 参数 output code for debug

debug info，wxml 模板的调试信息输出

已经完美支持。

#### [-cc] 参数 output compelete code for custom component

输出完整的自定义组件代码

已经完美支持。

#### [-ds] 参数 insert debug wxs info

debug info，wxs 代码的调试信息输出

主要是在 z 数组中添加数据的文件名、行号和列号等信息

已经支持

#### [-xc] 参数 output simplified code for custom component

输出简化的自定义组件代码

支持了部分特性，因为`wcc`的简化策略比较无规则，还没完美支持。

#### [-cb] 参数 add life cycle callback

暂不支持

#### [-om] 参数

暂不支持

#### [--config-path] 参数

windows 下，命令行参数太多会有问题，

当参数太长，将所有的命令行参数写入文件中，传递一个文件名给 wcc

不支持，qcc 的实现暂时不需要跨进程。

#### [-p] 参数

插件编译，需要传入-p 参数

已经支持

### 其他编译参数

#### [-cache]

如果需要启用编译 cache，可以开启该参数。

如果是服务器端，不建议开启该参数，因为服务器端内存缓存的作用不大，命中率不高。

编译的 cache 主要是两个方面，一个是 qml->ast 的 cache，另外一个是数据绑定->数据绑定目标代码的 cache。

根据压测结果，开启该参数至少有 30%的编译耗时提升。

### 自动化测试

运行：

```sh
npm run test
```

```sh
npm run test:fail
```

测试样例地址：

<https://git.code.oa.com/QQMiniApp/qcc/tree/master/test/succSuit>
<https://git.code.oa.com/QQMiniApp/qcc/tree/master/test/failSuit>

完善的测试样例，用例还在持续增加，以覆盖到大部分的场景。

正向用例-测试流程要点：

1. `qcc`将完整的小程序源码，编译成`js`代码[code.qcc.js](https://git.code.oa.com/QQMiniApp/qcc/blob/master/test/succSuit/suit1/out/code.qcc.js)
2. `wcc`将完整的小程序源码，编译成`js`代码[code.wcc.js](https://git.code.oa.com/QQMiniApp/qcc/blob/master/test/succSuit/suit1/out/code.wcc.js)
3. 运行`code.qcc.js`获取`$gwx`函数`qcc$gwx`
4. 运行`code.wcc.js`获取`$gwx`函数`wcc$gwx`
5. 对于每个小程序页面`path`，运行`qcc$gwx`获取渲染函数并运行该渲染函数，获取类似虚拟`dom`的数据结构[path.qcc.vd](https://git.code.oa.com/QQMiniApp/qcc/blob/master/test/succSuit/suit1/out/0_.index.wxml.vd.qcc.json)
6. 对于每个小程序页面`path`，运行`wcc$gwx`获取渲染函数并运行该渲染函数，获取类似虚拟`dom`的数据结构[path.wcc.vd](https://git.code.oa.com/QQMiniApp/qcc/blob/master/test/succSuit/suit1/out/0_.index.wxml.vd.wcc.json)
7. 对比`path.qcc.vd`和`path.wcc.vd`是否完全一致

备注：

有的样例，没有使用 wcc，而且需要预先定义还预期的输出

反向用例-测试流程要点：

测试样例均是不合法的样例

1. `qcc`编译样例项目，报错
2. `wcc`编译样例项目，报错
3. `qcc`和`wcc`是否都报错了

### 性能测试

运行：

```sh
npm run benchmark
```

复用了自动化测试的测试样例，对比测试了`qcc`和`wcc`编译相同的小程序项目的耗时对比

### 各种边缘条件的覆盖和处理

持续迭代

### 静态节点标记

`qcc@1.0.14` 版本开始支持，编译参数需要增加`-msn`，意思是`mark static node`。

标记距离根节点最近的静态子节点。

什么是静态子节点：

1. `ML`节点的属性不存在数据绑定，同时他的所有子节点的属性不存在数据绑定。
2. 如果是`text`节点，`text`节点不存在数据绑定。

什么是距离根节点最近：

```none
root -> staticNode1 -> staticNode2 -> staticNode3
```

`root`为根节点，`staticNode1`、`staticNode2`和`staticNode3`为静态子节点。

`staticNode1`距离`root`最近，只需要标记`staticNode1`，`staticNode2`和`staticNode3`不需要标记。

标记属性：

`tag`节点增加了`isStatic`属性和`rawHash`属性

`isStatic`为`true`，表示是一个静态子节点，`rawHash`为该静态子节点的`hash`值。如下所示：

```json
{
  "tag": "wx-page",
  "children": [
    {
      "tag": "wx-view",
      "attr": {
        "class": "className"
      },
      "n": [],
      "raw": {},
      "generics": {},
      "isStatic": true,
      "rawHash": "0e2c8e5985ff305bc6d40148666830b1724f31457a9201fac0e928c3bff5d529"
    }
  ]
}
```

## 数据绑定的细节

请参考:

[数据绑定](./dataBinding.md)

[数据绑定细节](./dataBindingDetail.md)

## 代码目录

- `benchmark`压测代码存放目录
- `docs`文档相关目录
- `script`一些脚本
- `src`逻辑代码目录
- `test`测试代码目录

## 目标代码的格式

目标代码的格式，可以阅读微信的`wcc`工具生成的目标代码

- [目标代码 1](./docs/wcc.out.0.js)添加了注释
- [目标代码 2](./docs/wcc.out.1.js)一个小 demo 生成的目标代码

## 更多的细节

参考`docs/qcc&qcsc.pptx`，<https://git.code.oa.com/QQMiniApp/qcc/blob/master/docs/qcc&qcsc.pptx>的`qcc`部分
