沐鳴平台首頁_Muse UI — Vue2.0 和 Material Design 結合

Vue 2.0 發布以來,很多 vue 的開源項目都開始了升級計劃,我也思考着 vue-carbon 的升級之路,9月開工,11月完工, Muse UI 閃亮登場。

先睹為快

Muse UI 主要用於移動端和一些對瀏覽器兼容性要求不高的桌面端應用,先上地址:

https://github.com/museui/muse-ui

官網和文檔在這:

https://museui.github.io/

特性

  • 基於 vue2.0 開發

  • 組件豐富

  • 豐富的主題,支持自定義主題

  • 可以很好的配合 vue 的其它插件vue-router , vue-validator 使用

  • 友好的 API

使用

npm install muse-ui --save

完整引入

import Vue from 'vue'
import MuseUI from 'muse-ui'
import 'muse-ui/dist/muse-ui.css'
Vue.use(MuseUI)

按需引入

首先需要需要修改 webpack 的配置

{
  // ...
  module: {
    loaders: [
      {
        test: /muse-ui.src.*?js$/,
        loader: 'babel'
      }
    ]
  },
  resolve: {
    // ...
    alias: {
      'muse-components': 'muse-ui/src'
    }
  }
}

main.js

import Vue from 'vue'
import 'muse-components/style/base.less' // 全局樣式包含 normalize.css
import appbar from 'muse-components/appbar'
import avatar from 'muse-components/avatar'
import {bottomNav, bottomNavItem} from 'muse-components/bottomNav'
import {retina} from 'muse-components/utils'

retina() // 1px 處理方案

// ...
Vue.component(appbar.name, appbar)
Vue.component(avatar.name, avatar)
Vue.component(bottomNav.name, bottomNav)
Vue.component(bottomNavItem.name, bottomNavItem)

示例 bottomNav 的使用

<template>
 <mu-bottom-nav :value="bottomNav" shift @change="handleChange">
    <mu-bottom-nav-item value="movies" title="Movies" icon="ondemand_video"/>
    <mu-bottom-nav-item value="music" title="Music" icon="music_note"/>
    <mu-bottom-nav-item value="books" title="Books" icon="books"/>
    <mu-bottom-nav-item value="pictures" title="Pictures" icon="photo"/>
</mu-bottom-nav>
</template>
<script>
export default {
  data () {
    return {
      bottomNav: 'movies'
    }
  },
  methods: {
    handleChange (val) {
      this.bottomNav = val
    }
  }
}
</script>

關於 Muse

為了配合Vue 2.0 改變了 vue-carbon 許多的 API,新增了許多的組件,由於改變的太多,於是更名為 Muse UI,做為一個全新的 UI 框架。

Muse 取自於古希臘神話中的女神,掌管科學與藝術。我希望 Muse 和 Vue 一樣能將科學與藝術完美的結合。

後續的工作

為了跟隨 Vue 2.0, Muse 以 2.0 版本為基礎,現在是 alpha 版,後續會不斷完善。

[ ] 修復現有的問題和合理化API

[ ] 增加單元測試

[ ] 增加更多快捷操作的api (簡單的消息提示,alert, confirm 等等)

[ ] 增加其它的功能性組件(Notification, Pagination 等等)

[ ] 開發 weex 版的 muse

沐鳴登錄平台_提高Node.js應用吞吐量的小優化技巧

內容提點

  • 盡可能地使用聚合IO操作,以批量寫的方式來最小化系統調用的次數。
  • 需要將發布的開銷考慮進內,清除應用中不同的定時器。
  • CPU分析器能夠給你提高一些有用信息,但是並不能完整地反饋整個流程。
  • 謹慎使用ECMAScript高級語法,特別是你還未使用最新的JavaScript引擎或者類似於Babel這樣的轉換器的時候。
  • 要洞察你的依賴樹的組成並且對你使用的依賴進行適當的性能評測

當我們希望去優化某個包含了IO功能的應用性能時,我們需要對於應用耗費的CPU周期以及那些妨礙到應用并行化執行的因素了如指掌。本文則是分享我在提升Apache Cassandra項目中的DataStax Node.js 驅動時的一些思考與總結出的導致應用吞吐量降級的關鍵因素。

背景

Node.js使用的標準JavaScript引擎V8會將JavaScript代碼編譯為機器碼然後以本地代碼的方式運行。V8引擎使用了如下三個組件來同時保證較低的啟動時間與最佳性能表現:

  • 能夠快速將JavaScript代碼編譯為機器碼的通用編譯器。
  • 能夠自動追蹤應用中代碼執行時間並且決定應該優化哪些代碼模塊的運行時分析器。
  • 能夠自動優化被分析器標註的待優化代碼的優化編譯器;並且如果操作被認為是過優化,該編譯器還能自動地進行逆優化操作。

儘管優化編譯器能夠保證最佳的性能表現,但是它並不會對所有的代碼進行優化,特別是那些不合適的代碼編寫模式。你可以參考來自Google Chrome DevTools團隊的建議來了解哪些代碼模式是V8拒絕優化的,典型的包括:

  • 包含try-catch語句的函數
  • 使用arguments對象對函數參數進行重新賦值

雖然優化編譯器能夠顯著提升代碼允許速度,但是對於典型的IO密集型的應用,大部分的性能優化還是依賴於指令重排以及避免高佔用的調用來提高每秒的操作執行數目;這也會是我們在接下來的章節中需要討論的部分。

測試基準

為了能夠更好地發現那些可以惠及最多用戶的優化技巧,我們需要模擬真實用戶場景,根據常用任務執行的工作量來定義測試基準。首先我們需要測試API入口點的吞吐量與時延;除此之外如果希望獲取更多的信息,你也可以選擇對於內部調用方法進行性能評測。推薦使用process.hrtime()來獲取實時解析與執行時長。雖然可能會對項目開發造成些許不便,但我還是建議盡可能早地在開發周期中引入性能評測。可以選擇先從一些方法調用進行吞吐量測試,然後再慢慢地增加譬如時延分佈這些相對複雜的測試。

CPU 分析

目前有多種CPU分析器可供我們使用,其中Node.js本身提供的開箱即用的CPU分析器已經能應付大部分的使用場景。內建的Node.js分析器源於V8內置的分析器,它能夠以固定地頻率對棧信息進行採樣;你可以在運行node命令時使用--prof參數來創建V8標記文件。然後你可以對分析結果進行聚合轉化處理,通過使用--prof-process參數將其轉化為可讀性更好的文本:

$ node --prof-process isolate-0xnnnnnnnnnnnn-v8.log > processed.txt

在編輯器中打開經過處理的記錄文件,你可以看到整個記錄被劃分為了部分,首先我們來看下Summary部分,其格式如下所示:

 [Summary]:

  ticks   total   nonlib  name

  20109   41.2%   45.7%  JavaScript

  23548   48.3%   53.5%  C++

    805    1.7%    1.8%  GC

   4774    9.8%          Shared libraries

    356    0.7%          Unaccounted

上面的值分別代表了在JavaScript/C++代碼以及垃圾收集器中的採樣頻次,其會隨着分析代碼的不同而變化。然後你可以根據需要分別查看具體的子部分(譬如[JavaScript], [C++], …)來了解具體的採樣信息。除此之外,分析文件中還包含一個叫做[Bottom up (heavy) profile]的非常有用的部分,它以樹形結構展示了買個函數的調用者,其基本格式如下:

223  32%      LazyCompile: *function1 lib/file1.js:223:20

221  99%        LazyCompile: ~function2 lib/file2.js:70:57

221  100%         LazyCompile: *function3 /lib/file3.js:58:74

上面的百分比代表該層調用者占目標函數所有調用者數目的比重,而函數之前的星號意味着該函數是經過優化處理的,而波浪號代表該函數是未經過優化的。在上面的例子中,function199%的調用是由function2發起的,而function3佔據了function2100%的調用佔比。CPU 分析結果與火焰圖是非常有用的分析棧佔用與CPU耗時的工具。不過需要注意的是,這些分析結果並不意味着全部,大量的異步IO操作會讓分析變得不那麼容易。

系統調用

Node.js利用Libuv提供的平台無關的接口來實現非阻塞型IO,應用程序中所有的IO操作(sockets, 文件系統, …)都會被轉化為系統調用。而調度這些系統調用會耗費大量的時間,因此我們需要盡可能地聚合IO操作,以批量寫的方式來最小化系統調用的次數。具體而言,我們應該將Socket或者文件流放入到緩衝中然後一次性處理而不是對每個操作進行單獨處理。你可以使用寫隊列來管理你的所有寫操作,常用的寫隊列的實現邏輯如下:

  • 當我們需要進行寫操作並且在某個處理窗口期內:
    • 將該緩衝區添加到待寫列表中
  • 連接所有的緩衝區並且一次性的寫入到目標管道中。

你可以基於總的緩衝區長度或者第一個元素進入隊列的時間來定義窗口尺寸,不過在定義窗口尺寸時我們需要權衡考慮單個寫操作的時延與整體寫操作的時延,不能厚此薄彼。你也需要同時考慮能夠聚合的寫操作的最大數目以及單個寫請求的開銷。你可能會以千字節為單位決定一個寫隊列的上限,我們的經驗發現8千字節左右是個不錯的臨界點;當然根據你應用的具體場景這個值肯定會有變化,你可以參考我們的這個寫隊列的完整實現。總結而言,當我們採用了批量寫之後系統調用的數目大大降低了,最終提升了應用的整體吞吐量。

Node.js 定時器

Node.js中的定時器與window中的定時器具有相同的API,可以很方便地實現簡單的調度操作;在整個生態系統中有很廣泛的應用,因此我們的應用中可能充斥着大量的延時調用。類似於其他基於散列的輪轉調度器,Node.js使用散列表與鏈表來維護定時器實例。不過有別於其他的輪轉調度器,Node.js並沒有維持固定長度的散列表,而是根據觸發時間對定時器建立索引。添加新的定時器實例時,如果Node.js發現已經存在了相同的鍵值(有相同觸發事件的定時器),那麼會以O(1)複雜度完成添加操作。如果還不存在該鍵值,則會創建新的桶然後將定時器添加到該桶中。需要銘記於心的是,我們應該盡可能地重用已存在的定時器存放桶,避免移除整個桶然後再創建一個新的這種耗時的操作。舉例而言,如果你使用滑動延時,那麼應該在使用clearTimeout()移除定時器之前使用setTimeout()創建新的定時器。我們對於心跳包的處理中在移除上一個定時器之前會先確定下以O(1)複雜度調度空閑的定時器。

Ecmascript 語言特性

當我們着眼於整體的性能保障時,我們需要避免使用部分Ecmascript中的高級語言特性,典型的譬如:Function.prototype.bind(), Object.defineProperty() 以及 Object.defineProperties()。我們可以在JavaScript引擎的實現描述或者問題中發現這些特性的性能缺陷所在,譬如Improvement in Promise performance in V8 5.3 以及 Function.prototype.bind performance in V8 5.4。另外你也需要謹慎使用ES2015或者ESNext中的新的語言特性,它們相較於ECMAScript 5中的語法會慢很多。six-speed 項目網站就追蹤了這些語言特性在不同的JavaScript引擎上的性能表現,如果你尚未發現某些特性的性能評測你也可以自己進行一些測試。V8 團隊也一直致力於提高新的語言特性的性能表現,最終使其與底層實現保持一致。我們可以在性能規劃中隨時了解他們對於ES2015性能優化的工作進展,這裏他們會收集使用者對於提升點的建議並且發布新的設計文檔來闡述他們的解決方案。你也可以在這個博客隨時了解V8的實現進展,不過考慮到V8的提升可能需要較長的時間才能合併入LTS版本的Node.js: 根據LTS規劃只有在Node.js大版本迭代時才會合併進最新的V8版本。你可能要等待6-12月才能發現新的V8引擎被合併進入Node.js的運行環境中,而目前Node.js的新的發布版本只會包含V8引擎中的部分修復。

依賴

