{:.small}
- recent Ruby (2.2+)
- recent Node (6.9.1+)
- an existing Rails app (4.2/5.0) – or example app
{:.small} {:.commandline}
brew update
brew install yarn
{:.small}
https://yarnpkg.com/en/docs/install
{:data-transition="convex"} {:.small}
The way we’re building our front-ends has changed irrevocably. Clean architecture is now a prerequisite and no longer simply nice-to-have. it's rich but we need to manage complexity. more than just a Rails view.{:data-background="pre-intro/asana.png"}
Whether you pick Flux (with React, for instance) or Front-end MVC with frameworks like ember.js and Angular, we have entered into an era of the Single-Page Application.{:data-background="pre-intro/typecast.png"}
When Rails came into being over ten years ago, it was ground-breaking. The libraries that Rails bundled - Prototype + Scripaculous - helped popularise Ajax. Still, Rails in its defaults, clings to a server-rendered page architecture.{:data-background="pre-intro/blocs.png"}
Rails developers shouldn’t have to forgo innovation and use of upcoming technologies like ES6 - the next version of JavaScript - and isomorphism. _So how can Rails keep up?_{:.small}
This practical talk that explains how to get back on track with Rails by ditching the asset pipeline. We’ll take a look at a JavaScript build toolchain - and how to make it work with Rails.{:.small}
Yep, seriously.{:data-transition="convex"}
Where I work I am currently available as a freelancer.
My most recent work has been paid, open-source work OpenProject.org. Hacked on things like DataMapper,
{:.small data-transition="concave"}
RAILS IS NO LONGER A SOLUTION FOR ALL PARTS OF APP. INCOMPLETE FOR RICH WEB APPSprockets previously a standalone gem. Rails 3.1 also the first release to ship with jQuery as default, rather than Prototype.
One product of the asset pipeline was gem wrappers for JavaScript libraries: following the release of Rails 3.1 there was proliferation of gem wrappers.
{:.commandline}
any guesses as to how many lines this yields?$ gem list --remote jquery | wc -l
{:.fragment} 349
There are better ways of getting your favourite JavaScript library into Rails. Bower support, for instance, is typically not something many people are aware of.
Using an integration like bower-rails or bower gem
gem install bower-rails
source 'https://rubygems.org'
gem 'bower-rails'
config.assets.paths << File.join(Rails.root, 'bower_components')
source 'https://rubygems.org'
gem 'rails'
source 'https://rails-assets.org' do
gem 'rails-assets-bootstrap'
gem 'rails-assets-angular'
gem 'rails-assets-leaflet'
end
But all of this doesn't go far enough, IMO.
- {:.fragment} Dependency management
- {:.fragment} Pre and post-processing
- {:.fragment} Code loading
- {:.fragment} Code bundling
- {:.fragment} Tree shaking
it's fair to say there is an ecosystem of Sprockets/Rails asset pipeline plugins.
One popular plugin with 243 stars on GitHub.
There are times, especially when dealing with third-party assets, that you want to opt-out of the mandatory digest in Sprockets 2.- ai/autoprefixer-rails
uses PostCSS project autoprefixer - TannerRogalsky/sprockets-es6
uses Babel (formerly 6to5), requires Sprockets 3
- No standard plugin configuration style
- Cannot control pre/post-processing order
- Asset dependencies
- Dependency management
- Pre and post-processing
- Code loading
- Code bundling
So let's look at a tool that can fulfil all of the previous criteria – well.
{:.small}
First thing you need is basic config and to define entry point into your application. From this initial entry point, Webpack will parse `require` statements in your code – and build a tree of dependencies and bundle. In Rails it's usually application.js.erb or application.coffee. You can define whatever you want though.// webpack.config.js
module.exports = {
context: __dirname + '/app',
entry: 'rubydayit-app.js',
output: {
filename: '[name].js',
path: path.join(__dirname, '..', 'app', 'assets', 'javascripts', 'bundles'),
publicPath: '/assets/bundles'
}
}
{:.small}
require('./another-file');
//= require ./another-file (Sprockets)
var angular = require('angular');
var jQuery = require('jquery');
require('jquery-ui');
{:.small}
var requireTemplate = require.context('./app/controllers', true, /\.js$/);
requireTemplate.keys().forEach(requireTemplate);
//= require_tree ./app/controllers (Sprockets)
{:.small}
require('jquery-ui/ui/jquery-ui'); // .js (default)
require('jquery-ui/themes/base/jquery.ui.core.css');
require('jquery-ui/themes/base/jquery.ui.datepicker.css');
require('select2/select2'); // .js (default)
require('select2/select2.css');
{:.small}
require('jquery-ui/ui/jquery-ui'); // .js (default)
require('!style-loader!css-loader!jquery-ui/themes/base/jquery.ui.core.css');
require('!style-loader!css-loader!jquery-ui/themes/base/jquery.ui.datepicker.css');
{:.small}
// webpack.config.js
module.exports = {
context: __dirname + '/app',
entry: 'rubydayit-app.js',
module: {
loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.png$/, loader: 'url-loader?limit=100000&mimetype=image/png' },
{ test: /\.gif$/, loader: 'file-loader' },
{ test: /\.jpg$/, loader: 'file-loader' }
]}
}
require('jquery-ui/ui/jquery-ui'); // .js (default)
require('jquery-ui/themes/base/jquery.ui.core.css');
require('jquery-ui/themes/base/jquery.ui.datepicker.css');
{:.small}
body {
background: url(/assets/bundles/background-texture.jpg)
}
/*
border-image: url(border-image.png);
*/
.box {
border-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAA…');
}
Webpack is built on the concept of loaders and plugins
Plugins change default configuration.Loaders are transformations that are applied on files. They preprocess files. I. e. they can transform CoffeeScript to JavaScript.
- {:.fragment} eslint ← coffee
- {:.fragment} json ← yaml
- {:.fragment} style ← postcss ← css ← sass
- {:.fragment} ngtemplate-loader ← markdown
{:data-background-image="webpack/loaders.png"}
And there is an ecosystem of ready-made loaders out there.{:.small}
$ yarn add --dev json-loader yaml-loader
I18n.translations = I18n.translations || {};
I18n.translations.en = require('!json!yaml!config/locales/en_US.yml').en;
I18n.translations.de = require('!json!yaml!config/locales/en_DE.yml').de;
{:.small}
$ yarn add --dev babel-loader
module: {
loaders: [
{ test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader'}
]
}
{:.small}
// app-defaults.js
export default {
favouriteConf: 'RubyDay Italia'
};
// app.js
import appDefaults from './app-defaults.js';
class ExampleApp {
constructor() {
console.log(appDefaults.favouriteConf);
}
}
export default ExampleApp;
{:.small}
Chances are you, you'll have libraries that are not-up-to-date. By default Webpack will help you eliminate globals. But if you're using `window`… (angular until recently didn't ship npm modules).$ yarn add --dev exports-loader
module: {
loaders: [
{ test: /[\/]angular\.js$/, loader: 'exports?angular' }
]
}
Like me, some of you may have come to Rails from the Java world. Thinking back to when you started doing Rails, what were some of the things that you found really attractive about developing with Rails?
{:.center}
For me certainly it was not having a manual compilation step. It was the ability to press Command + R (on my Mac) and have the request executed with the latest code - whether my changes be in data model, controller or views. That's something in the mid 2000's that you couldn't in the Java-world without the aid of a tool like JRebel. That's not to say constant reloading works perfectly. But it was a significant step in evolution of web development.What about hot-reloading of CSS and JavaScript though? This is something that Sprockets definitely cannot do it (although you can achieve live-reloading of your CSS with Ruby tools like guard). Webpack has you covered. It supports hot-reloading – both of your CSS and your JavaScript code. Switch to demo.
Song Song Song uses the SoundCloud API.
{:.small}
rails new TimeTracker
git init
and git commit
after each step.
./bin/rails generate scaffold Project name:string:required colour:string
./bin/rails generate scaffold TimeEntry project:references begin_at:datetime end_at:datetime notes:text
./bin/rake db:migrate
Add materialize-sass and spruce up the application
https://github.com/myabc/webpack-rails-rubydayit
yarn add --dev [email protected]
In webpack.config.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: './app/assets/javascripts/application.js',
output: {
filename: 'application.js',
path: path.join(__dirname, 'public', 'javascripts')
}
}
yarn add --dev babel-loader babel-core
yarn add --dev babel-preset-es2015
module.exports = {
// ...
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: { presets: ['es2015'] }
}
]
}
}
yarn add --dev sass-loader css-loader node-sass style-loader
yarn add --dev [email protected]
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
// ...
module: {
loaders: [
// ...
{
test: /\.scss$/,
loader: ExtractTextPlugin.extract({loader: "css-loader!sass-loader"})
}
]
},
plugins: [
new ExtractTextPlugin("../stylesheets/application.css")
]
}
yarn add --dev jquery
yarn add --dev expose-loader
module.exports = {
// ...
module: {
loaders: [
{
test: require.resolve("jquery"),
loader: "expose-loader?$!expose-loader?jQuery"
}
]
}
}
yarn add --dev materialize-css
module.exports = {
// ...
plugins: [
new ExtractTextPlugin("../stylesheets/application.css"),
new webpack.LoaderOptionsPlugin({
options: {
sassLoader: {
includePaths: [ path.resolve(__dirname, "./node_modules/materialize-css/sass") ]
}
}
})
]
}
(asset pipeline analogues)
yarn add --dev jquery-ujs
yarn add --dev turbolinks
//= require jquery
import jQuery from 'jquery';
//= require jquery_ujs
import 'jquery-ujs';
//= require turbolinks
import Turbolinks from 'turbolinks';
Turbolinks.start();
//= require materialize-sprockets
import 'materialize-css';
import './../stylesheets/application.scss';
-@import "materialize/components/color";
+@import "~materialize-css/sass/components/color";
$primary-color: #5d4ca0 !default;
$secondary-color: #38d59c !default;
-@import "materialize"
+@import "~materialize-css/sass/materialize";
yarn run webpack
All very well, but how do we make it work with Rails? Ideally, what we want is something like the following.
rake webpack
– or –
rake assets:precompile
Even better is hooking into/enhancing the existing `rake assets:precompile` task.
gem install foreman
foreman start
# Procfile
rails: bundle exec rails server -e ${RAILS_ENV:="development"} -p 3000
webpack: yarn webpack -- --watch --progress
rails new app --skip-sprockets
rails new app --skip-javascript --skip-turbolinks --skip-action-cable
# config/application.rb
-require 'rails/all'
+require 'rails'
+require 'active_model/railtie'
+require 'active_job/railtie'
+require 'active_record/railtie'
+require 'action_controller/railtie'
+require 'action_mailer/railtie'
+require 'action_view/railtie'
+require 'action_cable/engine'
+require 'rails/test_unit/railtie'
rm config/initializers/assets.rb
{:.small}
The most difficult part of integration is digest assets in production. If you've ever peaked under the Sprockets hood you'll know it generates a manifest file when you do rake assets:precompile. Fortunately, the manifest file generated by Webpack looks a lot like Sprockets' manifest file./// manifest-84b43dda218a2c29ce11f4f7b9ca4e5f.json
{
"assets": {
"1downarrow.png": "1downarrow-d2055955ce2927de07f2e33abdbfdc1b.png",
"1uparrow.png": "1uparrow-a4eef1942dd999e6a16e84c1c8122b8a.png",
"2downarrow.png": "2downarrow-e8bc5b59fa922f68637dc22b4a467f5c.png"
}
}
{:.small}
# app/helpers/application_helper.rb
def webpack_bundle_tag(bundle)
src =
if Rails.configuration.webpack[:use_manifest]
manifest = Rails.configuration.webpack[:asset_manifest]
filename = manifest[bundle]
"#{compute_asset_host}/assets/#{filename}"
else
"#{compute_asset_host}/assets/#{bundle}-bundle"
end
javascript_include_tag(src)
end
http://clarkdave.net/2015/01/how-to-use-webpack-with-rails/
Sprockets is not sufficient for complex front-end applications we're building now. Even though this presentation is mostly focussed on what's being used in productio now – Sprockets 2 – not that much in Sprockets 3 that changes my mind.
Many thanks for listening!