Rails Rails 5.1 使用 yarn 和 webpack 实战 (vue, 构建等)

reducm · 发布于 2017年04月30日 · 最后由 reducm 回复于 2017年08月11日 · 6806 次阅读
465
本帖已被设为精华帖!

之前就有听到Rails 5.1会迎来大更新, 主题是Loving Javascript,并会迎来yarn和webpack,目前前端小伙伴们最强大的工具。相信稍有关注前端发展的同学,均听过当今前端圈子里流行的种种名词: es6 es7 vue react react-native cs-modules等等, 因为node的出现,良好的包管理、服务端渲染等等新特性,另得前端的同学们写的代码在执行前有了更多的构建方式和选择,前端发展空前繁荣。然而,作为走在web开发提供最佳实践的Rails,直到5.0的版本,还是用着3.1版本时出现assets pipeline作为前端文件包管理,虽然有不少贡献者提供诸如browserify-rails vue-rails等等的gem,企图为Rails带来多少便利,但仍难改变Gem方式管理前端库的更新不及时、es6/es7新特性难支持,构建不灵便等缺陷。

我司最近其中两个项目霸屏电商,一个用到vue,一个是要求用react,正是前端交互均比较复杂的例子,使用webpack去做构建都是必须的。公司前端的小伙新技术玩得很溜,但这两个项目在开发期需要到服务端很多的交互数据去调试,最初后端和前端拆分开在两个项目里写,后来项目时间的原因后端的同学也加入到写前端代码,写完后后端同学拖代码再各种合并部署到staging测试,中间出现bug调试再上线再测试,浪费了不少时间,最后不得已地又把前端代码移到rails,又花了不少时间做项目构建, 手动把webpack和Rails前端管理体系结合,什么编译完后各种跑各种复制啊,前端里也要做环境区分啊,苦不堪言。

现在Rails 5.1出来后,一切都过去了!yarn解决了js之前模块交叉依赖、版本混乱和重复加载的问题,强大的构建工具webpack,能帮助你处理文件依赖,按需加载,还有强大的webpack-dev-server,你所能想到当前大部分前端构建和优化的问题,它有很好的解决方案。并且,它俩现在也能跟Rails很好的结合在一起了!

当然引入新的解决方案,也会带来新的问题。例如es6的语法,webpack的配置等,都会带来新的学习成本。你可能会说为啥前端要搞得那么复杂吧,但他们也是切切实实解决了很多实际问题,优化了过去很多的不足。本文不会去深入讲webpack es6等前端范畴的技术细节,webpack es6 react等,推荐大家可以去看看阮一峰老师的各种教程,深入浅出。文章引用了官方Webpacker gem, 本人水平有限,有问题请大家及时指出。

Rails 5.1 配置Webpack

Rails5.1新项目使要使用webpack,只需要执行

gem install rails -v '5.1'
rails new [your_project] --webpack
#也可以 rails new [your_project] --webpack=react 直接装上react

楼主是从旧项目升级,新开个rails5的git分支,项目是从4.2升级到5.1, 修改Gemfile

gem 'rails', '5.1'
gem 'webpacker'
  1. 执行bundle update rails,可能在运行的过程中会遇到一些rails 4.2时例如sass-rails, coffee-rails, rspec-rails, jbuilder之类的依赖问题,我是比较粗暴地去掉版本号跑过。跑完后,运行rails webpacker会发现Rails已经多了自带的各种webpack命令。

    jasav% rails webpacker
    Available webpacker tasks are:
    webpacker:install             
    webpacker:compile             
    webpacker:check_node          
    webpacker:check_yarn          
    webpacker:verify_install      
    webpacker:yarn_install        
    webpacker:install:react       
    webpacker:install:vue         
    webpacker:install:angular
    
  2. 执行rails webpacker:check_yarn,项目根目录便会多了一个package.jsonyarn.lock文件,熟悉node的同学知道,这是npm和yarn会用到的包管理文件,里面还可以添加一些执行命令例如scripts等

  3. 执行rails webpacker:install, 项目新建一个文件夹config/webpack,里面包含了开发环境、开发环境webpack服务器和生产环境等设置(对了, 记得在项目的.gitignore里面加入node_modules,不然会把开发环境里的安装包都提交到项目代码里了)。同时,在app目录下多了个javascript目录(注意不是app/assets/javascripts),里面还有个packs的目录,用了webpacker后,以后各种前端文件就写在这里了,区别于以前的app/assets目录。觉得config/webpack配置比较多,简单说说每个文件的作用

 webpack
   loaders  //各种文件预处理的模块,例如vue,coffee,assets等,下面的shared.js会require到webpack的module里头
    configuration.js //主要是加载paths.yml文件,定义好项目路径,包括还指定了CDN前缀
    development.js //开发环境配置文件,看到里头合并了shared.js, 还有用到sourcemap
    development.server.js //开发环境下,webpack-dev-server的运行配置文件
    development.server.yml //webpack-dev-server的host,port配置文件
    paths.yml //目录路径配置文件,例如制定了source是app/javascript
    production.js //生产环境配置文件
    shared.js //顾名思义叫shared.js,会被每个环境都合进去的一个主配置文件,例如要添加各种loader module, 或者添加plugin,都写在这里
    test.js //测试环境的配置文件