Node.js 運行時為我們提供了完整的IO操作庫,但是ECMAScript語法標準則僅提供了寥寥無幾的內建數據類型,很多時候我們不得不依賴第三方的庫來進行某些基本任務。沒有人能保證這些第三方的庫可以準確高效地工作,即使那些流行的明星模塊也可能存在問題。Node.js的生態系統是如此的繁榮茂盛,可能很多依賴模塊中只包含幾個你自己很方便就能實現的方法。我們需要在重複造輪子的代價與依賴帶來的性能不可控之間做一個權衡。我們團隊會盡可能地避免引入新的依賴,並且對所有的依賴持保守態度。不過對於bluebird這樣本身發布了可信賴的性能評測的庫我們是很歡迎的。我們的項目中使用async來處理異步操作,在代碼庫中廣泛地使用了async.series(), async.waterfall() 以及 async.whilst()。確實我們很難說這樣連接了多個層次的異步處理庫就是性能受損的罪魁禍首,幸好有很多其他開發者定位了其中存在的問題。我們也可以選擇類似於neo-async這樣的替代庫,它的運行效率明顯提高並且也有公開的性能評測結果。

總結

本文中提及的優化技巧有的屬於常識,有的則是涉及到Node.js生態系統以及JavaScript核心引擎的實現細節與工作原理。在我們開發的客戶端驅動中,通過引入這些優化手段我們達成了兩倍的吞吐量的提升。考慮到我們的Node.js應用以單線程方式運行,我們應用佔據CPU的時間片與指令的排布順序會大大影響整體的吞吐量與高平行的實現程度。

關於作者

Jorge Bay是Apache Cassandra項目中Node.js以及C#客戶端驅動的核心工程師,同時還是DataStax的DSE。他樂於解決問題與提供服務端解決方案,Jorge擁有超過15年的專業軟件開發經驗,他為Apache Cassandra實現的Node.js客戶端驅動同樣也是DataStax官方驅動的基礎

沐鳴總代理_axios – 基於 Promise 的 HTTP 異步請求庫

axios 是基於 Promise 的 HTTP 請求客戶端,可同時在瀏覽器和 node.js 中使用。Vue 更新到2.0之後,作者就宣告不再對 vue-resource 模塊更新,而是推薦使用 axios 來處理 HTTP 請求。

在線演示      免費下載

沐鳴測速註冊_異步流程控制:7 行代碼學會 co 模塊

首先請原諒我的標題黨(●—●),tj 大神的 co 模塊源碼200多行,顯然不是我等屌絲能隨便幾行代碼就能重寫的。只是當今大家都喜歡《7天學會xx語言》之類的速效仙丹,於是我也弄個類似的名字《7行代碼學會co模塊》來博眼球。

為了避免被拖出去彈小JJ,還是先放出所謂的 7 行代碼給大家壓壓驚:

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
} 

萬惡的回調

對前端工程師來說,異步回調是再熟悉不過了,瀏覽器中的各種交互邏輯都是通過事件回調實現的,前端邏輯越來越複雜,導致回調函數越來越多,同時 nodejs 的流行也讓 javascript 在後端的複雜場景中得到應用,在 nodejs 代碼中更是經常看到層層嵌套。

以下是一個典型的異步場景:先通過異步請求獲取頁面數據,然後根據頁面數據請求用戶信息,最後根據用戶信息請求用戶的產品列表。過多的回調函數嵌套,使得程序難以維護,發展成萬惡的回調。

$.get('/api/data', function(data) {
    console.log(data);
    $.get('/api/user', function(user) {
        console.log(user);
        $.get('/api/products', function(products) {
            console.log(products)
        });
    });
});

異步流程控制

  • 最原始異步流程的寫法,就是類似上面例子里的回調函數嵌套法,用過的人都知道,那叫一個酸爽。
  • 後來出現了 Promise ,它極大提高了代碼的可維護性,消除了萬惡的回調嵌套問題,並且現在已經成為 ES6 標準的一部分。
$.get('/api/data')
.then(function(data) {
    console.log(data);
    return $.get('/api/user');
})
.then(function(user) {
    console.log(user);
    return $.get('/api/products');
})
.then(function(products) {
    console.log(products);
});
  • 之後在 nodejs 圈出現了 co 模塊,它基於 ES6 的 generator 和 yield ,讓我們能用同步的形式編寫異步代碼。
co(function *() {
    var data = yield $.get('/api/data');
    console.log(data);
    var user = yield $.get('/api/user');
    console.log(user);
    var products = yield $.get('/api/products');
    console.log(products);
});
  • 以上的 Promise 和 generator 最初創造它的本意都不是為了解決異步流程控制。其中 Promise 是一種編程思想,用於“當xx數據準備完畢,then執行xx動作”這樣的場景,不只是異步,同步代碼也可以用 Promise。而 generator 在 ES6 中是迭代器生成器,被 TJ 創造性的拿來做異步流程控制了。真正的異步解決方案請大家期待 ES7 的 async 吧!本文以下主要介紹 co 模塊。

co 模塊

上文已經簡單介紹了co 模塊是能讓我們以同步的形式編寫異步代碼的 nodejs 模塊,主要得益於 ES6 的 generator。nodejs >= 0.11 版本可以加 --harmony 參數來體驗 ES6 的 generator 特性,iojs 則已經默認開啟了 generator 的支持。

要了解 co ,就不得不先簡單了解下 ES6 的 generator 和 iterator。

Iterator

Iterator 迭代器是一個對象,知道如何從一個集合一次取出一項,而跟蹤它的當前序列所在的位置,它提供了一個next()方法返回序列中的下一個項目。

var lang = { name: 'JavaScript', birthYear: 1995 };
var it = Iterator(lang);
var pair = it.next(); 
console.log(pair); // ["name", "JavaScript"]
pair = it.next(); 
console.log(pair); // ["birthYear", 1995]
pair = it.next(); // A StopIteration exception is thrown

乍一看好像沒什麼奇特的,不就是一步步的取對象中的 key 和 value 嗎,for ... in也能做到,但是把它跟 generator 結合起來就大有用途了。

Generator

Generator 生成器允許你通過寫一個可以保存自己狀態的的簡單函數來定義一個迭代算法。Generator 是一種可以停止並在之後重新進入的函數。生成器的環境(綁定的變量)會在每次執行后被保存,下次進入時可繼續使用。generator 字面上是“生成器”的意思,在 ES6 里是迭代器生成器,用於生成一個迭代器對象。

function *gen() {
    yield 'hello';
    yield 'world';
    return true;
}

以上代碼定義了一個簡單的 generator,看起來就像一個普通的函數,區別是function關鍵字後面有個*號,函數體內可以使用yield語句進行流程控制。

var iter = gen();
var a = iter.next();
console.log(a); // {value:'hello', done:false}
var b = iter.next();
console.log(b); // {value:'world', done:false}
var c = iter.next();
console.log(c); // {value:true, done:true}

當執行gen()的時候,並不執行 generator 函數體,而是返回一個迭代器。迭代器具有next()方法,每次調用 next() 方法,函數就執行到yield語句的地方。next() 方法返回一個對象,其中value屬性表示 yield 關鍵詞後面表達式的值,done 屬性表示是否遍歷結束。generator 生成器通過nextyield的配合實現流程控制,上面的代碼執行了三次 next() ,generator 函數體才執行完畢。

co 模塊思路

從上面的例子可以看出,generator 函數體可以停在 yield 語句處,直到下一次執行 next()。co 模塊的思路就是利用 generator 的這個特性,將異步操作跟在 yield 後面,當異步操作完成並返回結果后,再觸發下一次 next() 。當然,跟在 yield 後面的異步操作需要遵循一定的規範 thunks 和 promises。

yieldables

The yieldable objects currently supported are:

  • promises
  • thunks (functions)
  • array (parallel execution)
  • objects (parallel execution)
  • generators (delegation)
  • generator functions (delegation)

7行代碼

再看看文章開頭的7行代碼:

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}

首先生成一個迭代器,然後執行一遍 next(),得到的 value 是一個 Promise 對象,Promise.then() 裏面再執行 next()。當然這隻是一個原理性的演示,很多錯誤處理和循環調用 next() 的邏輯都沒有寫出來。

下面做個簡單對比:
傳統方式,sayhello是一個異步函數,執行helloworld會先輸出"world"再輸出"hello"

function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
function helloworld() {
    sayhello();
    console.log('world');
}
helloworld();

輸出

> "world"
> "hello"

co 的方式,會先輸出"hello"再輸出"world"

function co(gen) {
    var it = gen();
    var ret = it.next();
    ret.value.then(function(res) {
        it.next(res);
    });
}
function sayhello() {
    return Promise.resolve('hello').then(function(hello) {
        console.log(hello);
    });
}
co(function *helloworld() {
    yield sayhello();
    console.log('world');
});

輸出

> "hello"
> "world"

消除回調金字塔

假設sayhello/sayworld/saybye是三個異步函數,用真正的 co 模塊就可以這麼寫:

var co = require('co');
co(function *() {
    yield sayhello();
    yield sayworld();
    yield saybye();
});

輸出

> "hello"
> "world"
> "bye"

參考

《es7-async》 https://github.com/jaydson/es7-async
《Generator 函數的含義與用法》 http://www.ruanyifeng.com/blog/2015/04/generator.html
《Iterator》 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator

沐鳴總代_ES6 你可能不知道的事 – 基礎篇

ES6,或許應該叫 ES2015(2015 年 6 月正式發布),對於大多數前端同學都不陌生。

首先這篇文章不是工具書,不會去過多談概念,而是想聊聊關於每個特性 你可能不知道的事,希望能為各位同學 正確使用 ES6,提供一些指導。

對於 ES6,有些同學已經在項目中有過深入使用了,有些則剛剛開始認識他,但不論你是屬於哪一類,相信這篇文章都有適合你的部分。針對文章中的問題或不同意見,歡迎隨時拍磚、指正。

正文

Let + Const

這個大概是開始了解 ES6 后,我們第一個感覺自己完全明白並興緻勃勃的開始使用的特性。

以如下方式使用的同學請舉下手?

// 定義常量
const REG_GET_INPUT = /^\d{1,3}$/;

// 定義配置項
let config = {
  isDev : false,
  pubDir: './admin/'
}

// 引入 gulp
let gulp    = require('gulp');

// 引入gulp相關插件
let concat  = require('gulp-concat');
let uglify  = require('gulp-uglify');
let cssnano = require('gulp-cssnano');

很多人看完概念之後,第一印象都是:“const 是表示不可變的值,而 let 則是用來替換原來的 var 的。”

所以就會出現上面代碼中的樣子;一段代碼中出現大量的 let,只有部分常量用 const 去做定義,這樣的使用方式是錯誤的。

你可能不知道的事

const 的定義是不可重新賦值的值,與不可變的值(immutable value)不同;const 定義的 Object,在定義之後仍可以修改其屬性。

所以其實他的使用場景很廣,包括常量、配置項以及引用的組件、定義的 “大部分” 中間變量等,都應該以const做定義。反之就 let 而言,他的使用場景應該是相對較少的,我們只會在 loop(for,while 循環)及少量必須重定義的變量上用到他。

猜想:就執行效率而言,const 由於不可以重新賦值的特性,所以可以做更多語法靜態分析方面的優化,從而有更高的執行效率。

所以上面代碼中,所有使用 let 的部分,其實都應該是用 const 的。

Template Strings(字符串模板)

字符串模板是我剛接觸ES6時最喜歡的特性之一,他語法簡潔,語義明確,而且很好的解決了之前字符串拼接麻煩的問題。

因為他並不是 “必須” 的,而且原有的字符串拼接思維根深蒂固,導致我們很容易忽視掉他。

使用實例

我們先來看看他的一般使用場景:

const start = 'hi all';

const getName = () => {
  return 'jelly';
};

const conf = {
  fav: 'Coding'
};

// 模板
const msg = `${start}, my name is ${getName()}, ${conf.fav} is my favourite`;

你可能不知道的事

// 1. 與引號混用
const wantToSay = `I'm a "tbfed"`;

// 2. 支持多行文本
const slogan = 
`
I have a dream today!
`;

