Ruby on Railsでスプロケットの代わりにWebpackを使用する

Ruby on Railsのアプリケーションのフロントエンド部分は、 Sprocketsライブラリによって管理されています。Sprocketsライブラリは、最新のフロントエンドアプリケーションのニーズに対応していません。 正確に足りないものは、たとえばherehereで読むことができます







webpack + railsバンドルにはすでに十分な記事があり、特別なgemもありますが、展開できる別の自転車を検討することをお勧めします。











したがって、フロントエンドアプリケーション全体は#{Rails.root}/frontend



ます。 標準assets



image_tag



介して接続されている画像ファイルのみimage_tag



ます。

開始するには、Node JS、npm、webpack自体、およびそのプラグインが必要です。 また、次を.gitignore



に追加する必要があります。







 /node_modules /public/assets /webpack-assets.json /webpack-assets-deploy.json
      
      





Webpackの構成



コンソールユーティリティを使用する場合、webpackはwebpack.config.js



ファイルをロードします。

私たちの場合、 NODE_ENV



変数で定義されたさまざまな環境を分離するために使用されます。







 // frontend/webpack.config.js const webpack = require('webpack'); const merge = require('webpack-merge'); const env = process.env.NODE_ENV || 'development'; module.exports = merge( require('./base.config.js'), require(`./${env}.config.js`) );
      
      





すべての環境の基本構成では、ディレクトリ、ブートローダー、およびプラグインの一般設定を設定します。 フロントエンドアプリケーションのエントリポイントも定義します







 // frontend/base.config.js const path = require('path'); const webpack = require('webpack'); module.exports = { context: __dirname, output: { //     path: path.join(__dirname, '..', 'public', 'assets', 'webpack'), filename: 'bundle-[name].js' }, //   (entry point) entry: { //    : ['./app/base-entry'],    //       //     2.0 application: ['./app/base-entry'], main_page: ['./app/pages/main'], admin_panel: ['./app/pages/admin_panel'] }, resolve: { //   require    extensions: ['', '.js', '.coffee'], modulesDirectories: [ 'node_modules' ], //     require:      // require('libs/some.lib') alias: { libs: path.join(__dirname, 'libs') } }, module: { loaders: [ //    ES6 { test: /\.js$/, include: [ path.resolve(__dirname + 'frontend/app') ], loader: 'babel?presets[]=es2015' }, //  CoffeeScript { test: /\.coffee$/, loader: 'coffee-loader' }, //  Vue JS  { test: /\.vue$/, loader: 'vue' }, //   jquery  //     $  { test: require.resolve('jquery'), loader: 'expose?$!expose?jQuery' } ], }, plugins: [ //    RAILS_ENV  js  new webpack.DefinePlugin({ __RAILS_ENV__: JSON.stringify(process.env.RAILS_ENV || 'development') ), }) ] };
      
      





環境開発



開発環境の構成は、有効なデバッグモードとソースマップによって区別されます。 Vue JSを使用しているため、フレームワークコンポーネントのソースコードを正しく表示するための小さな修正もここに追加しました。

また、スタイル、画像、およびフォントのローダーを定義します(実稼働環境では、キャッシングの特性を考慮して、これらのローダーの設定が異なります)。







 // frontend/development.config.js const webpack = require('webpack'); const AssetsPlugin = require('assets-webpack-plugin'); module.exports = { debug: true, displayErrorDetails: true, outputPathinfo: true, //  source map devtool: 'eval-source-map', output: { //     source map  Vue JS  devtoolModuleFilenameTemplate: info => { if (info.resource.match(/\.vue$/)) { $filename = info.allLoaders.match(/type=script/) ? info.resourcePath : 'generated'; } else { $filename = info.resourcePath; } return $filename; }, }, module: { loaders: [ { test: /\.css$/, loader: 'style!css?sourceMap' }, //     resolve-url, //        //  *.scss  { test: /\.scss$/, loader: 'style!css?sourceMap!resolve-url!sass?sourceMap' }, //  { test: /\.(png|jpg|gif)$/, loader: 'url?name=[path][name].[ext]&limit=8192' }, //  { test: /\.(ttf|eot|svg|woff(2)?)(\?.+)?$/, loader: 'file?name=[path][name].[ext]' } ] }, plugins: [ //     -,    //    js  css new AssetsPlugin({ prettyPrint: true }) ] };
      
      





