深入浅出 solid.js 源码 (十三)—— jsx 编译

·  阅读 74

这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情

前面一直在看运行时部分的逻辑,solid 的运行时逻辑比较轻量,很多工作是在编译阶段完成的,这一节我们来看编译的工作。

solid 编译阶段是使用 babel-preset-solid 来处理的,它的源码也在 solid 仓库,是一个独立的包。不过这个包里面并没有做其他工作,只是单纯地为 babel 添加了一个插件 babel-plugin-jsx-dom-expressions。

babel-plugin-jsx-dom-expressions 也是 dom-expressions 库的一部分,它实现了 solid 的 jsx 编译工作。这个库最终是会以 babel 插件的形式进行引入的。有关 babel 插件的详细介绍可以参阅 babel handbook,本文只会提及一些涉及到的基础概念。

babel 插件的入口是 index.js,其中最重要的是 visitor,visitor 定义了 babel 对不同类型节点的处理操作。babel AST 中每一个节点都有一个 type 属性,对于某种特定的 type 类型,我们可以自定义其转化处理的逻辑:

		visitor: {
      JSXElement: transformJSX,
      JSXFragment: transformJSX,
      Program: {
        enter: preprocess,
        exit: postprocess
      }
    }
复制代码

visitor 中每个函数有 path 和 state 两个参数,这里比较重要的就是 transformJSX,我们来看它的实现:

export function transformJSX(path) {
  const config = getConfig(path);
  const replace = transformThis(path);
  const result = transformNode(
    path,
    t.isJSXFragment(path.node)
      ? {}
      : {
          topLevel: true
        }
  );

  const template = getCreateTemplate(config, path, result);

  path.replaceWith(replace(template(path, result, false)));
}
复制代码

这里面就是一系列转化逻辑了,这部分具体的实现就是一些 AST 的操作,详细的过程就不展开了,这里我们需要重点关注的还是转化结果。前面我们已经见到了转化的结果了,可以回顾一下之前在 playground 中看到的结果:

import { render, createComponent, delegateEvents, insert, template } from 'solid-js/web';
import { createSignal } from 'solid-js';

const _tmpl$ = /*#__PURE__*/template(`<div><div>+</div><div>reset</div><div>-</div></div>`, 8);

function Count() {
  const [num, setNum] = createSignal(0);
  return (() => {
    const _el$ = _tmpl$.cloneNode(true),
          _el$2 = _el$.firstChild,
          _el$3 = _el$2.nextSibling,
          _el$4 = _el$3.nextSibling;

    insert(_el$, num, _el$2);

    _el$2.$$click = () => setNum(p => p + 1);

    _el$3.$$click = () => setNum(0);

    _el$4.$$click = () => setNum(p => p - 1);

    return _el$;
  })();
}

render(() => createComponent(Count, {}), document.getElementById("app"));

delegateEvents(["click"]);
复制代码

首先来看 jsx 结构处理,其中需要渲染为 dom 的部分会调用 template,这里的效果是创建一个 html template 标签并添加内容,template 标签可以通过 cloneNode 的方式创建节点。

如果页面中引用到 signal 值,这里会通过 dom API 动态添加,这里的 num 是通过 insert 来插入的,底层实现的是 html 插入逻辑。

比较有意思的是事件处理,这里的事件是通过设置 $$xxx 方法来添加的,最终会调用 delegateEvents 来统一把这些函数转化为标准的事件处理函数。

当然目前的应用非常简洁,还没有涉及到 solid 内置的流程控制组件处理,在接下来几篇会继续展开这部分内容。

收藏成功!
已添加到「」, 点击更改