// 比較適合寫HTML
const resultTpl = 
`
 <section>
 <div>...</div>
 </section>
`;

Enhanced Object Literals(增強的對象字面量)

增強的對象字面量是 ES6 中的升華功能,他設計了很多簡寫,這些簡寫不但保留了明確的語義,還減少了我們多餘的代碼量。

當他的使用成為一個習慣時,我們會看到自己代碼變得更為優雅。

你可能不知道的事

const _bookNum = 4;

const basicConfig = {
  level: 5
}

const config = {
  // 直接指定原型對象
  __proto__: basicConfig,

  // 屬性簡寫
  _bookNum,

  // 方法簡寫
  getBookNum() {
    return this.bookNum;
  }
}

Arrows and Lexical This(箭頭函數)

箭頭函數是ES6中的一個新的語法特性,他的用法簡單,形態優雅,備受人們青睞。

大多數同學初識這個特性時,更多的僅僅用它作為函數定義的簡寫,這其實就有些屈才了。

// 未使用箭頭函數的寫法
{
  ...

  addOptions: function (options) {

    var self = this;

    options.forEach(function(name, opts){

      self[name] = self.addChild(name, opts);

    });

  } 
}

// 使用箭頭函數后的寫法
{
  ...

  addOptions: function (options) {

    options.forEach((name, opts) => {

      this[name] = this.addChild(name, opts);

    });

  } 
}

可以注意到上下兩段代碼的區別。

在未使用箭頭函數前,我們在過程函數中使用父級 this,需要將其顯式緩存到另一个中間變量中,因為過程函數有獨立的 this 變量,會覆蓋父級;使用箭頭函數后,不但簡寫了一個過程函數( forEach 的參數),還省略掉了 this 的中間變量的定義。

原因:箭頭函數沒有獨立執行上下文( this ),所以其內部引用 this 對象會直接訪問父級。

插播:原來我們定義這个中間變量還有一個有趣的現象,就是明明千奇百怪,例如self, that, me, _that, _me, Self...,快站出來說說你用過哪個,還是哪幾個~

當然,從這塊我們也可以看出,箭頭函數是無法替代全部 function 的使用場景的,例如我們需要有獨立 this 的函數。

你可能不知道的事

  1. 箭頭函數不但沒有獨立 this,他也沒有獨立的 arguments,所以如果需要取不定參的時候,要麼使用 function,要麼用 ES6 的另一個新特性 rest(具體在 rest 中會有詳解)。
  2. 箭頭函數語法很靈活,在只有一個參數或者只有一句表達式做方法體時,可以省略相應括號。
// 完整寫法
const getOptions = (name, key) => {
  ...
}

// 省略參數括號
const getOptions = key => {
  ... 
}

// 省略參數和方法體括號
const getOptions = key => console.log(key);

// 無參數或方法體,括號不能省略
const noop = () => {};

有個簡單小栗子,這一靈活的語法在寫連續的Promise鏈式調用時,可以使代碼更加優雅

gitPromise
  .then(() => git.add())
  .then(() => git.commit())
  .then(() => git.log())
  .then((msg) => {
      ...
  })
  .then(() => git.push())
  .catch((err) => {
      utils.error(err);
  });

Destructuring(解構)

解構這個特性可以簡單解讀為分別定義,用於一次定義多個變量,常常用於分解方法返回對象為多個變量,分別使用。
使用過ES6的同學應該或多或少接觸過這個特性,但是你可能不知道它如下幾個用法:

你可能不知道的事

const bookSet = ['UED', 'TB fed', 'Not find'];
const bookCollection = () => {
  return {book1: 'UED', book2: 'TB fed'};
};

// 1. 解構也可以設置默認值
const {book1, book3 = 'Not find'} = bookCollection();

// 2. 解構數組時候是可以跳過其中某幾項的
const [book1,,book3] = bookSet;  // book1 = 'UED', book3 = 'Not find'

// 3. 解構可以取到指定對象的任何屬性,包括它包含的方法
const {length: setLength} = bookSet;  // setLength = 3

Rest + Spread

Rest 和 Spread 主要是應用 ... 運算符,完成值的聚合和分解。

你可能不知道的事

// 1. rest 得到的是一個真正的數組而不是一個偽數組
const getOptions = function(...args){
  console.log(args.join); // function
};

// 2. rest 可以配合箭頭函數使用,達到取得所有參數的目的
const getOptions = (...args) => {
  console.log(args); // array
};

// 3. spread 可以用於解構時,聚合所得的值
const [opt1, ...opts] = ['one', 'two', 'three', 'four'];

// 4. spread 可以用於數組定義
const opts = ['one', 'two', 'three', 'four'];
const config = ['other', ...opts];

Classes

ES6 中實現的一個語法糖,用於簡化基於原型集成實現類定義的場景。
雖然有很多人不太喜歡這個特性,認為它作為一個簡單增強擴展,並沒有其他語言 class 應有的特點。
但是就我自己觀點來看,還是感覺這樣一種寫法確實比原有的原型繼承的寫法語義更清晰、明確,而且語法更簡單。

同樣,可能有些用法是你之前容易忽略掉的,在此做個補充。

你可能不知道的事

// 1. 靜態變量
// ES6 的類定義實現了靜態方法的定義,但靜態變量呢?
// 可以用如下方式實現: 
class TbFedMembers{
  static get HuaChen(){
    return 'jelly';
  }
}
TbFedMembers.HuaChen; // "化辰"

// 2. 私有屬性(私有屬性有多種實現方式,只談及其中一種)
// 閉包
const TbFedMembers = (() => {
  const HuaChen = 'jelly';

  return class{
    getOneMemberName(){
      return HuaChen;
    }
  };
})();

Promises

Promise 不只是一個對象、一個語法,他更是一種異步編程方式的變化
相信使用過 ES6 的同學都已經開始嘗試了 Promise,甚至在不支持ES6的時候,已經開始使用一些基於 Promise 思想的開源框架。

那麼我們之前用 Promise 究竟用的對么?有什麼需要注意的點呢?

你可能不知道的事

// 1. 多個異步任務同時執行用 Promise.all,順序執行使用鏈式調用
// Promise.all
Promise
  .all([jsBuildPromise, cssBuildPromise])
  .then(() => {
    ...
  });

// chain
jsBuildPromise
  .then(() => cssBuildPromise)
  .then(() => {
    ...
  });


// 2. Promise 的鏈式調用需要每一個過程返回一個 Promise 對象才能保證順序執行
gitPromise
  .then(() => git.add())  // 正確,箭頭函數簡寫
  .then(() => {
    git.commit(); // 錯誤,函數返回 undefined,會立即執行下一過程
  })
  .then(() => {
    return git.log(); // 正確
  });


// 3. Promise 需要調用 catch 方法來捕獲錯誤,而且過程內的錯誤不會阻塞後續代碼執行
new Promise(() => {
  f;  // not define error !
})
.catch((err) => {
  console.log(err)  // show 'f is not define'
});
console.log('error test');  // 此行可以被正常執行

結語

基礎篇主要是講了我們最常用的一些特性,後續如果大家感興趣,還可以再來個 “進階篇”,最後,希望文章中的部分內容可以對大家理解和使用 ES6 有所幫助。

參考資料

  • https://www.stackoverflow.com
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript
  • https://babeljs.io/docs/learn-es2015/
  • https://developer.mozilla.org/en-US/docs/Web/JavaScript
  • https://ponyfoo.com/articles/es6-spread-and-butter-in-depth
  • http://12devs.co.uk/articles/promises-an-alternative-way-to-approach-asynchronous-javascript/
  • http://www.2ality.com/2015/01/es6-destructuring.html
  • http://www.datchley.name/es6-rest-spread-defaults-and-destructuring/

沐鳴註冊平台_經典文摘:餓了么的 PWA 升級實踐

很榮幸在今年 2 月到 5 月的時間里,以顧問的身份加入餓了么,參与 PWA 的相關工作。這篇文章其實最初是在以英文寫作發表在 medium 上的:Upgrading Ele.me to Progressive Web Apps,獲得了一定的關注。所以也決定改寫為中文版本再次分享出來,希望能對你有所幫助

本文首發於 CSDN 與《程序員》2017 年 7 月刊,同步發佈於 餓了么前端 – 知乎專欄、Hux Blog,轉載請保留鏈接。

自 Vue.js 官方推特第一次公開到現在,我們就一直在進行着將餓了么移動端網站升級為 Progressive Web App 的工作。直到近日在 Google I/O 2017 上登台亮相,才終於算告一段落。我們非常榮幸能夠發布全世界第一個專門面向國內用戶的 PWA,但更榮幸的是能與 Google、UC 以及騰訊合作,一起推動國內 web 與瀏覽器生態的發展。

多頁應用、Vue、PWA?

對於構建一個希望達到原生應用級別體驗的 PWA,目前社區里的主流做法都是採用 SPA,即單頁面應用模型(Single-page App)來組織整個 web 應用,業內最有名的幾個 PWA 案例 Twitter Lite、 Flipkart Lite、Housing Go 與 Polymer Shop 無一例外。

然而餓了么,與很多國內的電商網站一樣,青睞多頁面應用模型(MPA,Multi-page App)所能帶來的一些好處,也因此在一年多將移動站從基於 Angular.js 的單頁應用重構為目前的多頁應用模型。團隊最看重的優點莫過於頁面與頁面之間的隔離與解耦,這使得我們可以將每個頁面當做一個獨立的“微服務”來看待,這些服務可以被獨立迭代,獨立提供給各種第三方的入口嵌入,甚至被不同的團隊獨立維護。而整個網站則只是各種服務的集合而非一個巨大的整體。

與此同時,我們仍然依賴 Vue.js 作為 JavaScript 框架。Vue 除了是 React/Angular 這種“重型武器”的競爭對手外,其輕量與高性能的優點使得它同樣可以作為傳統多頁應用開發中流行的 “jQuery/Zepto/Kissy + 模板引擎” 技術棧的完美替代。Vue 提供的組件系統、聲明式與響應式編程更是提升了代碼組織、共享、數據流控制、渲染等各個環節的開發效率。Vue 還是一個漸進式框架,如果網站的複雜度繼續提升,我們可以按需、增量地引入 Vuex 或 Vue-Router 這些模塊。萬一哪天又要改回單頁呢?(誰知道呢……)

2017 年,PWA 已經成為 web 應用新的風潮。我們決定試試,以我們現有的“Vue + 多頁”的架構,能在升級 PWA 的道路上走多遠,達到怎樣的效果。

實現 “PRPL” 模式

“PRPL”(讀作 “purple”)是 Google 的工程師提出的一種 web 應用架構模式,它旨在利用現代 web 平台的新技術以大幅優化移動 web 的性能與體驗,對如何組織與設計高性能的 PWA 系統提供了一種高層次的抽象。我們並不準備從頭重構我們的 web 應用,不過我們可以把實現 “PRPL” 模式作為我們的遷移目標。“PRPL”實際上是 Push/Preload、Render、Precache、Lazy-Load 的縮寫,我們會在下文中展開它們的具體含義。

1. PUSH/PRELOAD,推送/預加載初始 URL 路由所需的關鍵資源。

無論是 HTTP2 Server Push 還是 <link rel="preload">,其關鍵都在於,我們希望提前請求一些隱藏在應用依賴關係(Dependency Graph)較深處的資源,以節省 HTTP 往返、瀏覽器解析文檔、或腳本執行的時間。比如說,對於一個基於路由進行 code splitting 的 SPA,如果我們可以在 webpack 清單、路由等入口代碼(entry chunks)被下載與運行之前就把初始 URL,即用戶訪問的入口 URL 路由所依賴的代碼用 Server Push 推送或 <link rel="preload"> 進行提前加載。那麼當這些資源被真正請求時,它們可能已經下載好並存在在緩存中了,這樣就加快了初始路由所有依賴的就緒。

在多頁應用中,每一個路由本來就只會請求這個路由所需要的資源,並且通常依賴也都比較扁平。餓了么移動站的大部分腳本依賴都是普通的 <script> 元素,因此他們可以在文檔解析早期就被瀏覽器的 preloader 掃描出來並且開始請求,其效果其實與顯式的 <link rel="preload"> 是一致的。

