用 Webpack 和 ES6 轉換快速開發 Rails 的富客戶端

Fast Rich Client Rails Development With Webpack and the ES6 Transpilerjavascript

有更好的方式把 JavaScript 生態引入 Rails。css

輸入圖片說明

你有沒有:html

  • 想知道是否有更好的方式在現有 Ruby on Rails 工程下使用現代 JavaScript 客戶端框架?
  • 煩惱怎樣整合那些被打包成"模塊"的 JavaScript 庫和例子?
  • 發如今全局命名空間下亂掛垃圾的弊端。
  • 據說過 ES6 (又叫 Harmony),下一代 JavaScript,如今正被那些硅谷大牛逼正在用得熱火朝天(Facebook, Instagram, Square, 之類的)?

那你應該怎麼在你的 Rails 工程中,實現如下這些呢:java

  • 你的 UI 原型,若是發現 JS 和 CSS/Sass 代碼發生了變化,當保存的時候,馬上反應到界面,而無需重載頁面。
  • 成爲 Node 生態中的一等公民,只須要在 package.json 指定依賴,而後執行 npm install,而後就能夠在 JavaScript 文件裏面很容易的就導入模塊。
  • 無縫集成基於 Node 的 JavaScript assets 到 Rails 的 Asset Pipeline,無需規避 asset pipeline,而是讓它共存,並能利用它。
  • 無縫集成 node 客戶端生態到現存的 Rails 工程中。
  • 利用各類 JavaScript 工具,好比說 React JSX tranpilerES6 transpiler

這篇文章將會告訴你如何在 Rails 開發過程當中利用 Webpack 達成上面這些目標!node

首先,我要給你講個故事,告訴你我是怎麼發現須要一個更好的方式將 JavaScript 生態集成到 Rails 中的。react

#手工拷貝粘貼一大堆 JavaScript 到 /vendor/assets/javascripts 有錯?jquery

想一想若是你的 Ruby on Rails 工程沒有 Bundler 會怎樣?歐,屌炸了是否是!這就是手工拷貝 JavaScript 到 /vendor/assets/javascripts 的感受!我實際上也挺擔憂的,由於愈來愈多 JavaScript 庫開始依賴其餘的 AMD (也叫 require.js) 或者 CommonJs 模塊了。(有篇文章詳細介紹了這種模塊系統的工做方式,請看 Writing Modular JavaScript With AMD, CommonJS & ES Harmony。)對 Rails 社區來講,不得不面對的一個問題就是將流行的 JavaScript 庫打包成 gems,好比說 jquery-rails gem。固然你可能以爲這樣挺讚的,除非你發現你要用的 JavaScript 模塊沒有對應的 Gems。好比說,你想開始用 npm packaged上那些很讚的 react 組件,好比說 react-bootstrap,或者你會想要用到 JavaScript 的工具鏈,好比說 JSX 和 ES6 transpilers (es6-transpileres6-module-transpiler)。webpack

慶幸的是,我已經不是那個抱着 gemfiled 找 JavaScript 的小寶貝了!你如今就能夠成爲 JavaScript 的一等公民!git

輸入圖片說明

#動機: React 和 ES6es6

我正式開始蹲 Node 的兔子洞是從準備開始用 React 框架開始的,包括它的 JSX transpiler。總的來講,React 庫 看起來獨特,創新,使人印象深入。你能夠簡單的這樣認爲,客戶端的 UI 就是一組組件,基於單向流數據進行渲染,從頂層組件到它的每一個子組件。想要知道更多 React 的優勢,看個人另一篇文章 React on Rails Tutorial。而本文的目的在於,你能夠這樣認爲,我要拿 React 來替換掉你最喜歡的富客戶端框架。固然若是誰 fork 個人工程,而後創建一個用 EmberJS 的版本,我會很高興的。

