# Amove
# Amove 是什么?
专为代码编译而设计,结构化执行流程,原子级别的插件化机制。
Amove 是一个代码编译底层框架,采用结构化的插件机制管理所有编译事务并以相应的规则保证所有编译事务以一种可预测的流程执行。基于 Amove 实现的编译模型可以很容易实现增量编译,热更新。
Amove 中引入了编译原子的概念,基于编译原子可以实现强大的插件功能,基于此可以将整个编译流程暴露给用户,提供强大的定制化能力,这也是 Amove 最为强大的功能特性。
# 什么是代码编译?
代码编译涉及软件开发的多个领域,比如编译器、解释器实现。在前端领域中,比较知名的基于编译技术实现的工具有 babel、postcss、typescript 等。
- babel - babel 是一个 JavaScript 代码编译工具,借助于 babel 可以将 JavaScript 新特性运行在浏览器中。
- postcss - postcss 是一款 css 编译工具,通过它可以实现单位 rem2px 的转换,自动添加前缀等功能
- typescript - typescript 是一门静态类型的语言,其功能是 JavaScript 的超集,借助于 typescript 编译器可以将 ts 编译为常规的 js 代码从而在浏览器上运行。
除了上述例子为还有很多用到了代码编译的工具框架,比如 Angular、vue、react 等,可以说前端发展如此快速也是得益于如果多优秀的代码编译工具的支撑。
# Amove 与代码编译有什么关系?
代码编译即实现编译器的过程,而这个过程是很复杂的,为了保证编译器的长期可维护就必须设计一种可用的架构来支撑,Amove 也因此而诞生,Amove 是一个帮助开发者快速实现一个编译器的工具,开发只需关心局部代码场景的转换扩充。
# 如何使用?
Amove 编译器是基于编译原子组合而成,编写 Amove 应用即编译编译原子,并将编译原子关联起来,最后形成一个编译原子树,最后 Amove 会按照这棵树去执行每一个编译原子,从而实现代码编译。
# 示例
const Compile = require('@amove/core');
const path = require('path');
const input = path.join(__dirname, './src');
const output = path.join(__dirname, './dist');
Compile({
input,
output,
plugins: [
function Js (node, store) {
// 添加一个 Js 类型的编译原子
}
]
});
# 单文件插件
- 新建一个
a-plugin.js
- 编写如下代码
const { useReducer } = require('@amov/next');
const fs = require('fs-extra');
useReducer({
Js () {
// 将 js 内容挂载到 this.$node.content 上
this.$node.content = fs.readFileSync(this.$node.path, 'utf-8');
}
});
在入口文件引入该文件即可
require('./a-plugin.js');
完整代码如下:
const Compile = require('@amove/core');
const path = require('path');
const input = path.join(__dirname, './src');
const output = path.join(__dirname, './dist');
require('./a-plugin.js');
Compile({
input,
output,
plugins: [
function Js (node, store) {
// 添加一个 Js 类型的编译原子
console.log(this.$node.content); // 这里输出的值即为上述插件挂载的内容
}
]
});
# 示例 - antmove 编译原子树
在开始编写代码前可以通过思维导图这样的工具,拆分功能模块,得到的其实就是我们所需要的原子树,然后只需要按照各节点功能实现对应的编译原子即可。
如下是 Antmove 微信小程序到支付宝小程序转换的原子树结构图。
Antmove (opens new window) 是一个小程序转换工具,借助于 Antmove 可以快速实现小程序迁徙。
# 快速开始
# API
- useReducer
将一批编译原子挂载到原子树上。
这一批编译原子也可称为一个插件,它们对应相应的功能。从编译器的角度来说,即给编译器扩充对应的功能。
- runApp
编译入口执行函数,在这里传入编译全局配置
# 编译原子
一个编译原子对应一个函数。
如下是一个简单的编译原子,它的作用是忽略对 dist
目录的编译处理。
// 忽略某一个目录
Directory (node) {
if (node.filename === 'dist') {
node.children = [];
}
}
# 编译原子 API
- addChild
给当前编译原子添加一个子节点。
// 处理 css 文件
useReducer({
File (node) {
if (node.extname === '.css') {
this.addChild('CompileCss')
}
},
CompileCss () {
console.log('a');
}
})
useReducer({
CompileCss: {
hook: 'before',
body (node, store) {
// 这里在 CompileCss 之前插入一个编译原子
console.log('b');
}
}
})
如上所示,给后缀为 .css
的文件添加了一个名为 CompileCss
编译原子作为子节点,当 File 节点的编译事务处理完后就会对该子节点的事务进行处理。在这之后又给 CompileCss
插入了一个 before
编译原子,最终这两个同名原子会合并为一个原子在 File 之后执行,按顺序输出 b
, a
。
- hook
可以通过 hook 给已有的编译原子扩展功能,hook 类型有
- before - 执行前插入
- after - 执行之后插入
- mounted - 所以子节点执行完毕后插入
- replace - 替换编译原子
Amove 只有这么几个核心的 API,基于上述的 API 不断扩充插件会增加编译器的能力,从而具备代码编译的功能。
# 进阶
# Amove 流程图
如上图所示,我们可以看到,Amove 编译分为首次编译和二次编译,首次编译执行的过程中会建立编译原子树,同时基于此结构实现二次编译(增量编译)功能。而变更依赖的收集和依赖更新编译都由 Amove 来处理,用户不用太多关心细节。