我們還將所有關鍵的靜態資源都伺服在同一域名下(不再做域名散列),以更好的利用 HTTP2 帶來的多路復用(Multiplexing)。同時,我們也在進行着對 API 進行 Server Push 的實驗。

2. RENDER,渲染初始路由,儘快讓應用可被交互

既然所有初始路由的依賴都已經就緒,我們就可以儘快開始初始路由的渲染,這有助於提升應用諸如首次渲染時間、可交互時間等指標。多頁應用並不使用基於 JavaScript 的路由,而是傳統的 HTML 跳轉機制,所以對於這一部分,多頁應用其實不用額外做什麼。

3. PRE-CACHE,用 Service Worker 預緩存剩下的路由

這一部分就需要 Service Worker 的參与了,Service Worker 是一個位於瀏覽器與網絡之間的客戶端代理,它以可攔截、處理、響應流經的 HTTP 請求,使得開發者得以從緩存中向 web 應用提供資源而聞名。不過,Service Worker 其實也可以主動發起 HTTP 請求,在“後台” 預請求與預緩存我們未來所需要的資源。

我們已經使用 Webpack 在構建過程中進行 .vue 編譯、文件名哈希等工作,於是我們編寫了一個 webpack 插件來幫助我們收集需要緩存的依賴到一個“預緩存清單”中,並使用這個清單在每次構建時生成新的 Service Worker 文件。在新的 Service Worker 被激活時,清單里的資源就會被請求與緩存,這其實與 SW-Precache 這個庫的運行機制非常接近。

實際上,我們只對我們標記為“關鍵路由”的路由進行依賴收集。你可以將這些“關鍵路由”的依賴理解為我們整個應用的 “App Shell” 或者說“安裝包”。一旦它們都被緩存,或者說成功安裝,無論用戶是在線離線,我們的 web 應用都可以從緩存中直接啟動。對於那些並不那麼重要的路由,我們則採取在運行時增量緩存的方式。我們使用的 SW-Toolbox 提供了 LRU 替換策略與 TTL 失效機制,可以保證我們的應用不會超過瀏覽器的緩存配額。

4. LAZY-LOAD 按需懶加載、懶實例化剩下的路由

懶加載與懶實例化剩下的路由對於 SPA 是一件相對麻煩點兒的事情,你需要實現基於路由的 code splitting 與異步加載。幸運的是,這又是一件不需要多頁應用擔心的事情,多頁應用中的各個路由天生就是分離的。

值得說明的是,無論單頁還是多頁應用,如果在上一步中,我們已經將這些路由的資源都預先下載與緩存好了,那麼懶加載就幾乎是瞬時完成的了,這時候我們就只需要付出實例化的代價。

這四句話即是 PRPL 的全部了。有趣的是,我們發現多頁應用在實現 PRPL 這件事甚至比單頁還要容易一些。那麼結果如何呢?

根據 Google 推出的 Web 性能分析工具 Lighthouse(v1.6),在模擬的 3G 網絡下,用戶的初次訪問(無任何緩存)大約在 2 秒左右達到“可交互”,可以說非常不錯。而對於再次訪問,由於所有資源都直接來自於 Service Worker 緩存,頁面可以在 1 秒左右就達到可交互的狀態了。

但是,故事並不是這麼簡單得就結束了。在實際的體驗中我們發現,應用在頁與頁的切換時,仍然存在着非常明顯的白屏空隙,由於 PWA 是全屏運行的,白屏對用戶體驗所帶來的負面影響甚至比以往在瀏覽器內更大。我們不是已經用 Service Worker 緩存了所有資源了嗎,怎麼還會這樣呢?

從首頁點擊到發現頁,跳轉過程中的白屏

多頁應用的陷阱:重啟開銷

與 SPA 不同,在多頁應用中,路由的切換是原生的瀏覽器文檔跳轉(Navigating across documents),這意味着之前的頁面會被完全丟棄而瀏覽器需要為下一個路由的頁面重新執行所有的啟動步驟:重新下載資源、重新解析 HTML、重新運行 JavaScript、重新解碼圖片、重新布局頁面、重新繪製……即使其中的很多步驟本是可以在多個路由之間復用的。這些工作無疑將產生巨大的計算開銷,也因此需要付出相當的時間成本。

圖中為我們的入口頁(同時也是最重的頁面)在 2 倍 CPU 節流模擬下的 profile 數據。即使我們可以將“可交互時間”控制在 1 秒左右,我們的用戶仍然會覺得這對於“僅僅切換個標籤”來說實在是太慢了。

巨大的 JavaScript 重啟開銷

根據 Profile,我們發現在首次渲染(First Paint)發生之前,大量的時間(900 毫秒)都消耗在了 JavaScript 的運行上(Evaluate Script)。幾乎所有腳本都是阻塞的(Parser-blocking),不過因為所有的 UI 都是由 JavaScript/Vue 驅動的,倒也不會有性能影響。這 900ms 中,約一半是消耗在包括 Vue 運行時、組件、庫等依賴的運行上,而另一半則花在了業務組件實例化時 Vue 的啟動與渲染上。從軟件工程角度來說,我們需要這些抽象,所以這裏並不是想責怪 JavaScript 或是 Vue 所帶來的開銷。

但是,在 SPA 中,JavaScript 的啟動成本是均攤到整個生命周期的: 每個腳本都只需要被解析與編譯一次,諸如生成 Virtual DOM 等較重的任務可以只執行一次,像 Vue 的 ViewModel 或是 Virtual DOM 這樣的大對象也可以被留在內存里復用。可惜在多頁應用里就不是這樣了,我們每次切換頁面都為 JavaScript 付出了巨大的重啟代價。

瀏覽器的緩存啊,能不能幫幫忙?

能,也不能。

V8 提供了代碼緩存(code caching),可以將編譯后的機器碼在本地拷貝一份,這樣我們就可以在下次請求同一個腳本時一次省略掉請求、解析、編譯的所有工作。而且,對於緩存在 Service Worker 配套的 Cache Storage 中的腳本,會在第一次執行后就觸發 V8 的代碼緩存,這對於我們的多頁切換能提供不少幫助。

另外一個你或許聽過的瀏覽器緩存叫做“進退緩存”,Back-Forward Cache,簡稱 bfcache。瀏覽器廠商對其的命名各異,Opera 稱之為 Fast History Navigation,Webkit 稱其為 Page Cache。但是思路都一樣,就是我們可以讓瀏覽器在跳轉時把前一頁留存在內存中,保留 JavaScript 與 DOM 的狀態,而不是全都銷毀掉。你可以隨便找個傳統的多頁網站在 iOS Safari 上試試,無論是通過瀏覽器的前進後退按鈕、手勢,還是通過超鏈接(會有一些不同),基本都可以看到瞬間加載的效果。

Bfcache 其實非常適合多頁應用。但不幸的是,Chrome 由於內存開銷與其多進程架構等原因目前並不支持。Chrome 現階段僅僅只是用了傳統的 HTTP 磁盤緩存,來稍稍簡化了一下加載過程而已。對於 Chromium 內核霸佔的 Android 生態來說,我們沒法指望了。

為“感知體驗”奮鬥

儘管多頁應用面臨着現實中的不少性能問題,我們並不想這麼快就妥協。一方面,我們嘗試盡可能減少在頁面達到可交互時間前的代碼執行量,比如減少/推遲一些依賴腳本的執行,還有減少初次渲染的 DOM 節點數以節省 Virtual DOM 的初始化開銷。另一方面,我們也意識到應用在感知體驗上還有更多的優化空間。

Chrome 產品經理 Owen 寫過一篇 Reactive Web Design: The secret to building web apps that feel amazing,談到兩種改進感知體驗的手段:一是使用骨架屏(Skeleton Screen)來實現瞬間加載;二是預先定義好元素的尺寸來保證加載的穩定。跟我們的做法可以說不謀而合。

為了消除白屏時間,我們同樣引入了尺寸穩定的骨架屏來幫助我們實現瞬間的加載與佔位。即使是在硬件很弱的設備上,我們也可以在點擊切換標籤后立刻渲染出目標路由的骨架屏,以保證 UI 是穩定、連續、有響應的。我錄了兩個視頻放在 Youtube 上,不過如果你是國內讀者,你可以直接訪問餓了么移動網站來體驗實地的效果 最終效果如下圖所示。

在添加骨架屏后,從發現頁點回首頁的效果

這效果本該很輕鬆的就能實現,不過實際上我們還費了點功夫。

在構建時使用 Vue 預渲染骨架屏

你可能已經想到了,為了讓骨架屏可以被 Service Worker 緩存,瞬間加載並獨立於 JavaScript 渲染,我們需要把組成骨架屏的 HTML 標籤、CSS 樣式與圖片資源一併內聯至各個路由的靜態 *.html 文件中。

不過,我們並不準備手動編寫這些骨架屏。你想啊,如果每次真實組件有迭代(每一個路由對我們來說都是一個 Vue 組件)我們都需要手動去同步每一個變化到骨架屏的話,那實在是太繁瑣且難以維護了。好在,骨架屏不過是當數據還未加載進來前,頁面的一個空白版本而已。如果我們能將骨架屏實現為真實組件的一個特殊狀態 —— “空狀態”的話,我們理論上就可以從真實組件中直接渲染出骨架屏來。

而 Vue 的多才多藝就在這時體現出來了,我們真的可以用 Vue.js 的服務端渲染模塊 來實現這個想法,不過不是用在真正的服務器上,而是在構建時用它把組件的空狀態預先渲染成字符串並注入到 HTML 模板中。你需要調整你的 Vue 組件代碼使得它可以在 Node 上執行,有些頁面對 DOM/BOM 的依賴一時無法輕易去除得,我們目前只好額外編寫一個 *.shell.vue 來暫時繞過這個問題。

關於瀏覽器的繪製(Painting)

HTML 文件中有標籤並不意味着這些標籤就能立刻被繪製到屏幕上,你必須保證頁面的關鍵渲染路徑是為此優化的。很多開發者相信將 script 標籤放在 body 的底部就足以保證內容能在腳本執行之前被繪製,這對於能渲染不完整 DOM 樹的瀏覽器(比如桌面瀏覽器常見的流式渲染)來說可能是成立的。但移動端的瀏覽器很可能因為考慮到較慢的硬件、電量消耗等因素並不這麼做。不僅如此,即使你曾被告知設為 async 或 defer 的腳本就不會阻塞 HTML 解析了,但這可不意味着瀏覽器就一定會在執行它們之前進行渲染。

首先我想澄清的是,根據 HTML 規範 Scripting 章節,async 腳本是在其請求完成后立刻運行的,因此它本來就可能阻塞到解析。只有 defer(且非內聯)與最新的 type=module 被指定為“一定不會阻塞解析”。(不過 defer 目前也有點小問題……我們稍後會再提到)

而更重要的是,一個不阻塞 HTML 解析的腳本仍然可能阻塞到繪製。我做了一個簡化的“最小多頁 PWA”(Minimal Multi-page PWA,或 MMPWA)來測試這個問題,:我們在一個 async(且確實不阻塞 HTML 解析)腳本中,生成並渲染 1000 個列表項,然後測試骨架屏能否在腳本執行之前渲染出來。下面是通過 USB Debugging 在我的 Nexus 5 真機上錄製的 profile:

是的,出乎意料嗎?首次渲染確實被阻塞到腳本執行結束后才發生。究其原因,如果我們在瀏覽器還未完成上一次繪製工作之前就過快得進行了 DOM 操作,我們親愛的瀏覽器就只好拋棄所有它已經完成的像素,且一直要等待到 DOM 操作引起的所有工作結束之後才能重新進行下一次渲染。而這種情況更容易在擁有較慢 CPU/GPU 的移動設備上出現。

黑魔法:利用 setTimeout() 讓繪製提前