第一個任務用 React 看起來蠻簡單的,由於這有 Ruby 的 Gem,ract-rails gem,它提供了一個真的蠻無痛的把 react 整合到 Rails 工程的方式。真的挺簡單的。我作了一個例子,React on Rails Tutorial,還有相應的 github 倉庫,justin808/react-rails-tutorial,這個例子告訴你怎樣配合 Rails 4.2 scaffold generatorreact-rails gem。而後我想把 react-bootstrap 用上去。這就沒有 gem 了,我考慮過手工拷貝粘貼到個人 /vendor/assets/javascripts 目錄,可是這讓我以爲很是_不爽_,緣由以下:

  1. JavaScript 有一個成熟的依賴管理系統(packages 和模塊 modules): npm (和 bower)。
  2. 依賴一般還會依賴另外的包,無論在 Ruby 仍是 JavaScript 的世界。想象一下若是 Ruby 依賴項要手動解決。
  3. JavaScript 模塊一般會依賴別的 CommonJs 或者 RequireJs 不錯的模塊。

(注: 對 Node 而言,module 是一個被打包的能夠經過 require() 調用的 JavaScript 代碼。更詳細的信息,請查看 npm faq 以及 StackOverflow)

這有其餘一些不錯的方式來處理 Rails 應用的 assets: Five Ways to Manage Front-End Assets in Rails。我簡單的嘗試了一下這些技術,好比說加上使用 browserify-rails gem。可是,看起來都和 react-rails gem 衝突,而要是我不用這個 gem 的話,我就須要另尋出路去把 jsx 轉化成 js 文件。這讓我開始嘗試使用 webpack module bundler。

輸入圖片說明

#Webpack

什麼是 Webpack

webpack 爲模塊和模塊的依賴項生成靜態 assets 的。

爲何我會嘗試 Webpack?我推薦你看個人這篇Pete Hunt of the React team。這有些很充足的理由解釋"爲何是 Webpack":

  1. 利用 npm (也可選 bower) 做爲包管理。
  2. 支持全部的模塊語法,只要你喜歡就好。
  3. 有許多 loaders (就像 pipeline),包括 ES6 和 JSX。
  4. Webpack Dev Server 對 JS 和 CSS/Sass 的快速原型開發很贊(Hot Module Replacement)。

開始學習 Webpack 的一個好去處是 Pete Hunt 的 webpack-howto

我最初的時候想用 webpack module bundler 把 JS 從 Rails 分離出來,由於我想看到所謂的 react 代碼能夠"熱重載"。你能夠試試看這寫簡單的代碼: react-tutorial-hotHot module Replacement會在運行時刷新 JS 代碼(以及 CSS)而不須要任何的頁面刷新。甚至是 JS 對象裏面全部數據!這種技術比 Live Reload,要刷新整個頁面要酷得多。

而後我就開始使用 Webpack 的這些功能了:

  1. es6-loader,裏面包括了 es6-transpileres6-module-transpiler。你要想玩一下,試試看 ES6 語法,能夠經過 ES6 Fiddle。這裏有個 great references on ES6 features
  2. jsx-loader,用 es6 來處理 jsx 文件的加載器。
  3. 雞零狗碎的整合,經過 npm 能夠輕鬆的添加任何包,以及可使用任何模塊語法。

因爲 Webpack 生成的 "bundle" 並不必定是被壓縮過的,因此看起來應該能夠被 Rails 的 asset pipeline 直接拿來用,果真,它能夠!在這篇文章裏面解釋得很清楚: Setting Up Webpack with Rails 以及例子代碼解釋如何預編譯 Webpack: Webpack In The Rails Asset Pipeline

有了基礎部分,我開始想要實現如下功能:

  1. 能夠用 Webpack Dev Server (的 hot module replacement) 來作客戶端的 JS 原型開發,而且在個人 Rails 應用裏面有一樣的代碼。這包括了 JavaScript, Sass, 和 Image 文件這些在 Rails 和 Webpack Dev Server 兩邊都有的通用文件。
  2. 能夠很容易的發佈到 Heroku。

個人解決案放在了 github repo: justin808/react-webpack-rails-tutorial。這是基於我使用 react-rails gem 的例子: Rails 4.2, React, completed tutorial。接下來我會詳細的解釋一下這個工程。

#安裝

