SlideShare a Scribd company logo
WEWLJCIO - WORKING
EFFECTIVELY WITH LEGACY
JAVASCRIPT CODE IN OPAL
FORREST CHANG
FKCHANG2000@YAHOO.COM
BACKGOUND - OPAL-HOT-RELOADER
Hot reloader for
Hot loaded code
I merged a pull request that added hot css reloading
Opal
THE NEW CODE
Worked
But in Javascript
No tests
:(
Want to extend code
Want it to be easier to maintain
REAL LIFE REFACTORING
Real life better than canned examples
Need tests
Want to be in Ruby/Opal
BY MICHAEL
FEATHERS
WORKING EFFECTIVELY WITH LEGACY CODE
Similar situation
"Legacy code" == code w/o tests
How to put tests on something w/o tests
But also a little different
Want to convert languages
Subject to the predefined browser API and a particular way of
interacting with it
THE SUBMITTED CODE
Added a new if clause to OpalHotReloader#reload() to handle
CSS hot reloading
Implemented in Javascript via Opal x-string.
ORIGINAL RELOAD() METHOD
def reload(e)
# original code
reload_request = JSON.parse(`e.data`)
if reload_request[:type] == "ruby"
puts "Reloading ruby #{reload_request[:filename]}"
eval reload_request[:source_code]
if @reload_post_callback
@reload_post_callback.call
else
puts "not reloading code"
end
end
ADDED IF CODE TO RELOAD() METHOD
# the new css hot reloading code
if reload_request[:type] == "css"
url = reload_request[:url]
puts "Reloading CSS: #{url}"
# Work outsources Javascript via x-string
%x{
var toAppend = "t_hot_reload=" + (new Date()).getTime();
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) {
if (link.href.indexOf("?") === -1) {
link.href += "?" + toAppend;
} else {
if (link.href.indexOf("t_hot_reload") === -1) {
link.href += "&" + toAppend;
} else {
link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend)
}
}
}
}
}
end
end
OBSERVATIONS
reload() used to be small
Now 3x the size
Has an additional responsiblity
REFACTORING
Extract Method to handle new responsibilty
Extract class to hold that responsibilty (SRP)
Delegate to instance new class
New instance @css_reloader to be created in initialize method
REFACTORED RELOAD()
def reload(e)
reload_request = JSON.parse(`e.data`)
if reload_request[:type] == "ruby"
puts "Reloading ruby #{reload_request[:filename]}"
eval reload_request[:source_code]
if @reload_post_callback
@reload_post_callback.call
else
puts "not reloading code"
end
end
if reload_request[:type] == "css"
@css_reloader.reload(reload_request) # extracted method called here
end
end
THE NEW CSSRELOADER CLASS
class OpalHotReloader
class CssReloader
def reload(reload_request)
url = reload_request[:url]
%x{
var toAppend = "t_hot_reload=" + (new Date()).getTime();
var links = document.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) {
if (link.href.indexOf("?") === -1) {
link.href += "?" + toAppend;
} else {
if (link.href.indexOf("t_hot_reload") === -1) {
link.href += "&" + toAppend;
} else {
link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend)
}
}
}
}
}
end
end
end
TESTING THE SOLUTION
No automated tests yet.
Test manually
PITA
New code works!
WRITING AUTOMATED TESTS
Refactored works, we can now add automated tests
Using - minitest also available for those who don't like rspecopal-rspec
A PROBLEM
Technique to update css involves manipulating style links in the global
document object
Could
Create links in the actual DOM of the spec runner (not hard)
But don't like the non transparency of this
Don't like code that calls out directly to global document
Would be nice to be able to inject a test document
DEPENDENCY INJECTION
Desired outcome: Inject test doc for test, inject the real document for
application
Don't need standard dependency injection methods (constructor, setter,
interface)
Able to just pass document as a parameter
MORE ISSUES FOR DOCUMENT TEST DOUBLE
Limited options for hot reloading of CSS - have to do it a certain way
document interface not under my control - must match it
Stubbing Javascript objects in Opal
Opal/JS interface a problem here - Opal objects different
Opal-rspec has powerful mock/stub capability, but only Opal objects
Need to create own method
2 CONVENIENCE METHODS
Creates javascript objects directly via x-strings
create_link() to create the link DOM object that will get altered to
facillitate the css hot reloading and
fake_links_document() a convenience method which returns both a
test double for global document object, which responds to the
getElementsByTagName('link') call and a test double for the link
itself, that I will inspect to see whether it has been correctly altered.
CODE
def create_link( href)
%x|
var ss = document.createElement("link");
ss.type = "text/css";
ss.rel = "stylesheet";
ss.href = #{href};
return ss;
|
end
def fake_links_document(href)
link = create_link(href)
doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}`
{ link: link, document: doc}
end
ADD DOCUMENT TO RELOAD() SIGNATURE
# change from
def reload(reload_request)
# to
def reload(reload_request, document)
CALL WITH THE NEW SIGNATURE
# in OpalHotReloader#reload()
# instead of calling it this way
@css_reloader.reload(reload_request)
# we pass in the real browser document
@css_reloader.reload(reload_request, `document`)
MODIFY CSSRELOADER TO TAKE
DOCUMENT
class OpalHotReloader
class CssReloader
def reload(reload_request, document) # pass in the "document"
url = reload_request[:url]
%x{
var toAppend = "t_hot_reload=" + (new Date()).getTime();
// invoke it here
var links = #{document}.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) {
if (link.href.indexOf("?") === -1) {
link.href += "?" + toAppend;
} else {
if (link.href.indexOf("t_hot_reload") === -1) {
link.href += "&" + toAppend;
} else {
link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend)
}
}
}
}
}
end
end
end
TESTING SIGNATURE CHANGE
Must hand test again
Works!
REQUIRED TEST CASES
A plain stylesheet link where we add the hot reload argument for the first
time.
Updating a link that has already been updated with a hot reload argument.
Appending an additional hot reload argument parameter to a stylesheet
link that already has a parameter.
WRITING SPECS FOR THESE CASES
require 'native'
require 'opal_hot_reloader'
require 'opal_hot_reloader/css_reloader'
describe OpalHotReloader::CssReloader do
def create_link( href)
%x|
var ss = document.createElement("link");
ss.type = "text/css";
ss.rel = "stylesheet";
ss.href = #{href};
return ss;
|
end
def fake_links_document(href)
link = create_link(href)
doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}`
{ link: link, document: doc}
end
context 'Rack::Sass::Plugin' do
it 'should add t_hot_reload to a css path' do
css_path = 'stylesheets/base.css'
doubles = fake_links_document(css_path)
link = Native(doubles[:link])
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
subject.reload({ url: css_path}, doubles[:document])
expect(link[:href]).to match /#{Regexp.escape(css_path)}?t_hot_reload=d+/
end
PAGE 2
it 'should update t_hot_reload argument if there is one already' do
css_path = 'stylesheets/base.css?t_hot_reload=1111111111111'
doubles = fake_links_document(css_path)
link = Native(doubles[:link])
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
subject.reload({ url: css_path}, doubles[:document])
expect(link[:href]).to match /#{Regexp.escape('stylesheets/base.css?t_hot_reload=')
}(d)+/
expect($1).to_not eq '1111111111111'
end
it 'should append t_hot_reload if there are existing arguments' do
css_path = 'stylesheets/base.css?some-arg=1'
doubles = fake_links_document(css_path)
link = Native(doubles[:link])
expect(link[:href]).to match /#{Regexp.escape(css_path)}$/
subject.reload({ url: css_path}, doubles[:document])
expect(link[:href]).to match /#{Regexp.escape(css_path)}&t_hot_reload=(d)+/
end
end
end
SPECS PASS - SAFE TO REFACTOR
Added automated test coverage for the 3 cases
Now safe to rewrite the reload() method in Ruby/Opal
Spec provide safety net to prove we don't break the desired functionality
A trick - have reload() delegate to reload_ruby() reload_js() to
have both code side by side - handy for development and debugging
THE TRICK
require 'native'
class OpalHotReloader
class CssReloader
def reload(reload_request, document)
# currently using the Ruby version
reload_ruby(reload_request, document)
# reload_js(reload_request, document)
end
def reload_ruby(reload_request, document)
url = reload_request[:url]
puts "Reloading CSS: #{url}"
to_append = "t_hot_reload=#{Time.now.to_i}"
links = Native(`document.getElementsByTagName("link")`)
(0..links.length-1).each { |i|
link = links[i]
if link.rel == 'stylesheet' && link.href.index(url)
if link.href !~ /?/
link.href += "?#{to_append}"
else
if link.href !~ /t_hot_reload/
link.href += "&#{to_append}"
else
link.href = link.href.sub(/t_hot_reload=d{13}/, to_append)
end
end
end
}
end
PAGE 2
def reload_js(reload_request, document)
url = reload_request[:url]
%x{
var toAppend = "t_hot_reload=" + (new Date()).getTime();
var links = #{document}.getElementsByTagName("link");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) {
if (link.href.indexOf("?") === -1) {
link.href += "?" + toAppend;
} else {
if (link.href.indexOf("t_hot_reload") === -1) {
link.href += "&" + toAppend;
} else {
link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend)
}
}
}
}
}
end
end
end
IMPLEMENTATION
Ruby version - a line by line translation
Specs passed
Can remove Javascript version
OPAL ONLY CSSRELOADER
require 'native'
class OpalHotReloader
class CssReloader
def reload(reload_request, document)
url = reload_request[:url]
puts "Reloading CSS: #{url}"
to_append = "t_hot_reload=#{Time.now.to_i}"
links = Native(`document.getElementsByTagName("link")`)
(0..links.length-1).each { |i|
link = links[i]
if link.rel == 'stylesheet' && link.href.index(url)
if link.href !~ /?/
link.href += "?#{to_append}"
else
if link.href !~ /t_hot_reload/
link.href += "&#{to_append}"
else
link.href = link.href.sub(/t_hot_reload=d{13}/, to_append)
end
end
end
}
end
end
end
ONWARD
Now have specs
Converted code to Ruby
Much easier to implment hot reloading of Rails CSS/SASS
Already did it, it was easy, used TDD for that
LINKS TO BLOG POST
Blogger - code syntax highlighted
Medium - prettier but no syntax highlighting
http://funkworks.blogspot.com/2016/06/wewljcio-working-effectively-
with.html
https://medium.com/@fkchang2000/wewljcio-working-effectively-with-
legacy-javascript-code-in-opal-4fd624693de4
FIN
Questions?

