Rails 渐进式迁移到现代前端开发模式 — 用 Webpacker 替代 Sprockets 实现前端资源的管理

jasl · March 09, 2019 · Last by SpiderEvgn replied at August 02, 2019 · 8640 hits
Topic has been selected as the excellent topic by the admin.

这是我在 [北京] [2019 年 3 月 9 日] Ruby Saturday 的讲稿

Webpacker 4

Webpacker 是 Rails 官方团队提出的一套在 Rails 上集成 Webpack 的方案

项目动机

  • Webpack 是目前前端社区资源打包工具的主流选择
  • Webpack 很难配置
  • Rails 需要接纳现代的前端技术
  • 需要在视图中引用 Webpack 管理的资源

提供的功能

  • 包装了 webpack-server 监听前端资源的变化,按需编译
  • 提供了一套 Webpack 默认配置,集成了如 file-loader 等常见插件
  • 提供了一组 Rails 视图 Helpers,用于引用 Webpack 管理的资源

从应用中剥离 Sprockets

为什么

慢、不支持最新的 ES 标准、接入现代风格的 NPM 包困难

怎么做

对于新创建的应用,可以使用 --skip-sprockets

对于旧应用,将 config/application.rb 中默认的 require 'rails/all' 修改成

require "rails"

%w(
  active_record/railtie
  active_storage/engine
  action_controller/railtie
  action_view/railtie
  action_mailer/railtie
  active_job/railtie
  action_cable/engine
  action_mailbox/engine
  action_text/engine
  rails/test_unit/railtie
  sprockets/railtie
).each do |railtie|
  begin
    require railtie
  rescue LoadError
  end
end

(实际上就是 rails/all 的源码)

然后删除 sprockets/railtie

搜索 config 目录下,文本搜索所有 assets 关键字,注释或删除

怎样通过 Webpacker 完成 Sprockets 的工作

复用 Rails 的 assets 目录

参考 config/webpacker.yml 的注释,将 resolved_paths: [] 修改成 resolved_paths: ['app/assets']

以此将 Rails 的 app/assets 目录加入到 Webpack 的查找路径

自动构建图片、字体等静态资源

参考 Webpack 文档 Dependency Management 中提到的 require.context 函数,从其文档中摘抄

const importAll = (r) => r.keys().map(r)
importAll(require.context('images', false, /\.(png|jpe?g|svg)$/i));

javascript/application.js 即可

引入样式文件

用 Webpack 提供的 import,如果迁移自 Sprockets,样式文件在 assets/stylesheets 的话,代码形如 import "stylesheets/application"

用 Webpacker 提供的 Helper 替换 Sprockets 的

  • stylesheet_tag => stylesheet_pack_tag
  • javascript_tag => javascript_pack_tag
  • asset_path => asset_pack_path
  • image_tag => image_pack_tag
  • asset_url => asset_pack_url

Webpacker 额外提供了 stylesheet_packs_with_chunks_tag 用于暴露 Webpack 的 SplitChunksPlugin

扩展:禁用 ActionView 对模型验证失败时的 HTML 包装

Rails 会把模型验证失败的字段的 <input><div class="field_with_errors"> 包裹,这会破坏一些前端框架的行为

这个行为是 ActionView::Base.field_error_proc 的默认实现,可以覆盖他屏蔽掉这个行为,参考:

https://github.com/jasl/cybros_core/blob/master/config/initializers/action_view.rb

扩展:增强 Gem 中代码的行为

一个来自官方指南 Rails Guide 的隐藏技巧

https://edgeguides.rubyonrails.org/engines.html#overriding-models-and-controllers

TL;DR

利用 class_eval 特性实现的 Override pattern

应用:为 form helpers 增加当字段验证失败时增加 Bootstrap 渲染表单控件错误所需的 class

参考

https://github.com/jasl/cybros_core/blob/master/app/overrides/action_view/helpers/form_builder_override.rb

这段代码增强了 form helpers 的行为,增加了 class_for_error 参数,当字段验证失败时渲染指定的 class,并且增加了 error_message helper 当字段错误时渲染错误信息。

这样强化过的 form helpers 使得 simple_form 在通常情况下不在需要(当然我在示例中没有做太重的封装),并且让代码保持干爽。

效果形如:https://github.com/jasl/cybros_core/blob/master/app/views/admin/users/_form.html.erb

示例应用

https://github.com/jasl/cybros_core

扩展:集成前端库的坑

存在不能引入的库