你須要安裝 Node.js。我假設你已經有 Ruby 和 Rails 了。

  1. Node.js: 你能夠在這裏找到 Node.js 的下載文件。嗯,個人一些朋友建議我直接用 Node.js 的安裝包,而不是用 Brew。我還沒試過 Brew。

  2. 許多文章建議執行下面的命令,這樣你就不須要在 node 命令行前用 sudo,因此,你本身更改你的 /usr/local 目錄權限吧。

    $ sudo chown -R $USER /usr/local

  3. 你的 /package.json 文件用來配置全部的依賴,而後你只要執行 npm install 就能夠徹底安裝全部的包了。

一旦這些都開始工做了,這就像聖誕老人給個人應用帶來一個完整的 Node 生態同樣,爽呆了!

輸入圖片說明

##Bundler 和 Node Package Manager

全部的 Rails 開發者都熟悉 gems 和 Bundler(bundle)。對等的 Javascript 則是 package.json 文件和 Node Package Manager (npm) (請看下面的一節,我會說明爲何不選 Bower)。

這兩個包管理系統都是經過檢索值得信賴的在線資源來實現的。經過使用 package.json 文件遠比手工下載依賴文件,而後拷貝到 /vendor/assets 目錄要好得多!

輸入圖片說明

##爲何是 NPM 而不是 Bower?

許多流行的優秀的 JavaScript 在 Node Package Manager (npm)Bower 上都有。由於要用 webpack,你應該會比較傾向用 npm,文檔的理由以下:

許多狀況下,npm 的模塊質量要優於 bower。大多數狀況下, Bower 只包括了 concatenated/bundled 文件,這樣就會致使:

  • webpack 更難管理
  • webpack 更難優化
  • 有時候還不能用到模塊系統

因此愛上 CommonJs-風格模塊吧,讓 webpack 編譯它。

##Webpack 加上 Rails 的解決案說明

爲了把整合 webpack 到 Rails,用瞭如下兩種方式:

  1. Webpack 被單獨用到了 /webpack 目錄,這樣可使用 Webpack Dev Server 來提供客戶端 Javascript 原型快速開發工具。 webpack.hot.config.js 用來配置 Webpack Dev Server 用的 JS 和 CSS assets。
  2. Webpack 監控並打包 /webpack/assets/javascripts 文件夾下面的全部 Javascript,生成 rails-bundle.js。文件 webpack.rails.config.js 轉換 JSX 文件到 JS 文件,經過用 JSX 和 ES6 transpilers。

下面的這張圖說明了在 Rails 中用 Webpack 的文件組織結構:

輸入圖片說明

文件 備註
/app/assets/javascripts/rails-bundle.js webpack --config webpack.rails.config.js 輸出目錄
/app/assets/javacripts/application.js 引用 rails-bundle 這樣 webpack 的輸出會被 sprockets 使用
/app/assets/javascripts 清空這裏的全部文件,把它們放到 /webpack/assets/javascripts
/app/assets/stylesheets/application.css.scss 引用 /webpack/assets/stylesheets 的 sass 文件
/node_modules 存放 npm 模塊
/webpack 全部的 webpack 文件都放在這個目錄底下,除了 node_modulespackage.json
/webpack/assets/images 連接到 /app/assets/images。這樣 Webpack Dev Server 能夠和 Rails sprockets 看到一樣的圖片了
/webpack/assets/javascripts javascripts,將會被打包成 rails-bundle.js 以及用於 Webpack Dev Server
/webpack/assets/stylesheets 用於 asset pipeline 的 stylesheets (會被 /app/assets/stylesheets/application.css.scss 引用),同時也用於 Webpack Dev Server
/webpack/index.html 使用 Webpack Dev Server 的默認首頁
/webpack/scripts 只用於 Rails 或者 Webpack Dev Server 環境的文件
/webpack/server.js server.js 是 Webpack Dev Server 的代碼部分
/webpack/webpack.hot.config.js webpack 的 Webpack Dev Server 配置文件
/webpack/webpack.rails.config.js webpack 用來生成 rails-bundle.js 的配置文件
/.buildpacks 用於配置 Heroku 多環境 node + ruby 的 buildpacks
/npm-shrinkwrap.json 和 /package.json 定義執行 npm install 時的配置文件

##webpack.config

