十月 2, 2015
» Node 版的 SimpleHTTPServer

2008年的時候寫了篇如何在python上簡單開個web server的文章,現在來個node版的:

$ npm install -g http-server
$ http-server -p 9000

參考自StackOverflow這篇文章。

六月 1, 2015
» Javascript,征服世界是可能的嗎?



幾週前在 Modern Web 2015 分享了以「Javascript,征服世界是可能的嗎?」為主題的演講。

演講主題靈感來自於 The JavaScript World Domination 一文。

原本想用編年史的形式表現,一直發展到演講的前幾天,才演變成最終使用的形式。



Javascript征服世界是可能的嗎? 

公布講題後,一些人先跑來問我JS是不是真的可能征服世界。
我能理解大家想要知道最終的答案迫切。但其實大多數時候,聽別人的預測,還是不準確的比率更高。

就我而言,了解別人推論的過程,與他所引用的資料,會影響我對他預測結果的信賴度。哪些資訊是我所不知道的(秘密),哪些是我知道但沒有意識到與推斷目標關聯性的。從推敲的過程中,我可以學到一些新東西,也可以產生一些新想法。這樣的過程比偷看答案有趣地多。

這份投影片裡面分享了四個JS征服世界的秘密,你是否已經知道?我的觀察跟你的觀察一致嗎?有沒有什麼其他你觀察到的秘密想分享哩?


三月 1, 2015
» Future proved Javascript and CSS



This time, its different. The transpilers are build-time polyfills that fill the gap of current browser/server implementation and the newest JS/CSS specs.

Transpilers trans-compile Javascript and CSS to current workable version, so developers could be more productive with JS/CSS latest features and transpilers would translate them into current supported version of code.

From my opinion the most useful es6 feature is arrow functions (=>) which comes from coffeescript. This syntax sugar bind the this value automatically, so developer wont forgot the binding anymore.

The original code is

(function() {
  'use strict';
  document.addEventListener('DocumentLocalized', function() {             
     // App.init();
  }.bind(this));
}());

We can use arrow function to replace `function() {}.bind(this)` to `() => {}`

(function() {
  'use strict';
  document.addEventListener('DocumentLocalized', () => {             
     // App.init();
  });
}());
Currently the arrow functions only default enabled on Firefox. So developer could not use this directly on their project. With babel javascript transpiler the js could be translated to current workable version automatically.

(function() {
  'use strict';
  document.addEventListener('DocumentLocalized', function() {             
     // App.init();
  });
}());
The transpiler will know the content does not need 'this' reference and skip the binding. Note the Javascript transpiler still stick into vanilla javascript. It does not invent new syntax, but it make new specs could be used or experiment in current environment.

From CSS perspective,  CSS variables brings variables into CSS. Pre-define some color and use everywhere on project is possible.

The origin style is

a {
  color: #847AD1;
}

pre {
  padding: 10px;
}
It's frustrated when the stylesheets expand larger and we need to change the new color or size everywhere.

We can use CSS variables to predefine the changeable stuff into pseudo ':root' element.

:root {
  --maincolor: #847AD1;
  --size: 10px;
}

a {
  color: var(--maincolor);
}

pre {
  padding: var(--size);
}
Looks good. But the same situation occurred. Currently the CSS variables only support on Firefox. So developer could not use this directly on their project. With myth CSS transpiler the CSS could be translated to current workable version automatically.
a {
  color: #847AD1;
}

pre {
  padding: 10px;
}
Note the CSS transpiler still stick into CSS specs. It does not invent new syntaxes like LESS or SASS does. It just make new CSS specs could be used or experiment in current environment.

Besides the feature polyfill, running transpiler before deploy also help developer catch out the error since transpiler will traverse the source and parsing syntaxes. Transpiler will bark you when any error in your source.

You may wonder setup an environment with above JS/CSS transpiler seems take some time. webapplate 2.3.0 already integrated with babel  and myth grunt tasks to auto transpile your JS/CSS code to current workable version. webapplate jshint/githook also support es6 syntax validation (yes the .jshintrc is exactly borrowed from gaia project), so your project is future proved and keep maintainable.

二月 22, 2015
» 2015 年的 4 個 Web 技術趨勢

要預測未來不容易,但根據已經發生的一些蛛絲馬跡,來推測看看一年半載內可能會發生什麼,還是勉力可以為之。

1. Javascript 語法的改進

ECMAScript6 (ES6) 規格已經底定,主流的瀏覽器也已紛紛支援 ES6 的各種特性。好用的諸如 arrow functionpromises 等特性,都已在 Gaia 專案中大量被使用。

要追求專案與舊瀏覽器的相容性,有 Babeltraceur 等轉譯器可供使用。中文英文教學文件也已不少,就安心地用吧。


2. DOM 的改進

去年最紅的前端函式庫當推Facebook提出的 react.js。react.js 提出使用 Virtual DOM 來管理更新與繪製畫面,在不修改現有瀏覽器實作的現況下,提升操作DOM的效能。

Web Component 則是提供了多種新的瀏覽器特性,Gaia 專案裡也已經使用 Web Component 來設計新一代共用的介面元件。Custom Element 特性讓每個開發者可以自行定義新的 HTML 標籤,Shadow DOM 特性則讓每個 HTML 標籤的實作獨立,不會輕易被其他網頁內容改變。

好消息是這些改進是可以共用的




3. HTTPS 大量被採用

(離線存取的改進)


Service Worker 是規劃了多年的重頭戲。Service Worker 提供更完備的離線存取功能。在第一次使用 Web App 時會快取內容,之後再次使用 Web App 時只需更新不同的部分。由於這功能在各瀏覽器中都還在實驗或Beta階段,最快可能是年底上線。因此開發者能大量投入使用的時間點應該是2016年。

無論是 Service worker 或是也剛底定的 HTTP 2.0 草案,都需要運行在加密過的 HTTPS 上。Google 更是為使用 HTTPS 連線的網站提升搜索排序。過去由於證書取得不易,限制了 HTTPS 的使用率。Mozilla 與 Cisco 等廠商今年將合作提供免費的CA證書。當然,也可以透過現在已經存在的 StartSSL服務 來取得免費證書。


4. 預編譯網站

現在比較大型的網站或 web app 都是透過 build script 預做一些 packaging,optimize,或 trans compile 等動作後才部署上線。除了可以壓縮網頁大小以減少載入時間,同時也起到部分保護原始碼的效果。由於 GruntGulp 等工具的流行,預編譯這方面的門檻降低不少。中、小型,甚至個人網站,也可以很容易地使用這些技術來建構網站或Web App。


最後賣瓜一下:webapplate 這個範本已經整合好 grunt based packaging,optimize,trans compile 等動作,新版本也加進了 Babel trans compiler,可以直接使用 ES6 語法開發 web app。要開新 web 專案時歡迎取用。

十二月 4, 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 組織方式的第三個版本,可能有些錯謬之處,還迎大家討論或給予建議。


八月 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,可以參考看看。


biggo.com.tw

A Django site.