Webpack, Yarn, Npm in Rails
All code is available in example app - https://github.com/maxivak/webpacker-rails-example-app
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:
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:
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.
Gemfile:
gem 'webpacker', '~> 3.5'
Gemfile:
gem 'webpacker', '>= 4.0.x'
add package:
yarn add @rails/webpacker@4.0.0-pre.2
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
You can use webpack in Rails app without Webpacker gem.
Use Foreman.
Yarn is not necessary for Webpack. You can replace Yarn with npm for Webpacker gem.
Read more:
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.
= javascript_pack_tag 'application'
= stylesheet_pack_tag 'application'
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:
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.
When you are ready to compile run
bundle exec rails webpacker:compile
or
rails assets:precompile
new files should appear in public/packs/ folder.
# compiles in production mode by default unless NODE_ENV is specified
bundle exec rails assets:precompile
bundle exec rails webpacker:compile
config/webpack
directory has corresponding configuration file for each Rails environment.config/webpack/shared.js
- file, that is common for all environmentsconfig/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 Webpackconfig/webpacker.yml
- config file (analog of webpacker.config.js
) used by WebpackerWebpack 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'
{
"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.
rake webpacker:compile
see new file(s) in public/packs/
.
include javascript in View
= javascript_pack_tag 'application'
In contrast to Node.js modules, webpack modules can express their dependencies in a variety of ways. A few examples are:
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 MyModule from './my-module.js';
import { NamedExport } from './other-module.js';
yarn add bootstrap@4.1.0
app/javascript/packs/application.js
import 'bootstrap/dist/js/bootstrap';
Import CommonJS modules
require
var $ = require('jquery');
var myModule = require('my-module');
require.resolve
- Synchronously retrieve a module's IDrequire.resolve(dependency: String);
Loaders
List of loaders - https://github.com/webpack/docs/wiki/list-of-loaders
Using third-party libraries that are not CommonJS/AMD/ES6 modules
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.
yarn add imports-loader exports-loader
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")
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