你可能不需要Vue

richardo2016發表於2018-08-02

注意
本文假設你已具有以下內容的相關知識或者實踐經驗:

VAR

毫無疑問, 2016 ~ 2017 是 Vue 勢頭最強勁的兩年. 根據筆者的記憶, 2017 年, Vue 的在 Github 上的 Star 數目首次超過 4W, 彼時 React 的 star 數目還在 3W 內, Angular v2/v4 還在 beta 版本. 三大框架互相學習, 儘管粉絲間掐架不少, 利益相關的佈道者也上躥下跳, 但 Vue 2 引入了 vdom, 在實現上, 擺脫源自 Angular 1.x 的模板概念; AngularVue 的作者之間還保持著互通有無的關係; React 升級後放棄了對 IE 8 的支援, 基於 S/P 模式的狀態管理方案 mobx 在社群冉冉升起.

時間步入 2018 年, VAR 三大框架在各方面的差異此消彼長, 甚至不約而同地對 Typescript 做了官方的支援. 如果你在同一時間在專案中對三個框架都有實踐, 難免會生出”萬變不離其宗”的感慨: 說到底, 大家都玩起了預編譯和[渲染純函式].

進一步地說, [*.jsx(React)] , angular 的元件 和 [*.vue(sfc)], 最終都會轉化為 runtime 中對 DOM 不同的操作函式. 這一點在 [*.jsx(React)] 中體現得淋漓盡致 —— 連 *.jsx 這種語法的誕生都是為了讓 React 元件的 createElement/cloneElement 函式對開發者更友好; Vue 2 的單檔案元件(sfc) 將其包裝成了模板中的 v-* 指令、屬性和事件繫結, 後來乾脆也支援了 [*.jsx(Vue)]. 無論 *.jsx 還是 sfc, 這些框架的 DSL 語法, 都是為了儘可能地減少(乃至徹底清除)自家框架在執行時解析特定指令的耗時, 同時能讓開發者有良好的開發體驗.

*.vue

對於 vue 新手而言, 直接在瀏覽器裡引入完整版本的 vue.js 然後在 <script type="text/x-template"></script> 中書寫 vue 的 template 是不錯的開始, 但這樣會導致 vue 在執行時,

  1. 先花費一部分時間去解析這段模板, 生成 vue 例項上的[渲染純函式]
  2. 執行這些[渲染純函式], 處理 vue 例項中的 data 和 dom.

其中的第 1 步, 通過使用[單檔案元件(sfc)]是完全可以節省的.

vue 的[單檔案元件(sfc)] 是 xml 格式的檔案, 經過 Vue 官方工具鏈的處理(如使用基於 vue-cli 的 webpack 的腳手架進行編譯),後, 將變成可以在 Vue runtime 中執行的的一系列純函式集. 詳情可參考這裡.

如果你還沒安裝 vue-cli 並生成一個使用 [單檔案元件(sfc)] 來構建應用的腳手架, 你可以通過 [vue-template-explorer] 瞭解 vue template 和編譯後的[渲染純函式]的聯絡.

[單檔案元件(sfc)] 的優勢很多, 比如:

  • 通過 webpack/browserify 的配置, 可以安心地使用 typescript/es201X 的語法, 同時在編譯後得到經過 polyfill 處理的、可以在 es5 環境下執行的程式碼
  • 使用 css 和 html 預編譯器
  • 乾淨的元件內 scoped 特性.
  • 配合 webpack/browserify, 使用 nodejs 風格的模組管理.

但在此前, [單檔案元件(sfc)] 有個缺陷, 即需要較為完整的 vue-cli 工具鏈的支援, 如果你只是想:

  • 寫個簡單的頁面, 裡面只有一個 ajax/fetch 請求, 根據返回結果, 在頁面上渲染該結果
  • 使用 vue
  • 最好能夠支援 jsx(Vue), 這樣就不用
  • 能直接書寫 es6/typescript 語法, 並且能預編譯為 es5 相容的 js
  • 還希望能夠使用 less/stylus/scss/sass 來書寫樣式, 並預編譯為合法的 css

