estime = ecmascript + runtime, in javascipt(es5) environment
npm install estime基于 TypeScript 编写的 JavaScript 解释器,运行于es的环境,且原生支持es6\jsx等众多常用的新特性。独立、安全。
初版fork于eval5,目标是原生支持es2017(非严格)语法和JSX且修改bug,持续开发中,进度请查看最后的todoList
- 不支持eval Function的 JavaScript 运行环境:如 微信小程序。
- 支持eval的Javascript环境,但是又担心eval的安全性问题。
- 需要代码动态更新的场景。例如你的React应用需要热更新组件;你的规则系统需要动态下发规则脚本等等。
- 研究/学习用
``shell`
npm i estime -S
`javascript
import { Interpreter } from "estime";
const interpreter = new Interpreter({
console,
rt: (val) => (res = val)
});
try {
let res;
interpreter.evaluate(
class Test {
name = 'default_test';
setName = (name) => {
this.name = name
}
}
let t = new Test
t.setName('hello')
console.info(t.name)
rt(t.name)
);`
console.info('the result is ', res)
} catch (e) {
console.log(e);
}
`ts`
interface Options {
// 根作用域,只读
rootContext?: {} | null;
globalContextInFunction?: any;
}
Example
`javascript
import { Interpreter } from "estime";
const ctx = {};
const interpreter = new Interpreter(ctx, {
rootContext: window,
});
interpreter.evaluate(
a = 100;
console.log(a); // 100);
window.a; //undefined
`
global
默认值: {}
设置默认的全局作用域
`js`
Interpreter.global = window;
const interpreter = new Interpreter();
interpreter.evaluate('alert("hello estime")');
globalContextInFunction
默认值: undefined
estime 不支持 use strict 严格模式, 在非严格下的函数中this默认指向的是全局作用域,但在estime中是undefined, 你可以通过globalContextInFunction来设置默认指向。
`js`
Interpreter.globalContextInFunction = window;
const interpreter = new Interpreter();
interpreter.evaluate('alert("hello estime")');
javascript
import {Interpreter} from '../src/interpreter/main'let inter = new Interpreter(null)
let res = inter.evaluate(
);
console.info(res) // 123
`JSX支持
其中不支持JSXFragments、JSXNamespacedName和JSXSpreadChild。JSX标准参考fb的jsx specification
acorn-jsx不支持JSXSpreadChild,且用的比较少。例如下面两种content的用法,效果是一样的,为什么要我要去用spread呢?暂且不支持吧。使用estime完成React组件动态更新的例子,非常灵活
`typescript
let content = [1,2,3]
let t =
{content}
{...content}
`$3
`typescript
let code = class Panel extends React.Component{
render(){
return
class Test {
getCpt(code){
let interRes;
let inter = new Interpreter({
__rt: val => (interRes = val),
console,
React: React,
})
try{
inter.evaluate(code)
}catch(e){
console.info(e)
return e.message
}
return interRes
}
render(){
let C = this.state.C
return
{C && }
}
}
`
效果如下:
相关
- evaljs
- closure-interpreter
- typescript:generator.ts
- ecmascript-Specification
异步队列方案
要在沙箱内部支持异步方法,就必须用js去模拟整个task的执行流程,task分为micro task和macro task。即我们说的大队和小队。Promise入的是小队,setTimeout入的是大队。这里是难点,这一套机制是整个异步流程的基石。
那么,怎么实现大小队呢?参考现有的promise pollyfill库,
promise.js用的是asap,asap底层是process.nextTick降级到queueMicrotask再降级到MutationObserver最后降级到setTimeout。那么对于一个沙箱环境,我们是否有必要做得这么复杂呢?首先明确一点,在浏览器环境,用户可以任意编写方法调用
setTimeout或是queueMicrotask或是Promise.resolve等等,浏览器一般并没有限制。所以,当在浏览器实现一个小队的polyfill时候,就需要判断各种各样的api接口是否可用,然后处理各种降级,为的就是当用户调用你的fakeMicroTask放入的函数必定比他自己调用setTimeout放入的函数后执行。那么,如果是沙箱环境,任何函数的实际执行都是沙箱决定的,沙箱完全可以只提供一个虚拟的“队列”,无论是
setTimeout还是queueMicrotask还是Promise.resolve都放入同一个虚拟队列中,只是他们优先级不一样而已。那么,只要我们沙箱外部环境拥有setTimeout的能力(这几乎是100%兼容的),我们就能够提供这样的虚拟队列,保证沙箱环境的各种不同优先级的异步方法执行;且这样做还有个好处,就是沙箱中的异步方法,永远都是优先级最低的setTimeout,不和外部环境抢小队的时间片。实现虚拟的大小队的标准可以参照event-loop-processing-model。
generator相关实现方法
generator的实现也是难点,但其功能又如此重要(异步方法语法糖的基础),不得不支持。目前正在纠结中,将generator的定义转换成es5可执行的同等函数,其工作量不亚于再写一个js解释器。有现成的npm包比如regenerator可以将generator的代码转换成es5的形式,但其包体大小压缩有都有足足1M,我是肯定不会用的。typescript的源码中也带有转换generator,但由于依赖挺多的,拆分出来的成本较大。经过一段时间的源码阅读,发现regenerator实际是基于babel-plugin写的一个ast替换插件,整体核心部分大致有2000+行代码,加上运行时600行代码,比较合理。不过regenerator`基于babel-plugin,用到了babylon的语法分析能力,也基于babel-types和babel的travel能力,这部分代码庞大,需要去掉自己写;且babylon的语法分析结果和acorn.js的语法分析声明的都是遵守estree,不过两者最终输出的ast结构还是有差异的,estime基于acorn做语义分析,这部分适配工作也需要自己做。整体思路很简单,如果遇到了async或generator函数,先进行ast的转译,然后再进行接下来的编译闭包工作。对ast的转译工作单独放在了这个库里:estime-resync。
相关特性可以看这里,并不一定全部实现。但常用的都会实现的。
- [x] 块级作用域
- [x] let
- [x] const
- [x] Class
- [x] 基础声明
- [x] extends
- [x] class fields
- [x] static property
- [x] 箭头函数
- [x] 基础执行支持
- [x] context绑定
- [x] 解构
- [x] 对象解构
- [x] 数组解构
- [x] 函数实参解构
- [x] Rest element
- [x] ObjectPattern
- [x] ArrayPattern
- [x] 函数形参rest
- [x] Map + Set + WeakMap + WeakSet 由外部提供支持,沙箱不做特殊支持
- [x] for-of
- [x] Template Strings
- [x] Computed property
- [x] Symbols
- [x] Array新增方法等
- [x] Array.from
- [x] Array.of
- [x] Array.prototype.entries
- [x] Array.prototype.values
- [x] Array.prototype.keys
- [x] Array.prototype.reverse
- [x] Array.prototype.find
- [x] Array.prototype.fill
- [x] Array.prototype.lastIndexOf
- [x] Array.prototype.findIndex
- [x] Array.prototype.copyWithin
- [x] Array.prototype.includes
- [x] Array.prototype.flat
- [x] Array.prototype.flatMap
- [x] Array.prototype.reduceRight
- [x] 异步函数
- [x] 虚拟大小队列core
- [ ] 虚拟大小队列的自动销毁
- [x] setTimeout
- [x] setInterval 不支持,容易造成timer泄露,我鼓励自己用setTimeout来实现interval功能
- [x] Promise
- [x] queueMicrotask
- [ ] Generators
- [ ] async/await
- [X] Async generator functions 不支持
- [x] JSX支持,其中不支持JSXFragments、JSXNamespacedName和JSXSpreadChild。JSX标准参考fb的jsx specification
- [x] JSXElement
- [x] JSXIdentifier for React IntrinsicElement
- [x] SelfClosing
- [x] JSXExpressionContainer
- [x] JSXText
- [ ] 抽离acorn.js的依赖。为网络传输提供可靠安全的基础。
- [ ] AST的压缩(可能是二进制)表示形式
- [ ] 源码打包器
- [ ] 压缩AST解释器runtime