More Related Content

PDF
Apache Click
PPTX
Parsing strange v3
PDF
SQLite in Adobe AIR
PDF
DBD::SQLite
PPTX
Python Code Camp for Professionals 1/4
PDF
Nativescript angular
PPT
More Asp
PDF
Knowledge is Power: Getting out of trouble by understanding Git - Steve Smith...
Apache Click
Parsing strange v3
SQLite in Adobe AIR
DBD::SQLite
Python Code Camp for Professionals 1/4
Nativescript angular
More Asp
Knowledge is Power: Getting out of trouble by understanding Git - Steve Smith...

What's hot (20)

ODP
Introduction to Angular js
KEY
JavaScript Testing for Rubyists
PDF
Going Beyond LAMP Again - Manchester WordPress User Group
PPTX
How to perform debounce in react
PDF
Hidden Treasures in Project Wonder
PDF
Operacion Guinda 2
PDF
Demo how to create visualforce and apex controller to update, delete custom o...
PPTX
Python and EM CLI: The Enterprise Management Super Tools
PDF
Merrill's Journey to CI-CD and Continuous Testing by Ashish Mukherjee
PDF
Adapters db-104-informixstoredprocedure
DOC
Cis407 a ilab 5 web application development devry university
PDF
4 introduction-php-mvc-cakephp-m4-controllers-slides
PPTX
Introduction to windows power shell in sharepoint 2010
PPTX
Build Lightweight Web Module
PPT
Google Bot Herding, PageRank Sculpting and Manipulation
PDF
Knowledge is Power: Getting out of trouble by understanding Git - Steve Smith...
PDF
Me and my importers
PDF
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014
PDF
Node.js: scalability tips - Azure Dev Community Vijayawada
PDF
PowerShell Tips & Tricks for Exchange
Introduction to Angular js
JavaScript Testing for Rubyists
Going Beyond LAMP Again - Manchester WordPress User Group
How to perform debounce in react
Hidden Treasures in Project Wonder
Operacion Guinda 2
Demo how to create visualforce and apex controller to update, delete custom o...
Python and EM CLI: The Enterprise Management Super Tools
Merrill's Journey to CI-CD and Continuous Testing by Ashish Mukherjee
Adapters db-104-informixstoredprocedure
Cis407 a ilab 5 web application development devry university
4 introduction-php-mvc-cakephp-m4-controllers-slides
Introduction to windows power shell in sharepoint 2010
Build Lightweight Web Module
Google Bot Herding, PageRank Sculpting and Manipulation
Knowledge is Power: Getting out of trouble by understanding Git - Steve Smith...
Me and my importers
Hardcore URL Routing for WordPress - WordCamp Atlanta 2014
Node.js: scalability tips - Azure Dev Community Vijayawada
PowerShell Tips & Tricks for Exchange
Ad