開発には、静的なファイルを提供し、ファイルの変更を監視し、必要に応じて再生成するサーバーが必要です。 素晴らしいボーナスは、ホットモジュールの交換です。ページをリロードせずに変更が適用されます。 私のスタイルの場合、これは常に機能し、JavascriptはVue JSコンポーネント専用です







 // frontend/server.js const webpack = require('webpack'); const WebpackDevServer = require('webpack-dev-server'); const config = require('./webpack.config'); const hotRailsPort = process.env.HOT_RAILS_PORT || 3550; config.output.publicPath = `http://localhost:${hotRailsPort}/assets/webpack`; ['application', 'main_page', 'inner_page', 'product_page', 'admin_panel'].forEach(entryName => { config.entry[entryName].push( 'webpack-dev-server/client?http://localhost:' + hotRailsPort, 'webpack/hot/only-dev-server' ); }); config.plugins.push( new webpack.optimize.OccurenceOrderPlugin(), new webpack.HotModuleReplacementPlugin(), new webpack.NoErrorsPlugin() ); new WebpackDevServer(webpack(config), { publicPath: config.output.publicPath, hot: true, inline: true, historyApiFallback: true, quiet: false, noInfo: false, lazy: false, stats: { colors: true, hash: false, version: false, chunks: false, children: false, } }).listen(hotRailsPort, 'localhost', function (err, result) { if (err) console.log(err) console.log( '=> Webpack development server is running on port ' + hotRailsPort ); })
      
      





本番環境



extract-text-webpack-plugin



を使用してCSSを別のファイルにextract-text-webpack-plugin



できextract-text-webpack-plugin



。 生成されたコードのさまざまな最適化も適用されます。







 // frontend/production.config.js const path = require('path') const webpack = require('webpack'); const CleanPlugin = require('clean-webpack-plugin'); const ExtractTextPlugin = require("extract-text-webpack-plugin"); const CompressionPlugin = require("compression-webpack-plugin"); const AssetsPlugin = require('assets-webpack-plugin'); module.exports = { output: { //      filename: './bundle-[name]-[chunkhash].js', chunkFilename: 'bundle-[name]-[chunkhash].js', publicPath: '/assets/webpack/' }, module: { loaders: [ //     CSS    { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css?minimize") }, // sourceMap   -  { test: /\.scss$/, loader: ExtractTextPlugin.extract( "style-loader", "css?minimize!resolve-url!sass?sourceMap" ) }, { test: /\.(png|jpg|gif)$/, loader: 'url?limit=8192' }, { test: /\.(ttf|eot|svg|woff(2)?)(\?.+)?$/, loader: 'file' }, ] }, plugins: [ //     ,      // developoment  new AssetsPlugin({ prettyPrint: true, filename: 'webpack-assets-deploy.json' }), //    js-     // Webpack   ,    new webpack.optimize.CommonsChunkPlugin( 'common', 'bundle-[name]-[hash].js' ), //  CSS    new ExtractTextPlugin("bundle-[name]-[chunkhash].css", { allChunks: true }), // ... new webpack.optimize.DedupePlugin(), new webpack.optimize.OccurenceOrderPlugin(), new webpack.optimize.UglifyJsPlugin({ mangle: true, compress: { warnings: false } }), //  gzip  new CompressionPlugin({ test: /\.js$|\.css$/ }), //     new CleanPlugin( path.join('public', 'assets', 'webpack'), { root: path.join(process.cwd()) } ) ] };
      
      





Ruby on Railsとの統合



