2018年9月27日 星期四

如何成為更好的軟體工程師

六月份的時候,被 G 社找去面試,雖然最後沒有上,不過在準備的過程中,以及面試後的自我反省,了解到自己還有很多可以進步的地方,於是踏上了尋找工作的旅程。在之後又持續面試了幾間公司,雖然到目前為止都還沒獲得新工作,不過每次的失敗,都給了我一些對於工作的想法與啟發,想在這篇文章記錄下來,未來可以回來複習一下。

我覺得我一直以來工作算蠻順遂的,第一份工作就有機會進入世界級的外商公司上班,開了眼界;其次我工作很認拼,也都碰到不錯的老闆,升遷也算蠻快的。只是我一直不知道,我到底是怎麼表現好,因此受到賞識,進而維持這些優點,維持這個好的狀態。一直到我加入了一間不一樣的公司,讓我不得不修改原本的做事方式,遺忘了原本的做事方式,一些能力也漸漸一起遺忘了,最後連工作的熱情也漸漸遺忘了。

透過這些面試,讓我重新檢視自己如何工作,把一些能力與感覺抓回來,也更確立了未來的工作態度 -- 持續保持熱情。


一、好的工程師能夠提出 Edge case 驗證想法

Google 主要是面試技術題,在面試前我寫了 Leetcode Easy & Medium 的考題。
我最後幾年,都在做系統架構設計,因此對於這些演算法相對生疏,因為很久沒寫比較深的程式了,一開始寫的時候,平均一題要寫一個小時。

在解了演算法的這些題目之後,我忽然了解到,演算法設計和系統架構設計的相同之處,都是在於「目標情境」的「邊界條件」的處理,能夠將這些邊界條件處理的越好,就是越好的方法,最後的結果才會有好的「品質」。

「目標情境」指的是我們真正要解決的問題。因此邊界條件也不是無窮亂舉,而必須要屬於這個問題的範圍內。

「品質」意味著沒有錯誤,也就是運算的結果都是對的;在正確性最優先的前提下,我們才選擇複雜度、運算時間、運算空間較優的方法。也就是說,如果結果不正確,那麼演算法跑多快都沒有意義。

「邊界條件」指的是演算法的限制,或是系統的限制。例如要設計一個除法的演算法,那麼其中一個邊界條件就是整數最小值除以 -1 的情況是多少?如果設計一個批次處理的系統,那麼最極端應用案例是什麼?

二、好的工程師會先提出能正確解決問題的方法,之後再優化他

由第一點可以知道,「正確」是最重要的,也正代表著「品質」。
因此我們首先必須得到一個「正確」的參考設計,即使這不是最優的解法,例如時間複雜度是 O(n^2)。
接著就是腦力激盪的時候了,是否可以改善現有的方法,例如變成 O(nlogn),而且適用所有情境?又或是的確存在一個比較快速的方法,但是只有當特定條件滿足時,我們才可以使用這個方法?
之所以特別把這個列成一點,是因為重要的是分析問題,而不是找到捷徑找到所謂「正確」的答案。即使用感覺的得到一個很好的解法,但沒有好的分析問題能力,也無法證明這個方法是正確的,這麼一來結果還是不確定的,品質就是不穩定的。
只要學會分析,再搭配一些創意,永遠可以得到更好的解法。

三、學會和別人討論,並嘗試兩個人合作找到更好的解法

每個人的思考邏輯都不一樣,因此每個人切入角度不同,發展出來的長處也都不同。
若是一個人想解法,可能需要很多時間才能找到比較好的答案。
因此與自己觀點不同的人討論,可以幫助自己避開盲點;不同思路的碰撞,更容易激發出更好的方法。
與人討論,如果只是要證明自己的想法是對的,除了傷害彼此感情,而且永遠只會得到「自己永遠是對的」的結論。

四、好的工程師重視基礎能力

工作得越久,一些高級工具用得越多,基礎能力用得越少也忘得越多。
但是要用上述的方式找到更好的解法,最考驗的還是基礎能力,包含一些基礎知識、問題分析能力、溝通能力等。平常工作的時候,也可以多找一些機會練習溫習。

五、好的工程師能夠提前反應風險,對專案做良好的預估

在一個讓工程師發揮的環境裡面,每個人都是專案的擁有者。
專案一定是有挑戰性的,因此我們要針對專案做風險控管,才能避開陷阱、準時交件。
因此在接案的時候,無論是多大多重要的案子,如果能夠提早發現風險並勇敢地提出,也就能越早準備需要的資源,最後也會有比較好的結果。
若是硬吃案子最後開天窗,那麼有可能也是離開公司的那一天。

六、只有對自己正在做的事情保有熱情,才能持續地精進自己的技術