Similar to Working Effectively with Legacy Javascript code in Opal (20)

PDF
Redux vs Alt
PPTX
Share point hosted add ins munich
PPTX
Bare-knuckle web development
PPT
ODP
HTML Templates Using Clear Silver
PDF
[Bristol WordPress] Supercharging WordPress Development
PDF
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
PDF
async/await in Swift
PDF
Django Rest Framework and React and Redux, Oh My!
PPTX
Childthemes ottawa-word camp-1919
PDF
TurboGears2 Pluggable Applications
PDF
WordPress as the Backbone(.js)
PDF
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
PPTX
Data and information about anatical subject
KEY
Summer - The HTML5 Library for Java and Scala
PDF
Url programming
PDF
React mit TypeScript – eine glückliche Ehe
PDF
PHP-Part4
PPT
68837.ppt
ODP
Advanced Perl Techniques
Redux vs Alt
Share point hosted add ins munich
Bare-knuckle web development
HTML Templates Using Clear Silver
[Bristol WordPress] Supercharging WordPress Development
QA Lab: тестирование ПО. Яков Крамаренко: "KISS Automation"
async/await in Swift
Django Rest Framework and React and Redux, Oh My!
Childthemes ottawa-word camp-1919
TurboGears2 Pluggable Applications
WordPress as the Backbone(.js)
Be lazy, be ESI: HTTP caching and Symfony2 @ PHPDay 2011 05-13-2011
Data and information about anatical subject
Summer - The HTML5 Library for Java and Scala
Url programming
React mit TypeScript – eine glückliche Ehe
PHP-Part4
68837.ppt
Advanced Perl Techniques
Ad