再次重申一下,咱們之因此用 Webpack 是由於如下理由:

  1. 爲了可以使用 JS "模塊",包括使用 AMD(也叫 require.js) 或者 CommonJS 模塊語法
  2. 爲了轉換 JSX 文件(ES6 和 JSX 語法)爲 JS 文件。注意,或者你並不須要把全部的 JS 文件都改爲 ES6,由於極可能有些導進來的模塊會發生衝突。

設置 webpack.config 文件,根據需求咱們要有兩個版本,Webpack Dev Server 和 Asset Pipeline。

輸入圖片說明

修改 webpack.config

你也許會想要不要修改這個 webpack config 文件。那麼下面有些你要注意的點。

  1. module.exports.enty: 這個入口點會決定 webpack 將會把包放到哪裏。雖然看起來很像 manifest 文件 /app/assets/javascripts/application.js,可是在這裏你須要指定的僅僅是entry點。因此若是你指定了 ./assets/javascripts/example (你不須要文件後綴)做爲入口點,那麼你就不須要而且不要再指定 ./assets/javascripts/CommentBox 這樣的做爲入口點了。再次聲明,這個依賴是爲 Webpack 作的,而不是 Rails。
module.exports = {
 context: __dirname,
 entry: [
   "./assets/javascripts/example"
 ],
  1. module.exports.externals: 若是你須要從 CDN 或者從 Rails gem 加載 jQuery,你應該像這樣指定:
module.exports.externals: {
  jquery: "var jQuery"
},
  1. module.exports.module.loaders: 這裏是你能夠用來暴露 Webpack 的 jQuery 給rails-bundle.js 的地方。這樣那些 Rails 中非模塊的 javascript 部分也也能夠用上 jQuery 了。
module.exports.module: {
  loaders: [
    // Next 2 lines expose jQuery and $ to any JavaScript files loaded after rails-bundle.js
    //   in the Rails Asset Pipeline. Thus, load this one prior.
    { test: require.resolve("jquery"), loader: "expose?jQuery" },
    { test: require.resolve("jquery"), loader: "expose?$" }
  ]
}

##Webpack Dev Server 和 Hot Module Replacement

由於等待 webpack 生成 rails-bundle.js 文件,而後刷新 Rails 頁面是很是耗時的,和使用 Webpack Dev Server 以及 Hot Module Replacement 比起來簡直就是渣渣,用後者若是能夠,還能不修改客戶端數據直接加載新的 JavaScript 和 Sass 代碼。若是你以爲 Live Reload 很酷,那麼你確定會超愛這個功能。請看這段引用:

webpack-dev-server 是一個小型的 node.js express 服務,經過使用 webpack-dev-middleware 爲 webpack 提供服務。它還有一個小型的運行時,經過 socket.io 連接到服務。服務推送狀態變化到客戶端,而後客戶端響應這個變化。

它從當前目錄來處理靜態 assets 文件。若是文件沒有找到引用當前 javascript 的一個 HTML 頁面,那它會自動生成一個。

簡而言之,文件 /webpack/server.js 是使用了 Webpack Dev Server API 的 http 服務:

  1. /webpack/webpack.hot.config.js 配置 webpack 的 asset
  2. 有屢次 json 的響應
  3. 設置 "hot" 爲 true,這樣就能夠 hot module replacement 了。

##JavaScripts

Webpack 處理文件夾 /webpack/assets/javascripts 的這些個方面的問題:

  1. 爲 Rails 或者 Webpack Dev Server 準備 JavaScript 文件"包"。包括執行 jsx 和 es6 loader,將 jsx 和 es6 語法轉換成標準的 javascript。下面是相應的配置:
module.loaders = [{ test: /\.jsx$/, loaders: ["react-hot", "es6", "jsx?harmony"] }]
  1. Webpack 一樣處理你選擇的模塊加載語法(RequireJs, CommonJs, 或者 ES6)。

##Sass 和圖像

對於 Webpack Dev Serve(不是 Rails 版本的 rails-bundle.js),Sass 經過 webpack 加載主要基於兩個緣由:

  1. Webpack 能處理 sass 編譯器。
  2. 任何的 sass 或者 css 文件變化均可以經過 hot module loader 加載到瀏覽器。

文件 /webpack/scripts/webpack_only.jsx 包含下面:

require("test-stylesheet.css");
require("test-sass-stylesheet.scss");

這個 "require" 樣式文件和 "require" JavaScript 同樣。因此 /webpack/index.html 不會引用任何從 Sass 生成的東西。這個文件 webpack_only.jsx 也僅在 webpack.hot.config.js 文件裏面做爲一個"入口點",也就是說它被明確的在 bundle 文件裏面調用了。

Images 就有點搞了,在發佈的時候,出於緩存目的,你但願你的圖片可以加指紋(fingerprint)。對於那些使用新版本的 Rails 的用戶來講,基本上就能夠無視的,固然這要謝謝 Rails 的 asset pipeline 的這個功能。固然 webpack 也能夠 fingerprint 圖片,可是這個不必,由於在咱們的 Railse 發佈環境中根本不須要用到 Webpack 的這個功能。因此咱們只須要在 Webpack Dev Server 裏面訪問一樣的圖片就好。也就是,咱們須要在 scss 文件中可以引用同一張圖片,無論是 Webpack Dev Server 仍是 Rails asset pipeline。

好比說,這裏有一個 sass 代碼的片斷,用來加載 twitter_64.png 圖片,在頂級目錄 /app/assets/images 。這須要在 Rails 的 Asset Pipeline 和 Webpack Dev Serve 都能用。

.twitter-image {
  background-image: image-url('twitter_64.png');
}

問題是怎麼從 Rails 和 Express 版本的服務端的樣式文件裏面拿到同一張圖片,咱們能夠經過 symlink,並且 Git 存儲方便。

  1. /webpack/assets/image 指向 /app/assets/images 的文件夾
  2. image-url sass helper 能夠處理正確的圖片目錄映射關係。圖片目錄在 webpack 服務中經過下面這一行來配置:
module.loaders = [{ test: /.scss$/, loader: 「style!css!sass?outputStyle=expanded&imagePath=/assets/images」}]

而 Rals 裏面的 sass gem 則負責處理 Asset Pipline。

  1. symlink 是必須的,由於 Webpack Dev Server 是不能從根目錄上取到圖片的。

以這種方式,圖片在生產環境中就能夠經過 Rails 的 Asset Pipeline 正確的被獲取到,同時在 Webpack Dev Server 中也能很好的被使用。

##Sourcemaps

當經過 Rails 應用調試 JavaScript 的時候,我不想在巨大的 rails-bundle.js 裏面翻來翻去。Webpack 的 Sourcemap 能解決這個問題。首先我嘗試用純粹的 sourcemaps (分割文件,而不是集成),不過由於一個錯誤致使失敗了。而後,我又玩一些花招把文件移動到正確的地方,也就是 /public/assets。並且你要注意,在發佈到 Heroku 過程當中建立 sourcemap 文件會致使 Heroku 編譯失敗。全部這些動做都在文件 webpack.rails.config.js 的最底下處理。

在 Chrome 中, sourcemap 看起來大概像這樣:

輸入圖片說明

#Heroku 發佈

在 Heroku 上發佈的時候,咱們還須要作一些事情。

  1. 若是在發佈的時候安裝 package.json 中的全部依賴是很是重的,因此只須要安裝 dependencies 而_不是_ devDependencies 。你只須要在本地 Webpack Dev Server 作開發的時候安裝 devDependencies 的工具。
  2. 清理你的編譯緩存:
heroku plugins:install https://github.com/heroku/heroku-repo.git
heroku repo:purge_cache -a <my-app>
  1. 在對 package.jsondependencies 作了任何變動以後,都要確保執行 npm-shrinkwrap
  2. 我須要經過 Webpack 用文件 /lib/tasks/assets.rake 配置 compile_environment 任務來建立 rails-bundle.js
  3. Heroku 須要 node 和 ruby 兩種環境。爲了發佈到 Heroku,你須要執行下面這個命令來設置一個自定義 buildpack:
heroku config:add BUILDPACK_URL=https://github.com/ddollar/heroku-buildpack-multi.git

這會返回兩個 buildpack 在文件 /.buildpacks 中,請查看 ddollar/heroku-buildpack-multi 瞭解更多細節。

###爲何 node_modules 和 package.json 不放在 webpack 目錄

固然把 node=modulespackage.json 放到 /webpack 目錄會更加整潔,可是問題是,由於用了自定義 buildpack,Heroku 會本身再安裝一個 node_modules 出來。

###爲何在 Webpack 下須要第二層 Assets 目錄

起初,我確實是從 /app/assets/javascripts 目錄下引用 JSX 文件的。可是我但願能夠用 WebStorm 工程來管理 JavaScript 代碼。我要麼把 WebStorm 工程放到根目錄,這樣會把全部的 Ruby 目錄都引進來,要麼我用連接到 JavaScript 目錄。你絕對不會想同時運行兩個不一樣的 JetBrains 產品在同一個目錄的,因此,就否決了我在 Rails 應用頂層用 WebStorm 的方案。用 Symlink 方式彷佛能夠達到目的,不過有時候會致使困惑,好比說有些時候我要用 Emacs 來打開 JSX。

把 webpack 打包文件放到 /webpack/assets 目錄這種方式對我來講很管用。Webpack 打包這些文件,而後把生成的 rails-bundle.js 文件放到 /app/assets/javascripts 目錄這看起來很是天然。

一樣,我也把 Webpack 引用的樣式文件都放到了 /webpack 目錄。注意,我經過 Webpack 來加載樣式文件,由於這樣容許熱加載樣式文件到瀏覽器!若是你修改任何在 /webpack/assets/stylesheets 目錄下的文件,你會看到幾乎在你保存的同時,瀏覽器上就會反映出來變化。標準的 Rails 文件 /app/assets/stylesheets/application.css.scss 引用了 /webpack/assets/stylesheets 下面的式樣文件。

###如何添加 NPM(JavaScript) 模塊依賴?

這有點像你用 Gemfile 來添加一個新的 gem 依賴。

  1. 修改你的 /package.json 文件中 dependencies 節相應的行,添加你想要的包。你會但願指定版本,這是 Node 社區強烈建議的。你只要 Google 一下 "npm <啥模塊>" 而後你會獲得這個 npm 包的頁面連接,而後你去看看你要的版本。好比說你想添加一個 marked 依賴,我在 package.json 裏面加上這行:

"marked": "^0.3.2",

  1. 插入模塊引用。好比,導入 marked 包:
var marked = require("marked");

###如何升級 Node 依賴

你準備要升級你的包的時候,你應該會用到下面這一步。導入 npm-check-updatesnpm-shrinkwrap.

cd <top level of your app>
rm -rf node_modules
npm install -g npm-check-updates
npm-check-updates -u
npm install
npm-shrinkwrap

#快速客戶端開發

恭喜!你已經知道了我認爲能夠進行快速開發的 JavaScript 開發的祕密武器了。一旦你安裝好,上面的每一個步驟,那麼像下面這樣:

  1. 運行 Webpack Dev Server 在 3000 端口
cd webpack && node server.js
  1. 在瀏覽器中訪問 http://0.0.0.0:3000
  2. 打開另一個 shell 而後執行
foreman start -f Procfile.dev
  1. 在瀏覽器打開 http://0.0.0.0:4000 來看看你是否能看到 rails-bundle.js 文件。
  2. 更新 /webpack/assets 下面的 jsxscss 文件,而後看看 3000 端口上的瀏覽器所發生的變化。
  3. 建立 JSX 再帶些靜態的數據,而後再試試看從 server.js 把 JSON 發送到客戶端。
  4. 一旦這些都能運做起來,嘗試從 Rails 服務建立 JSON。
  5. 發佈到 Heroku!
  6. 搞定!

#連接

  1. 本文的 Github 倉庫:justin808/react-webpack-rails-tutorial
  2. Heroku 上可用版本: http://react-webpack-rails-tutorial.herokuapp.com/
  3. Storing or Excluding Node Modules in Rails Git Repositories
  4. Pete Hunt 的 introductory guide to getting started with Webpack
相關文章
相關標籤/搜索