アプリケーション構成に新しいオプションを追加して、ページへのwebpack静的の挿入を有効/無効にします。 たとえば、静的を生成する必要がないときにテストを実行する場合に便利です。







 # config/application.rb config.use_webpack = true
      
      





 # config/environments/test.rb config.use_webpack = false
      
      





Railsアプリケーションの起動時にマニフェストを解析するための初期化子を作成します







 # config/initializers/webpack.rb assets_manifest = Rails.root.join('webpack-assets.json') if File.exist?(assets_manifest) Rails.configuration.webpack = {} manifest = JSON.parse(File.read assets_manifest).with_indifferent_access manifest.each do |entry, assets| assets.each do |kind, asset_path| if asset_path =~ /(http[s]?):\/\//i manifest[entry][kind] = asset_path else manifest[entry][kind] = Pathname.new(asset_path).cleanpath.to_s end end end Rails.configuration.webpack[:assets_manifest] = manifest #   Sprockets       ; #  webpack  (. )      Sprockets Rails.application.config.assets.configure do |env| env.context_class.class_eval do include Webpack::Helpers end end else raise "File #{assets_manifest} not found" if Rails.configuration.use_webpack end
      
      





また、webpackヘルパーwebpack_bundle_js_tags



webpack_bundle_css_tags



も便利です。これらはjavascript_include_tag



stylesheet_link_tag



ラッパーです。 引数は、webpack configからのエントリポイントの名前です







 # lib/webpack/helpers.rb module Webpack module Helpers COMMON_ENTRY = 'common' def webpack_bundle_js_tags(entry) webpack_tags :js, entry end def webpack_bundle_css_tags(entry) webpack_tags :css, entry end def webpack_tags(kind, entry) common_bundle = asset_tag(kind, COMMON_ENTRY) page_bundle = asset_tag(kind, entry) if common_bundle common_bundle + page_bundle else page_bundle end end def asset_tag(kind, entry) if Rails.configuration.use_webpack manifest = Rails.configuration.webpack[:assets_manifest] if manifest.dig(entry, kind.to_s) file_name = manifest[entry][kind] case kind when :js javascript_include_tag file_name when :css stylesheet_link_tag file_name else throw "Unknown asset kind: #{kind}" end end end end end end
      
      





ベースコントローラーにヘルパーメソッドを追加して、コントローラーをエントリポイントに接続します







 # app/controllers/application_controller.rb class ApplicationController < ActionController::Base attr_accessor :webpack_entry_name helper_method :webpack_entry_name def self.webpack_entry_name(name) before_action -> (c) { c.webpack_entry_name = name } end end
      
      





コントローラーでこれを行うことができます:







 # app/controllers/main_controller.rb class MainController < ApplicationController webpack_entry_name 'main_page' end
      
      





ビューでの使用:







 <html> <head> <%= webpack_bundle_css_tags(webpack_entry_name) %> </head> <body> <%= webpack_bundle_js_tags(webpack_entry_name) %> </body> </html>
      
      





npm



チーム



これで、すべてのフロントエンドライブラリが次のようにインストールされます。







 npm install <package_name> --save
      
      





npm-shrinkwrap.json



内のすべてのパッケージの正確なバージョンを「フリーズ」することを強くお勧めしnpm-shrinkwrap.json



Gemfile.lock



と同様)。 コマンドでこれを行うことができます( npm



はパッケージのインストール/更新時にnpm-shrinkwrap.json



関連性を監視しますが、安全である方が良いです):







 npm shrinkwrap --dev
      
      





便宜上、webpackコマンドをpackage.jsonのscripts



セクションに追加して、すばやく起動できます。







 "scripts": { "server": "node frontend/server.js", "build:dev": "webpack -v --config frontend/webpack.config.js --display-chunks --debug", "build:production": "NODE_ENV=production webpack -v --config frontend/webpack.config.js --display-chunks" }
      
      





たとえば、次のコマンドでwebpackサーバーを起動できます。







 npm run server
      
      





デプロイ:カピストラーノのレシピ



