shimadama
5/30/2018 - 3:14 PM

Webpack, Yarn, Npm in Rails

Webpack, Yarn, Npm in Rails

Webpack in Rails

All code is available in example app - https://github.com/maxivak/webpacker-rails-example-app

Table of Contents

NPM and Yarn

NPM is a package manager for Node based environments. NPM manages dependencies and store its data in file package.json.

Yarn is a package manager that uses NPM registry as its backend. Yarn is like modernized npm. Yarn stores the exact versions of package dependencies in file yarn.lock. Yarn checks package.json file.

Read more:

Webpack and Webpacker

What's wrong with Sprockets

Sprockets - Rails Asset Pipeline. Sprockets is a rails-specific tool, but the frontend evolves by itself and the community prefers to use and create universal tools that don't have any specific limits.

Sprockets (Rails Asset Pipeline) has become obsolete long time ago. It has many problems:

  • Limited support for new frontend tools. For example, with SASS.
  • No support for ES2015 transpiling
  • No support for javascript modules
  • No support for sourcemaps

Webpack

Webpack is a manager for all your front-end code.

Webpacker gem makes it easy to use Webpack to manage application-like JavaScript in Rails.

Webpack provides modularization for JavaScript. Webpack implements a module system as well as a way to translate JavaScript code that doesn't work in any web browser to JavaScript code that works in most web browsers. The whole reason we are using Webpack is because JavaScript has no way to compose source files or package code in any useful way.

Webpacker

Webpacker is a Rails gem that provides integration with webpack module bundler and yarn package manager.

Webpacker coexists with the asset pipeline, as the primary purpose for webpack is app-like JavaScript, not images, CSS. However, it is possible to use Webpacker for CSS, images and fonts assets as well, in which case you may not even need the asset pipeline.

  • NOTE! Using Webpacker you won't need these gems: sass-rails, uglifier, jquery-rails, turbolinks, coffee-rails.

Installation

Webpack with Webpacker

  • Install NPM, yarn in your system
  • Install Webpacker
  • for Webpacker 3.5

Gemfile:

gem 'webpacker', '~> 3.5'
  • for Webpacker 4.x

Gemfile:

gem 'webpacker', '>= 4.0.x'

add package:

yarn add @rails/webpacker@4.0.0-pre.2 
  • install
bundle exec rails webpacker:install

It generates the following file structure

app/javascript:
  ├── packs:
  │   # only webpack entry files here
  │   └── application.js
  └── src:
  │   └── application.css
  └── images:
      └── logo.svg

Webpack without Webpacker in Rails app

You can use webpack in Rails app without Webpacker gem.

Use Foreman.

Webpacker with NPM without Yarn in Rails app

Yarn is not necessary for Webpack. You can replace Yarn with npm for Webpacker gem.

Read more:

Usage

By default, Webpacker builds JavaScript from source files located in app/javascript (a new folder in Rails app) and from node_modules installed via yarn.

  • use in View
= javascript_pack_tag 'application'
= stylesheet_pack_tag 'application'
  • run webpack in development

In development, Webpacker compiles on demand rather than upfront by default.

When in development run bin/webpack-dev-server - this will watch changes to your app and rebuild when required, pushing changes to the browser.

./bin/webpack-dev-server 

# or 
ruby ./bin/webpack-dev-server

Use it in development when:

  • you want live code reloading
  • you have enough JavaScript that on-demand compilation is too slow

Webpacker will automatically start proxying all webpack asset requests to this server. When you stop the server, it'll revert back to on-demand compilation.

Compile assets

When you are ready to compile run

bundle exec rails webpacker:compile

or

rails assets:precompile

new files should appear in public/packs/ folder.

  • Webpacker hooks up a new webpacker:compile task to assets:precompile, which gets run whenever you run assets:precompile. If you are not using Sprockets, webpacker:compile is automatically aliased to assets:precompile. Similar to sprockets both rake tasks will compile packs in production mode but will use RAILS_ENV to load configuration from config/webpacker.yml (if available).
  • compile in production
# compiles in production mode by default unless NODE_ENV is specified
bundle exec rails assets:precompile
bundle exec rails webpacker:compile

Configuration

  • config/webpack directory has corresponding configuration file for each Rails environment.
  • config/webpack/shared.js - file, that is common for all environments
  • config/webpack/environment.js - file responsible for processing settings from config/webpacker.yml.
  • webpacker.config.js - file in the root folder of Rails app, used by Webpack
  • config/webpacker.yml - config file (analog of webpacker.config.js) used by Webpacker

