关于javascript:所见即所得-iMove-在线执行代码探索

3次阅读

共计 5030 个字符,预计需要花费 13 分钟才能阅读完成。

自身 iMove 的定位就是“一个逻辑可复用的,面向函数的,流程可视化的 JavaScript 工具库。”

对开发者而言,iMove 恰好是能够实现这些指标的现实工具。动动鼠标,写一下节点函数,代码导出,放到具体工程里就能够间接应用,是不是很不便?

那么,为什么咱们还要做 iMove 在线执行代码性能呢?站在用户视角看,对于开发者来说,选中节点,右键在线执行代码是必要的。这样能够把 iMove 的工具属性做到极致,让开发者体验更好,操作更简略。

上次在《登上 Github 趋势榜,iMove 原理技术大揭秘!》一文中咱们卖了个关子,明天就和大家分享下 iMove 是如何实现在线执行节点代码的~

引子

故事还得从组内的一个小伙伴说起,过后她是第一次应用 iMove,询问我如何测试运行刚写好的代码。二话不说,我就在她电脑上一通操作:

  1. 装置 @imove/cli
  2. 在我的项目根目录执行命令: imove -d
  3. 找到我的项目中的组件,增加代码 logic.invoke('trigger')
  4. 启动我的项目,关上控制台面板,筛选 log  信息
  5. … …

“我就想运行下刚写好的代码,看看输入后果是什么 …”

『步骤繁琐』、『操作麻烦』、『有上手老本』、『运行后果不直观』… 此刻,脑中第一工夫闪过的就是以上这些词汇,再看向小伙伴那纳闷的眼神,好像下一刻她就要被劝退了 …

这可怎么行,体验必须优化,应用老本必须降下来!于是就有了右键在线执行代码的性能。

其实上述问题的诉求很简略: 写完节点的代码之后,旁边有个运行按钮,一点间接就能看到运行后果就行了。咱们来看下就是这么一个优化都能带来哪些扭转:

  • 无装置工具要求,0 上手老本: 代码在浏览器端就能运行,不再须要装置命令行工具,大大降低了学习老本。
  • 后果可视化,简单明了: 开发调试过程中最罕用到的就是日志,每次都要关上控制台极不不便,间接将节点的运行后果用可视化的形式展现进去,简略直观。
  • 在线 mock 输出,不便测试: 每个节点的代码都会有各种可能的输出,如果能够间接 mock 输出,测试效率将得以极大的晋升。
  • 测试用例可保留,保障代码品质: 除了不便测试节点代码之外,如果能够将成对的输出 / 输入作为测试用例加以保留,渐而造成齐备的测试用例集,将能进一步保障节点的代码品质。
  • … …

想法是很美妙,但具体该如何实现呢?接着往下看~

摸索 1:节点代码在哪运行

首先,要解决的第一个问题就是:节点代码在哪运行?

通过评估,咱们认为次要有两个抉择:

  1. 浏览器端间接运行节点代码;
  2. 本地起一个服务,将节点信息发送至本地,本地构建编译完之后将 bundle 发送回浏览器端执行。

对于以上两个计划,iMove 抉择了前者,理由很简略:在线运行节点代码的初心就是为了升高上手老本,如果还须要本地起一个服务,使用者势必又要学习一个新命令,这无形中又进步了应用门槛。

然而,要想在浏览器中间接运行节点代码,还是有很多妨碍,并非一个 eval 就完事了~

摸索 2:如何运行 import/export

看过 iMove 的出码就晓得,流程图中的每个节点代码最终都会被编译成一个独自的 js 文件。因而,每个节点是反对 import 其余 npm 包的,这也是为什么不能间接调用 eval 的起因。

1)浏览器原生反对 ES Module

当然,兴许聪慧的你早已想到浏览器是反对 native ES module 的,包含之前挺火的 Vite 其实底层原理也是基于这个。为此,咱们先来简略介绍下如何让 import/export 代码在浏览器中跑起来~

先来看 MDN 上的 官网文档:

能够看到,其实古代浏览器早就反对了,因而咱们能够不必思考兼容性问题。除此之外,持续浏览文档你会发现要让浏览器反对 ES Module 很要害的一点是要在 script 标签上增加 type="module" 属性。来看一个例子:

# 文件目录
index.html
main.mjs

<!--index.html-->
<script type="module">
  import sayHello from './main.js';
  say('Hello iMove!');
</script>

// main.js 
const say = (words) => console.log(words);

留神: 运行上述例子时不要在本地间接关上 index.html 文件,因为浏览器会默认用 file:// 协定关上,申请 main.js 资源时会跨域。该问题能够在本地开一个 http 服务解决,或在 codesandbox 上尝试也可。

如上所示,要让浏览器跑 ES Module 代码也太简略了,能够说简直没有老本,但问题真解决了吗?能够在代码中加一行 import get from 'lodash.get' 试试。

能够看到控制台报错了,原来是 lodash.get 被浏览器当做相对路径加载了,其实浏览器是反对 http 门路加载的,为此咱们能够将其改成 import get from 'https://unpkg.com/lodash.get'

可怜的是,控制台还是报错了。谬误的起因是 lodash.get 这个 package 遵循的是 cjs 标准,但浏览器只认 esm 标准,所以无奈解析并执行这个 package 代码。

2)SystemJS 尝试

