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 发送到客户端。#连接