[RAILS] Webpack and webpacker I want to tell Ruby people right now

Introduction

Introduction

I think that there are quite a few people who mainly develop Ruby and Rails and say "I don't really understand JS". But as long as the browser is there, humanity cannot escape JS. This article was written to help Rubists who are not good at JS make friends with webpack.

If you know what webpacker is, it will definitely be useful, so if you don't know it, please read it. If I'm a Rails developer who isn't familiar with JS, I'd like to know who webpack and webpacker are.

Target audience

--Developers who don't know what webpack is for --Rails developers using webpacker without knowing it

Overview

What to explain in this article

――Why we use webpack (first half) --What are webpack and webpacker doing (second half)

What not to explain in this article

--Detailed way to write webpack.config.js

Understand the purpose of webpack

Relationship between webpack and webpacker

First of all, ** webpack and webpacker are different **. ** webpack is a JS npm package **. I grew up in the JS community. npm is a package management tool that can be used with Node.js, that is, bundler in Ruby. JS developers are all using this npm or its alternative yarn. And ** webpacker is a Ruby gem **. Rails also ** made webpack easy to use **. So by knowing who the main webpack is, you can also know what webpacker wants to do. So what is webpack?

webpack monster theory

In a nutshell, "what is webpack" is usually described as ** module bundler **. Module bundler ……? It doesn't make sense. There may have been a monster with that name in Ultraman. 怪獣のイラスト However, even if you look at the monster pictorial book, the explanation of the module bundler is not written anywhere.

I can understand the feeling that "** JS people are unfriendly, JS is not a language for me. I will live with open classes, method_missing and monkey patches **", but my feelings Please listen to the story.

A module bundler is for bundling modules together. Wait, I haven't explained anything yet. Please listen a little more.

Rails can do this, right?

If you were to build a web app of a reasonable size with Rails, you would probably have a lot of files in that project. Some classes are written in those files, and methods are written. Perhaps the class is also in a module.

For example, like this. This is a fictitious code that seems to be common in Rails. It is a controller that the administrator is likely to display a page for batch CSV import of user information.

admin/import_users_controller.rb


module Admin
  class ImportUsersController < ApplicationController
    # [GET] /admin/import_users(.:format)
    def index
      #Processing something A
    end
  end
end

By the way, while adding functions, I made such a controller in a place completely different from the above code.

user/import_users_controller.rb


module User
  class ImportUsersController < ApplicationController
    # [GET] /user/import_users(.:format)
    def index
      #Something processing B
    end
  end
end

I have two controllers named ʻImportUsersController` in different modules. Is there any problem? …… There is no problem. This is because the namespace is separated by module.

It's a bit of a forceful example, but even when you say "I really want to call the contents of Admin in another module!" By specifying ʻAdmin :: ImportUsersController`, it is possible to call it. It doesn't matter if you do it.

JS folk tales

This kind of thing that Ruby can take for granted has long been difficult to do with JS. What I couldn't do specifically was that I couldn't just divide the modules and separate the scopes between the modules. Even if the files were separated, if multiple files were read at the same time, the names would easily conflict.

"Is JS all global scope?"

No, it's not that far. Even in ancient JS, there was a scope that was not global. ** Function scope **. What is a function scope? It is a scope that variables declared in a function cannot be accessed from the outside.

Old javascript.js


var globalScope = "Global scope";

function hogeFunc() {
    var functionalScope = "No matter what variable name you give in the function, it will not be overwritten from the outside";
}

Older JS didn't have a namespace-separating mechanism like Ruby's module, or even class syntax. Therefore, people use their wisdom and use function scope to use ** crappy mysterious syntax called "closures" ** and ** "immediate function" parenthesis ghosts ** I created it and managed to deceive it. But that's a thing of the past.

The strongest JS I thought of

JS is too painful to have to use a nasty syntax to prevent global pollution, but the geniuses of the world are naturally cut off, and the shortness of the three major virtues of programmers is the power to make it better. I tried to create JS. In order to improve the development experience with JS, each developer has started to formulate JS specifications. ブチギレる人のイラスト As a result of each developer making specifications with "the strongest JS I thought of" in each place, various module mechanisms are created.