此時最快的途徑, 似乎是使用 vue-cli 初始化一個 webpack-simple 的專案, 然後修改 webpack 配置, 使得 src/index.js 在編譯後被注入 index.html, 得到一個頁面…..稍等, 這類似於有時候你只是想寫個簡單的窗體應用, 卻不得不去下載一個帶完整 MFC 的 Visual Studio 一樣. 我真的想這樣做麼? 我很可能配置 webpack 到一半就放棄了, 轉而新建一個 index.html 檔案, 然後直接在 <script type="text/x-template"></script> 裡書寫 vue 元件的模板, 然後執行起來 —— 儘管這樣會引入完整的 vue.js 的檔案(> 100KB), 並且 vue 需要耗時去解析模板, 但是這樣的開發效率, 非常高啊!

別的思路

即便是在 [vue-component-compiler] 釋出以後, 社群依然缺乏直接在 index.html 中書寫 vue 元件並且能進行預編譯的方案.

那麼, 如果我們只是想寫個簡單的頁面(就像 jQuery 盛行的時代中我們經常做的那樣), 我們非得安裝 vue-cli, 再初始化專案, 再安裝龐大的 webpack 及其周邊依賴麼?

也許 [parcel] 適合做這件事, 它允許你在 index.html 中直接引入相對路徑下的 .js 檔案, 並根據 [parcel] 的配置(預設配置通常已足夠你使用)合適地處理其中所有的資源, 包括 .js 檔案自身超前的語法(es201X) 和 css 預編譯器.

不過, 截至筆者書寫到這裡(北京時間 2018-02-11 17:14) 的時候, 根據 騰訊 imweb嘗試, [parcel] 還有以下缺點:

  • 不支援SourceMap:在開發模式下,Parcel也不會輸出SourceMap,目前只能去除錯可讀性極低的程式碼;
  • 不支援剔除無效程式碼(TreeShaking):很多時候我們只用到了庫中的一個函式,結果Parcel把整個庫都打包了進來;
  • 一些依賴會讓Parcel出錯:當你的專案依賴了一些Npm上的模組時,有些Npm模組會讓Parcel執行錯誤;

注意 實際上, 這些缺點在下文筆者介紹的方案中也存在. 相信以上問題 [parcel] 會在不久的未來解決.

也許我們可以嘗試用 [parcel] 來寫一個簡單的頁面, 但如果你希望去了解 [parcel] 內部處理資源的細節, 對自己的專案有更多的瞭解, 又不必太符合 [parcel] 的社群形象(開箱即用, 零配置). 此時, 似乎 webpack 又更吸引你(儘管它的配置相對繁冗, 但至少對開發者可見)

有沒有什麼細節對開發者更為透明, 可以完全自己 DIY、不必造太多輪子的方案呢?如果拋開平時習慣 webpack/browserify 下的思維慣性, 整理一下, 其實真正幫助我們提高了效率的庫是:

  • babel/bubble
  • JSX
  • pugjs/ejs/swig
  • stylus/less/scss/sass

回到本文的初衷: “我只是要寫個簡單的 html 頁面, 裡面需要執行一些 js 來操作 DOM/BOM, 如果可能, 我希望利用 vue runtime”. 為了滿足這個需求, 筆者認為, 至少要解決以下兩個問題:

  •   支援 js/css/html 預編譯器
  •   以 html 為入口, 直接在 html 中書寫超前的的預編譯器語法

預編譯器處理的一種介面標準: jstransformer

[jstransformers] 是一系列 jstransformer-* 的統稱, [jstransformer] 的目的, 是統一 js(一般指nodejs) 社群中各類預編譯器庫的 API, 將不同思路、不同風格、不同目的, 但都基於 js、處理 js 或其它資源(比如 less/typescript)的庫進行介面標準化. 參看其 github 上的 README:

There are many good template engines and compilers written for Node.js. But there is a problem: all of them have slightly different APIs, requiring slightly different usage. JSTransformer unifies them into one standardized API. Code written for one transformer will work with any other transformer.

(Node.js 社群中)有非常多好的模板引擎和模板編譯器. 但是存在這樣一個問題: 它們的 API 有些微差異, 用法有細微的不同. JSTransformer 將它們統一為標準的 API. 一個 transformer 應該能與另一個 transformer (以同樣的 API)進行協作.

