├── .gitignore ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.md ├── Rakefile ├── example ├── Procfile ├── dot_gitignore ├── package.json └── webpack.config.js ├── lib ├── generators │ └── webpack_rails │ │ └── install_generator.rb ├── tasks │ └── webpack.rake ├── webpack-rails.rb └── webpack │ ├── rails.rb │ ├── rails │ ├── helper.rb │ ├── manifest.rb │ └── version.rb │ └── railtie.rb ├── spec ├── helper_spec.rb ├── manifest_spec.rb └── spec_helper.rb └── webpack-rails.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle/ 2 | .ruby-version 3 | pkg/ 4 | log/ 5 | rdoc/ 6 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Reference: 2 | # https://github.com/bbatsov/rubocop/blob/v0.33.0/config/default.yml 3 | # https://github.com/bbatsov/rubocop/blob/v0.33.0/config/enabled.yml 4 | # https://github.com/bbatsov/rubocop/blob/v0.33.0/config/enabled.yml 5 | 6 | # Many of the variations are because rubocop prefers things to look like: 7 | # foo = foo(a, 8 | # b 9 | # ) 10 | # 11 | # where we prefer: 12 | # foo = foo(a, 13 | # b 14 | # ) 15 | 16 | AllCops: 17 | DisplayCopNames: true 18 | DisplayStyleGuide: true 19 | Exclude: 20 | - "*.gemspec" 21 | 22 | # Don't force end alignment to be versus the opening if/while - it should 23 | # be with the code flow, which isn't an option. 24 | Lint/EndAlignment: 25 | Enabled: false 26 | 27 | # Can't configure it to force last argument to be via code flow, so we ignore the rule 28 | Style/AlignHash: 29 | EnforcedLastArgumentHashStyle: always_ignore 30 | 31 | # indent call parameters against code flow, not method call opening 32 | Style/AlignParameters: 33 | EnforcedStyle: with_fixed_indentation 34 | 35 | # indent case statements against code flow, not assignment 36 | Style/CaseIndentation: 37 | IndentWhenRelativeTo: end 38 | 39 | # indents against opening paren, not code flow 40 | Style/ClosingParenthesisIndentation: 41 | Enabled: false 42 | 43 | # if branches should be indented against code flow, not if 44 | Style/ElseAlignment: 45 | Enabled: false 46 | 47 | # indent first param on next line against code flow, not opening statement 48 | Style/FirstParameterIndentation: 49 | EnforcedStyle: consistent 50 | 51 | # sprintf is much more common than format 52 | Style/FormatString: 53 | EnforcedStyle: format 54 | 55 | # hashes should be indented against code flow, not assignment 56 | Style/IndentHash: 57 | EnforcedStyle: consistent 58 | 59 | # indents against opening block, not code flow 60 | Style/IndentationWidth: 61 | Enabled: false 62 | 63 | # align with code flow 64 | Style/MultilineOperationIndentation: 65 | EnforcedStyle: indented 66 | 67 | # different methods calls that do exactly the same thing are a smell, regardless of semantics 68 | Style/SignalException: 69 | EnforcedStyle: only_raise 70 | 71 | # we don't care whether you use ' or " 72 | Style/StringLiterals: 73 | Enabled: false 74 | 75 | # "Refactor" severity - intended as warnings, not violations 76 | Metrics/LineLength: 77 | Severity: refactor 78 | Max: 180 79 | 80 | Metrics/ClassLength: 81 | Severity: refactor 82 | Max: 300 83 | 84 | Metrics/MethodLength: 85 | Max: 30 86 | Severity: refactor 87 | 88 | Metrics/CyclomaticComplexity: 89 | Max: 10 90 | Severity: refactor 91 | 92 | Metrics/PerceivedComplexity: 93 | Severity: refactor 94 | 95 | Metrics/AbcSize: 96 | Max: 30 97 | Severity: refactor 98 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - "2.1.7" 4 | - "2.2.2" 5 | - "2.3.1" 6 | script: bundle exec rake spec 7 | 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## v0.9.10 4 | 5 | * Only error if manifest error was a module build failure (Juan-Carlos Medina and Naomi Jacobs ) 6 | * Change dependency to railties (Mike Auclair ) 7 | * Only enable dev server in development & test (Agis Anastasopoulos ) 8 | * Use existing NODE_ENV if available (Alex ) 9 | * Switched README & generators to use yarn over npm 10 | * Allow SSL certificate verification for localhost connections (Marek Hulan ) 11 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rspec' 4 | gem 'webmock' 5 | gem 'rubocop' 6 | gem 'rake' 7 | gemspec 8 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | webpack-rails (0.9.11) 5 | railties (>= 3.2.0) 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | actionmailer (4.2.6) 11 | actionpack (= 4.2.6) 12 | actionview (= 4.2.6) 13 | activejob (= 4.2.6) 14 | mail (~> 2.5, >= 2.5.4) 15 | rails-dom-testing (~> 1.0, >= 1.0.5) 16 | actionpack (4.2.6) 17 | actionview (= 4.2.6) 18 | activesupport (= 4.2.6) 19 | rack (~> 1.6) 20 | rack-test (~> 0.6.2) 21 | rails-dom-testing (~> 1.0, >= 1.0.5) 22 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 23 | actionview (4.2.6) 24 | activesupport (= 4.2.6) 25 | builder (~> 3.1) 26 | erubis (~> 2.7.0) 27 | rails-dom-testing (~> 1.0, >= 1.0.5) 28 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 29 | activejob (4.2.6) 30 | activesupport (= 4.2.6) 31 | globalid (>= 0.3.0) 32 | activemodel (4.2.6) 33 | activesupport (= 4.2.6) 34 | builder (~> 3.1) 35 | activerecord (4.2.6) 36 | activemodel (= 4.2.6) 37 | activesupport (= 4.2.6) 38 | arel (~> 6.0) 39 | activesupport (4.2.6) 40 | i18n (~> 0.7) 41 | json (~> 1.7, >= 1.7.7) 42 | minitest (~> 5.1) 43 | thread_safe (~> 0.3, >= 0.3.4) 44 | tzinfo (~> 1.1) 45 | addressable (2.3.8) 46 | arel (6.0.3) 47 | ast (2.1.0) 48 | astrolabe (1.3.1) 49 | parser (~> 2.2) 50 | builder (3.2.2) 51 | concurrent-ruby (1.0.2) 52 | crack (0.4.2) 53 | safe_yaml (~> 1.0.0) 54 | diff-lcs (1.2.5) 55 | erubis (2.7.0) 56 | globalid (0.3.6) 57 | activesupport (>= 4.1.0) 58 | i18n (0.7.0) 59 | json (1.8.3) 60 | loofah (2.0.3) 61 | nokogiri (>= 1.5.9) 62 | mail (2.6.4) 63 | mime-types (>= 1.16, < 4) 64 | mime-types (3.0) 65 | mime-types-data (~> 3.2015) 66 | mime-types-data (3.2016.0221) 67 | mini_portile2 (2.0.0) 68 | minitest (5.9.0) 69 | nokogiri (1.6.7.2) 70 | mini_portile2 (~> 2.0.0.rc2) 71 | parser (2.2.2.6) 72 | ast (>= 1.1, < 3.0) 73 | powerpack (0.1.1) 74 | rack (1.6.4) 75 | rack-test (0.6.3) 76 | rack (>= 1.0) 77 | rails (4.2.6) 78 | actionmailer (= 4.2.6) 79 | actionpack (= 4.2.6) 80 | actionview (= 4.2.6) 81 | activejob (= 4.2.6) 82 | activemodel (= 4.2.6) 83 | activerecord (= 4.2.6) 84 | activesupport (= 4.2.6) 85 | bundler (>= 1.3.0, < 2.0) 86 | railties (= 4.2.6) 87 | sprockets-rails 88 | rails-deprecated_sanitizer (1.0.3) 89 | activesupport (>= 4.2.0.alpha) 90 | rails-dom-testing (1.0.7) 91 | activesupport (>= 4.2.0.beta, < 5.0) 92 | nokogiri (~> 1.6.0) 93 | rails-deprecated_sanitizer (>= 1.0.1) 94 | rails-html-sanitizer (1.0.3) 95 | loofah (~> 2.0) 96 | railties (4.2.6) 97 | actionpack (= 4.2.6) 98 | activesupport (= 4.2.6) 99 | rake (>= 0.8.7) 100 | thor (>= 0.18.1, < 2.0) 101 | rainbow (2.0.0) 102 | rake (10.5.0) 103 | rspec (3.2.0) 104 | rspec-core (~> 3.2.0) 105 | rspec-expectations (~> 3.2.0) 106 | rspec-mocks (~> 3.2.0) 107 | rspec-core (3.2.2) 108 | rspec-support (~> 3.2.0) 109 | rspec-expectations (3.2.0) 110 | diff-lcs (>= 1.2.0, < 2.0) 111 | rspec-support (~> 3.2.0) 112 | rspec-mocks (3.2.1) 113 | diff-lcs (>= 1.2.0, < 2.0) 114 | rspec-support (~> 3.2.0) 115 | rspec-support (3.2.2) 116 | rubocop (0.33.0) 117 | astrolabe (~> 1.3) 118 | parser (>= 2.2.2.5, < 3.0) 119 | powerpack (~> 0.1) 120 | rainbow (>= 1.99.1, < 3.0) 121 | ruby-progressbar (~> 1.4) 122 | ruby-progressbar (1.7.5) 123 | safe_yaml (1.0.4) 124 | sprockets (3.6.0) 125 | concurrent-ruby (~> 1.0) 126 | rack (> 1, < 3) 127 | sprockets-rails (3.0.4) 128 | actionpack (>= 4.0) 129 | activesupport (>= 4.0) 130 | sprockets (>= 3.0.0) 131 | thor (0.19.1) 132 | thread_safe (0.3.5) 133 | tzinfo (1.2.2) 134 | thread_safe (~> 0.1) 135 | webmock (1.21.0) 136 | addressable (>= 2.3.6) 137 | crack (>= 0.3.2) 138 | 139 | PLATFORMS 140 | ruby 141 | 142 | DEPENDENCIES 143 | rails (>= 3.2.0) 144 | rake 145 | rspec 146 | rubocop 147 | webmock 148 | webpack-rails! 149 | 150 | BUNDLED WITH 151 | 1.15.3 152 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015 Michael Pearson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # No Longer Maintained 2 | 3 | Hi! `webpack-rails` is no longer being maintained. Please see #90 for more info. 4 | 5 | [![Build Status](https://travis-ci.org/mipearson/webpack-rails.svg?branch=master)](https://travis-ci.org/mipearson/webpack-rails) [![Gem Version](https://badge.fury.io/rb/webpack-rails.svg)](http://badge.fury.io/rb/webpack-rails) 6 | 7 | # webpack-rails 8 | 9 | **webpack-rails** gives you tools to integrate Webpack in to an existing Ruby on Rails application. 10 | 11 | It will happily co-exist with sprockets but does not use it for production fingerprinting or asset serving. **webpack-rails** is designed with the assumption that if you're using Webpack you treat Javascript as a first-class citizen. This means that you control the webpack config, package.json, and use yarn to install Webpack & its plugins. 12 | 13 | In development mode [webpack-dev-server](http://webpack.github.io/docs/webpack-dev-server.html) is used to serve webpacked entry points and offer hot module reloading. In production entry points are built in to `public/webpack`. **webpack-rails** uses [stats-webpack-plugin](https://www.npmjs.com/package/stats-webpack-plugin) to translate entry points in to asset paths. 14 | 15 | It was designed for use at [Marketplacer](http://www.marketplacer.com) to assist us in migrating our Javascript (and possibly our SCSS) off of Sprockets. It first saw production use in June 2015. 16 | 17 | Our examples show **webpack-rails** co-existing with sprockets (as that's how environment works), but sprockets is not used or required for development or production use of this gem. 18 | 19 | This gem has been tested against Rails 4.2 and Ruby 2.2. Earlier versions of Rails (>= 3.2) and Ruby (>= 2.0) may work, but we haven't tested them. 20 | 21 | ## Using webpack-rails 22 | 23 | **We have a demo application: [webpack-rails-demo](https://github.com/mipearson/webpack-rails-demo)** 24 | 25 | ### Installation 26 | 27 | 1. Install [yarn](https://yarnpkg.com/en/docs/install) if you haven't already 28 | 1. Add `webpack-rails` to your gemfile 29 | 1. Run `bundle install` to install the gem 30 | 1. Run `bundle exec rails generate webpack_rails:install` to copy across example files 31 | 1. Run `foreman start` to start `webpack-dev-server` and `rails server` at the same time 32 | 1. Add the webpack entry point to your layout (see next section) 33 | 1. Edit `webpack/application.js` and write some code 34 | 35 | 36 | ### Adding the entry point to your Rails application 37 | 38 | To add your webpacked javascript in to your app, add the following to the `` section of your to your `layout.html.erb`: 39 | 40 | ```erb 41 | <%= javascript_include_tag *webpack_asset_paths("application") %> 42 | ``` 43 | 44 | Take note of the splat (`*`): `webpack_asset_paths` returns an array, as one entry point can map to multiple paths, especially if hot reloading is enabled in Webpack. 45 | 46 | If your webpack is configured to output both CSS and JS, you can use the `extension:` argument to filter which files are returned by the helper: 47 | 48 | ```erb 49 | <%= javascript_include_tag *webpack_asset_paths('application', extension: 'js') %> 50 | <%= stylesheet_link_tag *webpack_asset_paths('application', extension: 'css') %> 51 | ``` 52 | 53 | #### Use with webpack-dev-server live reload 54 | 55 | If you're using the webpack dev server's live reload feature (not the React hot reloader), you'll also need to include the following in your layout template: 56 | 57 | ``` html 58 | 59 | ``` 60 | 61 | ### How it works 62 | 63 | Have a look at the files in the `examples` directory. Of note: 64 | 65 | * We use [foreman](https://github.com/ddollar/foreman) and a `Procfile` to run our rails server & the webpack dev server in development at the same time 66 | * The webpack and gem configuration must be in sync - look at our railtie for configuration options 67 | * We require that **stats-webpack-plugin** is loaded to automatically generate a production manifest & resolve paths during development 68 | 69 | ### Configuration Defaults 70 | 71 | * Webpack configuration lives in `config/webpack.config.js` 72 | * Webpack & Webpack Dev Server binaries are in `node_modules/.bin/` 73 | * Webpack Dev Server will run on port 3808 on localhost via HTTP 74 | * Webpack Dev Server is enabled in development & test, but not in production 75 | * Webpacked assets will be compiled to `public/webpack` 76 | * The manifest file is named `manifest.json` 77 | 78 | #### Dynamic host 79 | 80 | To have the host evaluated at request-time, set `host` to a proc: 81 | 82 | ```ruby 83 | config.webpack.dev_server.host = proc { request.host } 84 | ``` 85 | 86 | This is useful when accessing your Rails app over the network (remember to bind both your Rails app and your WebPack server to `0.0.0.0`). 87 | 88 | #### Use with docker-compose 89 | 90 | If you're running `webpack-dev-server` as part of docker compose rather than `foreman`, you might find that the host and port that rails needs to use to retrieve the manifest isn't the same as the host and port that you'll be giving to the browser to retrieve the assets. 91 | 92 | If so, you can set the `manifest_host` and `manifest_port` away from their default of `localhost` and port 3808. 93 | 94 | ### Working with browser tests 95 | 96 | In development, we make sure that the `webpack-dev-server` is running when browser tests are running. 97 | 98 | #### Continuous Integration 99 | 100 | In CI, we manually run `webpack` to compile the assets to public and set `config.webpack.dev_server.enabled` to `false` in our `config/environments/test.rb`: 101 | 102 | ``` ruby 103 | config.webpack.dev_server.enabled = !ENV['CI'] 104 | ``` 105 | 106 | ### Production Deployment 107 | 108 | Add `rake webpack:compile` to your deployment. It serves a similar purpose as Sprockets' `assets:precompile` task. If you're using Webpack and Sprockets (as we are at Marketplacer) you'll need to run both tasks - but it doesn't matter which order they're run in. 109 | 110 | If you deploy to Heroku, you can add the special 111 | [webpack-rails-buildpack](https://github.com/febeling/webpack-rails-buildpack) 112 | in order to perform this rake task on each deployment. 113 | 114 | If you're using `[chunkhash]` in your build asset filenames (which you should be, if you want to cache them in production), you'll need to persist built assets between deployments. Consider in-flight requests at the time of deployment: they'll receive paths based on the old `manifest.json`, not the new one. 115 | 116 | ## TODO 117 | 118 | * Drive config via JSON, have webpack.config.js read same JSON? 119 | * Custom webpack-dev-server that exposes errors, stats, etc 120 | * [react-rails](https://github.com/reactjs/react-rails) fork for use with this workflow 121 | * Integration tests 122 | 123 | ## Contributing 124 | 125 | Pull requests & issues welcome. Advice & criticism regarding webpack config approach also welcome. 126 | 127 | Please ensure that pull requests pass both rubocop & rspec. New functionality should be discussed in an issue first. 128 | 129 | ## Acknowledgements 130 | 131 | * Len Garvey for his [webpack-rails](https://github.com/lengarvey/webpack-rails) gem which inspired this implementation 132 | * Sebastian Porto for [Rails with Webpack](https://reinteractive.net/posts/213-rails-with-webpack-why-and-how) 133 | * Clark Dave for [How to use Webpack with Rails](http://clarkdave.net/2015/01/how-to-use-webpack-with-rails/) 134 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require 'bundler/setup' 3 | rescue LoadError 4 | puts 'You must `gem install bundler` and `bundle install` to run rake tasks' 5 | end 6 | 7 | require 'rdoc/task' 8 | require 'rspec/core/rake_task' 9 | require 'rubocop/rake_task' 10 | 11 | RuboCop::RakeTask.new 12 | RSpec::Core::RakeTask.new(:spec) 13 | 14 | RDoc::Task.new(:rdoc) do |rdoc| 15 | rdoc.rdoc_dir = 'rdoc' 16 | rdoc.title = 'WebpackRails' 17 | rdoc.options << '--line-numbers' 18 | rdoc.rdoc_files.include('README.md') 19 | rdoc.rdoc_files.include('lib/**/*.rb') 20 | end 21 | 22 | Bundler::GemHelper.install_tasks 23 | 24 | task default: [:rubocop, :spec] 25 | -------------------------------------------------------------------------------- /example/Procfile: -------------------------------------------------------------------------------- 1 | # Run Rails & Webpack concurrently 2 | # Example file from webpack-rails gem 3 | rails: bundle exec rails server 4 | webpack: ./node_modules/.bin/webpack-dev-server --config config/webpack.config.js 5 | -------------------------------------------------------------------------------- /example/dot_gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore bundler config. 3 | /.bundle 4 | 5 | # Ignore all logfiles and tempfiles. 6 | /log/* 7 | !/log/.keep 8 | /tmp 9 | 10 | # Don't commit node_modules (vendored npm bits) or built assets 11 | /node_modules 12 | /public/webpack 13 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-rails-example", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "dependencies": { 6 | "stats-webpack-plugin": "^0.4.3", 7 | "webpack": "^1.14.0", 8 | "webpack-dev-server": "^1.16.2" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | // Example webpack configuration with asset fingerprinting in production. 2 | 'use strict'; 3 | 4 | var path = require('path'); 5 | var webpack = require('webpack'); 6 | var StatsPlugin = require('stats-webpack-plugin'); 7 | 8 | // must match config.webpack.dev_server.port 9 | var devServerPort = 3808; 10 | 11 | // set NODE_ENV=production on the environment to add asset fingerprints 12 | var production = process.env.NODE_ENV === 'production'; 13 | 14 | var config = { 15 | entry: { 16 | // Sources are expected to live in $app_root/webpack 17 | 'application': './webpack/application.js' 18 | }, 19 | 20 | output: { 21 | // Build assets directly in to public/webpack/, let webpack know 22 | // that all webpacked assets start with webpack/ 23 | 24 | // must match config.webpack.output_dir 25 | path: path.join(__dirname, '..', 'public', 'webpack'), 26 | publicPath: '/webpack/', 27 | 28 | filename: production ? '[name]-[chunkhash].js' : '[name].js' 29 | }, 30 | 31 | resolve: { 32 | root: path.join(__dirname, '..', 'webpack') 33 | }, 34 | 35 | plugins: [ 36 | // must match config.webpack.manifest_filename 37 | new StatsPlugin('manifest.json', { 38 | // We only need assetsByChunkName 39 | chunkModules: false, 40 | source: false, 41 | chunks: false, 42 | modules: false, 43 | assets: true 44 | })] 45 | }; 46 | 47 | if (production) { 48 | config.plugins.push( 49 | new webpack.NoErrorsPlugin(), 50 | new webpack.optimize.UglifyJsPlugin({ 51 | compressor: { warnings: false }, 52 | sourceMap: false 53 | }), 54 | new webpack.DefinePlugin({ 55 | 'process.env': { NODE_ENV: JSON.stringify('production') } 56 | }), 57 | new webpack.optimize.DedupePlugin(), 58 | new webpack.optimize.OccurenceOrderPlugin() 59 | ); 60 | } else { 61 | config.devServer = { 62 | port: devServerPort, 63 | headers: { 'Access-Control-Allow-Origin': '*' } 64 | }; 65 | config.output.publicPath = '//localhost:' + devServerPort + '/webpack/'; 66 | // Source maps 67 | config.devtool = 'cheap-module-eval-source-map'; 68 | } 69 | 70 | module.exports = config; 71 | -------------------------------------------------------------------------------- /lib/generators/webpack_rails/install_generator.rb: -------------------------------------------------------------------------------- 1 | module WebpackRails 2 | # :nodoc: 3 | class InstallGenerator < ::Rails::Generators::Base 4 | source_root File.expand_path("../../../../example", __FILE__) 5 | 6 | desc "Install everything you need for a basic webpack-rails integration" 7 | 8 | def add_foreman_to_gemfile 9 | gem 'foreman' 10 | end 11 | 12 | def copy_procfile 13 | copy_file "Procfile", "Procfile" 14 | end 15 | 16 | def copy_package_json 17 | copy_file "package.json", "package.json" 18 | end 19 | 20 | def copy_webpack_conf 21 | copy_file "webpack.config.js", "config/webpack.config.js" 22 | end 23 | 24 | def create_webpack_application_js 25 | empty_directory "webpack" 26 | create_file "webpack/application.js" do 27 | <<-EOF.strip_heredoc 28 | console.log("Hello world!"); 29 | EOF 30 | end 31 | end 32 | 33 | def add_to_gitignore 34 | append_to_file ".gitignore" do 35 | <<-EOF.strip_heredoc 36 | # Added by webpack-rails 37 | /node_modules 38 | /public/webpack 39 | EOF 40 | end 41 | end 42 | 43 | def run_yarn_install 44 | run "yarn install" if yes?("Would you like us to run 'yarn install' for you?") 45 | end 46 | 47 | def run_bundle_install 48 | run "bundle install" if yes?("Would you like us to run 'bundle install' for you?") 49 | end 50 | 51 | def whats_next 52 | puts <<-EOF.strip_heredoc 53 | 54 | We've set up the basics of webpack-rails for you, but you'll still 55 | need to: 56 | 57 | 1. Add the 'application' entry point in to your layout, and 58 | 2. Run 'foreman start' to run the webpack-dev-server and rails server 59 | 60 | See the README.md for this gem at 61 | https://github.com/mipearson/webpack-rails/blob/master/README.md 62 | for more info. 63 | 64 | Thanks for using webpack-rails! 65 | 66 | EOF 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/tasks/webpack.rake: -------------------------------------------------------------------------------- 1 | namespace :webpack do 2 | desc "Compile webpack bundles" 3 | task compile: :environment do 4 | ENV["TARGET"] = 'production' # TODO: Deprecated, use NODE_ENV instead 5 | ENV["NODE_ENV"] ||= 'production' 6 | webpack_bin = ::Rails.root.join(::Rails.configuration.webpack.binary) 7 | config_file = ::Rails.root.join(::Rails.configuration.webpack.config_file) 8 | 9 | unless File.exist?(webpack_bin) 10 | raise "Can't find our webpack executable at #{webpack_bin} - have you run `npm install`?" 11 | end 12 | 13 | unless File.exist?(config_file) 14 | raise "Can't find our webpack config file at #{config_file}" 15 | end 16 | 17 | sh "#{webpack_bin} --config #{config_file} --bail" 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/webpack-rails.rb: -------------------------------------------------------------------------------- 1 | require 'webpack/rails' 2 | -------------------------------------------------------------------------------- /lib/webpack/rails.rb: -------------------------------------------------------------------------------- 1 | require 'webpack/rails/version' 2 | require 'webpack/railtie' if defined? ::Rails::Railtie 3 | -------------------------------------------------------------------------------- /lib/webpack/rails/helper.rb: -------------------------------------------------------------------------------- 1 | require 'action_view' 2 | require 'webpack/rails/manifest' 3 | 4 | module Webpack 5 | module Rails 6 | # Asset path helpers for use with webpack 7 | module Helper 8 | # Return asset paths for a particular webpack entry point. 9 | # 10 | # Response may either be full URLs (eg http://localhost/...) if the dev server 11 | # is in use or a host-relative URl (eg /webpack/...) if assets are precompiled. 12 | # 13 | # Will raise an error if our manifest can't be found or the entry point does 14 | # not exist. 15 | def webpack_asset_paths(source, extension: nil) 16 | return "" unless source.present? 17 | 18 | paths = Webpack::Rails::Manifest.asset_paths(source) 19 | paths = paths.select { |p| p.ends_with? ".#{extension}" } if extension 20 | 21 | port = ::Rails.configuration.webpack.dev_server.port 22 | protocol = ::Rails.configuration.webpack.dev_server.https ? 'https' : 'http' 23 | 24 | host = ::Rails.configuration.webpack.dev_server.host 25 | host = instance_eval(&host) if host.respond_to?(:call) 26 | 27 | if ::Rails.configuration.webpack.dev_server.enabled 28 | paths.map! do |p| 29 | "#{protocol}://#{host}:#{port}#{p}" 30 | end 31 | end 32 | 33 | paths 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/webpack/rails/manifest.rb: -------------------------------------------------------------------------------- 1 | require 'net/http' 2 | require 'uri' 3 | 4 | module Webpack 5 | module Rails 6 | # Webpack manifest loading, caching & entry point retrieval 7 | class Manifest 8 | # Raised if we can't read our webpack manifest for whatever reason 9 | class ManifestLoadError < StandardError 10 | def initialize(message, orig) 11 | super "#{message} (original error #{orig})" 12 | end 13 | end 14 | 15 | # Raised if webpack couldn't build one of your entry points 16 | class WebpackError < StandardError 17 | def initialize(errors) 18 | super "Error in webpack compile, details follow below:\n#{errors.join("\n\n")}" 19 | end 20 | end 21 | 22 | # Raised if a supplied entry point does not exist in the webpack manifest 23 | class EntryPointMissingError < StandardError 24 | end 25 | 26 | class << self 27 | # :nodoc: 28 | def asset_paths(source) 29 | raise WebpackError, manifest["errors"] unless manifest_bundled? 30 | 31 | paths = manifest["assetsByChunkName"][source] 32 | if paths 33 | # Can be either a string or an array of strings. 34 | # Do not include source maps as they are not javascript 35 | [paths].flatten.reject { |p| p =~ /.*\.map$/ }.map do |p| 36 | "/#{::Rails.configuration.webpack.public_path}/#{p}" 37 | end 38 | else 39 | raise EntryPointMissingError, "Can't find entry point '#{source}' in webpack manifest" 40 | end 41 | end 42 | 43 | private 44 | 45 | def manifest_bundled? 46 | !manifest["errors"].any? { |error| error.include? "Module build failed" } 47 | end 48 | 49 | def manifest 50 | if ::Rails.configuration.webpack.dev_server.enabled 51 | # Don't cache if we're in dev server mode, manifest may change ... 52 | load_manifest 53 | else 54 | # ... otherwise cache at class level, as JSON loading/parsing can be expensive 55 | @manifest ||= load_manifest 56 | end 57 | end 58 | 59 | def load_manifest 60 | data = if ::Rails.configuration.webpack.dev_server.enabled 61 | load_dev_server_manifest 62 | else 63 | load_static_manifest 64 | end 65 | JSON.parse(data) 66 | end 67 | 68 | def load_dev_server_manifest 69 | host = ::Rails.configuration.webpack.dev_server.manifest_host 70 | port = ::Rails.configuration.webpack.dev_server.manifest_port 71 | http = Net::HTTP.new(host, port) 72 | http.use_ssl = ::Rails.configuration.webpack.dev_server.https 73 | http.verify_mode = ::Rails.configuration.webpack.dev_server.https_verify_peer ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE 74 | http.get(dev_server_path).body 75 | rescue => e 76 | raise ManifestLoadError.new("Could not load manifest from webpack-dev-server at http://#{host}:#{port}#{dev_server_path} - is it running, and is stats-webpack-plugin loaded?", e) 77 | end 78 | 79 | def load_static_manifest 80 | File.read(static_manifest_path) 81 | rescue => e 82 | raise ManifestLoadError.new("Could not load compiled manifest from #{static_manifest_path} - have you run `rake webpack:compile`?", e) 83 | end 84 | 85 | def static_manifest_path 86 | ::Rails.root.join( 87 | ::Rails.configuration.webpack.output_dir, 88 | ::Rails.configuration.webpack.manifest_filename 89 | ) 90 | end 91 | 92 | def dev_server_path 93 | "/#{::Rails.configuration.webpack.public_path}/#{::Rails.configuration.webpack.manifest_filename}" 94 | end 95 | 96 | def dev_server_url 97 | "http://#{::Rails.configuration.webpack.dev_server.host}:#{::Rails.configuration.webpack.dev_server.port}#{dev_server_path}" 98 | end 99 | end 100 | end 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/webpack/rails/version.rb: -------------------------------------------------------------------------------- 1 | module Webpack 2 | # :nodoc: 3 | module Rails 4 | VERSION = "0.9.11" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/webpack/railtie.rb: -------------------------------------------------------------------------------- 1 | require 'rails' 2 | require 'rails/railtie' 3 | require 'webpack/rails/helper' 4 | 5 | module Webpack 6 | # :nodoc: 7 | class Railtie < ::Rails::Railtie 8 | config.after_initialize do 9 | ActiveSupport.on_load(:action_view) do 10 | include Webpack::Rails::Helper 11 | end 12 | end 13 | 14 | config.webpack = ActiveSupport::OrderedOptions.new 15 | config.webpack.dev_server = ActiveSupport::OrderedOptions.new 16 | 17 | config.webpack.config_file = 'config/webpack.config.js' 18 | config.webpack.binary = 'node_modules/.bin/webpack' 19 | 20 | # Host & port to use when generating asset URLS in the manifest helpers in dev 21 | # server mode. Defaults to the requested host rather than localhost, so 22 | # that requests from remote hosts work. 23 | config.webpack.dev_server.host = proc { respond_to?(:request) ? request.host : 'localhost' } 24 | config.webpack.dev_server.port = 3808 25 | 26 | # The host and port to use when fetching the manifest 27 | # This is helpful for e.g. docker containers, where the host and port you 28 | # use via the web browser is not the same as those that the containers use 29 | # to communicate among each other 30 | config.webpack.dev_server.manifest_host = 'localhost' 31 | config.webpack.dev_server.manifest_port = 3808 32 | 33 | config.webpack.dev_server.https = false 34 | # Below will default to 'true' in 1.0 release 35 | config.webpack.dev_server.https_verify_peer = false 36 | config.webpack.dev_server.binary = 'node_modules/.bin/webpack-dev-server' 37 | config.webpack.dev_server.enabled = ::Rails.env.development? || ::Rails.env.test? 38 | 39 | config.webpack.output_dir = "public/webpack" 40 | config.webpack.public_path = "webpack" 41 | config.webpack.manifest_filename = "manifest.json" 42 | 43 | rake_tasks do 44 | load "tasks/webpack.rake" 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'webpack_asset_paths' do 4 | let(:source) { 'entry_point' } 5 | let(:asset_paths) { %w(/a/a.js /b/b.css) } 6 | 7 | include Webpack::Rails::Helper 8 | 9 | before do 10 | allow(Webpack::Rails::Manifest).to receive(:asset_paths).with(source).and_return(asset_paths) 11 | end 12 | 13 | it "should return paths straight from te manifest if the dev server is disabled" do 14 | ::Rails.configuration.webpack.dev_server.enabled = false 15 | expect(webpack_asset_paths source).to eq(asset_paths) 16 | end 17 | 18 | it "should allow us to filter asset paths by extension" do 19 | ::Rails.configuration.webpack.dev_server.enabled = false 20 | expect(webpack_asset_paths(source, extension: 'js')).to eq(["/a/a.js"]) 21 | expect(webpack_asset_paths(source, extension: 'css')).to eq(["/b/b.css"]) 22 | expect(webpack_asset_paths(source, extension: 'html')).to eq([]) 23 | end 24 | 25 | it "should have the user talk to the dev server if it's enabled for each path returned from the manifest defaulting to localhost" do 26 | ::Rails.configuration.webpack.dev_server.enabled = true 27 | ::Rails.configuration.webpack.dev_server.host = 'webpack.host' 28 | ::Rails.configuration.webpack.dev_server.port = 4000 29 | 30 | expect(webpack_asset_paths source).to eq([ 31 | "http://webpack.host:4000/a/a.js", "http://webpack.host:4000/b/b.css" 32 | ]) 33 | end 34 | 35 | it "should use https protocol when https is true" do 36 | ::Rails.configuration.webpack.dev_server.https = true 37 | expect(webpack_asset_paths(source, extension: 'js').first).to be_starts_with('https:') 38 | end 39 | 40 | it "should use http protocol when https is false" do 41 | ::Rails.configuration.webpack.dev_server.https = false 42 | expect(webpack_asset_paths(source, extension: 'js').first).to be_starts_with('http:') 43 | end 44 | 45 | it "should have the user talk to the specified dev server if it's enabled for each path returned from the manifest" do 46 | ::Rails.configuration.webpack.dev_server.enabled = true 47 | ::Rails.configuration.webpack.dev_server.port = 4000 48 | ::Rails.configuration.webpack.dev_server.host = 'webpack.host' 49 | 50 | expect(webpack_asset_paths source).to eq([ 51 | "http://webpack.host:4000/a/a.js", "http://webpack.host:4000/b/b.css" 52 | ]) 53 | end 54 | 55 | it "allows for the host to be evaluated at request time" do 56 | # Simulate the helper context 57 | request = double(:request, host: 'evaluated') 58 | 59 | ::Rails.configuration.webpack.dev_server.enabled = true 60 | ::Rails.configuration.webpack.dev_server.port = 4000 61 | ::Rails.configuration.webpack.dev_server.host = proc { request.host } 62 | 63 | expect(webpack_asset_paths source).to eq([ 64 | "http://evaluated:4000/a/a.js", "http://evaluated:4000/b/b.css" 65 | ]) 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /spec/manifest_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'json' 3 | 4 | describe "Webpack::Rails::Manifest" do 5 | let(:manifest) do 6 | <<-EOF 7 | { 8 | "errors": [], 9 | "assetsByChunkName": { 10 | "entry1": [ "entry1.js", "entry1-a.js" ], 11 | "entry2": "entry2.js" 12 | } 13 | } 14 | EOF 15 | end 16 | 17 | shared_examples_for "a valid manifest" do 18 | it "should return single entry asset paths from the manifest" do 19 | expect(Webpack::Rails::Manifest.asset_paths("entry2")).to eq(["/public_path/entry2.js"]) 20 | end 21 | 22 | it "should return multiple entry asset paths from the manifest" do 23 | expect(Webpack::Rails::Manifest.asset_paths("entry1")).to eq(["/public_path/entry1.js", "/public_path/entry1-a.js"]) 24 | end 25 | 26 | it "should error on a missing entry point" do 27 | expect { Webpack::Rails::Manifest.asset_paths("herp") }.to raise_error(Webpack::Rails::Manifest::EntryPointMissingError) 28 | end 29 | end 30 | 31 | before do 32 | # Test that config variables work while we're here 33 | ::Rails.configuration.webpack.dev_server.host = 'client-host' 34 | ::Rails.configuration.webpack.dev_server.port = 2000 35 | ::Rails.configuration.webpack.dev_server.manifest_host = 'server-host' 36 | ::Rails.configuration.webpack.dev_server.manifest_port = 4000 37 | ::Rails.configuration.webpack.manifest_filename = "my_manifest.json" 38 | ::Rails.configuration.webpack.public_path = "public_path" 39 | ::Rails.configuration.webpack.output_dir = "manifest_output" 40 | end 41 | 42 | context "with dev server enabled" do 43 | before do 44 | ::Rails.configuration.webpack.dev_server.enabled = true 45 | 46 | stub_request(:get, "http://server-host:4000/public_path/my_manifest.json").to_return(body: manifest, status: 200) 47 | end 48 | 49 | describe :asset_paths do 50 | it_should_behave_like "a valid manifest" 51 | 52 | it "should error if we can't find the manifest" do 53 | ::Rails.configuration.webpack.manifest_filename = "broken.json" 54 | stub_request(:get, "http://server-host:4000/public_path/broken.json").to_raise(SocketError) 55 | 56 | expect { Webpack::Rails::Manifest.asset_paths("entry1") }.to raise_error(Webpack::Rails::Manifest::ManifestLoadError) 57 | end 58 | 59 | describe "webpack errors" do 60 | context "when webpack has 'Module build failed' errors in its manifest" do 61 | it "should error" do 62 | error_manifest = JSON.parse(manifest).merge("errors" => [ 63 | "somethingModule build failed something", 64 | "I am an error" 65 | ]).to_json 66 | stub_request(:get, "http://server-host:4000/public_path/my_manifest.json").to_return(body: error_manifest, status: 200) 67 | 68 | expect { Webpack::Rails::Manifest.asset_paths("entry1") }.to raise_error(Webpack::Rails::Manifest::WebpackError) 69 | end 70 | end 71 | 72 | context "when webpack does not have 'Module build failed' errors in its manifest" do 73 | it "should not error" do 74 | error_manifest = JSON.parse(manifest).merge("errors" => ["something went wrong"]).to_json 75 | stub_request(:get, "http://server-host:4000/public_path/my_manifest.json").to_return(body: error_manifest, status: 200) 76 | 77 | expect { Webpack::Rails::Manifest.asset_paths("entry1") }.to_not raise_error 78 | end 79 | end 80 | 81 | it "should not error if errors is present but empty" do 82 | error_manifest = JSON.parse(manifest).merge("errors" => []).to_json 83 | stub_request(:get, "http://server-host:4000/public_path/my_manifest.json").to_return(body: error_manifest, status: 200) 84 | expect { Webpack::Rails::Manifest.asset_paths("entry1") }.to_not raise_error 85 | end 86 | end 87 | end 88 | end 89 | 90 | context "with dev server disabled" do 91 | before do 92 | ::Rails.configuration.webpack.dev_server.enabled = false 93 | allow(File).to receive(:read).with(::Rails.root.join("manifest_output/my_manifest.json")).and_return(manifest) 94 | end 95 | 96 | describe :asset_paths do 97 | it_should_behave_like "a valid manifest" 98 | 99 | it "should error if we can't find the manifest" do 100 | ::Rails.configuration.webpack.manifest_filename = "broken.json" 101 | allow(File).to receive(:read).with(::Rails.root.join("manifest_output/broken.json")).and_raise(Errno::ENOENT) 102 | expect { Webpack::Rails::Manifest.asset_paths("entry1") }.to raise_error(Webpack::Rails::Manifest::ManifestLoadError) 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "rspec" 2 | require "rails" 3 | require "webpack/rails" 4 | require 'webmock/rspec' 5 | 6 | module Dummy 7 | # :nodoc: 8 | class Application < Rails::Application 9 | config.eager_load = false 10 | end 11 | end 12 | 13 | Rails.application.initialize! 14 | 15 | # Load support files 16 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 17 | -------------------------------------------------------------------------------- /webpack-rails.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "webpack/rails/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "webpack-rails" 9 | s.version = Webpack::Rails::VERSION 10 | s.authors = ["Michael Pearson"] 11 | s.email = ["mipearson@gmail.com"] 12 | s.homepage = "http://github.com/mipearson/webpack-rails" 13 | s.summary = "No longer maintained - use webpacker instead." 14 | s.description = "No longer maintained - use webpacker instead." 15 | s.license = "MIT" 16 | 17 | s.files = Dir["{app,config,db,lib,example}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] 18 | s.test_files = Dir["test/**/*"] 19 | 20 | s.add_dependency "railties", ">= 3.2.0" 21 | s.add_development_dependency "rails", ">= 3.2.0" 22 | s.required_ruby_version = '>= 2.0.0' 23 | end 24 | --------------------------------------------------------------------------------