8 ms·
Unholy Rails: External Scripts, jQuery Plugins, and Page-Specific JavaScript
- DanielKehoe 14y agoI wrote the article to explore what to do when your application is not wholly Rails. I got a lot of help and advice but I don't think there's full agreement on recommended practices yet. I've seen a range of opinion but a lot of advice doesn't hold up to analysis. The article reflects the best advice I've gotten.
- dchuk 14y agoWell thank you for compiling all of this together because about to deploy a new version of my product that has a significant amount of fairly complex JS and I found it very frustrating to not have any goto resource in regards to structuring and optimizing the code. I'm going to try and incorporate some of your tips and will report back if you'd like
- DanielKehoe 14y agoYou're welcome. Take a look at the link (in the article) to Ken Collins’s "Secrets Of The Asset Pipeline" slide deck. It's got some great recommendations about organizing your JS code. Let me know how it goes (you can leave comments).
- Finbarr 14y agoI dislike the approach described where one concatenates every single javascript file into application.js and then tests for the presence of particular dom elements or classes within each file. This taxes the creation of new scripts somewhat as you must ensure you're not accidentally creating a dom element or class which is going to enable some undesired scripts. Another possible option with "require_tree ." is to use a framework like http://requirejs.org/ http://requirejs.org/ and then use in line script tags within pages to require a particular module. I also think it's a bad idea to enable asset compilation in production. From the Rails asset guides (http://guides.rubyonrails.org/asset_pipeline.html#live-compilation http://guides.rubyonrails.org/asset_pipeline.html#live-compi...): "This mode uses more memory, performs more poorly than the default and is not recommended." My advice would be to use application.js to concatenate scripts you're highly likely to use on every page, e.g., jquery, bootstrap, etc. Then organize the rest of your page specific scripts into app/assets/javascripts/<controller>/<action>.js<.extensions> and add a `javascript_include_tag "#{params[:controller]}/#{params[:action]}` in any views that rely on page specific scripts (within a content_for(:head) block). This way you'll likely have 2 local scripts loading for each page - the application.js file and any page specific script. The upsides are that you don't need to worry about undesired javascript running, the application.js file will be cached and reused across all pages, and your lightweight page specific js file will be served the first time a user loads the page then cached with every subsequent visit. The potential downside is that you either need to specify a precompile array by hand in your environment specific config file or automatically glob files to be precompiled. Note that you can also use the manifest declarations inside of your regular javascript files, e.g., `//= require 'backbone'` at the top of one of your page specific javascript files.
- Finbarr 14y agoOne more thing - if you concatenate all of your javascript files into a single application.js, the cache will be invalidated every time you make a change to any of your javascript files. I think all in all, this is a bad approach.
- DanielKehoe 14y agoI like your suggestion of using RequireJS. I'll add that to the next revision of the article. Your strategy of using two JS scripts per view, one site-wide and one page-specific is interesting. I wonder how to test the performance to really determine the value. Maybe look at time-to-render?
- Finbarr 14y agoYes I guess that would be the right metric. You'd need to look at average time to render over a number of deploys with both strategies. I'm pleased to see articles approaching this subject as there's definitely not enough solid discussion around this. One other optimization you may want to talk about in your next article is to use a gem like the asset_sync gem to upload your assets to S3 or CloudFront (or similar) at compile time.
- DanielKehoe 14y agoI'd like to look deeper into this option. I didn't mention it in the article, but @jo_liss suggested hosting all assets on CloudFront in production. She points out that you can set up CloudFront and get your own content-delivery network in five minutes. Once you have your own CDN, there's no advantage to using any 3rd-party JS host.
- jwilliams 14y agoThat's somewhat true. However, using Google does have an advantage over your own CDN -- it's more likely to already be cached in someone's browser. On these occassions the hit to download JQuery (or something big like JQueryUI) is zero. This is more a consideration for a landing page than a heavy-use application though.
- christopherscot 14y agoI'm suprised no mention of RequireJS...the part labeled "JavaScript’s Missing Directive" seems to be specifically gearing up for it. Good overview though.
- ScotterC 14y agoI've been using this technique with great success. http://viget.com/inspire/extending-paul-irishs-comprehensive-dom-ready-execution http://viget.com/inspire/extending-paul-irishs-comprehensive... You still have to load in all your js but at least you can execute exactly what you want per controller and action and have it documented all in one place.
- DanielKehoe 14y agoThat's the so-called Garber-Irish technique that I briefly mention in the article. I want to investigate it further. I'm not sure whether to use classes or ids in the body tag or try the data-* attributes he suggests. Also I'm a bit reluctant to use page-specific CSS selectors. It seems to be accepted practice (Modernizr and other libraries) but still seems like a hack. After all, there is never more than one body tag so it seems inconsistent with the HTML spec to apply a class or id to it.
- pault 14y agoCSS selectors should describe visual design, not content. Selector chains like .landing-page #buy-button are a CSS code smell. Unfortunately this seems to be more common than not, in my experience.
- eurleif 14y agoWhat? IDs and classes are part of the HTML, not the CSS, and as such, they should specify semantics, not presentation. If a class or ID specifies presentation, like .red-button, you have to change the HTML when the design changes, not just the CSS.
- pault 14y agoSorry, just generally ranting, not in your direction. :) My personal (strong) preference is to use semantic IDs as JS hooks (or just for markup readability), and keep all class names content-agnostic. Obviously there will be cases where you need to use (CSS) classes in your JS as well, so a good practice is to prefix them with js- and not base any style definitions on them. Separation of concerns and all that. You should be able to refactor your CSS, markup, and JS independently and not have anything break. Additionally, you should be able to copy paste a chunk of markup and have it look identical no matter where you put it, which definitely won't be the case if you are using long, content-referencing selector chains. Keeping your CSS flat, low level, and generic increases reusability and prevents CSS bloat, which can quickly get out of hand even in a medium sized app. Of course, all this is probably overkill if you are making small, static sites.
- railsjedi 14y agoAwesome writeup! Rails asset management can get pretty complex for newbies, but with a few good strategies like described in the article, it can be tamed and managed without too much trouble.
- purephase 14y agoGood article. Through trial and error, this is the approach that we've taken. My only gripe is that it appears (to me) to be the only way that works, as all my earlier faltering efforts were not successful. Asset pipeline is great, but it is a pain to get going initially with disparate assets/gems etc. Which, I assume, almost every Rails project has.