These all define the grammar for resolving the module. The ones that still survive are CJS and ʻESM`. I will explain how to import modules ** roughly **.

How to write CommonJS

From that historical background, I think that you often see it in Node.js. It is like this.

const a = require('./AnotherFile.js');

a.anotherFileMethod();

By writing this way, you can now separate files without polluting the namespace. Please note that ** is different from the // = require of Sprockets **.

ECMAScript Module

ECMAScript is, roughly speaking, the most formal JavaScript specification. The grammar defined there. It's okay to think that this will become mainstream in the future.

import a from './AnotherFile.js';

a.anotherFileMethod();

This method also allows you to separate files without polluting the namespace. Since the explanation is quite complicated, please refer to another article etc. for detailed usage.

Anyway, I have a grammar that can separate namespaces

That's why all JS developers are happy with the modularization of their code. I have one thing I want to solve, but I have multiple specifications, but ** it's a trivial matter, so let's close your eyes **. I'm happy.

However, IE and Teme are not good

Just as Ruby has CRuby, mruby, and JRuby, JS has various implementations. Just as Ruby isn't one, so is JS.

Each company has its own JS engine, such as ** V8 ** installed in Chrome, ** SpiderMonkey ** in Firefox, and ** Chakra ** in Internet Explorer. You have individual implementations to interpret JS itself. These engines are also used outside the browser. For example, it is also used in Node.js and JScript on Windows. It is a form that further functions are added to the original engine. It seems that everywhere has recently converged on Google's V8, but let's leave that for now.

** The problem is IE. ** ** IEのイラスト Now [even Microsoft is discontinuing IE support in Office 365](https://blogs.windows.com/japan/2020/08/18/microsoft-365-apps-say-farewell-to- It was very unlikely that internet-explorer-11 /) would discontinue support for IE five or six years ago.

Think about it, even if you say, "I've added a nice new grammar to the JS spec!", The spec is just a spec, and if you don't implement it, you won't be able to talk about it. If you say "** Please add ECMAScript Module to Chakra in IE. Please ☆ **", will Microsoft add it? On the contrary, if you are a Microsoft executive, can you decide to spend a lot of money to implement it? I'm already developing a new browser, Edge.

That's why ** neither ECMAScript Module nor CommonJS [works] in IE (https://caniuse.com/mdn-javascript_statements_import). ** **

If you give up then it all ends here

** I definitely don't want to write clunky syntax like closures or immediate functions, even though the new grammar is right there. ** ** ** But I have to run the code in IE as well. ** ** 悩む人のイラスト To solve that dilemma, the developers have once again squeezed their wisdom. "If so, wouldn't it be nice if there was a development tool that could nicely combine what was written in a modern module into one file so that it could be read in IE?"

That development tool is so! !! !! webpackのイラスト It's ** webpack **!

What webpack and webpacker do

What webpack can do

webpack is a module bundler

The image below is not made by me, it is just a screenshot of webpack official HP. webpackHP.png I feel that various kinds of files are compiled and it seems to be js or css. In fact, the Rubist brothers who tried to generate a static file by executing the bin / webpack command at hand may have felt that way. But ** don't get me wrong. webpack is not a compiler. It is a module bundler. ** **

As I wrote earlier, webpack solves file dependencies based on ESM's ʻimport and CJS's require`, and makes it a new JS file that can be read by IE. Does not have the ability to compile sass into css or TypeScript into JavaScript.

Take a closer look at the image. It says "bundle your scripts" at the top. It's not "compile your scripts".

