Saturday, December 13, 2014

移民效應,是自幹一套還是拿別人的 Open Source 來用?

Marissa Mayer 與起死回生的雅虎 一文中,可以看到雅虎工程師對自家 CEO 的讚美。其中一點提到雅虎內部有很多系統設計是與外界完全不一樣的,直到新 CEO 上任才放下使用多年的YUI而擁抱其他Open Source團隊維護的專案。

從樂觀的角度看:因為當初Yahoo工程師夠強(Douglas Crockford),覺得自己做的東西比別家的好,所以乾脆自己做。有這種「愛自幹」的精神,所以才會做出很多跟其他地方不一樣的東西,這是feature。從反面看則是因為長期跟人家用的工具不一樣,所以沒辦法直接使用別人做的Open source工具,這也是bug。

以前的Yahoo「不擁抱其他Open Source團隊維護的專案」嗎?Yahoo當然某種程度是支持Open Source的公司。就我所知,Yahoo是間很早開始支援 Hadoop 開源專案的公司。Yahoo使用 Hadoop,也延攬 Hadoop 開發者進公司,所以「不擁抱其他Open Source團隊維護的專案」這個論點也許是存在的。

我們來看看 Google。類似於 YUI,Google 自己也有一套 Closure Library,多數 Google 的產品都建構於其上,雖然開源,但外面專案會使用它的也少之又少。
而 Android 呢,AOSP專案裡也有不少只能在 Android 工具鏈中使用的工具,但從以前基於 Eclipse 的 Android 開發工具到最近釋出的Android Studio 1.0是基於 IntelliJ 的開發工具,部分的Google似乎較接受擁抱其他Open Source團隊維護的專案。


在寫下這兩段話的同時,我比較這兩家公司擁抱其他Open Source團隊維護的專案的原因,竟發現兩者改變的相同點在於「併購」。當Google併購Android,就接受了Android團隊與他們提出的開發工具。當Yahoo開始大採購,許多新創團隊進入Yahoo大家庭的同時,也帶進各團隊使用的「其他Open Source團隊維護的專案」。也就是說,當新移民團隊進入這個團體時,帶來了改變。

比起大公司而言,新移民團隊在被併購前的資源有限,使用其他Open Source團隊維護的專案是很自然的事。團隊被併購後若需使用併購方的技術重新打造產品,將是相當痛苦的事情。因此在Yahoo大採購後許多原有規矩被改寫,恐怕背後也有這些新團隊帶來的影響。

用社會學可能可以解釋為什麼是團隊而不是個人?因為改變一直是困難的事。原有的團隊在過去磨合/開發的過程中已經凝聚出了一些共識。移民到新團體的過程中,新移民團隊需要參與和適應團體的一些運作方式,而團體則需接納和適應新移民團隊的一些習俗。


回來想想 Mozilla 也做了很多 Open Source 專案,但整套工具常常僅供自己相關的專案使用。
從自身找原因,自家工具的開源專案原本就是為了滿足自己的需求。當外部出現對這套工具的需求時,由於公司人力資源調配,優先要滿足的還是自身專案時程的需求。
當用到的Open Source專案在公司裡就有人可以諮詢,當然比去找其他Open Source團隊維護的專案來地可靠(?)。許多專案在這樣的思維下就使用自己寫的工具,當意識到的那天,已經回不去了。

若想避免現在的feature變成未來的bug,在決定自幹專案工具前,先想一想(或search一下),也許結果會不一樣。


Thursday, November 13, 2014

使用 FLUX 架構的概念,漸進改善一般 Javascript 程式碼的組織方式

前陣子 Facebook 推出一套名為 FLUX 的前端程式架構,期望能幫過去沒有條理,程式多了結構就亂得像一團麵條的 Javascript 程式寫法找到一個理想的組織方法。

FLUX 簡介

視圖(View)-> 操作(Action)-> 分配器(Dispatcher)-> 資料處理器(Store)-> 繪圖者(Renderer)-> 視圖(View)

