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.
Installation
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
.
Packs
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.
Adding your own JavaScript
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
withimport '../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.
Using external libraries
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:
- Code dependencies are managed within package.json
- It has a lockfile (
yarn.lock
) with exact version numbers. - The lockfile should be checked into Git.
- You never touch the lockfile by hand. You change it with a
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.
The dev server
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.
Adding stylesheets
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$/)
Adding Images
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')
Deployment
Follow Configuring Webpacker deployments with Capistrano.
Tests
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.
jQuery
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.
A word about loaders
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.
- First, the
sass-loader
converts SASS to regular CSS. - Then, the
postcss-loader
uses PostCSS to postprocess the CSS. What it does exactly is configured inpostcss.config.js
. By default it mostly does auto-prefixing. - Next, the CSS loader searches the CSS for
@import
statements, to find any dependencies. - Lastly, the
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 separatemain.css
file is performed by a webpack plugin calledMiniCssExtract
.
Luckily, Webpacker configures all this for us and we usually don't have to touch it.
A word about plugins
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
- the IconFontPlugin to package .svg files as icon fonts
- the BundleAnalyzePlugin to visualize the size of the generated JavaScript
Further reading
You might want to check out