(* Although I often say "one JS file" for ease of communication, it is not one file that is actually generated, but multiple files are read asynchronously. However, ʻimport and require` have disappeared from the generated file)

loader (the one that can be compiled before bundling)

However, when I run the build with webpacker, TypeScript actually becomes JavaScript that can be read even in IE, and sass becomes css that feels good. This means that you can also compile with webpack.

In fact, webpack can use an external library to compile ** as a pre-process and then bundle the modules. ** The library is called loader.

For example, there are npm packages such as ts-loader for TypeScript and sass-loader for Sass. By setting that loader to webpack, webpack can understand that "first use ts-loader to convert TypeScript to JavaScript and then resolve the module. " As a result, you can generate a single bundled JavaScript file from multiple files that were originally TypeScript.

There are so many many loaders just on the official webpack website, and if you do your best, you can make your own.

You can set multiple loaders. For example, like this.

  1. Compile sass with sass-loader
  2. Compile PostCSS with postcss-loader
  3. Load CSS written in JS with css-loader
  4. Insert the CSS string in JS into the DOM with style-loader

In order to process this in order from 1, you need to write it in webpack.config.js in the proper order. ** The loader array in webpack.config is processed first from the later loader, so put the loader you want to process first at the end of the array. ** **

js:webpack.config.concept of js


use: [
  { loader: 'style-loader' },   //(4th)
  { loader: 'css-loader' },     //(The third)
  { loader: 'postcss-loader' }, //(The second)
  { loader: 'sass-loader' }     //(First)
]

Suddenly the story of CSS and images came out! ?? (Added on 2020/11/03)

Until now, I was only talking about JS, but suddenly there was a story about CSS, and the official website of webpack has an image extension written on it, so I think some people may be surprised. ** You don't have to understand everything **, so take a look at the React + TypeScript code below ** somehow **.

React+TypeScript code(File with extension tsx)


import React from "react";
import style from "./Layout.module.css"; //point! importing css

const Layout: React.FC = ({ children }) => {
  return (
    <div className={style.app}>
      <div className={style.appContainer}>{children}</div>
    </div>
  );
};
export default Layout;

In addition to generating HTML on the Rails server side, nowadays, by using React and Vue, HTML may be generated on the JS side of the front end. In that case, webpack should also be able to read code in the form .jsx, .tsx and .vue. It may be an unfamiliar extension, but it's all like a variant of .js. The point of this code is ** importing the CSS file to the JS side (although TS is an example) **. Today, styles and images are also imported to JS side, so ** webpack has targets to resolve dependencies other than JS and TS **. Otherwise, you will not be able to convert these programs so that they can be read by IE. But keep in mind that webpack only resolves dependencies around JS. Please think that it is like a bonus to support CSS import etc.

plugin (The one that can compress JS file size after being bundled)

Apart from the loader, there is also a plugin. Plugins in webpack are distinct from loaders. The loader does the pre-processing, while the plugin can do the processing after bundling, that is, after it becomes one JS file. For example, reducing the file size of the generated JS is called ** minify **, and I think that terser-webpack-plugin is famous as a plugin that does that minify.

Click here for a list of plugins on the official webpack website.

How to use loader and plugin

If you use ** raw webpack instead of webpacker **, first add the loader or plugin you want to use to your project with yarn add or npm install (like JS version bundle install) is). After that, I will set what to use and how to use it in the webpack configuration file such as webpack.config.js. This article is meant to give you an overview, so I won't go into specifics on how to set it up. See another article or the official webpack page. The official page is also in English, but there are quite a few introduction methods. (For example, terser-webpack-plugin)

Summary of what webpack can do

I will summarize it once.

--webpack can solve require and ʻimport` and put the files together so that they can be read by IE. --By using loader, you can also convert from TypeScript to JavaScript in the preprocessing of webpack. --By using plugin, you can also minify JS in post-processing of webpack.

Of course, you can do various things by adding loader and plugin as well as compiling TS and minify JS, but I wrote it concretely here for easy understanding.

What Ruby webpacker can do

"I don't want to write webpack.config.js ..."

"** I don't understand JS at all I don't want Rails programmers to set up webpack.config.js. Please set it up. Webpack.config.js itself is difficult in the first place ... **" Have you ever thought? Webpacker is a gem that creates an atmosphere that seems to solve such common problems (I do not say that it will be solved).

By the way, webpacker makes it easy to use webpack in Rails projects, as described in its README. Let's take a look at the webpacker package.json. package.json also acts like a Gemfile in managing your project's npm packages. (There are various other roles)

package inside webpaker.json


  //Abbreviation
  "dependencies": {
    //* Only those that are relatively easy to understand are extracted.
    "babel-loader": "^8.1.0",
    "core-js": "^3.6.5",
    "css-loader": "^5.0.0",
    "file-loader": "^6.1.1",
    "mini-css-extract-plugin": "^1.0.0",
    "optimize-css-assets-webpack-plugin": "^5.0.4",
    "postcss": "^8.1.3",
    "postcss-loader": "^4.0.4",
    "sass-loader": "^10.0.3",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "^4.0.0",
    "webpack": "^4.44.1",
    //Abbreviation
  }

Looking at it, it seems that various loaders (sass-loader etc.) and plugins (terser-webpack-plugin etc.) are already ** already included ** in dependencies!

(Ts-loader is not included at first, but as you can see in the webpacker official documentation, bundle It seems to enter when you type a command like exec rails webpacker: install: typescript.)

By reading the webpacker-specific yml configuration file called config / webpacker.yml, webpacker decides what to do and how to do it, and writes the file nicely.

How the behavior of webpacker is set

config / webpacker.yml (with concrete example)

For example, config / webpacker.yml has an item called ʻextract_css`. By setting this item to true, you can export CSS as a separate file from JS.

webpacker.yml


production:
  <<: *default
  extract_css: true

Let's take a look at the webpacker code to see what happens when ʻextract_css` is true.

Code inside webpaker


// ...abridgement...
const styleLoader = {
  loader: 'style-loader'
}

// ...abridgement...
//use is an array of objects. unshift is a method that adds an element to the beginning of an array.
  if (config.extract_css) {
    use.unshift(MiniCssExtractPlugin.loader)
  } else {
    use.unshift(styleLoader)
  }

If config.extract_css is true, you know that MiniCssExtractPlugin.loader is being read as loader. Since webpack basically resolves dependencies around JS, it's basically CSS in JS. mini-css-extract-plugin is an npm package that allows you to export CSS in JS CSS as a single CSS file. Although it is named plugin, here the process is added at the beginning as a loader. (MiniCssExtractPlugin is a plugin, but it also needs to be set as a loader)

On the other hand, if false, you can see that {loader:'style-loader'} has been added to the beginning. style-loader is the loader you need to actually draw the CSS in JS into the DOM.

Since it is important, I will write it many times, but ** the array of loaders in webpack.config is processed in order from the last loader **. So adding a setting to the beginning of an array means doing that at the end of the loader. ʻExtract_css: If you set it to true, you'll find that the final process of the loader is to execute mini-css-extract-plugin`, resulting in the CSS being exported.

mini-css-extract-plugin may be in a different package in the future, but what does webpacker.yml configuration do and how webpacker makes it easy to configure webpack I think you've got a general idea.

config/webpack/environment.js

There is another way to configure the webpacker. It is a method provided by webpacker and describes the difference from the default setting of webpacker. This needs to be written with webpack.config.js in mind when compared to the settings in yml. By writing to environment.js, for example, you can add another loader at the beginning or end of the loader.

Why you should know who webpack and webpacker are

If you want to leave the company early, you should know too

If you're a Rails developer at work, you should know 100%. Especially ** if the release person doesn't know, there is a risk of being very painful **. As you probably know, webpacker is included from Rails6. By using webpacker, I have to know how my code is processed and what artifacts are deployed in the production environment ... For example, an error occurred during or immediately after the production deployment. At times, it is not possible to troubleshoot the problem or eliminate the root cause. The problem with ** webpacker is that it's harder to identify problems than raw webpack **. This is because it is difficult to understand what kind of webpack.config will be in the end. 夜にコードを書く人のイラスト I don't think it's a problem if you write the code as a hobby, but if you have a customer and you get "I can't deploy a bug fix," the customer is waiting for ** Buchigire **. ..

Example: Build artifacts are too big

"I don't know anything while building assets: precompile for production deployment, but the compilation failed !!!" "Why is this error!"

Error: write EPIPE
    at ChildProcess.target._send (internal/child_process.js:806:20)
    at ChildProcess.target.send (internal/child_process.js:676:19)
    at ChildProcessWorker.send (/XXX/hoge/releases/XXXXXXXXXXXXXX/node_modules/terser-webpack-plugin/node_modules/jest-worker/build/workers/ChildProcessWorker.js:299:17)

"It seems that something has failed at the TerserPlugin of the webpacker that minifies JS. " "When I looked it up, it seems to be an error that tends to occur when memory is insufficient." "When I see / var / log / messages, OOM Killer kills the node process. " "Why does the memory run out ... Is there no choice but to raise the server specs just for the build ...?" "The build artifacts that webpack holds in memory are huge." "How can I make build artifacts smaller more efficiently?" "Oh, it seems that it will be smaller if you put SplitChunksPlugin in webpack!

Example: application.css does not exist and error

"When I access the site immediately after deployment, a mysterious error appears and the full screen is not displayed !!!" "Why is this error!"

ActionView::Template::Error: Webpacker can't find application.css
  in /XXX/hoge/releases/20XXXXXXXXXXXX/public/packs/manifest.json

"There is no application.css in Honma ...? Why?" "It seems that this css is an implementation that webpacker's MiniCssExtractPlugin writes out when ʻextract_css: true is set. " "Even though css is not good, I die trying to call css with stylesheet_pack_tag'application' on the slim side. " "Why did css suddenly come out when it was written out?" "Ah, the Vue component that was dead code and erased ... You were ... The CSS was always written out by webpacker ..." "Originally it was a big job, ʻextract_css: false and delete stylesheet_pack_tag'application' "Once solved with this"

After all, should I know who webpack and webpacker are?

If you know webpack to some extent, you'll be better at troubleshooting when building webpack, as in the example above, and you'll be able to avoid becoming an overtime warrior because you can't deploy. You'll be less scared of black boxes when deploying. Conversely, if you had no knowledge of webpack and were working on debugging, it would take days to determine the cause of the problem.

webpacker seems to make webpack easy, but if you don't know webpack well, you may end up having a hard time in case of emergency. ** webpacker simplifies the setup when you deploy webpack, but that doesn't mean you can give up your understanding of webpack **. When you continue to run webpacker in a production environment, you'll know it. I think one option is to remove the webpacker and use raw webpack.

in conclusion

I hope webpack feels a little closer to you. If you still don't understand webpack at all, I'm sorry for my lack of power.

The history of JS is fairly crudely simplified. Apart from that, I'm neither in Microsoft nor in the process of developing ES, so there may be some inaccuracies. Then, in fact, another module bundler came out before webpack, and the grammar of JS changed completely after ES2015, and block scope came out in addition to function scope, but it seems to deviate from the main subject. I have omitted it. After that, building with webpack is not only for IE, but once under HTTP / 1.1, there was an advantage in terms of page display speed. Now that HTTP / 2 is so widespread, I don't know its speed advantage. There are many places where I've gone astray for the sake of simplicity, but I think it's "generally the same", so please forgive me unless I've made a fatal mistake.

Let's troubleshoot with me while watching webpack errors. It's best not to make an error!

reference

Recommended Posts

Webpack and webpacker I want to tell Ruby people right now
[Ruby] I want to extract only the value of the hash and only the key
I want to use arrow notation in Ruby
[Ruby] I want to do a method jump!
[Rails] I want to load CSS with webpacker
I want to get the value in Ruby
I want to transition screens with kotlin and java!
I want to bring Tomcat to the server and start the application
I want to perform high-speed prime factorization in Ruby (ABC177E)
I want to make a list with kotlin and java!
I want to call a method and count the number
I want to make a function with kotlin and java!
[jackson] I want to receive JSON values "0" and "1" as boolean
I want to use the Java 8 DateTime API slowly (now)
I want to create a Parquet file even in Ruby
I want to implement various functions with kotlin and java!
[Ruby] I want to reverse the order of the hash table
I want to convert characters ...
I want to understand Rice that connects Ruby and C ++, so I look at Ankane's project morph-ruby.
I want to give edit and delete permissions only to the poster
I want to be able to think and write regular expressions myself. ..
I want to add a browsing function with ruby on rails
I want to return to the previous screen with kotlin and java!
[Ruby] I want to put an array in a variable. I want to convert to an array
I really want to do "new T ()"! (And without inspection exceptions)
I want to change the value of Attribute in Selenium of Ruby
I want to perform asynchronous processing and periodic execution with Rail !!!
I want to download a file on the Internet using Ruby and save it locally (with caution)
Swift: I want to chain arrays
I want to use FormObject well
I want to convert InputStream to String
I want to docker-compose up Next.js!
I tried to summarize Java 8 now
[Ruby] I want to output only the odd-numbered characters in the character string
I want to display images with REST Controller of Java and Spring!
I was a little addicted to running old Ruby environment and old Rails
[Active Admin] I want to customize the default create and update processing
I want to pass the argument of Annotation and the argument of the calling method to aspect
[Ruby] I want to display posted items in order of newest date
[Ruby] I want to make an array from a character string with the split method. And vice versa.