[jstransformers] 要處理的目標很明確: 預編譯器, 比如下表中的這些預編譯器

預編譯器種類 典型庫
html 預編譯器
  • pug/jade
  • ejs
  • juice
  • swig
css 預編譯器
  • sass/scss
  • stylus
  • less
  • postcss
js 預編譯器
  • coffeescript
  • babel
  • typescript

[jstransformers] 讓筆者想到 [webpack-loader] 概念. 儘管 [webpack-loader] 整合給 webpack 專案的類庫不只是預編譯器, 但在只討論”如何在專案中使用預編譯器”的語境下, [jstransformers] 可以類比於 [webpack-loader].

現在我們的第一個問題解決了

  •   支援 js/css/html 預編譯器
  •   以 html 為入口, 直接在 html 中書寫超前的的 js 語法(es201X)

直接在 HTML 中寫預編譯器語法

all-in-js ·PK· html-first

在考慮第二個問題之前, 先討論一下目前社群對 html/css/js 前端三大基石的一種態度: all-in-js

前兩年, React 社群有一種 all-in-js 的思路, 即 html 提供一個頁面載入的入口, 把所有的資源(包括所有的 css 一些體積可接受的圖片)打包到 js 中, 這種思路傾向於:

  • 如果不必要, css 可以完全不在 html 中書寫(使用 <style> 標籤)或引入(使用 <link> 標籤)
  • html 檔案除了 head 中的 meta 屬性需要做一些定製, 其餘部分基本不重要, 反正打包後的 js 會在 html 中注入開發者需要的一切.

這種思路和 React 單頁應用(SPA) 的配合可謂天衣無縫, 不止資源, 業務邏輯也 all-in-js , 這似乎沒什麼問題. 但過去社群對 html 語義化的努力的成果在這種思路下被嚴重削弱了: 反正對 SEO 也不友好, 我為什麼要語義化?並且, 這種思路對一些簡單的需求(比如”我就想寫個靜態頁面, 裡面有大量的靜態元素, 就一個 input 中輸入的內容需要被實時顯示到另一個元素裡”)天然不太友好, 可能開發者只希望 vue 或者 React 處理頁面中有動態渲染的元素, 其餘相對靜態的 DOM 元素留用於保持頁面結構、語義表達(比如您現在正在閱讀的這篇部落格, 右邊的標籤列表就完全只是靜態的 ul > li 列表)

現在, 無論你使用 vue-cli 還是 create-react-app, 這類工具都可以給你一個基於 webpack 的腳手架, 並鼓勵你: all-in-(js/vue/react), 把 Vue/React 的例項掛載到 body 元素下的一個根元素吧, 讓 Vitural DOM 來為你處理一切…

那麼問題來了, 如果我現在只是想寫一篇部落格, 但部落格裡有一些演示性的片段, 它只佔了頁面中的一小部分元素, 比如{% post_link fix-parse-date-error-in-chrome-50 “這篇文章” %}的末尾, 提供了一個小工具, 我希望使用 vue 來管理, 我可不希望把我所有的部落格內容寫在 vue 的元件裡, 而是靜態的 html, 畢竟, 部落格的文字是靜態的.

好, 現在我們寫一個靜態的 html 檔案, 它可能是這樣的:

<html>
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.min.js"></script>
  </head>
  <body>
    <p>
      我是部落格中靜態的問題, 部落格內容的末尾會有一個使用 vue 來處理的、具有互動性的區域, 用於說明部落格中講解的內容.
    </p>
    <div id="vue-app"></div>

    <script type="text/x-template" id="component-tpl">
      <div>
          我是具有互動性的內容
          <div>
            <input v-model="inputstr" />
          </div>
          <div>
            我輸入的值 {{inputstr}}
          </div>
        </div>
    </script>

    <script>
      var inputstr = window.preStr
      if (typeof inputstr !== `string`) {
        inputstr = ``
      }

      var app = new Vue({
        el: `#vue-app`,
        data () {
          return {
            inputstr
          }
        },
        template: `#component-tpl`
      })
    </script>
  </body>
