カイシャで唯一 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
が実行されてから webpack
で bundle.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
の定義元について調べてみる。
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
Rails
の config/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
の定義元を読む。
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_instance
は Webpacker::Instance
のインスタンスだ。
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)
の処理を追ってみる。
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
はどうなっているだろうか。
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
を作りましょう✨✨✨✨✨✨✨
詳しくはこちら