不難發現,骨架屏的繪製與腳本執行實際是一個競態。大概是 Vue 太快了,我們的骨架屏還是有非常大的概率繪製不出來。於是我們想着如何能讓腳本執行慢點,或者說,“懶”點。於是我們想到了一個經典的 Hack: setTimeout(callback, 0)。我們試着把 MMPWA 中的 DOM 操作(渲染 1000 個列表)放進 setTimeout(callback, 0) 里……

噹噹!首次渲染瞬間就被提前了。如果你熟悉瀏覽器的事件循環模型(event loop)的話,這招 Hack 其實是通過 setTimeout 的回調把 DOM 操作放到了事件循環的任務隊列中以避免它在當前循環執行,這樣瀏覽器就得以在主線程空閑時喘息一下(更新一下渲染)了。如果你想親手試試 MMPWA 的話,你可以訪問 github.com/Huxpro/mmpwa 或 huangxuan.me/mmpwa/訪問代碼與 Demo。我把 UI 設計為了 A/B Test 的形式並改為渲染 5000 個列表項來讓效果更誇張一些。

回到餓了么 PWA 上,我們同樣試着把 new Vue() 放到了 setTimeout 中。果然,黑魔法再次顯靈,骨架屏在每次跳轉后都能立刻被渲染。這時的 Profile 看起來是這樣的:

現在,我們在 400ms 時觸發首次渲染(骨架屏),在 600ms 時完成真實 UI 的渲染並達到頁面的可交互。你可以拉上去詳細對比下優化前後 profile 的區別。

被我 “defer” 的有關 defer 的 Bug

不知道你發現沒有,在上圖的 Profile 中,我們仍然有不少腳本是阻塞了 HTML 解析的。好吧讓我解釋一下,由於歷史原因,我們確實保留了一部分的阻塞腳本,比如侵入性很強的 lib-flexible,我們沒法輕易去除它。不過,profile 里的大部分阻塞腳本實際上都設置了 defer,我們本以為他們應該在 HTML 解析完成之後才被執行,結果被 profile 打了一臉。

我和 Jake Archibald 聊了一下,果然這是 Chrome 的 Bug:defer 的腳本被完全緩存時,並沒有遵守規範等待解析結束,反而阻塞了解析與渲染。Jake 已經提交在 crbug 上了,一起給它投票吧~

最後,是優化后的 Lighthouse 跑分結果,同樣可以看到明顯的性能提升。需要說明的是,能影響 Lighthouse 跑分的因素有很多,所以我建議你以控制變量(跑分用的設備、跑分時的網絡環境等)的方式來進行對照實驗。

最後附上一張圖,這張圖當時是做給 Addy Osmani 的 I/O 演講用的,描述了餓了么 PWA 是如何結合 Vue 來實現多頁應用的 PRPL 模式,可以作為一個架構的參考與示意圖。

一些感想

多頁應用仍然有很長的路要走

Web 是一個極其多樣化的平台。從靜態的博客,到電商網站,再到桌面級的生產力軟件,它們全都是 Web 這個大家庭的第一公民。而我們組織 web 應用的方式,也同樣只會更多而不會更少:多頁、單頁、Universal JavaScript 應用、WebGL、以及可以預見的 Web Assembly。不同的技術之間沒有貴賤,但是適用場景的差距確是客觀存在的。

Jake 曾在 Chrome Dev Summit 2016 上說過 “PWA !== SPA”。可是儘管我們已經用上了一系列最新的技術(PRPL、Service Worker、App Shell……),我們仍然因為多頁應用模型本身的缺陷有着難以逾越的一些障礙。多頁應用在未來可能會有“bfcache API”、Navigation Transition 等新的規範以縮小跟 SPA 的距離,不過我們也必須承認,時至今日,多頁應用的局限性也是非常明顯的。

而 PWA 終將帶領 web 應用進入新的時代

即使我們的多頁應用在升級 PWA 的路上不如單頁的那些來得那麼閃亮,但是 PWA 背後的想法與技術卻實實在在的幫助我們在 web 平台上提供了更好的用戶體驗。

PWA 作為下一代 Web 應用模型,其嘗試解決的是 web 平台本身的根本性問題:對網絡與瀏覽器 UI 的硬依賴。因此,任何 web 應用都可以從中獲益,這與你是多頁還是單頁、面向桌面還是移動端、是用 React 還是 Vue 無關。或許,它還終將改變用戶對移動 web 的期待。現如今,誰還覺得桌面端的 web 只是個看文檔的地方呢?

還是那句老話:讓我們的用戶,也像我們這般熱愛 web 吧。

最後,感謝餓了么的王亦斯、任光輝、題恭弘=叶 恭弘,Google 的 Michael Yeung、DevRel 團隊, UC 瀏覽器團隊,騰訊 X5 瀏覽器團隊在這次項目中的合作。感謝尤雨溪、陳蒙迪和 Jake Archibald 在寫作過程中給予我的幫助。

沐鳴登錄測速_深入視角:變態的靜態資源緩存與更新

這是一個非常有趣的 非主流前端領域,這個領域要探索的是如何用工程手段解決前端開發和部署優化的綜合問題,入行到現在一直在學習和實踐中。 在我的印象中,facebook是這個領域的鼻祖,有興趣、有梯子的同學可以去看看facebook的頁面源代碼,體會一下什麼叫工程化。 接下來,我想從原理展開講述,多圖,較長,希望能有耐心看完。

讓我們返璞歸真,從原始的前端開發講起。上圖是一個“可愛”的index.html頁面和它的樣式文件a.css,用文本編輯器寫代碼,無需編譯,本地預覽,確認OK,丟到服務器,等待用戶訪問。前端就是這麼簡單,好好玩啊,門檻好低啊,分分鐘學會有木有! 然後我們訪問頁面,看到效果,再查看一下網絡請求,200!不錯,太™完美了!那麼,研發完成。。。。了么? 等等,這還沒完呢!對於大公司來說,那些變態的訪問量和性能指標,將會讓前端一點也不“好玩”。 看看那個a.css的請求吧,如果每次用戶訪問頁面都要加載,是不是很影響性能,很浪費帶寬啊,我們希望最好這樣: 利用304,讓瀏覽器使用本地緩存。但,這樣也就夠了嗎?不成!304叫協商緩存,這玩意還是要和服務器通信一次,我們的優化級別是變態級,所以必須徹底滅掉這個請求,變成這樣: 強制瀏覽器使用本地緩存(cache-control/expires),不要和服務器通信。好了,請求方面的優化已經達到變態級別,那問題來了:你都不讓瀏覽器發資源請求了,這緩存咋更新? 很好,相信有人想到了辦法:通過更新頁面中引用的資源路徑,讓瀏覽器主動放棄緩存,加載新資源。好像這樣: 下次上線,把鏈接地址改成新的版本,就更新資源了不是。OK,問題解決了么?!當然沒有!大公司的變態又來了,思考這種情況: 頁面引用了3個css,而某次上線只改了其中的a.css,如果所有鏈接都更新版本,就會導致b.css,c.css的緩存也失效,那豈不是又有浪費了?! 重新開啟變態模式,我們不難發現,要解決這種問題,必須讓url的修改與文件內容關聯,也就是說,只有文件內容變化,才會導致相應url的變更,從而實現文件級別的精確緩存控制。 什麼東西與文件內容相關呢?我們會很自然的聯想到利用 數據摘要要算法 對文件求摘要信息,摘要信息與文件內容一一對應,就有了一種可以精確到單個文件粒度的緩存控制依據了。好了,我們把url改成帶摘要信息的: 這回再有文件修改,就只更新那個文件對應的url了,想到這裏貌似很完美了。你覺得這就夠了么?大公司告訴你:圖樣圖森破! 唉~~~~,讓我喘口氣 現代互聯網企業,為了進一步提升網站性能,會把靜態資源和動態網頁分集群部署,靜態資源會被部署到CDN節點上,網頁中引用的資源也會變成對應的部署路徑: 好了,當我要更新靜態資源的時候,同時也會更新html中的引用吧,就好像這樣: 這次發布,同時改了頁面結構和樣式,也更新了靜態資源對應的url地址,現在要發布代碼上線,親愛的前端研發同學,你來告訴我,咱們是先上線頁面,還是先上線靜態資源?

  1. 先部署頁面,再部署資源:在二者部署的時間間隔內,如果有用戶訪問頁面,就會在新的頁面結構中加載舊的資源,並且把這箇舊版本的資源當做新版本緩存起來,其結果就是:用戶訪問到了一個樣式錯亂的頁面,除非手動刷新,否則在資源緩存過期之前,頁面會一直執行錯誤。
  2. 先部署資源,再部署頁面:在部署時間間隔之內,有舊版本資源本地緩存的用戶訪問網站,由於請求的頁面是舊版本的,資源引用沒有改變,瀏覽器將直接使用本地緩存,這種情況下頁面展現正常;但沒有本地緩存或者緩存過期的用戶訪問網站,就會出現舊版本頁面加載新版本資源的情況,導致頁面執行錯誤,但當頁面完成部署,這部分用戶再次訪問頁面又會恢復正常了。 好的,上面一坨分析想說的就是:先部署誰都不成!都會導致部署過程中發生頁面錯亂的問題。所以,訪問量不大的項目,可以讓研發同學苦逼一把,等到半夜偷偷上線,先上靜態資源,再部署頁面,看起來問題少一些。

但是,大公司超變態,沒有這樣的“絕對低峰期”,只有“相對低峰期”。So,為了穩定的服務,還得繼續追求極致啊! 這個奇葩問題,起源於資源的 覆蓋式發布,用 待發布資源 覆蓋 已發布資源,就有這種問題。解決它也好辦,就是實現 非覆蓋式發布。 看上圖,用文件的摘要信息來對資源文件進行重命名,把摘要信息放到資源文件發布路徑中,這樣,內容有修改的資源就變成了一個新的文件發布到線上,不會覆蓋已有的資源文件。上線過程中,先全量部署靜態資源,再灰度部署頁面,整個問題就比較完美的解決了。 所以,大公司的靜態資源優化方案,基本上要實現這麼幾個東西:

  1. 配置超長時間的本地緩存 —— 節省帶寬,提高性能
  2. 採用內容摘要作為緩存更新依據 —— 精確的緩存控制
  3. 靜態資源CDN部署 —— 優化網絡請求
  4. 更資源發布路徑實現非覆蓋式發布 —— 平滑升級

全套做下來,就是相對比較完整的靜態資源緩存控制方案了,而且,還要注意的是,靜態資源的緩存控制要求在 前端所有靜態資源加載的位置都要做這樣的處理 。是的,所有!什麼js、css自不必說,還要包括js、css文件中引用的資源路徑,由於涉及到摘要信息,引用資源的摘要信息也會引起引用文件本身的內容改變,從而形成級聯的摘要變化,大概示意圖就是: 好了,目前我們快速的學習了一下前端工程中關於靜態資源緩存要面臨的優化和部署問題,新的問題又來了:這™讓工程師怎麼寫碼啊!!! 要解釋優化與工程的結合處理思路,又會扯出一堆有關模塊化開發、資源加載、請求合併、前端框架等等的工程問題,以上只是開了個頭,解決方案才是精髓,但要說的太多太多,有空再慢慢展開吧。

總之,前端性能優化絕逼是一個工程問題!

以上不是我YY的,可以觀察 百度 或者 facebook 的頁面以及靜態資源源代碼,查看它們的資源引用路徑處理,以及網絡請中靜態資源的緩存控制部分。再次讚歎facebook的前端工程建設水平,跪舔了。 建議前端工程師多多關注前端工程領域,也許有人會覺得自己的產品很小,不用這麼變態,但很有可能說不定某天你就需要做出這樣的改變了。而且,如果我們能把事情做得更極致,為什麼不去做呢? 另外,也不要覺得這些是運維或者後端工程師要解決的問題。如果由其他角色來解決,大家總是把自己不關心的問題丟給別人,那麼前端工程師的開發過程將受到極大的限制,這種情況甚至在某些大公司都不少見! 媽媽,我再也不玩前端了。。。。5555

業界實踐

Assets Pipeline