开发环境下使用Webpack

了解完webpack初始化后执行了的操作和生成的目录结构,下面我们来试试在开发环境里跑跑vue

  1. 上面我们看到,rails 加了直接安装vue的task, 执行

    rails webpacker:install:vue
    

    执行后我们会发现先跑了yarn add的安装指令,随后package.json文件多了vue、vue-loader、vue-template-compiler几个库,同时还贴心地在app/javascript/packs里加了两个文件: app.vue和hello_vue.js

  2. 接下来把js引入到我们的页面,在assets pipeline年代,我们写好了css和js后,会在例如layouts/application.html.erb加入<%= stylesheet_link_tag 'xxx' %>或者<%= javascript_include_tag 'xxx' %>。在webpack年代,做法也是类似,我们可以在layouts/application.html.erb 用以下标签引入静态文件:

    #这是这个示例的
    <%= javascript_pack_tag 'hello_vue' %>
    #也举举官方的例子, 例如你有如下入口
    // app/javascript/packs/calendar.js
    require('calendar')
    #例如你有如下的目录结构
    app/javascript/calendar/index.js // gets loaded by require('calendar'),自动会读引入目录下的index.js
    app/javascript/calendar/components/grid.jsx
    app/javascript/calendar/styles/grid.sass
    app/javascript/calendar/models/month.js     
    <%# app/views/layouts/application.html.erb %>
    <%= javascript_pack_tag 'calendar' %>
    <%= stylesheet_pack_tag 'calendar' %>
    通过Rails的xxxx_pack_tag helper,你就可以加载好对应的静态文件
    
  3. 接下来要讲讲的是开发环境下启动项目的变化。以前我们在开发环境下一般跑rails s就够了,如果用到一些sidekiq、solr之类的库,也会多开一个窗口跑其他的进程。Rails 5.1在引入webpack后,在开发时还要启动

    bin/webpack-dev-server 
    

    听上去多开个窗口很麻烦(用tmux, 谁用谁知道),但webpack-dev-server除了增量构建、livereload外,还有 Hot Module Replacement等好处。另外,楼主之前在用默认配置跑webpack-dev-server时,见到静态文件加载编译完日志一切正常,但用localhost发现会有请求打不进webpack的问题,只要修改config/webpack/development.server.yml的host为127.0.0.1就好,这时Rails的xxx_pack_tag helper也会自动修改host。 ​

    hello_vue.js代码很简单,引入app.vue,然后在html body上显示个大大的"Vue"。这时我们访问http://localhost:3000就可以看到结果了。

  4. 这时我们随便改改app.vue里面的文字或css,也可以立刻见到webpack-dev-server检测到文件变动,之后页面会自动更新,cool!

其他静态文件的引入

webpack另外一个带来的好处是可以在js代码直接引入图片/svg之类的静态文件,并处理运行环境时的依赖关系,这里直接引用官方gem的例子

// React component example
// app/javascripts/packs/hello_react.jsx
import React from 'react'
import ReactDOM from 'react-dom'
import helloIcon from '../hello_react/images/icon.png'
import '../hello_react/styles/hello-react.sass'

const Hello = props => (
  <div className="hello-react">
    <img src={helloIcon} alt="hello-icon" />
    <p>Hello {props.name}!</p>
  </div>
)

webpack遇到js文件里引入到静态文件或图片,会自动把依赖到的文件打包到output(public/packs),所以要注意,上面的文件例如引入了css文件,除了js文件外,我们在页面也要include css进来。

<%= stylesheet_pack_tag 'hello_react' %>

获取webpack静态文件的asset_pack_path helper

过去assets pipeline年代,我们可以在view里使用asset_pathimage_tag等去调用assets里面的静态文件,又或者可以在scss里使用image-url等helper,Rails会帮我们处理不同环境下对这些静态文件的引用路径问题。

现在webpacker也提供了一个asset_pack_path的helper,方便我们在不同地方引用javascirpt/pack下的静态文件,

<%= asset_pack_path 'hello_react.css' %>
<% # => "/packs/hello_react.css" %>
<img src="<%= asset_pack_path 'calendar.png' %>" />
<% # => <img src="/packs/calendar.png" /> %>