Basic configuration

Webpack needs to know which directories to read from, what transformations it needs to apply to what files, and where to put everything once it’s completed its run.

`package.json'

  • add necessary packages for webpack
{
  "name": "webpacker-rails-example-app",
  "private": true,
  "dependencies": {
    "@rails/webpacker": "3.5",
    "babel-core": "",
    "babel-loader": "",
    "webpack": "3.4.1"
  },
  "devDependencies": {
    "webpack-dev-server": "2.11.2"
  }
}

webpack.config.js

const webpack = require("webpack");

module.exports = {
  context: __dirname + "/app/javascript/packs",

  entry: {
    application: ["application.js"],
  },

  output: {
    path: __dirname + "/public/packs",
  },
};

It takes an input (the "entry" block) from app/javascript/packs folder and producing an output (the "output" block). It will read application.js file from /app/javascript/packs, perform actions required by this file, and output the resulting file to /public/packs/application-__HASH_HERE__.js.

Webpacker is appending hashes to all the assets by default.

  • run in command line
rake webpacker:compile
  • see new file(s) in public/packs/.

  • include javascript in View

= javascript_pack_tag 'application'

Modules

In contrast to Node.js modules, webpack modules can express their dependencies in a variety of ways. A few examples are:

  • An ES2015 import statement
  • A CommonJS require() statement
  • An AMD define and require statement
  • An @import statement inside of a css/sass/less file.
  • An image url in a stylesheet (url(...)) or html (<img src=...>) file.

The webpack compiler can understand modules written as ES2015 modules, CommonJS or AMD. Not all JS files can be used directly with webpack. Webpack supports modules written in a variety of languages and preprocessors, via loaders. Loaders describe to webpack how to process non-JavaScript modules and include these dependencies into your bundles.

Webpack 1 supports two of the most common module formats out-of-the-box: CommonJS and AMD. Webpack 2 supports ES6 module syntax natively, meaning you can use import and export without a tool like babel to handle this for you.

Import ES6 modules

  • import - Statically import the exports of another module.
import MyModule from './my-module.js';
import { NamedExport } from './other-module.js';
  • Example - import bootstrap module from node_modules
yarn add bootstrap@4.1.0
  • app/javascript/packs/application.js
import 'bootstrap/dist/js/bootstrap';

Import CommonJS modules

  • use require
  • Synchronously retrieve the exports from another module.
var $ = require('jquery');
var myModule = require('my-module');
  • require.resolve - Synchronously retrieve a module's ID
require.resolve(dependency: String);

Loaders

List of loaders - https://github.com/webpack/docs/wiki/list-of-loaders

  • babel-loader - transpile from ECMA6 to ECMA5 script
  • file-loader - A file loader module for webpack.
  • resolve-url-loader** - Loader that resolves relative paths in url() statements based on the original source file.

Using third-party libraries

Using third-party libraries that are not CommonJS/AMD/ES6 modules

Shimming

  • Some third party libraries may expect global dependencies (e.g. $ for jQuery). The libraries might also create globals which need to be exported. These "broken modules" can be used with shimming.

imports-loader, exports-loader

  • Some dependencies use a module style in an unusual way that may conflict with webpack. In this case it may help to fool the third-party code that there is no module system at all. Most modules will then fall back to a global variable which you can export using the exports-loader.

  • Use the imports-loader to configure this. Some legacy modules rely on this being the window object. This becomes a problem when the module is executed with webpack where this equals module.exports (in the style of CommonJS). In this case, you can override this with the imports-loader.

  • install loaders
yarn add imports-loader exports-loader
  • Example.

webpack.config.js


module.exports = {
  ...

  module: {
        loaders: [
            {
                test: require.resolve('tinymce/tinymce'),
                loaders: [
                    'imports?this=>window',
                    'exports?window.tinymce'
                ]
            },
            {
                test: /tinymce\/(themes|plugins)\//,
                loaders: [
                    'imports?this=>window'
                ]
            },
        ]
  }

}

  • Examples

  • Broken AMD

require("imports-loader?define=>false!./file.js")
  • Broken CommonJs
require("imports-loader?require=>false!./file.js")

script-loader

  • import scripts globally.

  • If you don’t care about global variables and just want legacy scripts to work, you can use the script-loader.

  • The script-loader evaluates code in the global context, similar to inclusion via a