Rails中的Assets Pipeline完成了以上所說的優化細節,對整個靜態資源的管理上的設計思考也是如此,了解rails的人也可以把此答案當做是對rails中assets pipeline設計原理的分析。 rails通過把靜態資源變成erb模板文件,然後加入<%= asset_path ‘image.png’ %>,上線前預編譯完成處理,fis的實現思路跟這個幾乎完全一樣,但我們當初確實不知道有rails的這套方案存在。 相關資料:

  • 英文版:http://guides.rubyonrails.org/asset_pipeline.html
  • 中文版:http://guides.ruby-china.org/asset_pipeline.html

FIS的解決方案

用 F.I.S 包裝了一個小工具,完整實現整個回答所說的最佳部署方案,並提供了源碼對照,可以感受一下項目源碼和部署代碼的對照。

  • 源碼項目:https://github.com/fouber/static-resource-digest-project
  • 部署項目:https://github.com/fouber/static-resource-digest-project-release

部署項目可以理解為線上發布后的結果,可以在部署項目里查看所有資源引用的md5化處理。 這個示例也可以用於和assets pipeline做比較。fis沒有assets的目錄規範約束,而且可以以獨立工具的方式組合各種前端開發語言(coffee、less、sass/scss、stylus、markdown、jade、ejs、handlebars等等你能想到的),並與其他後端開發語言結合。 assets pipeline的設計思想值得獨立成工具用於前端工程,fis就當做這樣的一個選擇吧。

沐鳴總代_npm的package.json字段含義中文文檔

簡介

本文檔有所有package.json中必要的配置。它必須是真正的json,而不是js對象。

本文檔中描述的很多行為都受npm-config(7)的影響。

默認值

npm會根據包內容設置一些默認值。

  • "scripts": {"start": "node server.js"}如果包的根目錄有server.js文件,npm會默認將start命令設置為node server.js
  • "scripts":{"preinstall": "node-waf clean || true; node-waf configure build"}如果包的根目錄有wscript文件,npm會默認將preinstall命令用node-waf進行編譯。
  • "scripts":{"preinstall": "node-gyp rebuild"}如果包的根目錄有binding.gyp文件,npm會默認將preinstall命令用node-gyp進行編譯。
  • "contributors": [...]如果包的根目錄有AUTHORS文件,npm會默認逐行按Name <email> (url)格式處理,郵箱和url是可選的。#號和空格開頭的行會被忽略。

name

在package.json中重要的就是name和version字段。他們都是必須的,如果沒有就無法install。name和version一起組成的標識在假設中是唯一的。改變包應該同時改變version。

name是這個東西的名字。注意:

  • 不要把node或者js放在名字中。因為你寫了package.json它就被假定成為了js,不過你可以用”engine”字段指定一個引擎(見後文)。
  • 這個名字會作為在URL的一部分、命令行的參數或者文件夾的名字。任何non-url-safe的字符都是不能用的。
  • 這個名字可能會作為參數被傳入require(),所以它應該比較短,但也要意義清晰。
  • 在你愛上你的名字之前,你可能要去npm registry查看一下這個名字是否已經被使用了。http://registry.npmjs.org/

version

在package.json中重要的就是name和version字段。他們都是必須的,如果沒有就無法install。name和version一起組成的標識在假設中是唯一的。改變包應該同時改變version。