FLUX 的基本原理有別於常用的 MVC(Model/View/Controller)或 MVVM(Model/View/ViewModel)是在M,V,C(VM)三者之間互相傳遞或修改資料,

MVC (image from fluxxer)

FLUX 重新定義整個組織架構為單向的視圖(View)-> 操作(Action)-> 分配器(Dispatcher)-> 資料處理器(Store)-> 繪圖者(Renderer)->  視圖(View)的運作流程。

FLUX (image from fluxxer)

就我的理解,FLUX 的架構可以拆分為三個重點流程:
  • 跟視圖(View)有關的操作(Action),都透過事件註冊到分配器(Dispatcher)去。
  • 分配器 (Dispatcher)負責將操作(Action)傳遞給需要的資料處理器(Store)。
  • 資料處理器(Store)負責跟資料直接相關的操作。若資料處理器(Store)修改的結果需要反映到視圖(View)上,可以透過發送訊息通知給繪圖者(Renderer)處理。
這邊講到了原本 FLUX 概念圖中沒有提到的繪圖者(Renderer)這個角色,在 Facebook 中他們是用 ReactJS 處理。

瞭解其基本架構之後,我發現其實就算不用他們提供的函式庫,用 Javascript 內建的 addEventListener, handleEvent, customEvent 等方法,也可以利用前面所提的三個重點,漸進寫出符合 FLUX 精神的程式。

目前的 JS 組織方式


一個常見的JS檔案,一般的架構是

var App = {    init: function app_init() {
      // get view
      this.view1 = document.getElementById('xxx1');
      this.view2 = document.getElementById('xxx2');
     
      // do stuff
      this.view1.addEventListener('click', function() {
        // do something
      });
      this.view2.addEventListener('keyup', function() {
        // do something
      });
    }
};
若我們想要將視圖(View)的操作從 init 分離開來,大部分的人會這樣做

 var App = {
    init: function app_init() {
      // get view
      this.view1 = document.getElementById('xxx1');
      this.view2 = document.getElementById('xxx2');
     
      // do stuff
      this.view1.addEventListener('click', this.clickView1);
      this.view2.addEventListener('keyup', this.keyupView2);
    },

    clickView1: function app_clickView1() {
       // do something
    },

    keyupView2: function app_keyupView2() {
       // do something
    }

};

如果在 clickView1 或 keyupView2 中要呼叫到 App 裡的參數或方法,那麼我們需要在addEventListener 時使用 bind(this)


 var App = {
    init: function app_init() {
      // get view
      this.view1 = document.getElementById('xxx1');
      this.view2 = document.getElementById('xxx2');
     
      // do stuff
      this.view1.addEventListener('click', this.clickView1.bind(this));
      this.view2.addEventListener('keyup', this.keyupView2.bind(this));
    },

    clickView1: function app_clickView1() {
       // do something
    },

    keyupView2: function app_keyupView2() {
       // do something
       this.clickView1();
    }

};

大多數書籍的範例大概就停在這裡,沒有再進一步探討程式的組織架構了。即使是龐大的 Javascript 專案如 Gaia,不少部分的程式碼組織方式也是如此。在這種組織方式裡,若有很多的視圖(View)需要操作或修改,我們的程式碼就會開始亂起來。

下面來試著將以上程式漸進改為 FLUX 架構。

改進建議一:將 handleEvent 當作 Dispatcher 來使用

跟視圖(View)有關的操作(Action),都透過事件註冊到分配器(Dispatcher)去。
 (溫馨提示:IE 9以上版本才有支援 handleEvent 方法,在之前版本上使用要加 polyfill)

我們先來想想看視圖(View)跟操作(Action)在前端 Javascript 程式中分別代表著什麼。
視圖(View)很明顯,就是透過 getElementById 等方法,從 HTML 中取得代表對應節點(Node)的元素(element)。