More from Forrest Chang (11)

PDF
Crystal is a Rubyists friend (quick anecdote)
PDF
Making terminal based apps w:ruby
PDF
Opal-hot-reloader
PDF
Ruby-ying Javascript: Avoiding jQuery Spaghetti
PDF
Rubyconf 2014 recap
PDF
6 reasons Jubilee could be a Rubyist's new best friend
PDF
Opal a new_hope
PDF
Opal chapter 4_a_new_hope
PDF
Data Intensive RIAs on Rails with very little code (Netzke)
PDF
Rubyconf2012 recap
KEY
Opal - Ruby Style!! Ruby in the browser
Crystal is a Rubyists friend (quick anecdote)
Making terminal based apps w:ruby
Opal-hot-reloader
Ruby-ying Javascript: Avoiding jQuery Spaghetti
Rubyconf 2014 recap
6 reasons Jubilee could be a Rubyist's new best friend
Opal a new_hope
Opal chapter 4_a_new_hope
Data Intensive RIAs on Rails with very little code (Netzke)
Rubyconf2012 recap
Opal - Ruby Style!! Ruby in the browser

Recently uploaded (20)

PDF
Product Update: Alluxio AI 3.7 Now with Sub-Millisecond Latency
PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
PPTX
assetexplorer- product-overview - presentation
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PPTX
Oracle Fusion HCM Cloud Demo for Beginners
PDF
Tally Prime Crack Download New Version 5.1 [2025] (License Key Free
PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
Designing Intelligence for the Shop Floor.pdf
PDF
17 Powerful Integrations Your Next-Gen MLM Software Needs
PDF
CCleaner Pro 6.38.11537 Crack Final Latest Version 2025
PDF
How to Make Money in the Metaverse_ Top Strategies for Beginners.pdf
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PPTX
Reimagine Home Health with the Power of Agentic AI​
PPTX
Transform Your Business with a Software ERP System
PPTX
history of c programming in notes for students .pptx
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PPTX
Monitoring Stack: Grafana, Loki & Promtail
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PDF
medical staffing services at VALiNTRY
Product Update: Alluxio AI 3.7 Now with Sub-Millisecond Latency
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Adobe Premiere Pro 2025 (v24.5.0.057) Crack free
assetexplorer- product-overview - presentation
wealthsignaloriginal-com-DS-text-... (1).pdf
Oracle Fusion HCM Cloud Demo for Beginners
Tally Prime Crack Download New Version 5.1 [2025] (License Key Free
Wondershare Filmora 15 Crack With Activation Key [2025
Designing Intelligence for the Shop Floor.pdf
17 Powerful Integrations Your Next-Gen MLM Software Needs
CCleaner Pro 6.38.11537 Crack Final Latest Version 2025
How to Make Money in the Metaverse_ Top Strategies for Beginners.pdf
Design an Analysis of Algorithms II-SECS-1021-03
Reimagine Home Health with the Power of Agentic AI​
Transform Your Business with a Software ERP System
history of c programming in notes for students .pptx
Design an Analysis of Algorithms I-SECS-1021-03
Monitoring Stack: Grafana, Loki & Promtail
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
medical staffing services at VALiNTRY

Working Effectively with Legacy Javascript code in Opal

  • 1. WEWLJCIO - WORKING EFFECTIVELY WITH LEGACY JAVASCRIPT CODE IN OPAL FORREST CHANG FKCHANG2000@YAHOO.COM
  • 2. BACKGOUND - OPAL-HOT-RELOADER Hot reloader for Hot loaded code I merged a pull request that added hot css reloading Opal
  • 3. THE NEW CODE Worked But in Javascript No tests :( Want to extend code Want it to be easier to maintain
  • 4. REAL LIFE REFACTORING Real life better than canned examples Need tests Want to be in Ruby/Opal
  • 5. BY MICHAEL FEATHERS WORKING EFFECTIVELY WITH LEGACY CODE Similar situation "Legacy code" == code w/o tests How to put tests on something w/o tests But also a little different Want to convert languages Subject to the predefined browser API and a particular way of interacting with it
  • 6. THE SUBMITTED CODE Added a new if clause to OpalHotReloader#reload() to handle CSS hot reloading Implemented in Javascript via Opal x-string.
  • 7. ORIGINAL RELOAD() METHOD def reload(e) # original code reload_request = JSON.parse(`e.data`) if reload_request[:type] == "ruby" puts "Reloading ruby #{reload_request[:filename]}" eval reload_request[:source_code] if @reload_post_callback @reload_post_callback.call else puts "not reloading code" end end
  • 8. ADDED IF CODE TO RELOAD() METHOD # the new css hot reloading code if reload_request[:type] == "css" url = reload_request[:url] puts "Reloading CSS: #{url}" # Work outsources Javascript via x-string %x{ var toAppend = "t_hot_reload=" + (new Date()).getTime(); var links = document.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) { if (link.href.indexOf("?") === -1) { link.href += "?" + toAppend; } else { if (link.href.indexOf("t_hot_reload") === -1) { link.href += "&" + toAppend; } else { link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend) } } } } } end end
  • 9. OBSERVATIONS reload() used to be small Now 3x the size Has an additional responsiblity
  • 10. REFACTORING Extract Method to handle new responsibilty Extract class to hold that responsibilty (SRP) Delegate to instance new class New instance @css_reloader to be created in initialize method
  • 11. REFACTORED RELOAD() def reload(e) reload_request = JSON.parse(`e.data`) if reload_request[:type] == "ruby" puts "Reloading ruby #{reload_request[:filename]}" eval reload_request[:source_code] if @reload_post_callback @reload_post_callback.call else puts "not reloading code" end end if reload_request[:type] == "css" @css_reloader.reload(reload_request) # extracted method called here end end
  • 12. THE NEW CSSRELOADER CLASS class OpalHotReloader class CssReloader def reload(reload_request) url = reload_request[:url] %x{ var toAppend = "t_hot_reload=" + (new Date()).getTime(); var links = document.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) { if (link.href.indexOf("?") === -1) { link.href += "?" + toAppend; } else { if (link.href.indexOf("t_hot_reload") === -1) { link.href += "&" + toAppend; } else { link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend) } } } } } end end end
  • 13. TESTING THE SOLUTION No automated tests yet. Test manually PITA New code works!
  • 14. WRITING AUTOMATED TESTS Refactored works, we can now add automated tests Using - minitest also available for those who don't like rspecopal-rspec
  • 15. A PROBLEM Technique to update css involves manipulating style links in the global document object Could Create links in the actual DOM of the spec runner (not hard) But don't like the non transparency of this Don't like code that calls out directly to global document Would be nice to be able to inject a test document
  • 16. DEPENDENCY INJECTION Desired outcome: Inject test doc for test, inject the real document for application Don't need standard dependency injection methods (constructor, setter, interface) Able to just pass document as a parameter
  • 17. MORE ISSUES FOR DOCUMENT TEST DOUBLE Limited options for hot reloading of CSS - have to do it a certain way document interface not under my control - must match it Stubbing Javascript objects in Opal Opal/JS interface a problem here - Opal objects different Opal-rspec has powerful mock/stub capability, but only Opal objects Need to create own method
  • 18. 2 CONVENIENCE METHODS Creates javascript objects directly via x-strings create_link() to create the link DOM object that will get altered to facillitate the css hot reloading and fake_links_document() a convenience method which returns both a test double for global document object, which responds to the getElementsByTagName('link') call and a test double for the link itself, that I will inspect to see whether it has been correctly altered.
  • 19. CODE def create_link( href) %x| var ss = document.createElement("link"); ss.type = "text/css"; ss.rel = "stylesheet"; ss.href = #{href}; return ss; | end def fake_links_document(href) link = create_link(href) doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}` { link: link, document: doc} end
  • 20. ADD DOCUMENT TO RELOAD() SIGNATURE # change from def reload(reload_request) # to def reload(reload_request, document)
  • 21. CALL WITH THE NEW SIGNATURE # in OpalHotReloader#reload() # instead of calling it this way @css_reloader.reload(reload_request) # we pass in the real browser document @css_reloader.reload(reload_request, `document`)
  • 22. MODIFY CSSRELOADER TO TAKE DOCUMENT class OpalHotReloader class CssReloader def reload(reload_request, document) # pass in the "document" url = reload_request[:url] %x{ var toAppend = "t_hot_reload=" + (new Date()).getTime(); // invoke it here var links = #{document}.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) { if (link.href.indexOf("?") === -1) { link.href += "?" + toAppend; } else { if (link.href.indexOf("t_hot_reload") === -1) { link.href += "&" + toAppend; } else { link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend) } } } } } end end end
  • 23. TESTING SIGNATURE CHANGE Must hand test again Works!
  • 24. REQUIRED TEST CASES A plain stylesheet link where we add the hot reload argument for the first time. Updating a link that has already been updated with a hot reload argument. Appending an additional hot reload argument parameter to a stylesheet link that already has a parameter.
  • 25. WRITING SPECS FOR THESE CASES require 'native' require 'opal_hot_reloader' require 'opal_hot_reloader/css_reloader' describe OpalHotReloader::CssReloader do def create_link( href) %x| var ss = document.createElement("link"); ss.type = "text/css"; ss.rel = "stylesheet"; ss.href = #{href}; return ss; | end def fake_links_document(href) link = create_link(href) doc = `{ getElementsByTagName: function(name) { links = [ #{link}]; return links;}}` { link: link, document: doc} end context 'Rack::Sass::Plugin' do it 'should add t_hot_reload to a css path' do css_path = 'stylesheets/base.css' doubles = fake_links_document(css_path) link = Native(doubles[:link]) expect(link[:href]).to match /#{Regexp.escape(css_path)}$/ subject.reload({ url: css_path}, doubles[:document]) expect(link[:href]).to match /#{Regexp.escape(css_path)}?t_hot_reload=d+/ end
  • 26. PAGE 2 it 'should update t_hot_reload argument if there is one already' do css_path = 'stylesheets/base.css?t_hot_reload=1111111111111' doubles = fake_links_document(css_path) link = Native(doubles[:link]) expect(link[:href]).to match /#{Regexp.escape(css_path)}$/ subject.reload({ url: css_path}, doubles[:document]) expect(link[:href]).to match /#{Regexp.escape('stylesheets/base.css?t_hot_reload=') }(d)+/ expect($1).to_not eq '1111111111111' end it 'should append t_hot_reload if there are existing arguments' do css_path = 'stylesheets/base.css?some-arg=1' doubles = fake_links_document(css_path) link = Native(doubles[:link]) expect(link[:href]).to match /#{Regexp.escape(css_path)}$/ subject.reload({ url: css_path}, doubles[:document]) expect(link[:href]).to match /#{Regexp.escape(css_path)}&t_hot_reload=(d)+/ end end end
  • 27. SPECS PASS - SAFE TO REFACTOR Added automated test coverage for the 3 cases Now safe to rewrite the reload() method in Ruby/Opal Spec provide safety net to prove we don't break the desired functionality A trick - have reload() delegate to reload_ruby() reload_js() to have both code side by side - handy for development and debugging
  • 28. THE TRICK require 'native' class OpalHotReloader class CssReloader def reload(reload_request, document) # currently using the Ruby version reload_ruby(reload_request, document) # reload_js(reload_request, document) end def reload_ruby(reload_request, document) url = reload_request[:url] puts "Reloading CSS: #{url}" to_append = "t_hot_reload=#{Time.now.to_i}" links = Native(`document.getElementsByTagName("link")`) (0..links.length-1).each { |i| link = links[i] if link.rel == 'stylesheet' && link.href.index(url) if link.href !~ /?/ link.href += "?#{to_append}" else if link.href !~ /t_hot_reload/ link.href += "&#{to_append}" else link.href = link.href.sub(/t_hot_reload=d{13}/, to_append) end end end } end
  • 29. PAGE 2 def reload_js(reload_request, document) url = reload_request[:url] %x{ var toAppend = "t_hot_reload=" + (new Date()).getTime(); var links = #{document}.getElementsByTagName("link"); for (var i = 0; i < links.length; i++) { var link = links[i]; if (link.rel === "stylesheet" && link.href.indexOf(#{url}) >= 0) { if (link.href.indexOf("?") === -1) { link.href += "?" + toAppend; } else { if (link.href.indexOf("t_hot_reload") === -1) { link.href += "&" + toAppend; } else { link.href = link.href.replace(/t_hot_reload=d{13}/, toAppend) } } } } } end end end
  • 30. IMPLEMENTATION Ruby version - a line by line translation Specs passed Can remove Javascript version
  • 31. OPAL ONLY CSSRELOADER require 'native' class OpalHotReloader class CssReloader def reload(reload_request, document) url = reload_request[:url] puts "Reloading CSS: #{url}" to_append = "t_hot_reload=#{Time.now.to_i}" links = Native(`document.getElementsByTagName("link")`) (0..links.length-1).each { |i| link = links[i] if link.rel == 'stylesheet' && link.href.index(url) if link.href !~ /?/ link.href += "?#{to_append}" else if link.href !~ /t_hot_reload/ link.href += "&#{to_append}" else link.href = link.href.sub(/t_hot_reload=d{13}/, to_append) end end end } end end end
  • 32. ONWARD Now have specs Converted code to Ruby Much easier to implment hot reloading of Rails CSS/SASS Already did it, it was easy, used TDD for that
  • 33. LINKS TO BLOG POST Blogger - code syntax highlighted Medium - prettier but no syntax highlighting http://funkworks.blogspot.com/2016/06/wewljcio-working-effectively- with.html https://medium.com/@fkchang2000/wewljcio-working-effectively-with- legacy-javascript-code-in-opal-4fd624693de4