UUUMエンジニアブログ

UUUMのエンジニアによる技術ブログです

Webpackerについてちゃんと調べてみた

カイシャで唯一 Emacs を使ってる✨✨✨✨キラキラエンジニア✨✨✨のtakeokunnです。

ギョームで webpacker を使っているのですが、中のコードを読む機会があったのでせっかくだからまとめてみました✨✨✨✨✨


コードについて書く前に、そもそも Webpacker がどんな機能を提供しているか書く。

READMEによると:

Features
* webpack 4.x.x
* ES6 with babel
* Automatic code splitting using multiple entry points
* Stylesheets - Sass and CSS
* Images and fonts
* PostCSS - Auto-Prefixer
* Asset compression, source-maps, and minification
* CDN support
* React, Angular, Elm and Vue support out-of-the-box
* Rails view helpers
* Extensible and configurable

これ入れとけば最新のwebpack使えて諸々の設定省いて最新 javascript をいい感じにrailsに組み込める!って書いてある。すっごーい✨✨✨✨✨


Webpacker のコードを追ってみる。$ ./bin/webpack が実行されてから webpackbundle.js が生成されるまでを見ていく。

./bin/webpack:

#!/usr/bin/env ruby

ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
ENV["NODE_ENV"]  ||= "development"

require "pathname"
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath)

require "rubygems"
require "bundler/setup"

require "webpacker"
require "webpacker/webpack_runner"
Webpacker::WebpackRunner.run(ARGV)

諸々の設定をしたあと、Webpacker::WebpackRunner.run(ARGV) が実行されている。

lib/webpacker/webpack_runner.rb抜粋:

def run
  env = Webpacker::Compiler.env

  cmd = if node_modules_bin_exist?
    ["#{@node_modules_bin_path}/webpack"]
  else
    ["yarn", "webpack"]
  end

  if ARGV.include?("--debug")
    cmd = [ "node", "--inspect-brk"] + cmd
    ARGV.delete("--debug")
  end

  cmd += ["--config", @webpack_config] + @argv

  Dir.chdir(@app_path) do
    Kernel.exec env, *cmd
  end
end

ここではもともと引数で渡していたものを元に webpack のcommandを生成して、 Kernel.exec env, *cmd で実行をしている。

@webpack_config の定義元について調べてみる。

lib/webpacker/runner.rb抜粋:

def initialize(argv)
  @argv = argv

  @app_path              = File.expand_path(".", Dir.pwd)
  @node_modules_bin_path = ENV["WEBPACKER_NODE_MODULES_BIN_PATH"] || `yarn bin`.chomp
  @webpack_config        = File.join(@app_path, "config/webpack/#{ENV["NODE_ENV"]}.js")

  unless File.exist?(@webpack_config)
    $stderr.puts "webpack config #{@webpack_config} not found, please run 'bundle exec rails webpacker:install' to install Webpacker with default configs or add the missing config file for your custom environment."
    exit!
  end
end

Railsconfig/webpack/#{ENV["NODE_ENV"]}.js から取得している。

config/webpack/development.js:

process.env.NODE_ENV = process.env.NODE_ENV || 'development'

const environment = require('./environment')

module.exports = environment.toWebpackConfig()

toWebpackConfig の定義元はここ: https://github.com/rails/webpacker/blob/b041a1d3e53c55ad801654892c99f5ec4ff099f2/package/environments/base.js#L150-L165

entry, plugins, module, resolve などをまとめて webpack.config.js を吐き出している。

config/webpack/environment.js:

const webpack = require('webpack')
const { environment } = require('@rails/webpacker')

environment.loaders.get('sass').use.splice(-1, 0, { loader: 'resolve-url-loader' });

environment.plugins.prepend(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    jquery: 'jquery',
    Popper: ['popper.js', 'default']
  })
)

module.exports = environment

ここで webpack の設定を書く。

記述の仕方は公式ドキュメントにある: https://github.com/rails/webpacker/blob/master/docs/webpack.md

以上が Webpacker$ webpack --config webpack.config.js の一連の処理だ。


次にRailsとのつなぎ込みの部分を見ていく。

<%= javascript_pack_tag 'application' %>
<%= stylesheet_pack_tag 'application' %>

上記のようにlayout部分に記述するといい感じに bundle.js を読み込んでくれる。

javascript_pack_tag の定義元を読む。

lib/webpacker/helper.rb抜粋:

def javascript_packs_with_chunks_tag(*names, **options)
    javascript_include_tag(*sources_from_manifest_entrypoints(names, type: :javascript), **options)
end

def sources_from_manifest_entries(names, type:)
    names.map { |name| current_webpacker_instance.manifest.lookup!(name, type: type) }.flatten
end

current_webpacker_instanceWebpacker::Instance のインスタンスだ。

lib/webpacker/instance.rb抜粋:

class Webpacker::Instance
    def initialize(root_path: Rails.root, config_path: Rails.root.join("config/webpacker.yml"))
        @root_path, @config_path = root_path, config_path
    end
end

config/webpacker.yml を読み込んでいる。

current_webpacker_instance.manifest.lookup!(name, type: type) の処理を追ってみる。

lib/webpacker/manifest.rb抜粋:

class Webpacker::Manifest
    # Computes the relative path for a given Webpacker asset using manifest.json.
    # If no asset is found, returns nil.
    #
    # Example:
    #
    #   Webpacker.manifest.lookup('calendar.js') # => "/packs/calendar-1016838bab065ae1e122.js"
    def lookup(name, pack_type = {})
compile if compiling?

find(full_pack_name(name, pack_type[:type]))
    end

    # Like lookup, except that if no asset is found, raises a Webpacker::Manifest::MissingEntryError.
    def lookup!(name, pack_type = {})
lookup(name, pack_type) || handle_missing_entry(name)
    end

    def full_pack_name(name, pack_type)
        return name unless File.extname(name.to_s).empty?
        "#{name}.#{manifest_type(pack_type)}"
    end

    def find(name)
        data[name.to_s].presence
    end
end

この lookup! 部分でpath解決をしている。 webpack をしてない状態でrenderingした時に compile が走るのはこの部分が原因みたいだ。

webpack 側の output はどうなっているだろうか。

package/config.js抜粋:

const configPath = resolve('config', 'webpacker.yml')

const config = deepMerge(defaults, app)
config.outputPath = resolve(config.public_root_path, config.public_output_path)

config/webpacker.yml を元に output 先を指定している。

webpack 側も rails 側もお互い config/webpacker.yml を元にしてるためpathを解決することができる。


webpack のconfigを拡張していくとなると結構大変だけれども、特に弄らないなら導入も運用も楽で良いですね!

create-react-app のように eject 出来るように( webpack のみの運用)なってくれるともっと運用が楽になると思います。

へーしゃでは emacs を使うのが好きなエンジニアを切に募集しています✨✨✨✨✨✨✨

一緒にインスタ映えする emacs を作りましょう✨✨✨✨✨✨✨

View this post on Instagram

インスタ映えするemacs

@ takeokunnがシェアした投稿 -

詳しくはこちら

www.wantedly.com www.wantedly.com