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