若想要套用FLUX架構,我們可以將附加在各個元素(element)上的事件行為分離,將事件註冊到一個統一的地方(分配器),在這個地方對不同的事件進行操作。

 Javascript 內建的分配器叫做 handleEvent,它可以拿來處理任何事件Event,寫法如下。
 var App = {
    init: function app_init() {
      // get view
      this.view1 = document.getElementById('xxx1');
      this.view2 = document.getElementById('xxx2');
     
      // do stuff
      this.view1.addEventListener('click', this);
      this.view2.addEventListener('keyup', this);
    },

    handleEvent: app_handleEvent(evt) {
       switch (evt.type) {
         case 'click':
           switch (evt.target) {
              case this.view1:
                this.clickView1();
                break;
           }
           break;
         case 'keyup':
            switch (evt.target) {
              case this.view2:
                this.keyupView2();
                break;             }
       }
    },

    clickView1: function app_clickView1() {
       // do something
    },

    keyupView2: function app_keyupView2() {
       // do something
       this.clickView1();
    }

};
這麼做帶來的明顯好處是所有的呼叫都統一在 handleEvent 中,可以更容易地追查到。

這麼寫也可以在 addEventListener/removeEventListener 時不用使用 bind(this),而 bind(this) 經常有些 side effect 需要特別留意。

例如假使我們想要反註冊 view1 上的 click 方法,使用以下寫法

 this.view1.removeEventListener('click', this.clickView1.bind(this));
其實並沒有將第一個 event 移除。因為使用了 .bind(this) 後,回傳的其實是一個新的 instance...。
正確的寫法是
this.bindClickView1 = this.clickView1.bind(this)
this.view1.addEventListener('click', this.bindClickView1);
...
this.view1.removeEventListener('click', this.bindClickView1);
用 handleEvent 可以省點事,要反註冊時也傳入 this 即可。
this.view1.removeEventListener('click', this);

改進建議二:將資料處理的部分分離,使用自訂事件來改變 Store 狀態

分配器 (Dispatcher)負責將操作(Action)傳遞給需要的資料處理器(Store)

資料處理器(Store)負責跟資料直接相關的操作。在稍大的 Web App 中,我們可以另外定一個 Object 來處理資料相關的事宜。一般我們的寫法會是

// Store.js
function Store() {
  this._data: 0;
};

Store.prototype = {
  getSomething: function s_getSomething() {
    return this._data;
  },

  doSomething: function s_doSomething() {
    this._data += 1;
  },

  setSomething: function s_setSomething(val) {
    this._data = val;
  }
};

// App.js
var App = {
  this.store = new Store();
  this.store.init();
  this.store.getSomething();
  this.store.doSomething();
  this.store.setSomething(2);
};
若想要套用FLUX架構,首先我們要避免從資料處理器(Store)外部直接改變資料處理器(Store)。我們可以透過在呼叫端使用 window.DispatchEvent 發送自訂事件(CustomEvent),並在資料處理器(Store)中接收自訂事件來做到。

如此一來,資料處理器(Store)將只留下 get 方法來讓外部取得 Store 想提供的資料。

另外如果程式碼改善進入到下一個重點,在操作(Action)時應該不需要再呼叫 Store.getSomething 了,我們將資料處理器(Store)的 getSomething 方法留著給繪圖者(Renderer)使用 。

// Store.js
function Store() {
  this._data: 0;
};

Store.prototype = {
  init: function s_init() {
     window.addEventListener('store_do', this);
     window.addEventListener('store_set', this);
  },

  handleEvent: s_handleEvent(evt) {
    switch(evt.type) {
      case 'store_do':
        this.doSomething();
        break;
      case 'store_set':
        this.setSomething(evt.detail.val);
        break;
    }
  },

  getSomething: function s_getSomething() {
    return this._data;
  },

  _doSomething: function s_doSomething() {
    this._data += 1;
  },

  _setSomething: function s_setSomething(val) {
    this._data = val;
  }
};

