webpack Show archive.org snapshot is a very powerful asset bundler written in node.js to bundle (ES6) JavaScript modules, stylesheets, images, and other assets for consumption in browsers.
Webpacker Show archive.org snapshot is a wrapper around webpack that handles integration with Rails.
This is a short introduction.
If you haven't already, you need to install node.js and Yarn.
Then, put
gem 'webpacker', '~> 4.x' # check if 4.x is still current!
in your Gemfile. Run
bundle install
Finally, run
bundle exec rails webpacker:install
Alternatively, you can add webpacker from the start when creating a new Rails app:
rails new myapp --webpack
We tend to put our assets in app/webpack
instead of the default app/javascripts
, since we not only bundle JavaScript, but stylesheets as well. To do this, change default.source_path
to app/webpack
in config/webpacker.yml
.
Since we will use webpack for all assets, you should also disable the (Sprockets) Asset pipeline by adding
config.assets.enabled = false
to your application.rb
.
Now we need to define our packs. A pack is a collection of assets that to be bundled into a single output JavaScript file, one single CSS file, plus separate files for images, fonts etc. Most applications will use a single pack (we often call it main
), some applications might for example use separate frontend
and backend
packs.
To add a pack named main
, add a file app/webpack/packs/main.js
. This will be your entry point. All assets belonging to that pack need to be imported here.
Since webpack is configured to use Babel Show archive.org snapshot , you can write modern ES6 syntax in all JavaScript files. Babel will transpile it to work in most browsers. You can optionally configure which browsers to support.
Add a
= javascript_pack_tag 'main'
at the bottom of your applications layout.
Now, all JavaScript you put into your packs/main.js
will run in the browser. However, you should not put any application code directly there. Instead
make a directory app/webpack/javascripts
add some file app/webpack/javascripts/my_component.js
import it in packs/main.js
with
import '../javascripts/my_component.js' // import always uses relative paths
And of course, your own code can (and should) use its own import
statements to load any dependencies it might have.
Since this quickly becomes cumbersome, we usually add the following to simply import all our JavaScript files at once:
// in packs/main.js
let webpackContext = require.context('../javascripts', true, /\.js$/)
for(let key of webpackContext.keys()) { webpackContext(key) }
webpack will automatically run all JavaScript through Babel. It will also compress and minify in production. The generated output will be fingerprinted, so it can be properly cached.
To use an external library, install it with yarn
, e.g.
yarn add unpoly
Then simply import it in your main.js
(or anywhere else), by adding
import 'unpoly'
If you don't specify a relative path, webpack will look in your node_modules
directory, which is exactly where Yarn puts all the libraries.
Yarn is very similar to Bundler:
yarn.lock
) with exact version numbers.yarn
command instead:yarn install
yarn add [package]
yarn upgrade [package]
yarn upgrade [package]@[version]
yarn remove [package]
Under the hood, Yarn calls npm for you. Do not use the npm
command yourself. Do not manipulate package.json
yourself. Yarn will do this for you.
If might have noticed that adding webpack made you development server pretty slow. To increase performance, open a separate terminal, and run bin/webpack-dev-server
. The dev server will watch all your changes and compile everything automatically in the background. It will also auto-reload your code in the browser.
If your JavaScript code has syntax errors, those will be shown here as well.
webpack's way of handling assets that are not JavaScript is to simply import
them, too.
So to add a stylesheet, put a .sass
(or .scss
or .css
file) in app/webpack/stylesheets
, then import it
// in packs/main.js
import '../stylesheets/application.sass'
At first this looks weird. We're now importing something that is not JavaScript from within our own JavaScript code. But this is simply how bundling non-JavaScript assets works in webpack. This will not actually add any useful code to our JavaScript, but it will let webpack know, it has to put the compiled application.sass
into our main.css
.
Of course, we have to tell the browser to actually load the stylesheet, by adding a
= stylesheet_pack_tag 'main'
into your layout's <head>
tag.
You can import stylesheets from external libraries the same way. To load Unpoly's default CSS for example, add
// packs/main.js
import 'unpoly/dist/unpoly.css'
As above, we usually want to load all CSS from some directories. Do this with
// packs/main.js
require.context('../stylesheets/blocks', true, /\.s[ac]ss$/)
Same deal. To emit an image, import it from your JavaScript. To import all images from app/webpack/images
add
// packs/main.js
require.context('../images', true, /\.(?:png|jpg|gif|ico|svg)$/)
If you want to reference images in your CSS (for example for a background-image), you can do so by using a relative path:
.logo
background-image: url(../images/logo.png)
Webpack will replace the relative path with the final path to the emitted image (including fingerprint).
To reference an image directly in your Ruby views, use
= image_tag(asset_pack_path('images/logo.png'))
With Webpack 4 other assets than stylesheets or javascripts are stored in a media folder (in this example the pack is application.js
):
= image_pack_tag('media/application/images/logo.png')
Follow Configuring Webpacker deployments with Capistrano.
In your cucumber test, you will want to regenerate your assets before each test suite run. Since this is a bit slow, especially when using multiple processes with parallel_test
, read this card.
If you still require jQuery, add it to your webpacker project by:
installing via
yarn add jquery
requiring and exposing it in your packs/main.js
:
import jQuery from 'jquery'
window.$ = jQuery
window.jQuery = jQuery
If you use any other jQuery plugins that expect the $
or jQuery
object to be globally accessible, you will have to use webpack's ProvidePlugin
:
// in config/environment.js
environment.plugins.prepend(
'Provide', new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
)
This plugin will check if any file uses a global object called $
or jQuery
, then load it from the given path (just jquery
meaning node_modules/jquery
in this case) and inject it.
As we've seen above, to bundle files that are not JavaScript, we can simply import
them. But how does webpack know what to do with each file? To emit images as their own files, to add stylesheets to a common CSS file? How does it know about SASS?
The answer is: You can define so called "loaders".
For example, the configuration for SASS files looks like this:
{
test: /\.(scss|sass)$/i,
use: [
{ loader: 'style-loader', options: { ... } },
{ loader: 'css-loader', options: { ... } },
{ loader: 'postcss-loader', options: { ... } },
{ loader: 'sass-loader', options: { ... }] }
],
}
When you import a file that matches the test
regexp (i.e. any .scss / .sass file), all loaders (which are usually npm modules) are run on it in reverse order.
sass-loader
converts SASS to regular CSS.postcss-loader
uses PostCSS to postprocess the CSS. What it does exactly is configured in postcss.config.js
. By default it mostly does auto-prefixing.@import
statements, to find any dependencies.style-loader
takes the CSS and emits it appropriately. By default, it would actually create some JavaScript code that programatically adds a <style>
tag to your page. The job of actually emitting a separate main.css
file is performed by a webpack plugin called MiniCssExtract
.Luckily, Webpacker configures all this for us and we usually don't have to touch it.
Webpack also allows to add plugins that hook more deeply into its workflow. The MiniCssExtract
plugin above is such an example. These plugins are also usually provided by npm modules.
Plugins can do a lot of things, but we don't use them too often. Two examples of plugins we do use are
You might want to check out