-
-
Notifications
You must be signed in to change notification settings - Fork 8
2022_01 Actualización a rails 7
Rails 7 da nuevas posibilidades para integración con Javascript al lado del cliente, sin embargo son muchas opciones mutuamente excluyentes por lo que experimentamos con:
- Alejarnos de webpacker porque requiere bastantes recursos computacionales, su configuración es compleja y emplea infraestructura de node.js con problemas (ver https://www.youtube.com/watch?v=M3BM9TB-8yA) y en su lugar probar con:
- Importmaps
-
jsbundling-rails
yesbuild
-
mrujs
como remplazo a@rails/ujs
(a futuro sería interesante experimentar conStimulusReflex
) - Módulos y más ES6 tanto en lo manejado por
jsbundling-rails
como en lo manejado por sprockets - Turbo y Stimulus
- Seguir distribuyendo javascript de motores a aplicaciones mediante sprockets, pues consideramos simplifica respecto a publicar paquetes npm. Publicar paquetes npm sigue siendo ruta interesante cuando refactoricemos librerías javascript con más utilidad que la requerida sólo por nuestros motores y aplicaciones.
- Pruebas al sistema con
cuprite
- Importmaps: es un objetivo a largo plazo interesante, pero requiere bastante esfuerzo en cambios, pues no soporta coffeescript y la mayoría de motores usan este lenguaje. Ver https://github.com/pasosdeJesus/sivel2_gen/issues/573
- En lugar de webpacker, encontramos viable el transito y uso de
jsbundling-rails
conesbuild
--posteriormente encontramos esa recomendación en https://noelrappin.com/blog/2021/09/rails-7-and-javascript/ -
mrujs
: aunque es interesante y se avanzó por el momento descartamos su uso (ver https://github.com/pasosdeJesus/sivel2_gen/issues/647) - Turbo: Nos parece viable y puede habilitarse en todo motor y aplicación de manera cauta (mayormente deshabilitado) e ir adoptando más paulatinamente
- stimulus: viable incluirlo de manera pre-determinada e ir migrando gradualmente.
- Módulos y ES6: resultó posible usarlos con sprockets 4,
babel-transpiler
y emulando minimamente CommonJS, junto con configuraciones para generar mapas usables por navegador. Como se compilan primero los módulos deapp/javascript
y despues los deapp/assets/javascripts
, es posible enapp/assets/javascripts
usar lo deapp/javascript
empleando el objeto global window (por eso enapp/javascript/application.rb
se hace por ejemplowindows.Rails = Rails
). - Continuar usando sprockets unificando en
app/assets/javascripts/recursos_sprockets.js
, es importante para:- Migrar paulatinamente. Es decir seguir usando tanto como sea posible lo que se ha legado en Javascript (incluyendo lo que hay en CoffeeScript y jQuery).
- Ir organizando paulatinamente como módulos y cuando se requiera refactorizando en paquete npm
- Debe cambiarse la inicialización en javascript para centralizarla en
app/javascript/application.rb
sin depender de eventos comoDOMContentLoaded
,load
(que son alterados por Turbo) niturbo:load
como recomienda la documentación de Turbo, pues encontramos que puede dispararse varias veces.
- cuprite: vuelve a hacer viables pruebas al sistema con minitest y capybara.
Es un transito difícil y puede toparse con fallas en rails7 o en gemas que se usen. Por ejemplo encontramos de la manera dura que la gema meta-request
(al menos hasta su versión 0.7.3) es incompatible con rails7 (ver https://github.com/rails/rails/issues/44157). El soporte de la comunidad rails ha sido efectivo.
- En lo existente pasar a rails7 con las opciones elegidas tras experimentación
- Cambiar
webpacker
porjsbundling-rails
+esbuild
(que es efectivamente rápido y liviano) - Agregar gema y paquete
turbo-rails
y quitarturbolinks
pasando eventos y atributos deturbolinks
aturbo
- Agregar gema
stimulus-rails
- Usar
gridstack
como módulo (y no mediante sprockets) - Reconfigurar javascript centralizando lo manejado por esbuild en
app/javascript/application.js
y lo manejado por sprockets enapp/assets/javascripts/recursos_sprockets.js
- El envío automático de formularios con remote=true ya no soporta format.script en controlador. En controladores cambiar uso de format.js (que sólo funcionaría con jQuery) respondiendo con HTML y con javascript remplazar
- Hemos notado que rails7 es más exigente con el token CSRF en las peticiones AJAX, es fácil de agregar como encabezado en peticiones jQuery
$.ajax y como parte de los parámetros en $ .post. - Cambiar inicialización. Los eventos
DOMContentLoaded
yloaded
no operan como es típico debido a Turbo. Turbo provee el eventoturbo:load
pero la documentación sugiere no usarlo. Hemos notado que suele dispararse varias veces. Lo que deba cargarse una sola vez para toda la aplicación dejarlo enapp/javascript/application.js
si es el caso dentro del bloque que se ejecuta tras la carga completa de los recursos sprockets y la presentación del documento. Lo que deba ejecutarse cada vez que turbo cambie página asegurar que no falla al ejecutarse varias veces consecutivas y dejarlo en un escuchador deturbo:load
preferiblemente el deapp/javascript/application.js
- Agregar gema
cuprite
y al menos una prueba al sistema de ingreso
- En lo nuevo que se haga:
- Evitar jQuery y coffescript
- En el caso de aplicaciones agregar funcionalidad en
app/javascript
de una vez como módulo. - En el caso de motores agregar nuevas funcionalidad como módulo y con sintaxis y opciones moderna (e.g promesas) en
app/javascript/*.es6
cargando e inicializando en función*eventos_comunes*
deapp/javascript/mimotor/motor.js
para facilitar futuro paso aapp/javascript
o empaquetamiento npm (como se ha hecho con autocompleta_ajax ).
-
Quitar la dependencia de
jQuery
que tiene como pre-requisitos cambiar los plugins y las funcionalidades de jQuery:- 2.1 Cambiar control de autocompletación y quitar
jquery-ui
. No encontramos un remplazo, aunque se ha experimentando con éxito con un control implementado desde ceros con el elementodatalist
en HTML5. Ver autocompleta_ajax. - 2.2 Cambiar uso de
cocoon
que depende de jquery. Para esto se ha empezado a experimentar con stimulus+turbo - 2.3 Cambiar uso de
chosen
en diversos cuadros de selección desdesip
. No hemos encontrado un reemplazo equivalente en Javascript puro, chosen no ofrece un camino de cambio, ver https://github.com/harvesthq/chosen/issues/3118 y por el contrario hay paquetes npm para react y vue que lo usan (con todo y jquery). Se trata de un paquete de 208K con fuentes Javascript para jQuery de 46K sin más dependencias. - 2.4 Cambiar uso de
gridstack
desdemr519_gen
. Encontramos que las versiones recientes posteriores a 1.0.0 no requierenjQuery
por lo que se propone actualizar a la versión más reciente. - 2.5 Cambiar
$.ajax
,$.post
a corto plazo porRails.ajax
pero preferible porwindow.fetch
--que es más complejo de lo esperado, pudiendo requerir cambios en controladores porque fetch no soporta el método js (que permitía al controlador enviar un javascript que era ejecutado en el cliente), llamando más al envio de HTML.
- 2.1 Cambiar control de autocompletación y quitar
-
Convertir coffeescript a Javascript
-
A mediano o largo plazo emplear importmaps y componentes Web (estos componentes son parte del estándar HTML reciente y buscan suplir lo que hacen librerías como react, que posiblemente hará obsoleto react así como previas novedades en HTML han hecho obsoleto jquery).
Para rediseñar la inicialización nos ha servido repasar conceptos de ES6, que refraseamos de Javascript Definitive Guide edición 7: En los módulos, el alcance de las declaraciones de nivel superior (top level o que están fuera de funciones o clases en el archivo) es el módulo mismo, lo que se requiera fuera del módulo debe exportarse explicitamente. Sin embargo, en los scripts que no son módulos, el alcance de las declaraciones de nivel superior es el documento completo, y las declaraciones son compartidas por todos los scripts en el documento. Las declaraciones hechas con function
y var
(al estilo antiguo) se comparten a través de propiedades del objeto global (window
). Las declaraciones hechas con const
, let
y class
también se comparten y tienen el mismo alcance del documento, pero no existen como propiedades del objeto global ni de objeto alguno al cual el código Javascript tenga acceso.
La inicialización de lo escrito en Javascript comienza en app/javascript/application.rb
donde preferimos usar la sintaxis moderna y modular de javascript con el espacio de nombres global. Los recursos manejados por sprockets tipicamente estarán en el objeto global y los cargamos después de cargar lo de app/javascript/application.rb
(difiriendo la ejecución de ambos tras la carga del HTML completo). Ver el detalle de la inicialización propuesta, más adelante, con las modificaciones propuestas a app/javascript/application.rb
Nos ha resultado útil dividir el transito en dos grandes partes que llamamos al lado del servidor y al lado del cliente.
-
Emplear rama
rails7esjs
-
Gemas: En
Gemfile
actualizarrails
a >~ 7.0 y actualizar gemas dependientes de sip a ramarails7esjs
. Cambiar#gem 'byebug'
porgem 'debug'
. Ejecutarbundle update; bundle
-
Cambiar versión en
lib/motor/version.rb
-
Cambiar en fuentes lo que requiere cambios en rails 7 al lado del servidor
- En
bin/gc.sh
actualizardb:structure:dump
pordb:schema:dump
- En modelos en relaciones
belongs_to
que no tienenoptional
agregaroptional: false
. Ayuda:find . -name "*rb" -exec grep -l "belongs_to" {} ';'
- En
-
Ejecutar
bin/rails app:update
. Tras esto, si es un motor, entest/dummy/config/boot.rb
dejar:ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../../Gemfile", __dir__) require "bundler/setup" # Configurar gemas listadas en Gemfile require "bootsnap/setup" # Acelerar tiempo de arranque dejando en colchón operaciones costosas $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
-
En directorio de aplicación
config/application.rb
cambiarconfig.load_defaults
por 7.0 y ejecutargit add config/initializers/new_framework_defaults_7_0.rb
-
Verificar que pasan pruebas de regresión con
minitest
- En
Gemfile
quitarwebpacker
yturbolinks
y agregarbabel-transpiler
,jsbundling-rails
,sprockets-rails
yturbo-rails
. Ejecutarbundle update; bundle
- En directorio de aplicación
package.json
quitar toda referencia awebpacker
,webpack
yexpose-loader
y agregar
"scripts": {
"build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds"
}
- Correr
yarn add esbuild @hotwired/turbo-rails @rails/ujs
- Reorganizar directorios
git rm -rf config/webpack* public/packs
mkdir app/assets/builds/; touch app/assets/builds/.mantiene; git add app/assets/builds/.mantiene
- Agregar
app/assets/builds/
a.gitignore
git mv app/assets/javascripts/application.js app/assets/javascripts/recursos_globales.js
git mv app/packs/entrypoints app/javascript
- Reconfigurar sprockets cambiando
app/assets/config/manifest.js
para que sea al menos el siguiente pero agregar otros que se requieran globalmente y preparados por sprockets:
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css
//= link_directory ../../../node_modules/chosen-js .png
//= link recursos_sprockets.js
//= link recursos_sprockets.js.map
//= link application.css
//= link_tree ../builds
- Reconfigurar lo de
app/javascript
- Editar
app/javascript/application.js
para quitarimport expose_loader...
, quitarimport
dejquery-ui
, quitar referencias aTurbolinks
y asegurar que tiene al comienzo:
- Editar
import Rails from "@rails/ujs";
import "@hotwired/turbo-rails";
Rails.start();
window.Rails = Rails
import './jquery'
import '../../vendedor/recursos/javascripts/jquery-ui'
import 'gridstack' //Solo en caso de que el motor o aplicación incluya a mr519_gen
// Al final agregar:
let esperarRecursosSprocketsYDocumento = function (resolver) {
if (typeof window.puntomontaje == 'undefined') {
setTimeout(esperarRecursosSprocketsYDocumento, 100, resolver)
return false
}
if (document.readyState !== 'complete') {
setTimeout(esperarRecursosSprocketsYDocumento, 100, resolver)
return false
}
resolver("Recursos sprockets cargados y documento presentado en navegador")
return true
}
let promesaRecursosSprocketsYDocumento = new Promise((resolver, rechazar) => {
esperarRecursosSprocketsYDocumento(resolver)
})
promesaRecursosSprocketsYDocumento.then((mensaje) => {
console.log(mensaje)
var root;
root = window;
sip_prepara_eventos_comunes(root);
})
document.addEventListener('turbo:load', (e) => {
/* Lo que debe ejecutarse cada vez que turbo cargue una página,
* tener cuidado porque puede dispararse el evento turbo varias
* veces consecutivas al cargar una página.
*/
··
console.log('Escuchador turbo:load')
sip_ejecutarAlCargarPagina(window)
})
* Crear `app/javascript/jquery.js` para que sea:
import jquery from 'jquery';
window.jQuery = jquery;
window.$ = jquery;
* De la distribución de `jquery-ui` copiar `jquery-ui.js` en `vendedor/recursos/javascripts/jquery-ui.js`
- Para asegurar que se generan mapas útiles para depuración en navegador:
-
En
config/environments/development.rb
agregar:config.assets.debug = true config.assets.resolv_with = %i[manifest]
-
En
app/assets/config/manifest.js
agregar://= link recursos_globales.js //= link recursos_globales.js.map
-
- Buscar usos de turbolinks y cambiar por turbo. Ayudas:
find app -exec grep -li turbolinks {} ';'
yfind test -exec grep -li turbolinks {} ';'
- Buscar posibles
root = exports
para cambiar porroot = window
. Ayuda:find app -exec grep -li "root.*=.*exports" {} ';'
yfind test -exec grep -li "root.*=.*exports" {} ';'
- Buscar botones submit sin
data-turbo=false
y agregarlo. Ayudas:find app -name "*html*" -exec grep -l "submit" {} ';
yfind test -name "*html*" -exec grep -l "submit" {} ';'
- Mover inicializaciones manejadas por sprockets en escuchadores de evento
turbo:load
a la nueva sección para esto enapp/javascript/applicaiton.js
. - Ejecutar en modo desarrollo, realizar pruebas de usuarios, asegurar que corren pruebas con sideex
- Si es el caso ejecutar en modo de ensayo/producción
-
En grupo de gemas
:test
agregargem 'cuprite'
-
Crear directorio
test/system
-
Crear archivo de configuración
test/application_system_test_case
con:
require "test_helper"
require "capybara/cuprite"
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
Capybara.javascript_driver = :cuprite
Capybara.register_driver(:cuprite) do |app|
Capybara::Cuprite::Driver.new(app, window_size: [1200, 800])
end
driven_by :cuprite
end
- Crear una primera prueba por ejemplo de ingreso al sistema en
test/system/iniciar_sesion_test.rb
con:
require "application_system_test_case"
class IniciarSesionTest < ApplicationSystemTestCase
test "iniciar sesión" do
Sip::CapybaraHelper.iniciar_sesion(
self, Rails.configuration.relative_url_root, 'usuario', 'clave')
end
end
Desarrollado por Pasos de Jesús. Dominio público de acuerdo a legislación colombiana. Agradecemos financiación para personalizaciones de dominio público a diversas organizaciones, ver https://github.com/pasosdeJesus/sivel2/blob/master/CREDITOS.md
- Validación de etiquetas de Colombia y sus departamentos entre OSM de Sep.2022 y DIVIPOLA 2022
- Actualización a DIVIPOLA 2022-07 y Resumen ejecutivo de la actualización a DIVIPOLA 2022-07
- Actualización a DIVIPOLA 2021 y Resumen ejecutivo de la actualización a DIVIPOLA 2021
- Actualización a Rails 7
- Actualización a DIVIPOLA 2020 y Resumen ejecutivo de la actualización a DIVIPOLA 2020
- Extensiones para Chomium útiles para desarrollo
- Actualización de sip 2.0b11 a 2.0b12
- Actualización de sip 2.0b10 a 2.0b11
- Actualización de Rails 6.0 a Rails 6.1
- Resumen ejecutivo de la actualización a DIVIPOLA 2019
- Actualización a DIVIPOLA 2019
- Actualización-de-sip-2.0b6-a-sip-2.0b7
- Pasando de sprockets a webpacker con Rails 6
- Actualización a Rails 6 en 6 pasos
- Actualización a DIVIPOLA 2018
- Actualización de Rails 5.1 a Rails 5.2
- Actualizando a Rails 5
- Actualización a PostgreSQL posterior a 10.2