開開心心擼一個VUE轉小程序原生的Webpack-loader(下)

render-html.js

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

接下來,咱們具體講解普通標籤的解析。首先要獲取到他的標籤類型,例如divp。 思路:找到結束標籤的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.

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,你爲啥還要作這些東西?

其實我以爲,大廠的東西是很好,靠我一我的也絕對作不出那麼好。(但願有小夥伴也能加入進來 ⭐️。⭐️)就像開汽車,會開汽車是一回事,學造汽車也是一回事。都是興趣所趨~ 任重而道遠 ~ 開心就好~

Future

不知道寫不寫的完,可是flag仍是要立的!

  • v-**標籤轉各類小程序裏的標籤
  • vue裏本身開發的組件模版該如和處理

拜哩個拜~
相關文章
相關標籤/搜索