Scoped CSS規範草案

【當耐特】發表於2016-12-26

原文連結:https://github.com/AlloyTeam/AlloyTouch/wiki/Scoped-CSS

寫在前面

問:什麼是Scoped CSS規範?

Scoped CSS規範是Web元件產生不汙染其他元件,也不被其他元件汙染的CSS規範。

面對元件化的普及,元件的複用很普遍的需求,然而CSS相互汙染是經常遇見的問題,建立規範讓開發者放心使用各種元件,甚至跨生態的元件是很有必要的一件事情。

目前業界的一些方案

方案一:
如果用webpack的話,可以參考css-loader的這個功能:

Scoped CSS規範草案

一段hash + 元件名,這個可能兼顧了辨識度 + 命名汙染的問題。

方案二:

用webpack和scss,less寫成模組化css就可以一定程度避免CSS汙染,不能完全避免

方案三:樣式規範上,使用與元件同名的巢狀名稱空間

如果只用自己的生態可以這麼搞,但是有的時候會引入第三方生態,第三方和自己的名稱空間一樣還是很有可能,比如scroller外掛,社群裡也有很多scroller外掛loading uplader外掛等等。

現有方案的侷限性

這裡還是會有汙染的情況,因為:

  • 模組化的粒度是大於等於元件化粒度,意思就是一個模組可能有多個元件
  • 非less和sass專案下的元件怎麼保證
  • 難以保證不汙染第三方元件
  • 難以保證不被第三方元件汙染
  • 同名元件的問題
  • 元件在第三方專案使用的問題
  • 元件自身生態閉環的問題

所以得出:

用意念或者規範約定不然注入程式自動化避免衝突

好處:

  • 能保證不汙染別的元件並且不被被的元件汙染可以更放心的複用
  • Scoped CSS規範是執行時產生唯一id~~ 永遠不會css碰撞
  • 返回的這個id那個指定給元件的頂層div就行,實施簡單

如果把這個過程放在構建過程就是工程問題。但是元件單獨抽離出來給第三方用,其實就是元件本身的問題。總之要保證:

  • 不汙染第三方的專案或元件
  • 不被第三元件或專案汙染(由於是層疊樣式,這個無法完全保證)

Scoped CSS程式碼

;(function () {

    function scoper(css) {
        var id = generateID();
        var prefix = "#" + id;
        css = css.replace(/\/\*[\s\S]*?\*\//g, '');
        var re = new RegExp("([^\r\n,{}]+)(,(?=[^}]*{)|\s*{)", "g");
        css = css.replace(re, function(g0, g1, g2) {

            if (g1.match(/^\s*(@media|@keyframes|to|from|@font-face)/)) {
                return g1 + g2;
            }

            if (g1.match(/:scope/)) {
                g1 = g1.replace(/([^\s]*):scope/, function(h0, h1) {
                    if (h1 === "") {
                        return "> *";
                    } else {
                        return "> " + h1;
                    }
                });
            }

            g1 = g1.replace(/^(\s*)/, "$1" + prefix + " ");

            return g1 + g2;
        });

        addStyle(css,id+"-style");
        return id;
    }

    function generateID() {

        var id =  ("scoped"+ Math.random()).replace("0.","");
        if(document.getElementById(id)){
            return generateID();
        }else {
            return id;
        }
    }

    var isIE = (function () {

        var undef,
            v = 3,
            div = document.createElement('div'),
            all = div.getElementsByTagName('i');

        while (
            div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
                all[0]
            );

        return v > 4 ? v : undef;

    }());

    function addStyle(cssText, id) {
        var d = document,
            someThingStyles = d.createElement('style');
        d.getElementsByTagName('head')[0].appendChild(someThingStyles);
        someThingStyles.setAttribute('type', 'text/css');
        someThingStyles.setAttribute('id', id);
        if (isIE) {
            someThingStyles.styleSheet.cssText = cssText;
        } else {
            someThingStyles.textContent = cssText;
        }
    }


    window.scoper = scoper;
})();

Scoped CSS實施

var id = scoper("h1 {\
               color:red;\
            /*color: #0079ff;*/\
                }\
        \
                /*  h2 {\
                color:green\
                }*/");

scoper返回的id,在元件的JS裡面賦給包裹的DOM便可以。這裡詳細說下生成id的過程:

function generateID() {
    var id =  ("scoped"+ Math.random()).replace("0.","");
    if(document.getElementById(id)){
        return generateID();
    }else {
        return id;
    }
}

通過Math.random得到隨機數並經過處理,然後通過document.getElementById去查詢頁面上有沒有同名ID,有的話則繼續重新生成,沒有的話就使用當前id。這裡需要特別注意的是,比如一些彈出層外掛,display hide的時候有的元件是直接從body裡面移除,所以這就帶來了CSS碰撞的可能性,所以這裡Scoped CSS 規範強行約定:後插入的HTML,一定要經過scoper過程重新生成唯一id。
最後,Scoped CSS規範已經在AlloyTouch外掛裡開始實施,並打算推廣開來。

你有什麼好的想法可以讓跨生態跨專案跨技術棧的元件複用更加愜意,可以交流交流。

相關文章