在面試的時候,我發現自己還有很多能力不足的地方。老實說,一開始面試我也只是想找一份穩定一點、福利好一點、薪水高一點的工作。但是越面試到後面,我發現只有持續不斷的去做去挑戰,我的技術能力才有辦法到達下一個層次。

於是我開始回顧過往碰到的許多神人同事,他們是如何工作的?

我發現他們除了上班之外,下班也不斷在做一些自己的專案。與其說他們很認真,下班不斷的在學習,不如說他們是因為對他們做的事情非常有熱情,因此不斷地投入鑽研。唯有找到自己有熱情的事情並且去做,而技術就不自覺的在執行過程中累積提升了。



我今年面試的心得大致就這樣了,對於自己的能力有失而復得的機會是很寶貴的,我從中體會了很多,學習了很多,也感覺到自己的一次蛻變。今後無論是否是在如 Google 這樣的公司工作,也都希望自己能夠謹記上面的要點,希望未來自己能夠成為更合格的工程師。

2018年4月12日 星期四

使用 OpenSSL 建立自己的 CA

最近有要自建 Self-signed CA,發現了這個好的教學。
OpenSSL Certificate Authority

特別在網誌推薦。

2017年3月18日 星期六

修正 fabric.js 在 android 移動物件時頁面捲動

最近被回報 Fabric.js 在 Android 手機上,移動物件時會造成頁面滑動。
去看了一下 fabric 的原始碼,原來他是利用 e.preventDefault() 來避免頁面捲動的。
原始碼:http://fabricjs.com/docs/fabric.js.html#line10465

   /**
     * @private
     * @param {Event} e Event object fired on mousemove
     */
    _onMouseMove: function (e) {
      !this.allowTouchScrolling && e.preventDefault && e.preventDefault();
      this.__onMouseMove(e);
    },

然後就找到這篇,原來 Chrome 為了讓瀏覽器速度變快,所以新增了 passive 參數讓 touch event 不會在 calling stack 中藉由 preventDefault() 給取消掉。而且這個參數在 Chrome 56 以後預設為 true。

window.addEventListener("touchstart", func, {passive: true} );

請見:


那麼要怎麼樣避免捲動呢?Chrome 給的答案是增加 CSS touch-action 為 none

#canvas-parent {
 touch-action: none;
}

請見: https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action

2017年3月8日 星期三

[連結] 台灣金流串接程式



https://www.facebook.com/recca.tsai/posts/10154973827344181


===== 以下節錄自上面的內文 =====

說明還沒弄完整所以可能看到程式會不知道如何使用
所有的程式都依附在
https://github.com/Payum/Payum
和omnipay是一樣的功能
原作者的比較
http://stackoverflow.com/....../difference-between......
所以要使用它必須得再去了解一下 payum 如何使用
基本的使用方式
https://github.com/....../Resources/docs/get-it-started.md
另外如果是開發 Laravel 的人門檻會比較低
可以真接使用
https://github.com/recca0120/laravel-payum
==============================================
另外如果想要參考程式碼的話
大家可以只閱讀專案內的src資料夾內的 Api 結尾的程式碼
==============================================
最後 PayumTW 的程式碼不一定會是最新的
所以的程式碼會在我個人的 github 內完成後
再 folk 到PayumTW內
所以最新的程式碼請到原作個人的 github
https://github.com/recca0120/payum-ezship
https://github.com/recca0120/payum-collect
https://github.com/recca0120/payum-ecpay
https://github.com/recca0120/payum-allpay
https://github.com/recca0120/payum-mypay

[連結] Javascript 教學: JavaScript Stack from Scratch

This is a straight-to-the-point guide to assembling a JavaScript stack. It requires some general programming knowledge, and JavaScript basics. It focuses on wiring tools together and giving you the simplest possible example for each tool. You can see this tutorial as a way to write your own boilerplate from scratch. Since the goal of this tutorial is to assemble various tools, I do not go into details about how these tools work individually. Refer to their documentation or find other tutorials if you want to acquire deeper knowledge in them.

https://github.com/verekia/js-stack-from-scratch

2017年2月7日 星期二

Let's Encrypt 工作原理