那如果我们想在webpack管理的文件里,引入过去assets pipeline(sprockets assets)里的静态文件呢? 得以于rails-erb-loader(loaders里),我们只要给js文件加个erb后缀,就可以通过helper引用回app/assets里的静态文件

// app/javascript/my_pack/example.js.erb

<% helpers = ActionController::Base.helpers %>
var railsImagePath = "<%= helpers.image_path('rails.png') %>";

生产环境deploy webpack打包

什么?原来的assets:precompile会自动跑新的taskswebpacker:compile?会自动处理页面xxxx_pack_tag引入的打包文件? webpack的production环境还会自动读取配好的cdn host? 那基本上如果你的cdn不是用回源而是用华顺ruby-china上传又拍云的方式,只需上传脚本多加个public/packs的文件夹就好了...

更新

开发环境中,例如加载字体文件遇到CORS的跨域问题解决方法

//development.server.js里头加入:
headers: {
    'Access-Control-Allow-Origin': '*'
}

加入Staging环境时, CDN HOST的问题

开发中常见我们会加入预生产环境staging, 如果staging环境也需要使用到cdn host, webpacker里的configuration.js需要做如下的修改

const ifHasCDN = env.ASSET_HOST !== undefined && ( env.NODE_ENV === 'production' || env.NODE_ENV === "staging")

const devServerUrl = `http://${devServer.host}:${devServer.port}/${paths.entry}/`

const publicUrl = ifHasCDN ? `${env.ASSET_HOST}/${paths.entry}/` : `/${paths.entry}/`

const publicPath = ( env.NODE_ENV !== 'production' && env.NODE_ENV !== "staging")  ? devServerUrl : publicUrl
共收到 31 条回复
1107

所以如果用 capistrano 或者 mina 的话,不需要对 webpacker 做任何处理么?我还没尝试新的体系,但是很奇怪搜 capistrano webpacker 搜不到相关的内容 mina webpacker 倒是有 PR

4375
1107jasl 回复

capistrano 实测是不用的,跟以前一样跑 assets:precompile就行了。但是会有一个 yarn install deps 的过程,慢得飞起,而把 node_modules 设置为 link_dir 会出问题。😂

1
1107jasl 回复

https://github.com/rails/webpacker#deployment

Webpacker hooks up a new webpacker:compile task to assets:precompile, which gets run whenever you run assets:precompile.

14174
1107jasl 回复

内存小的机器小心内存炸,webpack babel 编译还是很吃内存的

3547

是不是使用webpack之后,全部的js都通过yarn去安装了,不能混着以前的gem一起用?

465
3547QueXuQ 回复

可以的,原来的assets pipeline还在

De6df3 huacnlee 将本帖设为了精华贴 05月03日 11:51
5774

👍期待下集可以顺便讲讲和turbolinks的和谐相处~~

4366
14174imwildcat 回复

改改估计应该也可以支持 webpack compile assets local with capistrano

465
5774xofred2 回复

当然可以的,引入webpack和yarn,复杂的部分只是在于开发和构建上,结果出来都是在页面引用JS而已。只要处理好assets pipeline和pack出来的静态文件引用顺序,两者是可以并存的。

另外,打开webpacker的源码,找到javascript_pack_tag, 实际上也是调用了javascript_include_tag


  def javascript_pack_tag(name, **options)
    javascript_include_tag(Webpacker::Manifest.lookup("#{name}#{compute_asset_extname(name, type: :javascript)}"), **options) #从生成出来的manifest.json找到对应静态文件编译后的文件名
  end

```

```json
//manifest.json example
{
  "app.js": "/packs/app-c34667aca39442f786a0.js",
  "application.js": "/packs/application-912810e12140b29375d2.js",
  "page/Actor.js": "/packs/page/Actor-81e43cb17759d171104e.js",
  "page/Actors.js": "/packs/page/Actors-638afba3889927791378.js",
  "page/Movie.js": "/packs/page/Movie-c4cdf69886b211c14574.js",
  "page/Movies.js": "/packs/page/Movies-f04878a089ecec487810.js",
  "page/Search.js": "/packs/page/Search-f02e3e7031931ef876b9.js",
  "page/Tag.js": "/packs/page/Tag-75d6da5528f200354587.js",
  "page/Tags.js": "/packs/page/Tags-8aa0b6c7cc811618c51f.js",
  "router/index.js": "/packs/router/index-10afda1a26fd89c89880.js",
  "store/action_types.js": "/packs/store/action_types-aa75ce66ee2e3a622d51.js",
  "store/index.js": "/packs/store/index-5389b0582084ca0e4751.js",
  "store/module/actor.js": "/packs/store/module/actor-b67a50e248408fa8fa6a.js",
  "store/module/movie.js": "/packs/store/module/movie-20441ad04b207c3337cc.js",
  "store/module/search.js": "/packs/store/module/search-c7cae47a3a1058a7a6b5.js",
  "store/module/tag.js": "/packs/store/module/tag-8952d2f3ce45bf2fd3f7.js",
  "store/mutation_types.js": "/packs/store/mutation_types-e74d876aaaabbe43d43f.js"
}

