用 CirruScript 渲染 HTML

题叶

早的时候我尝试过用 Cirru 语法直接生成 HTML
后边也尝试过用 Cirru 语法生成 JavaScript 的模板
效果勉勉强强, 主要是学习的作用, 后来有了 React 就干脆不用了
不过未来加载静态资源, 还是需要有 HTML, 就觉得麻烦, 搞出点东西来

stir-template

这个模块经过几次演变, 最终定型成为 React 很相似的写法
https://github.com/mvc-works/stir-template
代码是用低版本的 CirruScript 写的, API 也能在 CoffeeScript 里调用

stir = require :stir-tempate
html = stir.html
head = stir.head
body = stir.body
div = stir.createFactory :div

renderPage = (data) ->
  stir.render
    stir.doctype()
    html null,
      head null,
      body null,
        div name: 'a', 'empty'
        div()

可以看到模仿了 React, 渲染函数的第一个参数是属性, 后边是子元素
也提供了相似的辅助函数 render, createElement, createFactory
这样写出来的风格, 跟 CoffeeScript 里写 React 组件也就差不多了
自己需要生成个标签的话, 可以尝试这样的语法:

newTag = stir.createFactory 'new-tag'

另外 <!DOCTYPE html> 算是个特殊的标签, React 当中不提供
那么 stir-template 就方便配合 React 用在后者不方便的地方

Cirru 的缩进

http://script.cirru.org/

我的个人项目目前已经大量使用 CirruScript.. 算是刷排名吧
https://github.com/mvc-works/webpack-workflow
CirruScript 首先是编译到 ES5 的代码生成器, 其次才当做模板语言用
我觉得 CoffeeScript 里逗号太多, 缩进有 bug, 不开心一直用
CirruScript 配合 stir-template 跟 React 可以这样用:

var
  stir $ require :stir-template
  React $ require :react

var
  Page $ React.createFactory $ require :./src/page

var
  ({}~ html head title body meta script link div a span) stir

var
  line $ \ (text) (div ({} (:className :line)) text)

= module.exports $ \ (data)
  return $ stir.render
    stir.doctype
    html null
      head null
        title null ":Coffee Webpack Starter"
        meta $ object (:charset :utf-8)
        link $ object (:rel :icon)
          :href :http://tp4.sinaimg.cn/5592259015/180/5725970590/1
        link $ {} (:rel :stylesheet)
          :href $ cond data.dev :src/main.css data.style
        script $ object (:src data.vendor) (:defer true)
        script $ object (:src data.main) (:defer true)
      body null
        div ({} (:class :intro))
          div ({} (:class :title)) ":This is a demo of Webpack usage."
          line ":Open Console to see how it loads."
          div null
            span null ":Read more at "
            a
              {} (:href :http://github.com/teambition/coffee-webpack-starter)
              , :github.com/teambition/coffee-webpack-starter
            span null :.
        div ({} (:class :demo))
          React.renderToString (Page)

首先 HTML 的结构大概还是能看到的, 接着就是语法了...

  • 缩进

Cirru 首先不是靠很多语法发挥作用的语言, 而是靠的缩进
整个代码会先被解析成一棵树, 这棵树才是后边执行的重点
至少我在写代码的时候, 时时刻刻脑补他的树是怎样的, 可以看 Demo:
http://repo.cirru.org/parser/

  • 美元符号 $

美元符号是为了解决一类缩进而引入的, 比如说下边这样的代码:

(set a (f1 (f2 (f3 x))))

缩进以后想想都觉得很累, 会是这样的, 还不如用括号

set a
  f1
    f2
      f3 x

那么我引入 $ 之后问题就轻松了, 直接就可以这样写:

set a $ f1 $ f2 $ f3 x
  • 逗号 ,

然后逗号也是为了解决一类情况引入的, 意思大概和 $ 相反, 这样的代码

(set a (f1 a) (b) (f2))

改成缩进的时候会是这样的, 注意单行的 bf2 因为换行是有括号的:

set a
  f1 a
  b
  f2

那么问题就来了, 有这样的代码怎么表示, 这里的 b 没有括号了啊:

(set a (f1 a) b (f2))

所以我引入了 ,, 作用就是去掉一层缩进, 大概就是这样:

(set a (f1 a) (, b) (f2))

然后再用缩进的写法, 就不会解析出错了:

set a
  f1 a
  , b
  f2

根据上边 3 条规则, Cirru 语法的缩进能表示绝大部分编程需求
除了表达式第一个操作符里狂用括号的... 放哪个语言里都没正常的办法

CirruScript 的操作符

CirruScript 的编译器是通过识别表达式第一个操作符来转换 AST 的
比如说 var return 会被识别成对应的表达式或者语句的 AST
另外的 div 这样的, 没有对应的操作符, 就会被处理为函数调用

object 操作符, 用来生成对象, 它的参数都是键值对
它有个 alias 是{}, 我觉得看起来更习惯一些, 毕竟花括号还是很眼熟的
array 操作符同样的意思, 用来生成数组, 它的参数就是元素了
它有个 alias 是 []. 空数组可能会写成 ([]) 表示为表达式了

字面量还有标识符我做了一些处理, 方便写代码用
这里边的 null true 都是回自动识别的, 数字也做了处理
另外 data.main 这种, 也通过成员表达式的 AST 进行了处理

这里边还有些挺奇怪的比如说 {}~, 其实是 object~
但是但是, 这种正确的名称应该是: **解构赋值**, 只是奇怪了点
奇怪的语法还是到 http://script.cirru.org 看吧, 可以看具体 AST 是什么

CirruScript 的字符串

字符串是个奇怪的事情, 因为 Cirru 是从图形编辑器退化而来的..
退化嘛... 自然有的东西, 原来图形很正常, 用文本表示就怪怪怪怪了的
Cirru 的语法里 a"a", 带不带引号, 实际上是一样的
不一样的是 "a1 a2"a1 a2, 没有引号就是两个节点, 有就是一个
所以... 引号实际上是为了特殊字符的转义用的, 还有这种呢 "a1\" a2"
你在 <textarea> 里打字的时候不用转义, 图形界面嘛, 那是正常的时候

然后为了表示字符串, 我动起来歪脑筋, 对, 用冒号 :
冒号开头的就是字符串了, 没有特殊字符的时候还可以少一个字符
:utf-8":utf-8" 表示的是一个意思, 编译的结果也是一样的
必须加引号的是 ":hello world" 这样的有特殊字符的节点
看多了 Clojure 的话其实冒号你是会习惯的...

总结

最新版的 Gulp 应该是能识别 gulpfile.cirru 这样的后缀的
我也写了插件, 应该问题不大, CoffeeScript 这么难用都用下来了
https://github.com/Cirru/gulp-cirru-script

一般我会写个 template.cirru 文件, 从 Gulp 里引用
最后执行一下函数, 就能从数据渲染出 HTML 来了:

require('cirru-script/lib/register')

gulp.task 'html', (cb) ->
  html = require'./template.cirru' # <-- 引用看这里
  fs = require 'fs'

  if not env.dev
    assets = require './build/assets.json'
    env.main = "./build/#{assets.main}"
    env.vendor = "./build/#{assets.vendor}"
  fs.writeFile 'index.html', (html env), cb # <-- 调用看这里

希望你有兴趣用 CirruScript 生成 HTML. 早日淘汰手写 HTML.

阅读 2.7k

题叶
ClojureScript 爱好者.

ClojureScript 爱好者.

17.2k 声望
2.6k 粉丝
0 条评论
你知道吗?

ClojureScript 爱好者.

17.2k 声望
2.6k 粉丝
宣传栏