一些几年没有更新的库,有可能 import 后为 null 的情况,需要检查这个库是否使用 js 的 export 进行导出,如果不是则无法用在 webpack 中。

jQuery 导出到 window 对象

在 webpack 的 entry 中引入的库,都无法在外部可见,这会影响大多数 jQuery 插件, 因为很多时候我们需要在 HTML 中插入 <script> 所以你需要通过这种方式将 jQuery 导出到 window对象,以便 HTML 中的 <script> 工作正常

window.$ = window.jQuery = require("jquery");

涉及引用静态资源的库

涉及引用静态资源的库,需要做一些处理,才能正确的让 webpack 包含这些静态资源, 比如示例应用中包含了 web font 引入的 Font Awesome,参考

https://github.com/jasl/cybros_core/blob/master/app/assets/stylesheets/application.scss#L5

遇到这种库,需要分析一下其源码,如果其路径不可定制,则可能无法引入到 webpack 中,这里不下定论,需要具体问题具体分析

作为初级前端,我仍然推崇 Turbolinks,因为无需特别处理,即可达到前后端分离的无闪屏页面切换体验,但是,一些新的库会与其冲突。

例子 1:Font Awesome 的 JS 方式引入

Font Awesome 的 JS 引入方式的效果是其 JS 会监视 DOM 变化,发现是图标的标签则将其替换成 SVG(这里不讨论这样设计的意义), 这种方式在 Turbolinks 下,有可能造成闪屏,建议使用传统 Web font 方式引入。

例子 2:React

在 Turbolinks 下直接使用 React 时,页面切换会导致 React 的状态不重置并且重复执行,有一些解决相关问题的库,如 https://github.com/renchap/webpacker-react

不错,以前我们也这么干过,但是研究没有那么深入,就是直接手动集成 Webpacker.

不错,用上了

为什么选择 core ui 呢?

Reply to kikyous

