render-html.js
是截取來自template字符串匹配出標籤和它上面的屬性等,而後生成一個node後變成一棵nodesTree。html
一開始定義了tagStart,tagEnd,commentStart,commentEnd幾個常量,分別表明vue
this.tagStart = '<' //普通標籤開始
this.tagEnd = '>' //普通標籤結束
this.commentStart = '<!--' //註釋標籤開始
this.commentEnd = '-->' //註釋標籤結束
this.voidTags = []//想要過濾掉的標籤
複製代碼
咱們來理一下思路,編寫html通常有如下幾個場景:html5
<!-- 註釋內容 -->
<開始 tag [定義的屬性,class,style等] 結束> </tag>
最簡單的是純文本,當str.indexOf(this.tagStart) === -1
時,該str就是一段純文本。還有一種狀況當str.indexOf(this.tagStart)
有具體值時,說明在標籤前,有一段純文本。node
這裏定義文字的node
是一個type=text,content=文本內容
小程序
if(nextTag == -1){
//文本
nodes.push({
type:'Text',
content:str
})
str = ''
break;
}
if(nextTag){
//若是有下標,說明在這下標前有一段文本,咱們將截取到這個標籤前的文本,儲存成text
let content = str.slice(0,nextTag)
if(content.trim() !== ''){
nodes.push({
type:'Text',
content:content
})
}
str = str.slice(nextTag) // 從新截取文本,而後再次進入函數
continue; //continue語句的做用是跳過本次循環體中餘下還沒有執行的語句,當即進行下一次的循環條件斷定,
}
複製代碼
接下來就是註釋標籤,當 startsWithCommentStart(str)爲true,說明這裏有一段註釋。bash
startsWithCommentStart(s){
return (
s.charAt(0) === '<' &&
s.charAt(1) === '!' &&
s.charAt(2) === '-' &&
s.charAt(3) === '-');
}
nodes.push({
type: 'Comment',
content:str.slice(this.commentStart,end+3) //這裏的+3是由於 -->
})
複製代碼
這裏定義註釋的node
是一個type=Comment,content=註釋內容
函數
最後,既不是文本也不是註釋,定義普通標籤node
是一個type=Element,tagName=標籤,attributes=屬性,chidren=[子元素]
ui
接下來,咱們具體講解普通標籤的解析。首先要獲取到他的標籤類型,例如div
、p
。 思路:找到結束標籤的index,判斷是否有空格,若是有空格,則取index和空格最小的index,否則就是閉合標籤的位置,取到對應的標籤,無論大小寫,所有變成小寫。this
parseTag()
:解析標籤spa
let idxTagEnd = str.indexOf(this.tagEnd)
let idxSpace = str.indexOf(' ')
let tagNameEnd = (~idxTagEnd && ~idxSpace) ? Math.min(idxTagEnd,idxSpace):idxTagEnd
let tagName = str.slice(1,tagNameEnd)
let lowTagName = tagName.toLowerCase();
let attrs = this.parseAttrs(str.slice(tagNameEnd)); //解析了標籤上的屬性
let tag = {
tagName: lowTagName,
attributes: attrs.attributes,
};
複製代碼
接下來,咱們要去處理標籤上的一些屬性,列入class
、<input type='text' placeholde='請輸入'>
,而處理這些的函數是parseAttrs()
思路:這裏的str是被截取的標籤上的屬性,利用tagPairs()和splitHead()
解析成屬性鍵值對的形式,循環處理屬性鍵值對,去匹配對象的屬性類型。
str = str.trim()
let results = this.tagPairs(str, 0);
str = str.slice(results.cursor);//
let _this = this
let attributes = results.kvs.map(function(pair) {
let kv = _this.splitHead(pair.trim(), '=');
kv[1] = kv[1] ? _this.unquote(kv[1]) : kv[0];
return kv;
}).reduce(function(attrs,kv){
let property = kv[0] //屬性
let value = kv[1] //屬性值
switch(property){
case 'class' : //屬性是不是class
attrs.class = value.split(' ');
break;
case 'style':
attrs.style = _this.parseStyle(value);
break;
case _this.startsWithDataDash(property): //判斷是不是data-的屬性格式
attrs.dataset = attrs.dataset || {};
var key = property.slice(5);
attrs.dataset[key] = _this.castValue(value);
break;
default:
attrs[property] = _this.castValue(value);
}
return attrs;
},{})
return {
str: str,
attributes: attributes
}
複製代碼
tagPairs()
將屬性變成屬性對
let words = [] //用於保存屬性的鍵值對
,let quote = null //引用
,let cursor = index //一開始是0,用來解析傳進來的字符串
、let wordBegin = cursor //index做爲word開始
、let len = str.length //截取字符串的長度
while( cursor < len ){
let char = str.charAt(cursor); //查找 index的位置上的字符串
let isTagEnd = !quote && (char === '/' || char === this.tagEnd); //首先判斷 引用沒有而且chat等與/和或者等於>
//若是已經到了標籤的尾部
if(isTagEnd){
if(cursor !== wordBegin){ //進不來這個判斷,這就說明這個標籤上沒有屬性
words.push(str.slice(wordBegin, cursor)) //說明已經把這個標籤上的屬性都遍歷完了
}
break;
}
let isWordEnd = !quote && char === ' ' ; // 若是沒有引用指針,且碰到了空格
if (isWordEnd) { //若是是
if (cursor !== wordBegin) {
words.push(str.slice(wordBegin, cursor));
}
wordBegin = cursor + 1; // 開始的指針等於當前的指針 +1
cursor++; // 當前的指針也++
continue;
}
let isQuoteEnd = char === quote;//當 char又一次等於了 quote 就說明結束啦
if (isQuoteEnd) {
quote = null; //
cursor++;
continue;
}
let isQuoteStart = !quote && (char === "'" || char === '"'); //檢測到了 '"',"'" 就說明後面的就是屬性值啦
if (isQuoteStart) {
quote = char;
cursor++;
continue;
}
cursor++;
}
複製代碼
最後循環words,生成相似attrs=['xxx=123','aaa=111'],
let attrs = [];
let wLen = words.length;
for(let i=0; i< wLen; i++){
let word = words[i]
if (!(word && word.length)) continue;
let isNotPair = word.indexOf('=') === -1; //若是沒有等號 =
if (isNotPair) {
let secondWord = words[i + 1];
let thirdWord = words[i + 2];
let isSpacedPair = secondWord === '=' && thirdWord; //是不是由於有空格
if (isSpacedPair) {
let newWord = word + '=' + thirdWord;
attrs.push(newWord);
i += 2;
continue;
}
}
attrs.push(word);
}
複製代碼
解析到這裏,回到parseTag()
,首先判斷是不是自閉合標籤,若是不是的話,再判斷是不是須要隔離的標籤,若是都不是則進入下一輪的循環,並將生成子node元素。
if(this.startsWithSelfClose(str)){
str = str.slice(2);
}else{
str = str.slice(1);
if(!~this.voidTags.indexOf(lowTagName)){ //
let results = this.parseAllNodes(str, stack.concat(tagName));
tag.children = results.nodes;
str = results.str;
stack = results.stack;
}
}
return {
tag: tag,
str: str,
stack: stack
}
複製代碼
在parseAllNodes()
裏每次循環都是先去判斷時候當前是否到告終束標籤,在這裏就會退出循環,這裏也是一個標籤的解析結束。
var isClosingTag = str.charAt(nextTag + 1) === '/';
if(isClosingTag){ //</div>
let endTagEnd = str.indexOf(this.tagEnd)
let innerTag = str.slice(2, endTagEnd);
let tagName = innerTag.trim().split(' ')[0];
str = str.slice(endTagEnd + 1);
var loc = stack.lastIndexOf(tagName);
if (~loc) {//若是有index 則截取出對應的內容賦值到stack裏 退出本次循環
stack = stack.slice(0, loc);
break;
}
continue;
}
複製代碼
最終,生成nodesTree以下圖
接下來到了將nodesTree轉成原生小程序,也就是template.js
.
首先,將nodeTree裏文本通過parseText()
後decode
處理,屬性通過parseElement()
拼接成字符串。
nodes.map(item => {
let {
type,
content,
children = []
} = item
if (type === 'Text') {
let c = this.parseText(content)//處理文本
item.content = c
}
if (type === 'Comment') {
item.content = content
}
if (type === 'Element') {
let {
wxTag,
attrStr
} = this.parseElement(item)
item.wxTag = wxTag
item.attrStr = attrStr //將屬性都拼接好
if (children && children.length > 0) {
this.parseWxml(children)
}
}
})
return nodes
複製代碼
parseText(c) {
return html5Entities.decode(c)
}
parseElement(item) {
let wxTag = '',
attrStr = '',
c = ''
let {
tagName,
attributes,
content
} = item
if (['i', 'span', 's'].indexOf(tagName) !== -1) {
wxTag = 'text'
} else if (tagName === 'a') {
wxTag = 'navigator'
} else if (tagName === 'img') {
wxTag = 'image'
} else if (['input', 'button', 'checkbox', 'checkbox-group', 'form', 'label', 'textarea'].indexOf(tagName) !== -1) {
wxTag = tagName
} else {
wxTag = 'view'
}
if (attributes) {
let styleStr = ''
for (let attr in attributes) {
let style = attributes['style']
if (attr === 'style') {
for (let s in style) {
styleStr += `${s}:'${style[s]}'; `
}
attrStr += `style="${styleStr}"`
} else {
attrStr += `${attr}="${attributes[attr]}" `
}
}
}
return {
wxTag: wxTag,
attrStr: attrStr
}
}
複製代碼
後續,處理好的nodeTrees
利用循環遞歸輸出成模版~
template(nodes) {
let str = this.childrenTemplate(nodes)
this.wxml = ` <view name="html-view-nodes">${str}</view>`
}
childrenTemplate(childrenNodes) {
let str = ''
childrenNodes.map((item, index) => {
let {
type = ''
} = item
let text = '',
view = ''
if (type === 'Text') {
text = `${item.content}`
str = `${text}`
} else {
view = this.viewTemplate(item)
str += `${view}`
}
})
return str
}
viewTemplate(item) {
let {
children = []
} = item
let str = `${item.wxTag !== 'text' ?
`<${item.wxTag} ${item.attrStr}>
${children.length >0 ? this.childrenTemplate(item.children):''}
</${item.wxTag}>` :''}
${item.wxTag === 'text' ?
`<text ${item.attrStr}>
${children.length >0 ? this.childrenTemplate(item.children):''}
</text>` :''} `
return str
}
複製代碼
最終,生成的模版以下圖
到這裏終於結束了。。。(是的,國慶假期也結束了。。。)
這個小項目,我寫了差很少1個多星期,其實一開始也是一拍腦門埋頭幹,寫出了不少bug,到後面慢慢修,去看早就忘記的一些html,js基礎。
仍是學到很多東西,第一篇文章發出去的時候,有人問我,都有mpvue,你爲啥還要作這些東西?
其實我以爲,大廠的東西是很好,靠我一我的也絕對作不出那麼好。(但願有小夥伴也能加入進來 ⭐️。⭐️)就像開汽車,會開汽車是一回事,學造汽車也是一回事。都是興趣所趨~ 任重而道遠 ~ 開心就好~
不知道寫不寫的完,可是flag仍是要立的!