把官方文件 (https://letsencrypt.org/how-it-works/) 看了一下,發現沒有正確的中文原理說明,所以來把我了解的發佈一下。

先來簡單介紹 Let's Encrypt (LE) 實際使用情況,你會在你機器上面裝一個小程式 (agent) 幫你去 LE 做申請 / 更新憑證的動作。所以這篇文章主要是講解這個小程式做了什麼事情,不然的話其實下指令就可以搞定所有事情,你不會知道中間發生什麼事。

Step 1. 產生 authorized key


這個只有 agent 第一次使用的時候會需要進行。authorized key 可以當作代表 agent 去操作 LE 的 API Key。所以我們可以說這是 API Key 產生流程。



  1. Agent 產生一組新的 key pair
  2. 通知 LE 要註冊 example.com 這個 domain
  3. LE 傳回來說可以用 DNS 驗證或是檔案驗證,2 選 1。然後以下是驗證資料
    1. 在 8303 的位址放 ed98
    2. 請將這個亂數 9cf0b331 加密
  4. 這個範例,agent 選擇使用檔案驗證。
    1. 他在 https://example.com/8303 內容放 ed98
    2. 將 9cf0b331 用私鑰加密,然後將加密結果以及公鑰一起傳給 LE
  5. LE 驗證網址和亂數都正確,這把公鑰就變成 authorized key 可以拿來操作 example.com 的憑證


Step 2. 申請 / 更新 / 註銷憑證



  1. 當 agent 拿到 authorized key 以後,就可以發送 PKCS#10 Certificate Signing Request (CSR) 給 LE 取得憑證。
  2. 產生一組新的 key pair
  3. CSR 裡面包含了
    1. 私鑰的 signature
    2. 公鑰
  4. 把整個 CSR 用 authorized key 加密傳給 LE
  5. LE 驗證 authorized key 以及欲申請的 domain。如果驗證通過,解開取得 CSR 之後核發憑證傳回 agent
這樣就完成取得憑證的程序。

更新以及註銷憑證的流程都類似,就不再多做說明了。

2016年12月30日 星期五

建置網路服務案例 - 以繪禮物為例

今天晚上受邀至朋友舉辦的 Meetup 分享。

分享我如何將一個現成的 business idea (繪禮物 http://www.giftpaint.com) 轉化為網路服務,並且建置出來。

由於此 Meetup 與 coding 有關,所以會簡單帶過 Laravel framework。
希望能讓一些想學程式或者有點子卻不知道如何開始的人有幫助。


2016年11月17日 星期四

Laravel 5.2 發送內部 Request 來呼叫現成 AJAX API

如果我在 Laravel Controller 內部,想要 call 另外一個現成的 AJAX 做事怎麼辦呢?
我們可以利用內部 Routing 機制,不用真的發 HTTP request,就可以達到類似的效果了。

以下這段程式情境如下:
  1. 我們的顯示購物車頁面,有一個參數是欲加入的商品代號。如果這個參數有值,會先將此商品加入購物車以後才出現商品清單畫面,使用者從這個畫面開始結帳流程。
  2. 在此之前,我們的使用者是利用一隻 AJAX API 來將商品加入購物車。
public function getCheckoutForm(Request $request) {
        $productId = $request->input('product_id', null);
        if(!empty($productId)) {
            // http://stackoverflow.com/questions/16597420/how-can-i-access-query-string-parameters-for-requests-ive-manually-dispatched-i
            $rr = Request::create(route('post.ajax.addcart'), 'POST', ['product_id' => $productId, '_token' => csrf_token()]);

            // Store the original input of the request and then replace the input with your request instances input.
            $originalInput = $request->input();
            $request->replace($rr->input());
            $response = Route::dispatch($rr);
            $request->replace($originalInput);
        }
        // getCheckoutForm() business logic
}

在這裡我們可以看到
  1. 我們先建立新的 Request 物件 $rr。
  2. 然後備份目前的 request input
  3. 將目前的 request input 置換成 $rr 的 input
  4. 利用 Route::dispatch() 產生 Request call
  5. 呼叫完畢以後再把目前 request input 換回來

參考

2016年8月17日 星期三

做行銷的人與做產品人的糾結

最近聽了一些故事,看了一些網站,然後產生心裡的惡魔:做行銷的人與做產品人的糾結。

這些小網站都是行銷高手創立的,內容是他們的武器,他們攻的是客人的「心」,再把自己的(銷售)能力包裝成產品賣給別人,然後賺了很多錢(至少自己夠用)。

天平的另外一端是工程師,講求用最好的方式高深的技術,創造一個有高度彈性,碰到颶風也不會倒的系統。

做行銷的人


行銷成精的人,你只要給他wordpress的頁面可以打打字貼連結,每個頁面有獨立網址,他們就可以開始做生意。
當他們碰到一個新想法的時候,心裡想的是,客群是誰,我的文案要下什麼套路能對顧客講故事,讓他越看越深最後買單。

做產品的人


做系統做到精的人(含PM, SA, RD, QA, QE),碰到新想法的時候,開始分析需求切功能,然後開始想像這些功能可以如何被使用,這個系統會不會被破解,流量大撐不撐的住,做不做的出來。

兩種人互看


行銷人看做系統的人:賺錢不用這麼複雜,能吸引客人的東西沒半樣,給我wordpress就好。
做系統的人看行銷人員:我做的系統功能這麼強大,居然只用那1%功能,到底懂不懂呀?

其實我們兩種人都需要


懂行銷的人懂市場,知道客戶在哪裡以及他們需要什麼。Wordpress 雖有彈性,但是其中很多小環節都是人工處理,例如產生 Email 名單並貼到另外一個系統,或是訂單細節與客戶電話來來往往討論,一旦需要將業務規模化(更大量)就會需要更多這些事務性的人,隨著人員擴張,公司會越來越沒有效率(人需要組織管理,另外每個人都是特別的,不可能請到一模一樣的人),最後公司隨著人員膨脹,而這些人員都在做事務性工作,對公司並沒有增額的產出,也因此沒辦法獲得更多資源做下一輪投資。

「系統」存在的目的是為了「規模化」,要達到規模化必須要自動化,以防止上述事務性人員擴張的問題。將已經市場驗證完成的商業模式,找出行為與規格,創建出系統自動化,或者讓失誤的機率降低以減少成本。到了這個階段,就變成針對該商業模式客製化系統的過程 (無論是擺脫 Wordpress 重新寫一個,或是找技術人才寫 Wordpress 外掛)。

要規模化,隨之而來的就是限制(做法與規格),才能產生流程與系統。
有了系統的幫忙,原有的人力可以空出來,搜尋下一個機會,等確認了以後,再轉換成系統,如此往復,系統價值不斷的累積,人力的成本卻沒有等比例增加,公司的價值就產生了正向的循環。

這兩種人活在不同的世界,做事方法也截然不同,甚至對方的長處可能是自己用不上的。但我想最重要的是互相理解並合作,或許用另外一種方式試試看,能獲得不一樣的收獲。

2016年7月6日 星期三

在 Shell 下 pretty print JSON

Python

$ python -m json.tool some.json
或
$ cat some.json | python -m json.tool

PHP json_decode

上面 python 的方法只是純排版,但是如果 unicode 已經被編碼為 \xxxxx 模式的話,不會幫你 unescape。
所以又想了這一招
在 .bashrc 或 .bash_profile 加入
alias pretty_json='php -r "\$json = file_get_contents(\"php://stdin\", \"r\"); \$d=json_decode(\$json); echo json_encode(\$d, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . PHP_EOL;"'

之後只要下
$ cat some.json | pretty_json

2016年2月13日 星期六

從大公司到創業小公司:尋找主題的心路歷程

2015 年九月,我離開了待了 3 年又 11 個月的 Yahoo ,跟著前老闆創業的夢想,加入了他的公司,成為第一號員工。

五個月過去了,雖然目前還沒有什麼說得上的進展,但是中間心路歷程還是紀錄一下,留給未來的我。

大公司的開發模式


大公司和小公司果然不一樣。

在大公司有做不完的案子,業務、營運、企劃都有一大堆讓產品更好想法、一大堆新功能、一大堆客戶的建議,希望工程部門能夠接受他們的想法幫忙把東西做出來。但是工程師就這麼些人,這樣海量的專案不可能做得完,所以要有人篩選。所謂篩選,其實是無盡的會議,來評估每一個案子未來效益如何,機會成本如何,值不值得捨棄其他的案子現在就做下去。

未來的事情尚未發生,沒有人知道東西做出來是不是真的可以達到指標,我們只能在平行時空裡面用最嚴謹的邏輯持續辯證,排除可以看得到的問題。再加上部門多人多意見多,團隊合作就是妥協的藝術。但是產品一旦妥協變更需求,就又跟當初想更不一樣,至於最後達成的成效,就只能求神拜佛,上線前一天就跟選舉投票前一天一樣,民調都只是參考用,希望投票當下選民能把票投給我們。

但無論大公司小公司、有雄厚資本的外商、或是純自幹的個人 SOHO 族,其實這就是產品誕生的過程,產品尚未存在這個世界上,在還沒做出來以前沒有人知道結果,我們所能做的就是上線前準備到最好。

但是天底下哪裡有這麼好的事情,東西初次做出來就是使用者要的。如果人們能夠通靈知道使用者心理在想什麼,要做到什麼程度他們才會願意掏錢,那必定存在一間公司永遠賺錢,獲利不會衰退,可惜世界上沒有不會衰退公司。身為人類我們只能從種種數據中看到使用者的反應如何,是不是有符合使用者的心但目前只是轉換率還不高,如何用方法提升使用者的轉換率於是再下去修改下一個版本。

好的產品就在這麼一次又一次的修改當中越來越好,最後達到 MVP,出現神奇的瞬間,服務就起來了。

如果你在心態上只是要當一個安穩的工程師,那麼上述一切就會與你無關。而這是在大公司的環境裡面,被無數多的案子雜事的壓榨凌虐之下,極有可能萌生心理上的小確幸。

小團隊的開發模式


到了小公司,什麼都不一樣了。

以前大公司的案子你都玩不起,我們公司就這麼兩三個人,資金是燒老闆自己的儲蓄,可能案子沒做完,資金就燒完了。

我們也沒有操作一個產品上線並且真正面對客戶的經驗,也不懂得行銷。作為一個工程師,學習工具對我們來說相對容易,甚至我們可以把工具再自幹一個出來,但是就是不知道怎麼用,這就是悲慘的地方。

最後,你要做什麼主題?再也不會像以前一樣有案子排隊硬擠給你。要自己想什麼東西有機會,什麼東西可以進去做,做完了以後拿不拿的到錢,可以讓公司活得更久一點,這個東西可以活多久?這種感覺像是雖然你是工程師,但你知道現在的資金夠你幾個月可以活,你投資未來幾個月自己的時間去創造了某個東西,是不是可以養活接下來的你。而以前我們的基準是去想產品利用這個工法是否可以準時上線,會不會只有這麼短的時間結果下不了班。

但是這並不代表你會永無止境的加班,反而這能讓你更有效的投資你的時間。你能計算出做這件事情報酬會是什麼,時程上的成本會是多少,風險能不能承擔。能了解自己投入的每分鐘的產值很有一種踏實感,因此也更能使用自己的步調前進,壓力也不會這麼大。

當然,這是人少的時候才會需要思考這些事情。或許人一開始便多就會變回以前大公司的模式,但是公司文化也會影響整個開發的思考模式、節奏與感覺。

我和我老闆都是大公司來的,一開始我們身上都帶有那種大公司處理事情的態度以及想法。但很快的我們發現憑我們兩個人玩不起這樣規模的專案。於是我們從小網站開始做,驗證自己的想法與觀點,學習最新以及開源的技術,行銷的技巧,怎麼做 landing page 下下廣告之類的。用以前的模式比喻,就是拿真的錢去玩 Hackday,但是這個想法做出來以後必須要能夠讓公司有機會活得更久。 

在這過程裡面,我有看到老闆思維上的轉變,少了那麼一點大公司的氣息,對產品的觀點卻更敏銳了。
 

挑選主題


我們團隊目前還在尋找想法和方向,有的時候閒聊想到一個新的想法就會開始進行辯論,討論這個想法有沒有機會幫公司賺錢,一辯論就兩三個小時過去了。

這一兩個月,我也早早下班,私底下想了幾個點子做了 Hack,一個是用 PHP 去爬 PTT 看板串接 Slack 做個爆掛通知,這是我從很小的時候就想做了,只是我發現繼續做下去就很像鄉民晚報了;另一個是之前同事的一個想法,把 APP 改成用 Responsive Web 目的是幫他趕快找到心儀的對象。
 
在做這兩件事情的時候,讓我更不得不去想一個兩三人的新創公司,到底該做什麼主題好呢?做什麼主題會有機會呢?什麼主題是值得投入自己兩年三年的時間去做的呢?如果讀者你也有機會加入這樣超級新的新創公司或者要當老闆不妨參考看看。

1. 擁抱新的東西與觀念。


如果還不知道要做什麼,可以去學習或試用最新的科技或商業模式。新科技和商業模式其實都是工具,他們比舊有的工具會更有效率。尋找看看你擅長的領域,這些新科技或商業模式有沒有派上用場的地方,如果能派得上用場,而且新的解法比原本的模式有更好的效益,而且沒有人做過,恭喜你,趕快去做吧!

2. 不要一開始做太大的東西,2 ~ 3 個人左右花幾個月時間做得出來的東西,這才是大小剛好的想法。


為了符合這一項,你必須要砍掉產品多餘的部分,只留下核心。趕快做好最小的產品丟到市場上,觀察使用者的使用,蒐集使用者的意見,持續改進你的產品,直到使用者買單。

3. 做新的東西。


所謂新的東西,有可能是從來不存在世界上的東西,或者類似的東西或服務已經有了,但是你融入新的做法,讓商業模式更容易規模化。

4. 熟悉與不熟悉的領域。


做熟悉的領域可以節省比較多的時間,也比較知道那裏是地雷,缺點是容易深陷舊思維裡面無法跳脫框架,於是做不出新的東西。也有可能過去認為的地雷其實不是地雷,但是你就是被舊思維限制住了。

做不熟悉的領域很多都要學要實驗要嘗試,所以會花很多時間,因為不知道哪裡是地雷,所以都很勇敢,大不了踩到地雷爆炸了,早點收收換下一個主題不會賠太多。

5. 做朋友可以幫助你的事情。


朋友能夠提供寶貴的經驗,即使是陌生的市場,如果有個朋友可以當個顧問,可以避免走很多冤枉路。如果能夠異業結盟做出雙贏局面,那就更好了。

6. 幫助別人,讓社會更好。


一個產品的價值在於解決的問題有多少價值,當這個產品解決的問題有價值,自然就會有人用。

當我們缺錢的時候,自然會陷入會計的精算,斤斤計較。這樣的方式結果就是無法解決任何問題帶來價值,唯一能做的就是成本上的精簡。如果是一個新產品,你會得到成本太高無法進行的結果。我們如果確保我們投入的資源不高於要解決的問題的價值,這樣就不會虧損太嚴重。

解決問題的本身是在幫助別人,當大家的問題都漸漸被解決了,社會也就變好了。



以上是我目前想到的,希望大家包含我們自己都能早點找到目標與方向。

2016年1月12日 星期二

在 Electron 裡使用 node_sqlite3

這幾天工作需要,所以需要在我們的 Electron 專案中使用 sqlite3,但是當程式跑起來需要用到 sqlite 的時候,卻發生找不到 module 的情況。

錯誤訊息如下: 
Error: Cannot find module 'C:\Users\hialan\electron-app\node_modules\
                       sqlite3\lib\binding\node-v47-win32-x64\node_sqlite3.node'
    at Function.Module._resolveFilename (module.js:338:15)
    at Function.Module._load (module.js:289:25)

這是因為 electron 嘗試要去找 node_modules\sqlite3\lib\binding\node-v47-win32-x64 但是我們安裝下來的 sqlite 卻只有 node_modules\sqlite3\lib\binding\node-v46-win32-x64,因此就找不到 module 摟。

後來找到了這一篇文章,照著的說明就可以成功的在 windows 以及 mac 上重新編譯 node_sqlite3 正常執行了。以下將步驟節錄在此。

依照 electron 的文件,node-gyp 可以幫助我們重新編譯 module。
如果是 windows ,要記得安裝編譯器,可以參考這篇文章 在 windows 安裝 electron-prebuilt。

主要的指令為
$ cd node_modules/sqlite3 && npm run prepublish 
$ node-gyp configure --module_name=node_sqlite3 --module_path=../lib/binding/node-v47-win32-x64 
$ node-gyp rebuild --target=0.36.2 --arch=x64 --target_platform=win32 --dist-url=https://atom.io/download/atom-shell --module_name=node_sqlite3 --module_path=../lib/binding/node-v47-win32-x64

  • --target 是我們 electron 的版本,目前我用的是 0.36.2
    • 可以用 ./node_modules/.bin/electron -v 看到你的版本
  • --module_path 是 electron 要找的路徑
  • --target_platform 是你的作業系統
    • Windows 為 win32
    • Mac 為 darwin
  •  --arch 為你的 CPU 架構 
target_platform 還有 arch 可以用這個指令拿到
$ npm config get user-agent
npm/2.14.12 node/v4.2.4 win32 x64

第三個是 target_platform, 第四個是 arch

把他合成一行加入我們的 package.json 的 script ,變成

"scripts": {
  "rebuild-sqlite3-win32": "cd node_modules/sqlite3 && npm run prepublish && node-gyp configure --module_name=node_sqlite3 --module_path=../lib/binding/node-v47-win32-x64 && node-gyp rebuild --target=0.36.2 --arch=x64 --target_platform=win32 --dist-url=https://atom.io/download/atom-shell --module_name=node_sqlite3 --module_path=../lib/binding/node-v47-win32-x64",
  "rebuild-sqlite3-darwin": "cd node_modules/sqlite3 && npm run prepublish && node-gyp configure --module_name=node_sqlite3 --module_path=../lib/binding/node-v47-darwin-x64 && node-gyp rebuild --target=0.36.2 --arch=x64 --target_platform=darwin --dist-url=https://atom.io/download/atom-shell --module_name=node_sqlite3 --module_path=../lib/binding/node-v47-darwin-x64"
}

就可以
  • 編譯 windows: npm run rebuild-sqlite3-win32 
  • 編譯 mac: npm run rebuild-sqlite3-darwin
參考資料

2016年1月10日 星期日

[教學] Electron + React: 使用 JavaScript 建立跨平台桌面應用程式

平常建立桌機用的視窗應用程式,我們都是用 C++, VB6 或是 C#。但是現在的產品,除了要提供網頁版,手機 APP,Windows / Mac 桌面的客戶端,不然使用者會很不方便。但是使用的語言不同,我們便需要使用不同的技術重複寫好多次。

Github 推出了 Electron 這個開源的專案,它的出現讓使用 JavaScript 來寫桌面應用程式變為可能。Electron 可以視為使用 io.js 控制的修改過的瀏覽器,利用網頁技術來做為畫面的輸出,但同時又可以存取本機資源 (沒有 sendbox)。

如此一來,網頁和桌面應用程式就沒有任何差別,只要瀏覽器可以跑的地方我們的程式幾乎都可以執行 (取決於是否用到特殊的 extension)。而因為是用網頁做為視窗畫面的輸出,所有網頁現成的工具如 jQuery, React, bootstrap 等,也馬上可以用在桌面應用程式上了!

目前 Github 的 Atom 編輯器,Slack 桌面端,微軟的 Visual Studio Code 都是建構在 Electron 上面。

Electron 簡介


Electron 是 Github 推出的 Atom 編輯器的底層,也是 Github 的一個開源項目。

我們可以將 Electron 視為是修改過的 Chromium 瀏覽器提供 Node.js API (Chromium Content API),讓我們可以寫 JavaScript 來控制視窗的行為,而 Electron 也是修改自 Chromium multi-process 架構。以下投影片來自 Electron × React — 前端開發者高速跨界桌面開發 。




他每開啟一個視窗,可以想像成開啟 Chrome 瀏覽器的一個 tab,一個 tab 是一個獨立的 process。每一個視窗都可以有自己的 WebKit,有自己的 DOM,執行自己的 JavaScript 來處理畫面,因此我們可以在這裡面套用 React 方便我們做頁面的互動。

一個應用程式可能會有很多個視窗,每個視窗都是獨立的 process,因此在 Electron 裡面,還會有一個幕後的 process 稱為 main process 來管理這些視窗 process,視窗 process 又叫做 renderer process。



接下來,我們來撰寫一個只有一個視窗的應用程式,並且作為範例。
 

撰寫第一個 Electron + React 應用程式


Electron 基本操作


我們應用程式的檔案結構如下:
your-app/
├── package.json
├── main.js
├── webpack.config.js
└── app/
        ├── mainWindow.html
        └── mainWindow.jsx
其中 package.json 格式跟 Node 模組的 package.json 一樣。其中最重要的是 main 欄位指定的檔案會成為 Electron 開始程式的檔案,這會啟動 main process。package.json 範例如下:


package.json
{
  "name": "electron-example",
  "version": "0.1.0",
  "main": "main.js"
}
我們也指定程式開啟的時候讀取同一層目錄的 main.js 這個檔案。

註:如果 package.json 沒有指定 main 欄位,Electron 預設會使用 index.js 這個檔案。

main.js 是用來建立視窗,處理系統事件。我們範例的 main.js 如下:


main.js
'use strict';

const electron = require('electron');
// app: 控制應用程式生命週期的模組
const app = electron.app;
// BrowserWindow: 建立系統原生視窗 (native window) 的模組
const BrowserWindow = electron.BrowserWindow;

// 保留一個全域的物件關聯以避免 JavaScript 物件 GC 機制造成視窗自動關閉
let mainWindow;

function createWindow () {
  // 建立 browser window
  mainWindow = new BrowserWindow({width: 800, height: 600});

  // 載入 mainWindow.html 作為畫面
  mainWindow.loadURL('file://' + __dirname + '/app/mainWindow.html');

  // 開啟開發者工具
  mainWindow.webContents.openDevTools();

  // 當 browser window 被關閉時,會送出 'closed' 訊號,並執行相關的 callback
  mainWindow.on('closed', function() {
    // 將此 window 物件解除關聯。
    // 如果你的應用程式支援多視窗,通常會將這些物件存在一個陣列裡面。
    // 現在就是刪除對應的視窗物件的時機。
    mainWindow = null;
  });
}

// 當 Electron 完成初始化並且可以開始建立視窗的時候,
// 會發送 'ready' 訊號,並執行對應的 callback
// 我們指定收到 'ready' 訊號時,執行 createWindow()
app.on('ready', createWindow);

// 當所有視窗都關閉時,結束應用程式 ( app.quit() )
app.on('window-all-closed', function () {
  // OS X 的使用習慣是當所有視窗關閉的時候,上方的 menu bar 仍然維持開啟
  // 此時應用程式還沒有完全關閉,除非使用者強制按 Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit();
  }
});

app.on('activate', function () {
  // OS X 通常在應用程式已經起來了,但是所有視窗關閉的時候,還可以重新建立主視窗
  if (mainWindow === null) {
    createWindow();
  }
});

而 app/mainWindow.html 是你想要在視窗中顯示的內容

app/mainWindow.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>My Electron-React app</title>
  </head>
  <body>
    <div id="content">
      Hello World!!
    </div>
  </body>
</html>

執行程式


安裝套件
$ npm install electron-prebuilt

如果有使用 Windows 的使用者碰到困難,可以參考我之前寫的這篇:

執行
$ ./node_modules/.bin/electron .
其中第二個路徑是 package.json 所在的目錄

加入 React


首先,將需要的套件加進來。以下是我們的 package.json 檔案。

package.json
{
  "name": "electron-example",
  "version": "0.1.0",
  "main": "main.js",
  "dependencies": {
    "react": "^0.14.5",
    "react-dom": "^0.14.5"
  },
  "devDependencies": {
    "babel-core": "^6.3.26",
    "babel-loader": "^6.2.1",
    "babel-preset-es2015": "^6.3.13",
    "babel-preset-react": "^6.3.13",
    "electron-packager": "^5.2.0",
    "electron-prebuilt": "^0.36.2",
    "electron-rebuild": "^1.0.2",
    "webpack": "^1.12.9",
    "webpack-target-electron-renderer": "^0.3.0"
  }
}

需要的工具說明如下:
  • webpack: 我們將使用 webpack 幫助我們處理壓縮 js / css 等,我們只處理 renderer 用到的 js。
  • babel: 我們將使用 JSX 來撰寫 React,他會幫助我們將 JSX 翻譯為 JavaScript ,中間透過 babel-loader 讓 webpack 執行 babel 進行翻譯。
  • webpack-target-electron-renderer: 由於 webpack 會嘗試處理 require 語句,而其中 electron 是原生的 node extension,處理的時候會壞掉。這個 package 會告訴 webpack 跳過那些 electron extension。

我們的 webpack設定檔 webpack.config.js 如下:

webpack.config.js
var webpack = require('webpack');
var webpackTargetElectronRenderer = require('webpack-target-electron-renderer');

var config = {
  entry: {
    mainWindow: ['./app/mainWindow.jsx']
  },
output: {
  path: './app/built',
  filename: '[name].js'
},
module: {
 loaders: [
    {
      test: /\.jsx$/,
      exclude: /node_modules/,
      loader: 'babel',
   query: {
     presets: ['react', 'es2015'],
   }
    },
    {
      test: /\.js$/,
      exclude: /node_modules/,
      loader: 'babel',
   query: {
     presets: ['es2015'],
   }
    }
 ]
},
 plugins: [
   new webpack.ExternalsPlugin('commonjs',['fs']),
   new webpack.IgnorePlugin(/vertx/)
 ]
}

config.target = webpackTargetElectronRenderer(config);
module.exports = config;

webpack 的使用不在本文範圍內,如果不熟的人可以去搜尋教學文件。
這裡我們做重點說明:
  • entry: 原則上一個 window 會有獨立的 js 檔案,因為我們目前只有 mainWindow 這個視窗,因此只有一個項目。
  • output: 輸出的目錄會在 ./app/built/,並搭配 entry 欄位的名稱。因此上面這個範例輸出檔案為 ./app/built/mainWindow.js
接下來,我們的 React 程式碼如下:

app/mainWindow.jsx
'use strict';
import React from "react";
import ReactDOM from "react-dom";

var MainWindow = React.createClass({
  getInitialState: function() {
      return {
          message: '',
      };
  },
  handleTextChange: function(event) {
    this.setState({message: event.target.value});
  },
  render: function() {
    return (
      <div>
        Hello world!!
        <hr/>
        <input type="text" onChange={this.handleTextChange} />
        <p><strong>你輸入的是</strong></p>
        <p>
          <span>{this.state.message}</span>
        </p>
      </div>
    );
  }
});

ReactDOM.render(
  <MainWindow/>,
  document.getElementById('content')
);

我們的範例是畫面中會有一個 textbox,在 textbox 輸入任何字會同步顯示在頁面上。

而 renderer 用到的 mainWindow.html 也要讀取新的 js 才會動喔。因此將 mainWindow.html 改為如下:


app/mainWindow.html
<html>
  <head>
    <title>My Electron-React app</title>
  </head>
  <body>
    <div id="content">
    </div>
  </body>
  <script src="./built/mainWindow.js"></script>
</html>

程式的部分就都完成了。

編譯與執行

執行 webpack 產生新的 app/built/mainWindow.js
$ ./node_modules/.bin/webpack

然後執行 Electron
$ ./node_modules/.bin/electron .



為了方便未來容易使用,我們將指令放到 package.json

"scripts": {
  "start": "./node_modules/.bin/electron ./",
  "electron-rebuild": "./node_modules/.bin/electron-rebuild",
  "webpack": "./node_modules/.bin/webpack"
}

之後只需要
$ npm run webpack && npm start
就行摟!

相關文件


在 windows 中使用 npm 安裝 electron-prebuilt

今天嘗試在家裡的 windows 10 建立 electron 開發環境。
碰到了一些問題,紀錄一下。

需要安裝的程式



設定


安裝完畢後,進入命令列模式 (cmd.exe)

$ npm config set msvs_version 2015 --global

(這樣可以避免每次安裝都要下參數,例如
npm install [package name] --msvs_version=2015 )

安裝


執行

$ npm install electron-prebuilt --save

應該就可以裝得起來了

參考資料