Fast Rich Client Rails Development With Webpack and the ES6 Transpilerjavascript
有更好的方式把 JavaScript 生態引入 Rails。css
你有沒有:html
那你應該怎麼在你的 Rails 工程中,實現如下這些呢:java
package.json
指定依賴,而後執行 npm install
,而後就能夠在 JavaScript 文件裏面很容易的就導入模塊。這篇文章將會告訴你如何在 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-transpiler 和 es6-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 generator 用 react-rails
gem。而後我想把 react-bootstrap 用上去。這就沒有 gem 了,我考慮過手工拷貝粘貼到個人 /vendor/assets/javascripts
目錄,可是這讓我以爲很是_不爽_,緣由以下:
(注: 對 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":
開始學習 Webpack 的一個好去處是 Pete Hunt 的 webpack-howto。
我最初的時候想用 webpack module bundler 把 JS 從 Rails 分離出來,由於我想看到所謂的 react 代碼能夠"熱重載"。你能夠試試看這寫簡單的代碼: react-tutorial-hot。Hot module Replacement會在運行時刷新 JS 代碼(以及 CSS)而不須要任何的頁面刷新。甚至是 JS 對象裏面全部數據!這種技術比 Live Reload,要刷新整個頁面要酷得多。
而後我就開始使用 Webpack 的這些功能了:
因爲 Webpack 生成的 "bundle" 並不必定是被壓縮過的,因此看起來應該能夠被 Rails 的 asset pipeline 直接拿來用,果真,它能夠!在這篇文章裏面解釋得很清楚: Setting Up Webpack with Rails 以及例子代碼解釋如何預編譯 Webpack: Webpack In The Rails Asset Pipeline。
有了基礎部分,我開始想要實現如下功能:
個人解決案放在了 github repo: justin808/react-webpack-rails-tutorial。這是基於我使用 react-rails
gem 的例子: Rails 4.2, React, completed tutorial。接下來我會詳細的解釋一下這個工程。
#安裝
你須要安裝 Node.js。我假設你已經有 Ruby 和 Rails 了。
Node.js: 你能夠在這裏找到 Node.js 的下載文件。嗯,個人一些朋友建議我直接用 Node.js 的安裝包,而不是用 Brew。我還沒試過 Brew。
許多文章建議執行下面的命令,這樣你就不須要在 node 命令行前用 sudo,因此,你本身更改你的 /usr/local 目錄權限吧。
$ sudo chown -R $USER /usr/local
你的 /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,用瞭如下兩種方式:
/webpack
目錄,這樣可使用 Webpack Dev Server 來提供客戶端 Javascript 原型快速開發工具。 webpack.hot.config.js
用來配置 Webpack Dev Server 用的 JS 和 CSS assets。/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_modules 和 package.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 是由於如下理由:
設置 webpack.config
文件,根據需求咱們要有兩個版本,Webpack Dev Server 和 Asset Pipeline。
修改 webpack.config
你也許會想要不要修改這個 webpack config 文件。那麼下面有些你要注意的點。
/app/assets/javascripts/application.js
,可是在這裏你須要指定的僅僅是entry點。因此若是你指定了 ./assets/javascripts/example
(你不須要文件後綴)做爲入口點,那麼你就不須要而且不要再指定 ./assets/javascripts/CommentBox
這樣的做爲入口點了。再次聲明,這個依賴是爲 Webpack 作的,而不是 Rails。module.exports = { context: __dirname, entry: [ "./assets/javascripts/example" ],
module.exports.externals: { jquery: "var 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 服務:
/webpack/webpack.hot.config.js
配置 webpack 的 asset##JavaScripts
Webpack 處理文件夾 /webpack/assets/javascripts
的這些個方面的問題:
module.loaders = [{ test: /\.jsx$/, loaders: ["react-hot", "es6", "jsx?harmony"] }]
##Sass 和圖像
對於 Webpack Dev Serve(不是 Rails 版本的 rails-bundle.js),Sass 經過 webpack 加載主要基於兩個緣由:
文件 /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 存儲方便。
/webpack/assets/image
指向 /app/assets/images
的文件夾image-url
sass helper 能夠處理正確的圖片目錄映射關係。圖片目錄在 webpack 服務中經過下面這一行來配置:module.loaders = [{ test: /.scss$/, loader: 「style!css!sass?outputStyle=expanded&imagePath=/assets/images」}]
而 Rals 裏面的 sass gem 則負責處理 Asset Pipline。
以這種方式,圖片在生產環境中就能夠經過 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 上發佈的時候,咱們還須要作一些事情。
package.json
中的全部依賴是很是重的,因此只須要安裝 dependencies
而_不是_ devDependencies
。你只須要在本地 Webpack Dev Server 作開發的時候安裝 devDependencies
的工具。heroku plugins:install https://github.com/heroku/heroku-repo.git heroku repo:purge_cache -a <my-app>
package.json
的 dependencies
作了任何變動以後,都要確保執行 npm-shrinkwrap
/lib/tasks/assets.rake
配置 compile_environment
任務來建立 rails-bundle.js
。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=modules
和 package.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 依賴。
/package.json
文件中 dependencies
節相應的行,添加你想要的包。你會但願指定版本,這是 Node 社區強烈建議的。你只要 Google 一下 "npm <啥模塊>" 而後你會獲得這個 npm 包的頁面連接,而後你去看看你要的版本。好比說你想添加一個 marked
依賴,我在 package.json
裏面加上這行:"marked": "^0.3.2",
marked
包:var marked = require("marked");
###如何升級 Node 依賴
你準備要升級你的包的時候,你應該會用到下面這一步。導入 npm-check-updates 和 npm-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 開發的祕密武器了。一旦你安裝好,上面的每一個步驟,那麼像下面這樣:
cd webpack && node server.js
foreman start -f Procfile.dev
/webpack/assets
下面的 jsx
和 scss
文件,而後看看 3000 端口上的瀏覽器所發生的變化。server.js
把 JSON 發送到客戶端。#連接