因为我倾向 Gitlab、Github 的传统方式前端和 React/Vue 的混合前端技术栈,传统前端这边我仍然认为 Bootstrap 是生态最好的,并且我调研过一些 React 的库(我的一些项目需要的组件),仍然提供的是 Bootstrap 标准的 CSS(比如 https://github.com/mozilla-services/react-jsonschema-form

此外,我想要一个类似 Gitlab 的带左右分栏的布局,这种方式的布局我觉得最适合后台管理,所以调研过一波,开源且相对质量靠谱的只有 CoreUI,所以做了这样的选择

Reply to kikyous

其实我现场还分享了一些别的观点,我认为 GraphQL 流行后,我就会转换到前后端分离方式去了,并且我会认为在那个时候,Rails 就可以进入历史书了。不过我个人对 GQL 的研究还很初级,暂时就不分享这部分了,现场我也只是说这是我的一部分私货

Reply to jasl

混合前端技术栈我同意,现下应该是比较平衡的一种方式。 但是 CoreUI 一些比较常用的组件都是收费的,比如 select2 基于 bootstrap 的 ui 库还有很多,比如 adminLTE 应该比 CoreUI 名气大吧,而且也提供更多的免费组件

Reply to kikyous

这些插件都开源的你可以自己集成啊,无非就是记得用他们提供 bootstrap 的 css 而已,这就是用 Bootstrap 的好处了,直接换 bootstrap 标准版本的,通常显示效果就是正确的了。

CoreUI 收费就是把他们帮你预先集成好,外加微调了一下样式

Reply to kikyous

另外不用 AdminLTE 的原因是他还停留在 Bootstrap 3,我还是希望尽量用最新版本的组件的,包括将来 Bootstrap 5 要去掉 jQuery 依赖,就算是传统风格的前端架构,也还是要跟上潮流,不然就很难补上了,这才是我标题提到的渐进式迁移到现代前端

Reply to jasl

~是的,可以自己集成,但是用 template 项目就是想一布到位,能偷些懒。 而且可能有更多的人熟悉 adminLTE,毕竟 github start,adminLTE 是 core ui 4 倍差不多~

看一看

Reply to kikyous

一个是 CoreUI 是去年的项目比较新,另一个,Boostrap 3 包括一干老式的 jQuery 插件很多不能直接集成进 Webpack 中,这跟那些维护不周的项目仍在使用老式 JS 模块加载方式有关。

所以这里我做出这个选择,我希望保持传统 Web MVC 的开发体验,同时甩掉过去的包袱,不然大可直接用 Sprockets,没必要迁移到 Webpacker 去了

Reply to kikyous

另外缺失一些有用插件的问题,我觉得既然开源了,完全可以群众贡献,或者我自己项目里需要用集成好的会同步过来

Reply to kikyous

最后就是我在这里提的是示例,那个 repo 里我自己也列了一些 todo 需要我自己做或者有人来帮忙,我一般每一个大 Rails 版本都会维护这么一套架子,方便开新坑,是个长期的事情

最后更新一下集成前端库时候的坑,我前端水平比较菜,如果有什么建议请提出

哈哈 在 medium 也看过一篇 https://medium.com/@coorasse/goodbye-sprockets-welcome-webpacker-3-0-ff877fb8fa79

只不过 rails 6 就默认 webpacker 了

Reply to jicheng1014

其实这帖子的事情,前年我已经在 Webpacker 2 上走一遍了,但是犯懒没发,代码也没公开,然后 3 出了,又重新踩了一遍,Webpacker 2 to 3 的踩坑经历还有引入一些库时候的无聊研究真的是血泪史...

ruby 已经没落了😭

挺好,不过目前的项目中是两个工具混着用。Vue 相关的用 webpacker 打包,其他的样式相关的还有 JQuery 的就走原来的 sprockets

Reply to lanzhiheng

Gitlab 也是这样做的

感觉直接用 webpack 打包上传到 CDN 也不错,配置稍微麻烦点

Reply to heroyct

Webpacker 也可以设置 CDN。

如果你要在后端渲染的视图里引入 Webpack 管理的资源的话,要引入带 digest 的文件名,这个独立使用 Webpack 会不会有问题?

@jasl 有的,需要自己加个 helper,从 webpack 的 manifest 文件 (webpack-assets.json) 里面读取带 digest 的文件名 (webpacker 好像自带)

Reply to heroyct

对嘛... 所以除非有啥特殊要求,还是直接 Webpacker 方便

Reply to jasl

我之前是懒得折腾,因为 sass 文件里面有些变量需要通过 sprocket 才能编译过。不知道现在 webpack 支持没有,考虑到迁移成本(加上自己比较懒)所以一直没有把所有东西都统一放到 webpack 那边去。又因为有些单页面的样式跟一些原生组件的样式都是公用的就干脆把所有样式都放到 sprocket 上去打包了。

Reply to lanzhiheng

你说的变量指?如果是 Ruby 的变量最好还是别用 Webpacker,那个 erb-loader 性能不佳。

新项目可以考虑下,老项目迁移还是要考虑成本的,另外 sass 的 ruby 实现这个版本终止维护了,要迁移到 sassc-rails 去

了解 webpack 配置的过程中真感觉 js 社区的设计理念有不少问题(配置优于约定),但前端社区的工具都是围绕这一套来发展的,新项目最好还是迁移到 webpacker。遗留项目没遇到什么问题就不需要迁移了。

Reply to jasl

https://stackoverflow.com/questions/8334573/asset-path-in-scss-file-rails 类似这种吧。这种需走 sprocket,迁移起来有点麻烦。

huacnlee mark as excellent topic. 25 Mar 10:45

使用这种方法本地 dev 模式,css 是被打包到 js 里的,每次刷新页面,样式都会经过从无到有的闪动,有同学遇到和我一样的问题吗?

Reply to jonnoj

初始状态没有 css 是吧,这个等我弄完手头的会的事情完善下

Reply to jasl

是的,谢谢

Reply to Rei

我記得 Webpack 4 要推行 0cjs 了?應該就是「約定優於配置」的另一種說法。

Reply to franklinyu

Webpacker 已经预设了很多配置,所以我不太了解 webpack 本身的配置。 😀

Reply to franklinyu

这个他们是这么说的,不过跟 Webpacker 项目的目标是不同的

Reply to jasl

这个问题的原因是啥?如何解决的?

Reply to SpiderEvgn

https://github.com/jasl/cybros_core/blob/master/config/webpacker.yml#L20

把 Webpacker 默认的 false 改成 true 就行了,原因是 CSS 是被 JS 加载的...那 JS 没加载的时候,自然就没样式了,但为啥这样做,得前端大佬来解释了...

哦~对,多谢大佬

jasl in rails 6 boostrap 尝鲜 mention this topic. 28 Aug 17:08
jasl in 6.1 是不是无法完全脱离 assets ? mention this topic. 20 Jan 20:04
You need to Sign in before reply, if you don't have an account, please Sign up first.