私は経済的なオプションを選択しました:JS動物園全体を実稼働サーバーにドラッグせず、webpackアセンブリをローカルで実行し、 rsync



を使用してサーバーにアップロードします。

これはdeploy:webpack:build



で実行され、その実装はcapistrano-faster-assets gemに基づいています。 生成は条件付きで発生します:フロントエンドコードに変更があった場合、またはパッケージがインストール/更新された場合。 必要に応じて、変数:webpack_dependencies



設定して、条件( diff



を作成するファイル、フォルダー)を追加できます。 生成された静的ファイルとマニフェストファイルのローカルフォルダーも指定する必要があります。







 # config/deploy.rb set :webpack_dependencies, %w(frontend npm-shrinkwrap.json) set :local_assets_dir, proc { File.expand_path("../../public/#{fetch(:assets_prefix)}/webpack", __FILE__) } set :local_webpack_manifest, proc { File.expand_path("../../webpack-assets-deploy.json", __FILE__) }
      
      





deploy:webpack:build



は、標準のdeploy:compile_assets



前に自動的に開始されます。







カピストラーノ自体のレシピコード:







 # lib/capistrano/tasks/webpack_build.rake class WebpackBuildRequired < StandardError; end namespace :deploy do namespace :webpack do desc "Webpack build assets" task build: 'deploy:set_rails_env' do on roles(:all) do begin latest_release = capture(:ls, '-xr', releases_path).split[1] raise WebpackBuildRequired unless latest_release latest_release_path = releases_path.join(latest_release) dest_assets_path = shared_path.join('public', fetch(:assets_prefix)) fetch(:webpack_dependencies).each do |dep| release = release_path.join(dep) latest = latest_release_path.join(dep) # skip if both directories/files do not exist next if [release, latest].map{ |d| test "test -e #{d}" }.uniq == [false] # execute raises if there is a diff execute(:diff, '-Nqr', release, latest) rescue raise(WebpackBuildRequired) end info "Skipping webpack build, no diff found" execute( :cp, latest_release_path.join('webpack-assets.json'), release_path.join('webpack-assets.json') ) rescue WebpackBuildRequired invoke 'deploy:webpack:build_force' end end end before 'deploy:compile_assets', 'deploy:webpack:build' task :build_force do run_locally do info 'Create webpack local build' %x(RAILS_ENV=#{fetch(:rails_env)} npm run build:production) invoke 'deploy:webpack:sync' end end desc "Sync locally compiled assets with current release path" task :sync do on roles(:all) do info 'Sync assets...' upload!( fetch(:local_webpack_manifest), release_path.join('webpack-assets.json') ) execute(:mkdir, '-p', shared_path.join('public', 'assets', 'webpack')) end roles(:all).each do |host| run_locally do `rsync -avzr --delete #{fetch(:local_assets_dir)} #{host.user}@#{host.hostname}:#{shared_path.join('public', 'assets')}` end end end end end
      
      





Webpackを好んで使用する印象:モジュール化されたモジュール性、ライブラリの明確なバージョン管理とそれらの簡単な更新、開発サーバーは静的処理で忙しくなく、展開は高速で、プリコンパイルで本番サーバーをロードしません。







それだけです;)







更新! 。 スプロケットが並行して使用されている場合(またはwebpack以外がpublic/assets



assetを使用してpublic/assets



場合)、webpackアセットを生成するには、たとえば、 public/assets/webpack



(投稿に適切な変更を加えます)など、個別のディレクトリを選択することをお勧めします。 これで、デプロイ時に--delete



オプションを使用してrsync



を実行できるようになり、本番rsync



で未使用のアセットが蓄積されないようになりました。 このソリューションには欠点があります。削除と同期すると、アセットを以前のリリースにロールバックできなくなります。 したがって、展開中に、ロールバックの場合にマニフェストをバックアップし、その上に必要なバージョンのアセットを復元する必要があります。







アップデート2 。 彼は、上記の統合プロセスをgem https://github.com/Darkside73/webpackedの形式で設計しました








All Articles