既然浏览器原生只反对 esm 标准的代码,那是否有方法反对 cjs 标准的代码呢?通过一番调研,咱们发现了 SystemJS 这个库(传送门:https://github.com/systemjs/systemjs)。

据其介绍,它就是一款用来解决浏览器运行 ES Module 的工具。然而当咱们依照其文档摸索一番后,发现加载 lodash.get 包时还是失败了,而且报的是雷同的谬误 … 好在咱们在它的 issue 区另有播种,发现了这么一个帖子:ES Modules and CommonJS?(PS:由此可见,该问题还是普遍存在的)

依据官网回复的内容,咱们能够提取以下几点要害信息:

  • 0.21 版本之前的 SystemJS 是反对 cjs 标准的,然而目前曾经不再反对。
  • 之前的版本反对 cjs 次要是因为:之前的做法是先下载代码字符串,用正则匹配 require 解析依赖,最初才执行代码;而当初的编译解析工作依附的是浏览器本身。
  • 以前的这种解法其实存在性能隐患,故新版本中将不再思考。

为此,咱们又找到 0.21 版本的文档:https://github.com/systemjs/systemjs/blob/0.21/docs/module-formats.md

能够看到该版本的 SystemJS 仿佛能够满足咱们的需要,但最终 iMove 并没有采纳。因为官网曾经不举荐这种形式而且也不再保护,所以这条路又断了 …

3)新一代 JS 模块 CDN 托管

上述的问题仿佛走进了死胡同,但认真想来问题的实质不就是 『浏览器不反对 cjs 标准代码』 吗?

可是反过来想,浏览器为什么要反对 cjs 标准代码呢?SystemJS 之前做的 cjs to esm 这个工作为什么要在浏览器侧做,而不是放在 CDN 上实现呢?狼叔的这篇《2021 再看 Deno(CDN for JavaScript modules 的思考)》讲的很分明,转换这事儿其实放在 CDN 上来做更适合~

简略介绍一下,jspm 做得就是这个事儿。

为了验证成果,咱们来做个试验比照,以下别离是浏览器从 unpkgjspm 上加载的 lodash.get 截图:

摸索 3:多文件合并成单文件执行

前文提到的『如何运行 import/export 问题』看起来仿佛曾经完满解决,但实操起来咱们却发现还是脱漏了一个细节:iMove 的出码是一个多文件的组织模式,因而浏览器将会以相对路径的模式引入其余文件,这意味着还需一个 http 服务来提供这些资源的加载,这是咱们不能承受的。

如何解?最简略的方法其实就是将 多文件合并成单文件执行,这样人造地就毁灭了相对路径的问题。咱们再来看个例子:

// a.js
import get from 'lodash.get';
const obj = {
  text: 'a',
  say: () => console.log(get(obj, 'text'))
};
export default obj;

// b.js
import get from 'lodash.get';
const obj = {
  text: 'b',
  say: () => console.log(get(obj, 'text'))
};
export default obj;

// main.js
import a from './a';
import b from './b';
a.say();
b.say();

上述是合并前的多文件组织模式,要合并它们其实并不难,只需注意以下 2 点:

  • import 某个文件时,该文件的代码须要立刻执行
  • 不同文件中的全局变量名可能雷同,须要解决命名净化的问题
// merged.js
const run = async () => {const modA = await (async () => {const get = (await import('https://jspm.dev/lodash.get')).default;
    const obj = {
      text: 'a',
      say: () => console.log(get(obj, 'text'))
    };
    return obj;
  })();
  const modB = await (async () => {const get = (await import('https://jspm.dev/lodash.get')).default;
  const obj = {
    text: 'b',
    say: () => console.log(get(obj, 'text'))
  };
  return obj;
  })();
  modA.say();
  modB.say();};
run();

须要留神的是原来的 import 因为合并的起因呈现在了函数体当中,所以须要应用 dynamic import 的形式来加载网络包。剩下的只有用 正则表达式匹配替换 或者 AST 转换 都能实现对应的成果,本文就不再开展详述。

摸索 4:Script 间如何传值通信

行文到这,能够说是『万事俱备,只欠东风』了。通过方才的多文件合并,咱们只需运行代码,获取后果展现即可。这里须要留神的是,因为待执行的代码遵循 esm 标准,如果想用 eval 执行代码就有一个前提:以后代码必须也在 type="module"script 标签下。不过咱们能够换个思路,不必 eval 也能执行字符串代码:

const script = document.createElement('script');
script.type = 'module';
script.text = 'code here';
document.body.appendChild(script);

如上所示,咱们能够通过动静插入 script 标签的形式来执行代码。可是随之而来的问题又变成了:咱们该如何在代码运行完之后触发回调拿到后果展现呢?何况当初这两段代码还是在两个 script 标签下。

最容易想到的方法就是用相似 jsonp 的形式,一方在全局 window 下注册惟一办法,另一方在代码执行完之后调用该办法。在这里,咱们介绍一种更优雅的形式,能够用事件通信的形式来解决~

MDN 官网文档传送门:Custom Events

// 监听事件 document.addEventListener(‘customEvent’, function (evt) {console.log(evt.detail); }, false); // 发送事件 document.dispatchEvent(new CustomEvent(‘customEvent’, {detail: {text: ‘iMove’} }));

总结

本文先后介绍了 iMove 在线运行节点代码这一路所踩过的坑,最终次要还是依附 http-import 的思维来解决问题。无疑,deno 扭转了大家的对包治理的认识。自身 deno 够小,试错成本低,它确确实实引领了一个潮流方向。这个改良虽说不算新,但反应的确很好,大略是天下人苦 npm(npm 开玩笑的说法是:你怕吗)久已,用法简略,高效,甚至是衍生出很多对于 CDN for JavaScript modules 的思考。如果你有更好的想法,欢送一起交换。

另外,做 iMove 在线执行代码性能是用户视角做的决定,小伙伴们都十分认可这个决策,并在落地过程中,可能摸索出一种全新的形式,这是十分值得称赞的。自身 iMove 就是一个以锤炼团队为目标开源的我的项目,大家可能定义问题,可能解决问题,可能建设信念,可能激发技术激情,它的目标就达到了。

正文完
 0