Comi 读 ['kəʊmɪ],相似中文 科米,是腾讯 Omi 团队开发的小程序代码高亮和 markdown 渲染组件。有了这个组件加持,小程序技术社区能够开始搞起来了。javascript
感谢【小程序•云开发】提供技术支持。css
Comi 基于下面的 5 个组件进行开发:html
先看 Comi 使用,再分析原理。java
先拷贝 此目录 到你的项目。node
js:react
const comi = require('../../comi/comi.js');
Page({
onLoad: function () {
comi(`你要渲染的 md!`, this)
}
})
复制代码
wxml:git
<include src="../../comi/comi.wxml" />
复制代码
wxss:github
@import "../../comi/comi.wxss";
复制代码
简单把!typescript
先拷贝 此目录 到你的项目。json
js:
import { WeElement, define } from 'omi'
import './index.css'
import comi from '../../components/comi/comi'
define('page-index', class extends WeElement {
install() {
comi(`你要渲染的 md`, this.$scope)
}
render() {
return (
<view> <include src="../../components/comi/comi.wxml" /> </view> ) } }) 复制代码
WeElement 里的 this 并非小程序里的 this,须要使用 this.$scope
访问小程序 Page或 Component 的 this。
css:
@import '../../components/comi/comi.wxss';
复制代码
在开发 Comi 以前,咱们进行了预研,是否有必要造这个轮子。
综合上面信息,决定基于 prismjs 二次开发。
rich-text
组件展现富文本,可是格式须要转成 json<rich-text nodes="{{nodes}}" bindtap="tap"></rich-text>
复制代码
Page({
data: {
nodes: [{
name: 'div',
attrs: {
class: 'div_class',
style: 'line-height: 60px; color: red;'
},
children: [{
type: 'text',
text: 'Hello World!'
}]
}]
},
tap() {
console.log('tap')
}
})
复制代码
综合上面信息,放弃 rich-text,决定基于 wxParse + remarkable 二次开发,移除 showdownjs。Comi 须要 remarkable 的高性能和灵活性。markdown 会持久化存在 db, 在小程序内运行时转换成 wxml,因此对性能仍是有必定要求。
tokens: function(text, grammar, language) {
var env = {
code: text,
grammar: grammar,
language: language
};
_.hooks.run('before-tokenize', env);
env.tokens = _.tokenize(env.code, env.grammar);
_.hooks.run('after-tokenize', env);
for (var i = 0, len = env.tokens.length; i < len; i++) {
var v = env.tokens[i]
if (Object.prototype.toString.call(v.content) === '[object Array]') {
v.deep = true this._walkContent(v.content)
}
}
return env.tokens
},
复制代码
这段代码增长 tokens 方法到 prismjs 中,原库自带的 prism.highlight 的会把 tokens 转成 html,由于咱们的目标的 wxml,因此这里提早把 tokens 做为方法返回值。固然还作了一件事,就是扩展了 token item 的 deep 属性来决定是否须要继续向下遍历生成 wxml。
原始的 jsx:
render() {
const { tks } = this.data
return (
<view class='pre language-jsx'> <view class='code'> {tks.map(tk => { return tk.deep ? <text class={'token ' + tk.type}>{ tk.content.map(stk => { return stk.deep ? stk.content.map(sstk => { return <text class={'token ' + sstk.type}>{sstk.content || sstk}</text> }) : <text class={'token ' + stk.type}>{stk.content || stk}</text> })}</text> : <text class={'token ' + tk.type}>{tk.content || tk}</text> })} </view> </view>
)
}
复制代码
jsx 编译出生成的 wxml,把这段 wxml 嵌入到 wxparse 里:
<!-- 千万 不要格式化下面的 wxml,否则 text 嵌套 text 致使换行所有出来了 -->
<template name="wxParseCode">
<view class="pre language-jsx">
<view class="code">
<block wx:for="{{item.tks}}" wx:for-item="tk">
<block wx:if="{{tk.deep}}"><text class="{{'token ' + tk.type}}"><block wx:for="{{tk.content}}" wx:for-item="stk"><block wx:if="{{stk.deep}}"><text class="{{'token ' + sstk.type}}" wx:for="{{stk.content}}" wx:for-item="sstk">{{sstk.content || sstk}}</text>
</block>
<block wx:else><text class="{{'token ' + stk.type}}">{{stk.content || stk}}</text>
</block>
</block>
</text>
</block>
<block wx:else><text class="{{'token ' + tk.type}}">{{tk.content || tk}}</text>
</block>
</block>
</view>
</view>
</template>
复制代码
这段 wxml 不能进行格式化美化,否则多出许多换行符,由于 text 嵌套 text 会保留换行符!!
修改 wxparse 里的分支逻辑:
<block wx:elif="{{item.tagType == 'block'}}">
<view class="{{item.classStr}} wxParse-{{item.tag}}" style="{{item.styleStr}}">
<block wx:if="{{item.tag == 'pre'}}">
<template is="wxParseCode" data="{{item}}" />
</block>
<block wx:elif="{{item.tag != 'pre'}}" >
<block wx:for="{{item.nodes}}" wx:for-item="item" wx:key="">
<template is="wxParse1" data="{{item}}" />
</block>
</block>
</view>
</block>
复制代码
当 item.tag
为 pre
的时候使用 wxParseCode 模板,数据传入 item。item 的数据从哪里来?
先修改 md 渲染器为 Remarkable:
} else if (type == 'md' || type == 'markdown') {
var converter = new Remarkable()
var html = converter.render(data)
transData = HtmlToJson.html2json(html, bindName);
}
复制代码
使用上面的 prism.tokens 计算出代码片断的 tokens,用于 wxparse 的模板渲染:
function transPre(transData) {
transData.nodes.forEach((node, index) => {
if (node.tag == 'pre') {
var lan = 'markup'
if (node.nodes[0].classStr) {
lan = node.nodes[0].classStr.split(' ')[0].replace('language-', '')
}
var tks = prism.tokens(node.nodes[0].nodes[0].text, prism.languages[lan], lan)
transData.nodes[index].tks = tks
}
})
}
复制代码
language- 支持多少种呢?目前 comi 默认支持:
默认使用的主题 css 是 okaidia。若是 comi 默认的配置不支持你的需求,你能够:
WXML 提供两种文件引用方式 import 和 include。和 import 不一样,include 能够将目标文件除了 template 和 wxs 外的整个代码引入,至关因而拷贝到 include 位置,如:
<!-- index.wxml -->
<include src="header.wxml" />
<view>body</view>
<include src="footer.wxml" />
复制代码
<!-- header.wxml -->
<view>header</view>
复制代码
<!-- footer.wxml -->
<view>footer</view>
复制代码
comi 利用了 import 和 include 特性简化使用流程:
comi.wxml
<import src="./wxParse.wxml"/>
<template is="wxParse" data="{{wxParseData:article.nodes}}"/>
复制代码
comi.js
var WxParse = require('./wxParse.js');
module.exports = function comi(md, scope) {
WxParse.wxParse('article', 'md', md, scope, 5);
}
复制代码
comi.wxss
@import './wxParse.wxss';
@import './prism.wxss';
复制代码
使用时,只须要 :
comi.js
comi.wxml
comi.wxss
另外,在 omip 使用 comi 时候发现不会拷贝 include 的文件到 dist,发现 taro/omip 的正则没有去匹配 include 文件,因此,把:
exports.REG_WXML_IMPORT = /<[import](.*)?src=(?:(?:'([^']*)')|(?:"([^"]*)"))/gi
复制代码
改为:
exports.REG_WXML_IMPORT = /<[import|inculde](.*)?src=(?:(?:'([^']*)')|(?:"([^"]*)"))/gi
复制代码
搞定。