// App.js
var App = {
  this.store = new Store();
  this.store.init();
  //this.store.getSomething();
  window..dispatchEvent(new CustomEvent('store_do'));
 
window..dispatchEvent(new CustomEvent('store_set',
    {'detail':{'val':2}}
  ));

};
這麼做帶來的明顯好處是測試時可以簡單地將 Store 與 App 分開來測試,這對大型App是很重要的。

改進建議三:讓 Renderer 來處理視圖

若資料處理器(Store)修改的結果需要反映到視圖(View)上,可以透過發送訊息通知給繪圖者(Renderer)處理
//  Renderer.js
var ClickRenderer = {
  init: function s_init(element, Store) {
     this.element = element;
     this.store = Store;
     window.addEventListener('render_view1', this);
  },

  handleEvent: s_handleEvent(evt) {
    switch(evt.type) {
      case 'render_view1':
        this.element.textConent = this.store.getSomething();
        break;
    }
  }};

// Store.js
function Store() {
  this._data: 0;
};

Store.prototype = {
  init: function s_init() {
     window.addEventListener('store_do', this);
     window.addEventListener('store_set', this);
  },

  handleEvent: s_handleEvent(evt) {
    switch(evt.type) {
      case 'store_do':
        this.doSomething();
        break;
      case 'store_set':
        this.setSomething(evt.detail.val);
        break;
    }
  },

  getSomething: function s_getSomething() {
    return this._data;
  },

  _doSomething: function s_doSomething() {
    this._data += 1;
    window..dispatchEvent(new CustomEvent('render_view1'));
  },

  _setSomething: function s_setSomething(val) {
    this._data = val;
  }
};