</html>

看起來似乎沒什麼問題, 但實際上, 我已經無意識地使用了 es6 的語法: 在 Vue 例項的 data() 函式返回值中, 筆者竟然直接使用了 inputstr 變數名作為返回物件的 key 名! 這在 es6 中是沒問題的, 但在 es5 環境下, 這樣的語法會導致致命錯誤.

在 webpack 環境下浸淫太久的開發者, 有時候可能忘記了某個特性是 es6 才支援的, 不自覺地在 html 的 <script> 標籤中直接使用了. 比如, 筆者經常使用的 Array.from 是 es6 才支援的, 我希望能在 html 中毫無顧慮地使用它.

此時, 可能你開始懷念 webpack 腳手架為你提供的便利: 配好好 .babelrcbabal-loader, 接下來在入口檔案(可能命名為 app.js)及其引用檔案中就可以放心地使用 es201X 的新特性. 不過等等, 其實我們只是需要 babel 對吧? 將 es6 編譯為 es5 甚至 es3 的, 是 babel 而非 webpack 啊!

真的只有 grunt/gulp/browserify/webpack/parcel 這些方案可以讓我們毫無顧忌地書寫 es6 語法麼? 真的必須在 html 中引入一個入口檔案再讓 babel 處理麼?明明我們只是需要 babel 來編譯下 es6.

有什麼辦法可以在 html 中書寫的 javascript 直接被 babel 編譯麼?

基於 pug 的預編譯器整合方案

一個在 html 中直接使用 js/css 預編譯器的方案是, 通過 [pug filters 特性], 在 HTML 中直接書寫各種預編譯器語法, 參看以下程式碼:

html
  head
  body
    :markdown-it
      我是部落格中靜態的問題, 部落格內容的末尾會有一個使用 vue 來處理的、具有互動性的區域, 用於說明部落格中講解的內容.

    #vue-app

    script(type="text/x-template" id="component-tpl").
      <div>
        我是具有互動性的內容
        <div>
          <input v-model="inputstr" />
        </div>
        <div>
          我輸入的值 {{inputstr}}
        </div>
      </div>

    <script>
      :babel
        let inputstr = window.preStr
        if (typeof inputstr !== `string`) {
          inputstr = ``
        }

        const app = new Vue({
          el: `#vue-app`,
          data () {
            return {
              inputstr
            }
          },
          template: `#component-tpl`
        })
    </script>

[jstransformer-babel] 是一個可以將 babel 介面的標準化中介軟體. 根據 [pug filters 特性], 我可以在 pug 中書寫一段 javascript, 將其置於一個 :babel filter block 中, 並提前安裝好 [jstransformer-babel], 配置好 .babelrc, [jstransformer-babel] 將自動幫我們把 es6 語法進行編碼.

一個更輕量的方案

綜合以上分析, 基於 [jstransformers] 和 [pug], 拋開 webpack 或者 parcel, 我們一樣可以完美書寫 es201X 的語法(甚至 typescript/coffeescript). 實際上, {% post_link fix-parse-date-error-in-chrome-50 “這篇文章” %} 就使用 pug 書寫靜態內容的, 文章中所有的互動性區域, 使用 vue 管理; 部分需要在頁面中特定書寫的 css, 使用 stylus 語法書寫, 並由 [jstransformer-stylus] 進行編譯.

至此, 我們解決了第二個問題,

  •   支援 js/css/html 預編譯器
  •   以 html 為入口, 直接在 html 中書寫超前的的 js 語法(es201X)

在這種方案中, 你可以自由地使用各種 [jstransformers] 的庫來編譯你內容, 如果這些庫不符合你的需要, 你也可以編寫自己的指令碼, 使用你自定義的 pug-fitler, 並使用 node.js 來執行.

為了更深入地講解這種方案, 我寫了一個[示例專案], 並通過一個小系列來討論一下這種方案的應用場景和它的侷限性, 你可以從專案和系列文章中獲得更多的關於這種方案的細節和應用場景. 如果你對這種專案感興趣, 歡迎到專案 issue 中提出討論, 或者發起 PR.


相關文章