# 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-and-yarn) - [Webpack and Webpacker](#webpack-and-webpacker) - [Installation](#installation) - [Webpack with Webpacker](#webpack-with-webpacker) - [Webpack without Webpacker in Rails app](#webpack-without-webpacker-in-rails-app) - [Webpacker with NPM without Yarn in Rails app](#webpacker-with-npm-without-yarn-in-rails-app) - [Usage](#usage) - [Compile assets](#compile-assets) - [Configuration](#configuration) - [Basic configuration](#basic-configuration) - [Modules](#modules) - [Using third-party libraries](#using-third-party-libraries) - [Import from node modules](#import-from-node-modules) - [Javascript compilers](#javascript-compilers) - [Uglify JS](#uglify-js) - [CSS, SASS](#css-sass) - [Images](#images) - [Erb](#erb) - [Import assets from Ruby Engine](#import-assets-from-ruby-engine) - [Examples](#examples) - [Basic example - CSS, SCSS](#basic-example-css-scss) - [jQuery, jquery-ui](#jquery-jquery-ui) - [font-awesome](#font-awesome) - [Bootstrap 4](#bootstrap-4) - [TinyMCE](#tinymce) - [elFinder](#elfinder) - [Resources](#resources) # 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: * [Yarn vs NPM](https://www.sitepoint.com/yarn-vs-npm/) # 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](https://github.com/rails/webpacker) 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 ```yml 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](https://github.com/theforeman/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: * https://itnext.io/how-to-use-webpacker-with-npm-instead-of-yarn-a8a764e3a8ab # 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 ```haml = 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 (``) 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](https://github.com/webpack-contrib/file-loader) - A file loader module for webpack. * [resolve-url-loader**](https://github.com/rails/webpacker/blob/master/docs/css.md#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](https://github.com/webpack-contrib/script-loader). * The script-loader evaluates code in the global context, similar to inclusion via a ``` # Resources Webpacker example app * https://github.com/maxivak/webpacker-rails-example-app * https://github.com/postNirjhor/webpack-boilerplate Webpack modules: * https://webpack.js.org/concepts/modules/ * https://webpack.js.org/api/module-methods/ * https://webpack.js.org/guides/shimming/ * https://medium.com/webpack/how-to-cope-with-broken-modules-in-webpack-4c0427fb23a * https://medium.com/webpack/webpack-4-import-and-commonjs-d619d626b655 * https://webpack.js.org/guides/shimming/ * https://medium.com/webpack/how-to-cope-with-broken-modules-in-webpack-4c0427fb23a * https://github.com/webpack/docs/wiki/shimming-modules Webpacker: * http://samuelmullen.com/articles/embracing-change-rails51-adopts-yarn-webpack-and-the-js-ecosystem/ * https://itnext.io/how-to-use-webpacker-with-npm-instead-of-yarn-a8a764e3a8ab - replace Yarn with npm for Webpacker gem. Import assets: * https://github.com/rails/webpacker/blob/master/docs/css.md * https://github.com/rails/webpacker/blob/master/docs/assets.md