version必須能被node-semver解析,它被包在npm的依賴中。(要自己用可以執行npm install semver

可用的“数字”或者“範圍”見semver(7).

description

放簡介,字符串。方便屌絲們在npm search中搜索。

keywords

關鍵字,數組、字符串。還是方便屌絲們在npm search中搜索。

homepage

項目官網的url。

注意:這和“url”一樣。如果你放一個“url”字段,registry會以為是一個跳轉到你發布在其他地方的地址,然後喊你滾粗。

嗯,滾粗,沒開玩笑。

bugs

你項目的提交問題的url和(或)郵件地址。這對遇到問題的屌絲很有幫助。

差不多長這樣:

{ "url" : "http://github.com/owner/project/issues"
, "email" : "project@hostname.com"
}

你可以指定一個或者指定兩個。如果你只想提供一個url,那就不用對象了,字符串就行。

如果提供了url,它會被npm bugs命令使用。

license

你應該要指定一個許可證,讓人知道使用的權利和限制的。

最簡單的方法是,假如你用一個像BSD或者MIT這樣通用的許可證,就只需要指定一個許可證的名字,像這樣:

{ "license" : "BSD" }

如果你又更複雜的許可條件,或者想要提供給更多地細節,可以這樣:

"licenses" : [
  { "type" : "MyLicense"
  , "url" : "http://github.com/owner/project/path/to/license"
  }
]

在根目錄中提供一個許可證文件也蠻好的。

people fields: author, contributors

author是一個人。contributors是一堆人的數組。person是一個有name字段,可選的有url、email字段的對象,像這樣:

{ "name" : "Barney Rubble"
, "email" : "b@rubble.com"
, "url" : "http://barnyrubble.tumblr.com/"
}

或者可以把所有的東西都放到一個字符串里,npm會給你解析:

"Barney Rubble <b@rubble.com> (http://barnyrubble.tumblr.com/)

email和url在兩種形式中都是可選的。

也可以在你的npm用戶信息中設置一個頂級的maintainers字段。

files

files是一個包含項目中的文件的數組。如果命名了一個文件夾,那也會包含文件夾中的文件。(除非被其他條件忽略了)

你也可以提供一個.npmignore文件,讓即使被包含在files字段中得文件被留下。其實就像.gitignore一樣。

main

main字段配置一個文件名指向模塊的入口程序。如果你包的名字叫foo,然後用戶require("foo"),main配置的模塊的exports對象會被返回。

這應該是一個相對於根目錄的文件路徑。

對於大多數模塊,它是非常有意義的,其他的都沒啥。

bin

很多包都有一個或多個可執行的文件希望被放到PATH中。npm讓媽媽再也不用擔心了(實際上,就是這個功能讓npm可執行的)。

要用這個功能,給package.json中的bin字段一個命令名到文件位置的map。初始化的時候npm會將他鏈接到prefix/bin(全局初始化)或者./node_modules/.bin/(本地初始化)。

比如,npm有:

{ "bin" : { "npm" : "./cli.js" } }

所以,當你初始化npm,它會創建一個符號鏈接到cli.js腳本到/usr/local/bin/npm

如果你只有一個可執行文件,並且名字和包名一樣。那麼你可以只用一個字符串,比如:

{ "name": "my-program"
, "version": "1.2.5"
, "bin": "./path/to/program" }

結果和這個一樣:

{ "name": "my-program"
, "version": "1.2.5"
, "bin" : { "my-program" : "./path/to/program" } }

man

指定一個單一的文件或者一個文件數組供man程序使用。

如果只提供一個單一的文件,那麼它初始化后就是man <pkgname>的結果,而不管實際的文件名是神馬,比如:

{ "name" : "foo"
, "version" : "1.2.3"
, "description" : "A packaged foo fooer for fooing foos"
, "main" : "foo.js"
, "man" : "./man/doc.1"
}

這樣man foo就可以用到./man/doc.1文件了。

如果文件名不是以包名開頭,那麼它會被冠以前綴,下面的:

{ "name" : "foo"
, "version" : "1.2.3"
, "description" : "A packaged foo fooer for fooing foos"
, "main" : "foo.js"
, "man" : [ "./man/foo.1", "./man/bar.1" ]
}

會為man fooman foo-bar創建文件。

man文件需要以数字結束,然後可選地壓縮后以.gz為後綴。The number dictates which man section the file is installed into.

{ "name" : "foo"
, "version" : "1.2.3"
, "description" : "A packaged foo fooer for fooing foos"
, "main" : "foo.js"
, "man" : [ "./man/foo.1", "./man/foo.2" ]
}

會為man fooman 2 foo創建。

directories

CommonJS Packages規範說明了幾種方式讓你可以用directorieshash標示出包得結構。如果看一下npm’s package.json,你會看到有directories標示出doc, lib, and man。

在未來,這個信息可能會被用到。

directories.lib

告訴屌絲們你的庫文件夾在哪裡。目前沒有什麼特別的東西需要用到lib文件夾,但確實是重要的元信息。

directories.bin

如果你指定一個“bin”目錄,然後在那個文件夾中得所有文件都會被當做”bin”字段使用。

如果你已經指定了“bin”字段,那這個就無效。

directories.man

一個放滿man頁面的文件夾。貼心地創建一個“man”字段。
A folder that is full of man pages. Sugar to generate a “man” array by
walking the folder.

directories.doc

將markdown文件放在這裏。最後,這些會被很好地展示出來,也許,某一天。
Put markdown files in here. Eventually, these will be displayed nicely,
maybe, someday.

directories.example

將事例腳本放在這裏。某一天,它可能會以聰明的方式展示出來。

repository

指定你的代碼存放的地方。這個對希望貢獻的人有幫助。如果git倉庫在github上,那麼npm docs命令能找到你。

這樣做:

"repository" :
  { "type" : "git"
  , "url" : "http://github.com/isaacs/npm.git"
  }

"repository" :
  { "type" : "svn"
  , "url" : "http://v8.googlecode.com/svn/trunk/"
  }

URL應該是公開的(即便是只讀的)能直接被未經過修改的版本控製程序處理的url。不應該是一個html的項目頁面。因為它是給計算機看的。

scripts

“scripts”是一個由腳本命令組成的hash對象,他們在包不同的生命周期中被執行。key是生命周期事件,value是要運行的命令。

參見 npm-scripts(7)

config

“config” hash可以用來配置用於包腳本中的跨版本參數。在實例中,如果一個包有下面的配置:

{ "name" : "foo"
, "config" : { "port" : "8080" } }

然後有一個“start”命令引用了npm_package_config_port環境變量,用戶可以通過npm config set foo:port 8001來重寫他。

參見 npm-config(7) 和 npm-scripts(7)。

dependencies

依賴是給一組包名指定版本範圍的一個hash。這個版本範圍是一個由一個或多個空格分隔的字符串。依賴還可以用tarball或者git URL。

請不要將測試或過渡性的依賴放在dependencieshash中。見下文的devDependencies

詳見semver(7).

  • version 必須完全和version一致
  • >version 必須比version
  • >=version 同上
  • <version 同上
  • <=version 同上
  • ~version 大約一樣,見semver(7)
  • 1.2.x 1.2.0, 1.2.1, 等,但不包括1.3.0
  • http://... 見下文’依賴URL’
  • * 所有
  • "" 空,同*
  • version1 - version2 同 >=version1 <=version2.
  • range1 || range2 二選一。
  • git... 見下文’依賴Git URL’
  • user/repo 見下文’GitHub URLs’

比如下面都是合法的:

{ "dependencies" :
  { "foo" : "1.0.0 - 2.9999.9999"
  , "bar" : ">=1.0.2 <2.1.2"
  , "baz" : ">1.0.2 <=2.3.4"
  , "boo" : "2.0.1"
  , "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"
  , "asd" : "http://asdf.com/asdf.tar.gz"
  , "til" : "~1.2"
  , "elf" : "~1.2.3"
  , "two" : "2.x"
  , "thr" : "3.3.x"
  }
}

依賴URL

可以指定一個tarball URL,這個tarball將在包被初始化的時候下載並初始化。

依賴Git URL

Git urls 可以是下面幾種形式:

git://github.com/user/project.git#commit-ish
git+ssh://user@hostname:project.git#commit-ish
git+ssh://user@hostname/project.git#commit-ish
git+http://user@hostname/project/blah.git#commit-ish
git+https://user@hostname/project/blah.git#commit-ish

commit-ish是可以被git checkout的任何tag、sha或者branch。默認為master

GitHub URLs

1.1.65版后,你可以僅僅用“user/foo-project”引用GitHub urls,比如:

{
  "name": "foo",
  "version": "0.0.0",
  "dependencies": {
    "express": "visionmedia/express"
  }
}

devDependencies

如果有人要使用你的模塊,那麼他們可能不需要你開發使用的外部測試或者文檔框架。

在這種情況下,最好將這些附屬的項目列在devDependencies中。

這些東西會在執行npm link或者npm install的時候初始化,並可以像其他npm配置參數一樣管理。詳見npm-config(7)。

對於非特定平台的構建步驟,比如需要編譯CoffeeScript,可以用prepublish腳本去實現,並把它依賴的包放在devDependency中。(譯者注:prepublish定義了在執行npm publish的時候先行執行的腳本)

比如:

{ "name": "ethopia-waza",
  "description": "a delightfully fruity coffee varietal",
  "version": "1.2.3",
  "devDependencies": {
    "coffee-script": "~1.6.3"
  },
  "scripts": {
    "prepublish": "coffee -o lib/ -c src/waza.coffee"
  },
  "main": "lib/waza.js"
}

prepublish腳本會在publishing前運行,這樣用戶就不用自己去require來編譯就能使用。並且在開發模式中(比如本地運行npm install)會運行這個腳本以便更好地測試。

peerDependencies

在一些場景中,如在一個host中不必須進行require時候,你想表現你的package與一個host工具或者庫的兼容關鍵。這一般用來引用插件。尤其是你的模塊可能要暴露一個特定的接口,並由host文檔來預期和指定。

比如:

{
  "name": "tea-latte",
  "version": "1.3.5"
  "peerDependencies": {
    "tea": "2.x"
  }
}

這能保證你的package可以只和tea的2.x版本一起初始化。npm install tea-latte可能會產生下面的依賴關係

├── tea-latte@1.3.5
└── tea@2.2.0

試圖初始化另一個有會衝突的依賴的插件將導致一個錯誤。因此,確保你的插件的需求約束越弱越好,而不要去把它鎖定到一個特定的版本。

假設這個host遵守semver規範,只改變這個package的主版本會打破你的插件。因此,如果你在package中用過每個1.x版本,就用”^1.0″或者”1.x”來表示。如果你依賴於功能介紹1.5.2,用”>= 1.5.2 < 2″。

bundledDependencies

一組包名,他們會在發布的時候被打包進去。

拼成"bundleDependencies"(缺d)也可以。

optionalDependencies

如果一個依賴可用,但你希望在它安裝錯誤的時候npm也能繼續初始化,那麼你可以把它放在optionalDependencies hash中。這是一個包名到版本或者url的map,就像dependencies hash一樣。只是它運行錯誤。

處理缺乏依賴也是你的程序的責任。比如像這樣:

try {
  var foo = require('foo')
  var fooVersion = require('foo/package.json').version
} catch (er) {
  foo = null
}
if ( notGoodFooVersion(fooVersion) ) {
  foo = null
}

// .. then later in your program ..

if (foo) {
  foo.doFooThings()
}

optionalDependencies會覆蓋dependencies中同名的項,所以通常比只放在一個地方好。

engines

你可以指定工作的node的版本:

{ "engines" : { "node" : ">=0.10.3 <0.12" } }

並且,像dependensies一樣,如果你不指定版本或者指定“*”作為版本,那麼所有版本的node都可以。

如果指定一個“engines”字段,那麼npm會需要node在裏面,如果“engines”被省略,npm會假定它在node上工作。

你也可以用“engines”字段來指定哪一個npm版本能更好地初始化你的程序,如:

{ "engines" : { "npm" : "~1.0.20" } }

記住,除非用戶設置engine-strict標記,這個字段只是建議值。

engineStrict

如果你確定你的模塊一定不會運行在你指定版本之外的node或者npm上,你可以在package.json文件中設置"engineStrict":true。它會重寫用戶的engine-strict設置。

除非你非常非常確定,否則不要這樣做。如果你的engines hash過度地限制,很可能輕易讓自己陷入窘境。慎重地考慮這個選擇。如果大家濫用它,它會再以後的npm版本中被刪除。

os

你可以指定你的模塊要運行在哪些操作系統中:

"os" : [ "darwin", "linux" ]

你也可以用黑名單代替白名單,在名字前面加上“!”就可以了:

"os" : [ "!win32" ]

操作系統用process.platform來探測。

雖然沒有很好地理由,但它是同時支持黑名單和白名單的。

cpu

如果你的代碼只能運行在特定的cpu架構下,你可以指定一個:

"cpu" : [ "x64", "ia32" ]

就像os選項,你也可以黑一個架構:

"cpu" : [ "!arm", "!mips" ]

cpu架構用process.arch探測。

preferGlobal

如果包主要是需要全局安裝的命令行程序,就設置它為true來提供一個warning給只在局部安裝的人。

它不會真正的防止用戶在局部安裝,但如果它沒有按預期工作它會幫助防止產生誤會。

private

如果你設置"private": true,npm就不會發布它。

這是一個防止意外發布私有庫的方式。如果你要確定給定的包是只發布在特定registry(如內部registry)的,用publishConfighash的描述來重寫registry的publish-time配置參數。

publishConfig

這是一個在publish-time使用的配置集合。當你想設置tag或者registry的時候它非常有用,所以你可以確定一個給定的包沒有打上“lastest”的tag或者被默認發布到全局的公開registry。

任何配置都可以被重寫,但當然可能只有“tag”和“registry”與發布的意圖有關。

參見npm-config(7)有可以被重寫的列表。

沐鳴測速登錄地址_12月份30個輕量的 JavaScript 庫和插件

下面這個列表中的免費 JavaScript 插件都是今年發布的,沒有臃腫的一體化的框架,它們提供輕量級的解決方案,幫助 Web 開發過程更容易和更快。提供的插件可以創建滑塊、響應式菜單、模態窗口、相冊和許多其他常見的組件。

baguetteBox.js

baguetteBox.js is a simple and easy to use responsive image lightbox script with swipe gesture support on mobile devices. it has been written in pure JavaScript.

ScrollReveal

The ScrollReveal plugin makes it super-easy to create web page scroll animations for both desktop and mobile browsers.

Marginotes

Marginotes takes your jQuery selection and adds notes to the margin with the text provided in HTML attributes. If jQuery is not your thing, there’s also a version available without it.

Loud Links

Loud Links is a lightweight JavaScript library for adding interaction sounds to your website. It does this by creating an HTML5 audio element and using it to play MP3 or OGG audio files.

Bricks.js

Bricks.js is a ‘blazing fast’ masonry layout generator for fixed width elements.

MediumEditor

Written using vanilla JavaScript, MediumEditor is a lightweight (28kb) Medium.com WYSIWYG inline editor toolbar clone. There are also a selection of Mediumditor extensions and themes available.

Philter

Available as either a jQuery plugin or as vanilla JavaScript, the Philter gives you the means to control CSS filters with HTML attributes.

SuperEmbed.js

SuperEmbed.js is a JavaScript ibrary that detects embedded videos on webpages and makes them responsive.

Substance

Substance is a JavaScript library for web-based content editing. It gives you all of the tools you need for creating custom text editors and web-based publishing systems.

List.js

List.js is a lightweight and fast vanilla JavaScript script that adds search, sort, filters and flexibility to lists, tables, or anything HTML.

jqGifPreview

jqGifPreview is a simple jQuery plugin for creating GIF previews just like you see on Facebook.

Datedropper.js

Datedropper.js is a jQuery plugin that gives you a an easy way to manage dates for input fields.

jfMagnify

jfMagnify is a jQuery plugin that creates a magnify glass effect on any HTML element, not just images.

jQuery formBuilder

jQuery formBuilder is a new jQuery plugin for quick, drag & drop form creation.

Popper.js

Popper.js is a lightweight (4kb minified) library for managing poppers, tooltips and popovers. You can quickly and easily position tooltips with just a single line code.

Image Blur Plugin

Image Blur Plugin is a lightweight cross-browser jQuery plugin for blurring images.

InlineTweet.js

InlineTweet.js allows you to easily create tweetable links out of any text on a webpage. All you have to do is wrap the tweetable text in a container with data-inline-tweet.

iMissYou.js

iMissYou.js is a handy little jQuery plugin for changing the title & favicon of the page when the user moves away from your website.

SweetAlert2

SweetAlert2 is a beautiful and customizable replacement for JavaScript’s popup boxes.

Turntable.js

Turntable.js is a responsive jQuery slider that will flip through a selection of images as your mouse moves across a container.

Force.js

Force.js is a JavaScript library that makes it simple to animate HTML elements and navigate around a web page.

Push.js

Push.js is a cross-browser solution for getting up and running with Javascript desktop notifications.

Bideo.js

Bideo.js is a JavaScript library that makes it very easy to add fullscreen background videos to web pages.

Microlight.js

Microlight.js is a lightweight code highlighting library, for any programming language, that greatly improves readability.

Algolia Places

Algolia Places is an easy way to use an address search autocomplete JavaScript library on your website.

flatpickr

Written in vanilla JavaScript, flatpickr is lightweight datetimepicker and calendar solution.

Slidebars

Slidebars is a jQuery Framework for adding off-canvas menus and sidebars to your website or web application.

anime.js

anime.js is a flexible and lightweight JavaScript animation library. It works with CSS, Individual Transforms, SVG, DOM attributes and JS Objects.

Cleave.js

Cleave.js – Format your <input/> content when you are typing.

Skippr

Skippr is a super simple and lightweight slideshow plugin for jQuery

iziModal.js

iziModal.js is an elegant, responsive, flexible and lightweight modal jQuery plugin.

Lightgallery.js

Lightgallery.js is a fully featured JavaScript lightbox gallery with no dependencies.

 

沐鳴主管:_GitHub中國區前100名到底是什麼樣的人?

本文根據Github公開API,抓取了地址显示China的用戶,根據粉絲關注做了一個排名,分析前一百名的用戶屬性,剖析這些活躍在技術社區的牛人到底是何許人也!後續會根據我的一些經驗出品《技術人員如何建立自己的個人品牌》《優雅的程序員列傳》歡迎我的簡書。

Github中國區前一百名城市分佈,令人比較意外的是IT重鎮深圳和廣州居然和北上杭差距那麼大!(其中China表示沒有註明具體城市用戶)

Github中國區前一百名語言分布圖,前端開發者依然霸佔着大多數,移動開發者加起來也已經達到半壁江山,讓人意向不到的是小眾語言的Ruby的開發者居然排在第六把PHP,C++這些甩到了身後!

廢話不多說直接開始單刀直入,以下為Github中國區排行榜前20名詳解,除了統計Github的粉絲排行之外,還分析了這20位社區大咖在知乎和微博的活躍度,從數據結果來看,Github排行榜上的諸君都是微博和知乎的技術紅人,這也符合我正在寫的《技術人員如何建立自己的個人品牌》一文(未發布,加微信可率先查看)。

1:daimajia

Github、知乎、微博情況

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github daimajia 8697 53
知乎 代碼家 1692 19 364 61
微博 代碼家 17508 6202

個人主頁:http://daimajia.com
個人介紹:林惠文,2013年畢業於西北大學,現於北京師範大學讀研究生。開源項目小熊詞典,AnimeTaste,EverMemo的作者,gank.io發起人。

2:ruanyf

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github ruanyf 7348 30
知乎 ruanyf 823 0 0 0
微博 ruanyf 33012 1181

博客:http://www.ruanyifeng.com/home.html
阮一峰,70后,英文名Frank。他原是上海財經大學世界經濟博士研究生。主要研究宏觀金融、貨幣政策與美國經濟。於2008年6月獲得博士學位,2014年加入支付寶。他本人也是一名IT技術人員,主要關注網站製作,並且對自由軟件有着堅定不移的信念。譯作:《軟件隨想錄》、《黑客與畫家》《異常流行幻象與群眾瘋狂》《下一個大泡泡》。

3:Trinea

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github Trinea 7105 19
知乎 Trinea 4458 24 3397 854
微博 Trinea 12624 2165

博客:http://www.trinea.cn/
吳更新:2010 年加入阿里巴巴,開始網站工具開發和性能優化工作,現滴滴出行技術專家;Android 開源項目源碼解析 codeKK 發起人,熱心推動國內 Android 開源發展。

4:JacksonTian

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github JacksonTian 7008 192
知乎 朴靈 7303 76 1694 289
微博 朴靈 28780 13359

博客:http://diveintonode.org/
朴靈,真名田永強,文藝型碼農,Node.js佈道者。現就職於阿里巴巴數據平台,任資深工程師,著有《深入淺出Node.js》。他活躍於CNode社區,是線下會議NodeParty的組織者,同時也是JSConf China(滬JS、京JS,以及杭JS)的組織者之一。朴靈熱愛開源,是多個Node.js模塊的作者。名言:叩首問路,碼夢為生

5:cloudwu

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github cloudwu 7008 192
知乎
微博 簡悅雲風 40152 2843

博客:http://blog.codingnow.com/
吳雲洋(Cloud),出生於1979年2月5日,原網易遊戲核心成員、杭州研究中心總監。 吳雲洋畢業於中南大學机械自動化963班,具有豐富的遊戲策劃和程序開發經驗,尤其擅長彙編語言及程序優化。早期在cfido和水木清華BBS發表了多篇關於遊戲製作的文章,後來製作了第一個專題介紹遊戲製作的GB碼中文網站,收錄個人文章超過百萬字,在遊戲界頗有名氣。

1998年中發布看書工具C-View 2.0,被多張工具光盤收藏。

1999年初開始製作二維遊戲引擎--風魂系列,被多家公司和小組用於遊戲製作。

他是《大話西遊》、《夢幻西遊》、《網易泡泡遊戲》等網絡遊戲的主力開發者,而《大話西遊》和《夢幻西遊》是網易最主要的兩款網絡遊戲。

2011年8月下旬從網易離職。吳雲洋或將與在2011離職的網易首席運營官詹鍾暉一起組建新團隊創業。

6:lifesinger

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github lifesinger 6692 1
知乎 玉伯 21305 131 5007 803
微博 玉伯也叫黑俠 30338 3852

博客:
玉伯(王保平),淘寶前端類庫 KISSY、前端模塊化開發框架SeaJS、前端基礎類庫Arale的創始人。2003-2006 年,中科院物理所研究生,Fortran 與 C 程序員,喜愛實驗模擬和數值計算。 2006-2008 年,在中科院軟件所互聯網實驗室從事項目管理軟件的研發,C# 與 Java 愛好者。 2008 年 4 月份加入淘寶,就職於 UED 部門。2009 年起,組建前端架構團隊,在首頁維護、全網性能優化、類庫研發、知識沉澱、工具應用等方面取得了豐碩成果。

7:michaelliao

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github michaelliao 5761 47
知乎 廖雪峰 10101 167 3215 721
微博 廖雪峰 30338 3852

博客:http://www.liaoxuefeng.com/
技術作家,著有《Spring 2.0核心技術與最佳實踐》

8:laruence

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github laruence 5252 33
知乎 Laruence 9771 23 1396 279
微博 Laruence 81296 13947

博客:http://www.laruence.com/
惠新宸 ,國內最有影響力的PHP技術專家, PHP開發組核心成員 , PECL開發者 , Zend公司外聘顧問, 曾供職於雅虎,百度,新浪微博現為鏈家網技術副總裁。 是PHP NG核心開發者,PHP5.4,5.5的主要開發者。作為PECL開發者貢獻了Yaf (Yet another framework),Yar(Yet another RPC framework) 以及Yac(Yet another Cache)、Taint等多個優秀開源作品,同時也是APC ,Opcache,Msgpack等項目的維護者。。
惠新宸:我也曾經是“不適合”編程的人(圖靈訪談)

9:ibireme

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github ibireme 5133 17
知乎
微博 ibireme 81296 13947

博客:http://blog.ibireme.com/
90后

10:Evan You

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github yyx990803 4873 92
知乎 尤雨溪 10852 261 9950 1647
微博 尤小右 13012 1994

博客:http://evanyou.me/
本名尤雨溪,目前在 Google Creative Lab 就職。vue.js 項目作者。

11:CoderMJLee

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github CoderMJLee 4713 4
知乎
微博 M了個J 28013 1063

博客:
小碼哥 CEO

12:sofish

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github sofish 4358 34
知乎 sofish 5001 65 2514 465
微博 sofish 16193 1826

博客:https://sofi.sh/
林建鋒,國內資深前端開發工程師,Web 標準佈道者,前支付寶前端開發部 CSS 樣式庫負責人。Trimidea 創始人,目前就職餓了么(了解博主近況的時候,博主跟我說已經很久木有寫代碼了,讓大家不要別像他一樣,不寫代碼從3跌到12,這段話給如此嚴肅的一個分析文章帶來了一絲逗比風,更新下,小魚騙我說他轉行了,嗚嗚嗚,今晚大家說他去餓了么)

13:astaxie

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github astaxie 4336 65
知乎 asta謝 1924 17 1259 397
微博 ASTA謝 10773 3257

博客:
謝孟軍,《Go Web編程》一書的作者。網名 @astaxie ,現就職於盛大雲,高級研究員,技術經理。主要從事盛大雲分發的系統研發工作。
相關新聞:採訪:關於Go語言和《Go Web編程》

14:stormzhang

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github stormzhang 3611 23
知乎 stormzhang 8888 304 12675 3542
微博 googdev 10773 3257

博客:http://stormzhang.com/
stormzhang,國內Android界的精神領袖,熱愛分享,擁抱開源,從編程小白的自學之路影響了無數Android開發者,目前運營的公眾號AndroidDeveloper「googdev」是Android界最有影響力的公眾號之一。現擔任薄荷科技Android開發主管。

15:tangqiaoboy

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github tangqiaoboy 3501 37
知乎 唐巧 2584 28 393 130
微博 唐巧_boy 40095 3975

博客:http://blog.devtang.com/
唐巧:資深 iOS 開發者和 Blogger,曾開發有道雲筆記、猿題庫和小猿搜題的 iOS 客戶端。他維護着 iOS 開發博客 http://www.devtang.com/ 和「iOS 開發」微信公眾賬號。

16:RubyLouvre

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github RubyLouvre 3460 86
知乎 司徒正美 5348 57 1646 340
微博 司徒正美 13450 6610

博客:http://www.cnblogs.com/rubylouvre/
鍾欽成,網名司徒正美,著名的JavaScript專家,去哪兒網前端架構師,立志做考古學家的日語系工程師,穿梭於二次元與二進制間的“魔法師”,做過陶藝,寫過小說,涉獵Java、Ruby、 JavaScript。曾出版《JavaScript框架設計》一書。

17:justjavac

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github justjavac 3447 35
知乎 justjavac 8066 482 46627 6645
微博 justjavac 5685 2706

博客:http://justjavac.com
justjavac,千行時線 Hybrid APP 開發工程師,活躍於國內外互聯網社區,知乎大V。曾翻譯《JavaScript quirks》,正在出版《代碼之謎》。HTML5+CSS3 佈道師,NoBackend 信徒,翻譯了 JSON API 規範文檔,Flaurm 中文社區創始人。平時混跡於Github,參与眾多開源項目,Star 數位列全球前 100 名。目前正在出版「代碼之謎」

18:wintercn

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github wintercn 3325 30
知乎 winter 66447 853 56739 8116
微博 寒冬winter 34073 4631

博客:http://winter-cn.cnblogs.com/
程劭非,阿里巴巴高級技術專家,著名JavaScript專家。目前是手機淘寶前端 leader,曾參与Bambook開發,更早為微軟工作,開發Windows CE平台的IE瀏覽器。知乎三大軟狗之一!
「計算機之子@寒冬winter 老師」是誰?

19:ChenYilong

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github ChenYilong 3272 39
知乎 ElonChan 196 22 23 7
微博 iOS程序犭袁 26716 5548

博客:
陳宜龍,iOS開發工程師,現任職於LeanCloud,熱愛開源與分享,GitHub 獲得的Star數過萬,其中 《iOS9適配系列教程》 一度成為該領域最有影響力的教程。 StackOverFlow 威望值高達3600。同時也是 CYLTabBarController 、iOSInterviewQuestions、
ParseSourceCodeStudy 等項目的維護者。

20:fouber

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github fouber 3297 126
知乎 張雲龍 10352 51 12083 3004
微博 前端农民工 15561 600

博客:https://github.com/fouber/blog
張雲龍UC(優視科技) 前端工程師

22、lepture

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github lepture 3212 154
知乎
微博 lepture 2773 530

http://lepture.com/
Hsiaoming Yang:介紹信息未知,我認認真真的拜讀作者的博客文章,我在文字的後面看到一個優雅的程序員的樣子,我想我一直追求形容真真切切的程序員應該有的品性和樣貌都在他身上有所體現把。(稍後將會出品《優雅的程序員列傳》歡迎加我微信diycodes交流)

23、phodal

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github phodal 2988 86
知乎 phodal 3116 487 5338 1351
微博 phodal 2773 530

https://www.phodal.com/
InfoQ社區編輯、 CSDN前端博客專家、 SegmentFault Top Writer、 稀土掘金 Editor、出生於閩南,畢業於西安文理學院(电子信息工程專業),就職於ThoughtWorks,前端、後台、SEO、硬件工程師。(這似乎是一個熱戀中的程序員,簽名是:待我代碼編成,娶你為妻可好 @花仲馬)

知乎三大軟狗:

21、JeffreyZhao

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github JeffreyZhao 2524 29
知乎 趙劼 97467 1159 92704 10796
微博 老趙 56680 46854

博客:http://blog.zhaojie.me/
趙劼,網名老趙,洋名Jeffrey Zhao,花名趙姐夫,金融行業程序員,Wind.js類庫作者,多年微軟MVP,InfoQ中文站兼職編輯,目前就職於摩根大通(香港)。知乎“溫趙輪”三大軟狗之一。(老趙一直叨嘮要去美利堅,不知道近況如何了)

vczh

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github JeffreyZhao 5032
知乎 vczh 282863 10648 584678 77159
微博 GeniusVczh 39071 33994

博客:http://www.gaclib.net/
vczh,知乎“溫趙輪”三大軟狗之一。本名陳梓瀚,因知乎的個人信息介紹上寫有 “專業造輪子”,所以江湖人稱 “輪子哥”。vczh 大學時代就在微軟實習,畢業后即加入微軟。開始時是在微軟上海,後來進入北京的微軟亞洲研究院。現已移居美國西雅圖,在 Office 組做工程師。妻子陳萌萌,原來在百度工作,已隨 vczh 共赴美國,入職 Google。
vczh 是誰?

Ruby二俠

27:huacnlee

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github huacnlee 2102 90
知乎
微博 huacnlee 1497 2623

博客:http://huacnlee.com/
李華順,者也(zheye.org)創始人之一,淘寶MED團隊成員,Ruby China 創始人。2015 年加入支付寶,參与內部 Node.js 框架開發,並負責阿里集團 GitLab 維護和開發工作。

46:chloerei

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github chloerei 1560 39
知乎
微博 chloerei 391 171

博客:http://chloerei.com/
黃增光,網名 Rei,廣西梧州人,現居深圳。Web 開發者,擅長 Ruby,開源項目Writings.io作者,Ruby China 管理員。

69:hzlzh

平台 用戶名 粉絲數 Github repos 知乎回答數 知乎贊同 知乎感謝 微博數
Github hzlzh 1028 74
知乎 hzlzh 1587 175 2305 398
微博 hzlzh 4204 5780

博客:http://hzlzh.io/
黃自力,前端工程師,就職於騰訊微信 TmT 團隊,十足的 Geek 果粉,折騰過很多有意思的項目和網站,如:Best-App、AlfredWorkflow.com,mou.li 等,開發上架了 App:雲收益 Pro。目前運營的公眾號「MacTips」 分享 Mac OS 的經驗和技巧。
另:本文數據參考網站githubrank作者

風雲天下

Milo Yip、劉未鵬、陳皓(左耳朵耗子)、雲風、陳碩、王垠、byvoid、傘哥、tombkeeper、刺總(吳翰清)

以上介紹的社區技術名人你認識幾個,如果有推薦的社區名人?一起聊聊吧。本文如有部分信息有誤或者不夠新請指正,一定修改。

參考資料:
Githubrank
知乎
新浪微博

文/優雅的程序員(簡書作者)