所以webpack的静态文件要使用turbolink,在页面里这样用就行

<%= javascript_pack_tag "application", :"data-turbolinks-track" => true %>
6864
4375saiga 回复

我把 node_modules 加入 linked_dirs,没遇到什么问题。

LINKED_DIRS = %w(
  data
  log
  tmp/pids tmp/cache tmp/sockets
  vendor/assets/bower_components
  node_modules
)
set :linked_dirs, fetch(:linked_dirs, []).push(*LINKED_DIRS)
1770
4375saiga 回复

我在link node_modules也出现问题了,排查是postcss导致的,需要在webpack的配置项 plugins 中加入如下配置

new webpack.LoaderOptionsPlugin({ options: { postcss: {} } }) 

@yuhaidonghd 没问题估计是大家依赖的组件版本不一样

Fd5e6f

"阮一峰老师的各种教程" ✋

官方文档 👌

8898

用了 rails5 + wepack 做 docker 镜像的时候,与之前不用 webpack 有什么区别吗

96

为什么不前后端分开,前端去做那些,rails只写API呢。

将来app开发也很容易调用API

1164
32anklos 回复

总有不需要这么干的时候的嘛。

2aeb4e

webpack 这玩意给我的感觉就像以前写 Makefile 一样,一坨翔

3444
1107jasl 回复

capistrano如果不想每次编译的话需要对assets path稍微改一下,我是直接把pack打包到public/assets里面了

96

可以用foreman这个gem一次启动rails s和webpack dev server。不过默认的web端口会变成5000。

465

更新了开发中遇到的一些小问题

22楼 已删除
23224

写的已经够详细,可是不是很清楚怎么在项目中引入 bootstrap或者 semantic-ui. 可否提供点思路?

23956
23224helapu 回复

下载semantic-ui的js,css文件到vendor,然后在application.css和application.js中require。

23224
23956fzzf0618 回复

现在项目不是已经用webpack了么 可否有适合webpack的方式呢

96
32anklos 回复

我也是這麼想,正在試驗這種想法。前後端代碼分離的話,前端可以有效利用 CDN 和緩存。

8042

👍马上试试

15208

楼主你好,最近升级项目,之前Rails5.0.0.1的时候为了用上ES6的特性,用了babel-transpiler和browserfily-rails,现在升级到5.1直接用上了webpack。本地开发的时候非常顺利,但是最后部署到服务器上的时候貌似rails的assets pipeline最后还是会把所有的assets打包成一个application.js文件,而且webpack使用的是uglifierJS最近的版本似乎不支持es6里面的某些语法,导致部署失败。

请问楼主有遇到过这个情况吗?

96
15208naivecai 回复

我遇到了,解法:新建 .babelrc

{
  "presets": [
    [
      "env",
      {
        "modules": false,
        "targets": {
          "uglify": true
        }
      }
    ]
  ],
  "plugins": [
    "transform-object-rest-spread"
  ]
}

plugins 可选。

15208
32FrankFang 回复

谢谢!回去试试。

但是还是不甚理解,为什么一定要写.babelrc而不是在config/webpack/production.js里面对webpack进行配置呢?是因为需要使用babel来翻译成ES5的缘故吗?

96
15208naivecai 回复

JS 社区喜欢一个工具一个 rc ……

8042

我这的前端开发给了点反馈,在Rails项目中打包出来的JS比独立前端项目打包的JS大了很多(2-3倍), webpack-dev-server会莫名的报nodejs内存溢出。 所以,我这最后还是拆成前后端各一个项目来做了

465
8042oldfritter 回复

Rails里的webpack默认配置文件shared.js,是会把packs文件夹下根目录js文件都当作entry打包,会导致不必要的entry重复打包的问题。这里可以手动指定一下自己的entry,这样会大幅减少文件体积和提高打包速度。

example:

module.exports = {
  //手动指定两个入口
  entry: {
    'main': ["./app/javascript/packs/main"],
    'operate/main': ["./app/javascript/packs/operate/main"]
  },
  //下面是原来的配置
  //packPaths.reduce( 
    //(map, entry) => {
      //const localMap = map
      //const namespace = relative(join(paths.source, paths.entry), dirname(entry))
      //localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry)
      //return localMap
    //}, {}
  //),

  output: {
    filename: '[name].js',
    path: resolve(paths.output, paths.entry),
    publicPath
  },
........
}

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册