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.
--Developers who don't know what webpack is for --Rails developers using webpacker without knowing it
――Why we use webpack (first half) --What are webpack and webpacker doing (second half)
--Detailed way to write webpack.config.js
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?
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.
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.
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.
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 **.
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.
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.
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. ** ** 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). ** **
** 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! !! !! It's ** webpack **!
The image below is not made by me, it is just a screenshot of webpack official HP.
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)
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.
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)
]
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.
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.
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)
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.
"** 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.
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.
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 **. ..
"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!
"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"
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.
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!
Recommended Posts