// App.js
var App = {
  init: function a_init() {
    // get view
    this.view1 = document.getElementById('xxx1');
    this.view2 = document.getElementById('xxx2');
    this.store = new Store();
    this.store.init();
    ClickRenderer.init(this.view1, this.store);
  },

 
  handleEvent: a_handleEvent(evt) {
    window..dispatchEvent(new CustomEvent('store_do'));
    //Store.setSomething(2)
    window..dispatchEvent(new CustomEvent('store_set',
      {'detail':{'val':2}}
    ));
};

上段程式在 App 中註冊了 ClickRenderer,並傳入 Store 與 所需的 View 元件。所有的介面更新全交由 ClickRenderer 處理。

(另一個方法是讓繪圖者(Renderer)監看資料處理器(Store)的狀態,然後去改變視圖(View))
 http://fluxxor.com/documentation/store-watch-mixin.html

總結

整理完後,一般 javascript 套用 flux 架構的運作流程如下:


簡而言之,上面的各種建議是鼓勵大家多使用 Javascript 內建的 addEventListener, handleEvent, customEvent 等方法。透過大量使用 event,我們可以改善 Javascript 程式邏輯,資料,與介面元件之間的關聯程度。

將 FLUX 架構拆分為三個重點流程來理解或實踐的好處,是我們能漸進地遵循其中一些方法來改善我們現有的程式架構。

以上是我關於如何使用 FLUX 架構在一般 Javascript 組織方式的第三個版本,可能有些錯謬之處,還迎大家討論或給予建議。


Sunday, August 24, 2014

一個 WebApp,各自表述

前陣子寫了一篇「像原生應用程式一樣的 WebApp?趕快學起來!」,稍微提到現在 WebApp 在桌面和移動端上已經可以像一般應用程式一樣安裝、移除、離線使用。

再進一步觀之,webapp 至今尚未有統一的標準,但Adobe、Google、Mozilla已分別為此推出 Cordova (PhoneGap)、Chrome Apps、WebApp等多種方式來達成此目標。


Cordova (PhoneGap)

Cordova 其實是在各個智慧手機平台上實作共用的Native Adapter,透過 Javascript Interface 來存取設備功能。所以得以使用同一套 web API,而能在不同的平台上執行。

因此 Cordova App 在各平台上執行的效果取決於該平台的 WebView 支援程度。所幸近期兩大 OS 的 WebView 都已隨自家瀏覽器更新,在新版 OS 上 Webapp 的效能已漸漸不再是太大的問題。


Chrome Apps

Chrome Apps 可以在 Chrome 桌面瀏覽器上執行,並提供修改版的 Cordova,Chrome App 可以使用相同的 API 移植到 Android App 上。

近期 Chrome 也進一步釋出 Chrome Dev Editor 與 App Dev Tool,提供在瀏覽器上即可編輯網頁App 與即時在 Android 手機上預覽的功能。


Mozilla Webapp

Mozilla Webapp 可以在「像原生應用程式一樣的 WebApp?趕快學起來!」這篇中找到比較詳細的解釋。

開發工具的部分,近期 Mozilla 亦將 WebIDE 整合進瀏覽器中。除HTML/JS/CSS編輯器外,也附帶Firefox OS 模擬器與 adb 工具,所以在桌面安裝了 Firefox 後可直接在 Firefox 中開發 Web app。開發的 Web app 除了在瀏覽器或模擬器中測試外,亦可以直接傳送到 Firefox OS裝置或 Android 裝置(需要裝 Firefox for Android)上測試。


以上三者之間各自有些異同之處,但並非不可調和。Cordova 已正式支援輸出 Firefox OS webApp,Chrome App 與 Mozilla Webapp 也已共用大部份的 manifest 格式。Chrome Apps 也透過修改 Cordova,來讓 Chrome Apps 的特有 web API能運行在 Android 手機上。


若想開始嘗試寫 Webapp,我寫的 Webapplate 除了可以幫你處理掉開新專案、封裝App、整合測試框架、程式碼風格檢查等問題,也已經同時支援 Mozilla WebApp 、 Chrome Apps,可以參考看看。


Sunday, July 06, 2014

Material Design(物質設計)是什麼?

物質設計(Material Design)

「物質設計」有人翻作材質設計,但我很容易把「材質」聯想到3D遊戲的材質貼圖去,這跟 Material Design 所想要表達的意涵差了十萬八千里。而從相關的英文詞彙聯想,「Material Girl」或譯為拜金女孩或物質女孩,那種對於追求「摸的到的實際東西」有所迷戀的意涵,似乎與 「Material Design」的內在涵義更為貼近,所以我更願意稱之為「物質設計」。

物質設計(Material Design)是什麼?

簡而言之,Material Design(物質設計)是 Google 公佈的一套同時適用於Android、iOS、Web 等各種平台,同時能適用於手機、平板、電視、電腦螢幕等不同裝置的跨平台/裝置的設計規則(仍需為各裝置設計,但鼓勵共用更多相同元素)。

讀者也可以透過觀看 Google I/O 2014 - Keynote(從14:18分開始,由 Matias Duarte 介紹 Material Design)https://www.youtube.com/watch?v=wtLJPvx7-ys  可以得到對於 Material Design 最精巧的介紹。

我不是設計師,僅從開發者角度分享一些個人想法,如果還有興趣請繼續看下去。


從擬物化、扁平化、到物質化的 App 設計

自從 2007 年 iOS 重新發明手機之後,「擬物」化(Skeuomorphism)一直是 iOS App 的設計原則。「擬物」化的設計,讓新接觸「智慧手機」、「智慧平板」的使用者得以沿用過去實物的使用經驗,愉悅地使用新的數位化工具。

但是這幾年智慧手機/平板實在太成功,軟體App 漸漸吞噬了實體世界的各種工具,造成原本「擬物」所參照的物品已紛紛變成老古董,要年輕人畫出電話的形狀,可能畫的卻是手機的模樣...總而言之,許多擬物的參照物已經漸漸被時間淘汰了。

微軟的設計師們很早看出這個趨勢,在2010年推出的 Window Phone 中使用了極端「扁平」化(Flat)的設計風格。
經過幾年來的大膽嘗試,最後也由市占極高的 iOS/Android 定調了使用者界面「扁平」化的發展潮流。「扁平」化的設計,讓使用者得以減少在使用 App 過程中,辨識「擬物」化界面所產生的認知疲勞。

不管是「擬物」化或者「扁平」化,在每個平台的設計指南中,都明確指出設計的App要提供最佳的使用者經驗,必須要符合該平台的風格。iOS還分別為手機與平板提供不同的設計指南,並指出手機與平板是不同的設備,App設計必須要符合該設備的使用情境。

其實平台們的意思很簡單:現在開發者必需要對應每個平台,針對平台不同的風格提出相符的設計。對於同平台的不同裝置,也請分開處理。

於是現在開發者(或提出 Material Design 的 Google,別忘了他們得支援多少種平台與裝置)要面對的,是針對不同裝置、多重平台開發設計 App 時所需面對的各種問題。

「物質設計」(Material Design)就是 Google 整理出的新跨平台、跨裝置適用 (For every screen, and for all devices) 的設計指南。並將首先套用到 Android 的最新版本「L」上。


物質化的 App 設計

「物質設計」是以「扁平」化的 App 設計為基礎,加上紙質分層的概念(即以「Google Now」為代表的卡片式設計),整理出字體、色彩、圖標等設計模式,並加入佈局(Layout)、圖像,與動畫效果(effect)等設計模式。紙質分層與「動畫效果」產生出的設計模式,就我所知,是在之前的各種介面設計中所未特別強調的。

想進一步了解Material Design上的動畫效果,可由 Google I/O 2014 - Keynote(從18.25分開始,由 Matias Duarte 介紹 Material Design Animation)https://www.youtube.com/watch?v=wtLJPvx7-ys  查看。

物質設計(Material Design)工具

看完這些大片的色彩、轉場、動畫效果,不知道身為開發者的你臉綠不綠,反正我綠了。 這樣該如何應用於 app 開發哩? 整個開發成本還了得?

還好 Google 還提供了一些配套工具(但卻不是出現在 Android L)。

(未完待續)

參考資料


Monday, June 16, 2014

遷移個人首頁經驗分享


在DNS代管商網站上輸入要切換的CNAME

在Openshift中輸入domain name alias

遷移成功!


趁週末將個人首頁 www.gasolin.idv.twGoogle App Engine(GAE) 遷移至 OpenShift. 從2009年開始使用 GAE 架設個人首頁,從剛開始 (python, GAEO),2012(Java, play framework),到今天遷移到 OpenShift(Node.js, webapplate / Express),使用了三種 Server SIde 技術。最近的這次遷移只用了兩天,就完成整個申請/移植/部署的過程。遷移的主因是 GAE 最近不再支援 Java 1.6,使得無法繼續使用 Play Framework 1.2.x 來部署。Play Framework 1.2.x 已經被完全改寫的 2.x 取代許久,而新版並未支援GAE。加上近年開始使用js的機會更多,於是乾脆搜尋了一下,選擇 OpenShift 作為最新的遷居點。

除了 Play Framework 例外,GAEO我有參與開發,webapplate 則乾脆是自己弄來撰寫 Web App 的樣板。透過實際使用,目前可無痛部署到 HerokuAppFogOpenShift 上,而不需修改程式碼。所以雖然說遷移只用了兩天,但其實為了弄好 webapplate,也準備了快一年。

由於 webapplate server side 使用 express 4搭配 swig (Django-like) 樣板(template)引擎使用,與原本使用的樣板架構一致,所以遷移上並沒有遇到什麼問題。

webapplate server side 已做過伺服器效能最佳化,使用yslowpagespeed都可以測到相當高分的結果。


Tuesday, May 06, 2014

來自未來的CSS - media 查詢

注意本篇提到的技巧僅適合用在特定平台的Webapp中,透過預處理器來針對特定使用平台做最佳化。但是在撰寫時仍然可以是完全跨平台的。


接續上一篇,我們繼續來重新發現一些對於有助於撰寫結構化CSS的一些CSS標準語法。


Media Query 是撰寫 Responsive design web 響應式網頁的重要元素之一。透過諸如

@media (min-width: 768px) {
  ...
}

這樣的語法, 瀏覽器可以根據條件,刪選並套用特定的規則來顯示。這邊需要注意的是桌面瀏覽器與移動設備瀏覽器/webapp的不同之處:在移動設備上看webapp時,除了基本Orientation之外,並不太需要調整螢幕大小。但是瀏覽器並不知道這之間的不同。瀏覽器看到@media 標簽時的運作模式,並不像一般條件式程式一般,若條件不符合時就不執行,而是一律讀進來並預先解析進 rendering tree。

在最近針對Gaia的一些load time performance measuring中,我們發現在Settings App中,每加入一行 @media 語句就會增加 100ms 左右的啟動延遲,很可能的原因就是每次瀏覽器看到@media標簽時就需要跑一次預先解析過程。因此在瀏覽器引擎本身有提供更好的方案之前,我們勢必得尋找中短期的解決方法來克服遇到的問題。


provecss 中我們嘗試提供了幾種預處理 media query 的方法。一種是搭配「Import」語法提供 filter,根據檔案名稱在 Import inlining 時將不符合條件的檔案拿掉。

例如上一篇中使用到的範例


@import url("app_mobile.css");
@import url("app_tablet.css");



我們傳入 import_filter = ["mobile"] ,則 Import Inlining 結果將只傳回「app_mobile.css」檔案中的內容:

headers {
  background-color: orange;
}

「app_tablet.css」檔案中的內容就被濾掉了。這種方式可以部分解決問題,但使用上的彈性並不是很好。

另一個方式是使用標準CSS語法預處理器(Pre-processor)來查詢 CSS 檔案中的內容,只將符合條件的@media 查詢結果留下。

 我們傳入 { width: '240px', height: '320px' },則
@media (min-width: 768px)並不符合查詢條件,所以輸出的結果也會是 

headers {
  background-color: orange;
}
這個方法能應對更複雜的情境。我們還能進一步 傳入「media_extract」參數,將輸出的 @media 標簽去除。

headers {
  background-color: orange;
}
// was-@media condition
headers {
  background-color: red;
}
於是原本@media標簽中的樣式將覆寫過原來的樣式,在特定機型中達成一樣的顯示效果。(目前主要目標是拿來用在Gaia內建App的編譯上)


如何安裝provecss
npm install -g provecss

使用命令行操作
provecss imprt.css imprt.out.css --import

加入「--import」參數即可啟用 import inlining 功能。

此外 provecss 還可以作為 node 函式庫或是 Grunt plugin 呼叫,更詳細的使用說明請參考 provecss 的 README。

Monday, May 05, 2014

來自未來的CSS - 匯入(Import)

接續上一篇,我們繼續來重新發現一些對於有助於撰寫結構化CSS的一些CSS標準語法。

對於比較複雜的CSS樣式,其實在CSS標準語法中,早已提供使用「import」語法來結構化地組織 CSS 樣式表。

語法如下:

@import url("app_mobile.css");
@import url("app_tablet.css");

瀏覽器在載入時會自動去「app_mobile.css」和「app_tablet.css」檔案中載入相應的CSS樣式。

有經驗的使用者在此會提出質疑:在CSS檔案中使用 import 語法將會拖慢載入速度,千萬別用!

是的,由於瀏覽器須先完全載入這個包含「import」語句的CSS檔案,然後才能再載入「import」語句中的檔案,整個載入流程會被阻塞住,需等待所有CSS檔案載入完成後才能繼續渲染(Render)過程,所以將比直接在檔案中平鋪CSS樣式的載入時間更長。

解決方法一樣是使用標準CSS語法預處理器(Pre-processor)來將使用到「import」語法的CSS檔案「扁平化」。即將「import」語法中讀到的檔案直接嵌入到原檔案中,以達到平鋪的目的。

如果 app_mobile.css 檔案的內容為

headers {
  background-color: orange;
}
 
app_tablet.css 檔案的內容為
 
@media (min-width: 768px) {
  headers {
    background-color: black;
  }
} 
經過「Import Inlining」處理的檔案將變成如下


headers {
  background-color: orange;
}

@media (min-width: 768px) {
  headers {
    background-color: black;
  }
}



 
處理後原來共三個檔案會縮減為一個檔案,達成一樣使用效果的同時,還避免了效能上的疑慮。
 
在 provecss 專案,「Import Inlining」是其中一個主要與 myth 不同的特性。

如何安裝provecss
npm install -g provecss

使用命令行操作
provecss imprt.css imprt.out.css --import

加入「--import」參數即可啟用 import inlining 功能。

此外 provecss 還可以作為 node 函式庫或是 Grunt plugin 呼叫,更詳細的使用說明請參考 provecss 的 README。

來自未來的CSS - CSS 變量

 CSS變量(CSS Variable)是我最近才注意到的新玩意。它要解決的問題其實跟已經行之有年的LESS或SASS相似,即讓 CSS 能用上變量(變數)。

語法像是這樣

:root {
  --main-color: orange;
}

body {
  color: var(--main-color);
}
 
在一個 CSS 根元素中以「--」開頭定義CSS變量,在一般的CSS 樣式中使用「var(--)」來使用變量。

就如範例所示,只要改變一個變量的值,就可以改變整份CSS的參數。例如將「--main-color」變量的值改為「red」,則整個Body的color就會變成紅色。

這參考標準草案有多新?在目前所有的瀏覽器中,只有剛發佈的Firefox 29才有正式支援。

...對於暫時只有在特定瀏覽器上能運作的新功能,一般網頁開發者都是抱著敬謝不敏的態度。就算不提瀏覽器跨平台的支援不足,「CSS變量」一聽起來,跟原本的靜態CSS比起來,感覺就會有效能上的疑慮。

但是前陣子發現了 mythrework-vars工具,這些工具的作用是對使用標準/新 CSS 語法的 CSS 檔案預處理(pre-processing),將新的CSS變量語法轉換成向前相容的靜態語法。透過這樣的方式,開發者在開發時可以使用最新的CSS語法來撰寫更加結構化的CSS,而不需要使用額外的函式庫。同時在部署時可以透過標準CSS語法預處理器(Pre-processor)來將有瀏覽器相容/效能疑慮的 CSS 轉換成「現實版」的 CSS 語法,好讓現在的所有瀏覽器能識別來自未來的 CSS。

經過預處理後,CSS樣式表變成如下
body {
  color: orange;
}
可以看見變量宣告與賦值的部分都直接被整合成靜態CSS了。

在開發 Gaia/webapp 的過程中,我們正遇到了類似的狀況:對於在 FIrefoxOS 平台上執行的 webapp 而言,1.4 版(對應Firefox 28)和以前的 FirefoxOS 版本也無法使用 CSS 變量,但是在未來的版本上卻肯定應該試試這些新網頁技術。對於一般的webapp開發者而言亦然。

於是我建立了 provecss 專案,provecss 專案吸收了 myth 的特性,但為 gaia 和 webapp 做了更多的調整。就 CSS 變量這特性而言,與 myth 不同的地方是 provecss 預設並不處理 CSS 變量。provecss 其他的功能,將在接下來的文章中進一步做說明。





如何安裝provecss

npm install -g provecss

使用命令行操作

provecss vars.css vars.out.css --vars

加入「--vars」參數即可啟用 CSS 變量取代功能。

此外 provecss 還可以作為 node 函式庫或是 Grunt plugin 呼叫,更詳細的使用說明請參考 provecss 的 README。