├── .gitignore ├── CODE_OF_CONDUCT.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── webpackrails.rb └── webpackrails │ ├── railtie.rb │ ├── tasks │ └── npm.rake │ ├── version.rb │ ├── webpack_error.rb │ ├── webpack_logger.rb │ └── webpack_processor.rb └── webpackrails.gemspec /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | */.DS_Store 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://ruby.taobao.org/' 2 | 3 | # Specify your gem's dependencies in webpack-rails.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 towry 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WebpackRails 2 | 3 | **v2.0.0** 4 | 5 | This gem make [Webpack](http://webpack.github.io) works with [Rails](http://github.com/rails/rails). 6 | 7 | ## Installation 8 | 9 | *Rails: >= 3* 10 | 11 | Add this line to your application's Gemfile: 12 | 13 | ```ruby 14 | gem 'webpackrails' 15 | ``` 16 | 17 | And then execute: 18 | 19 | $ bundle 20 | 21 | Or install it yourself as: 22 | 23 | $ gem install webpackrails 24 | 25 | Create `package.json` in your Rails root: 26 | 27 | ```json 28 | { 29 | "name": "app", 30 | "dependencies": { 31 | "webpack": "^1.11.0", 32 | "railshot-webpack-plugin": "*" 33 | } 34 | } 35 | ``` 36 | 37 | Run `npm i` to install the dependencies. 38 | 39 | ## Usage 40 | 41 | Make sure you have a `webpack.config.js` file in your Rails root dir, or specific your 42 | webpack config file within `application.rb` 43 | 44 | `config.webpackrails.config_file = "path_to_the_config_file"` 45 | 46 | #### Watch file for changes 47 | 48 | Use webpack plugin `railshot-webpack-plugin` in your `webpack.config.js`. 49 | If you haven't install the plugin yet, run `npm install railshot-webpack-plugin`. 50 | 51 | ```js 52 | // webpack.config.js 53 | 54 | var railshotPlugin = require('railshot-webpack-plugin'); 55 | 56 | // no entry here. 57 | module.exports = { 58 | plugins: [ 59 | railshotPlugin() 60 | ] 61 | } 62 | ``` 63 | 64 | #### Troubleshooting 65 | 66 | [Wiki troubleshooting](https://github.com/towry/webpackrails/wiki/Troubleshooting) 67 | 68 | 69 | ## Feature 70 | 71 | * Support embedding erb in javascript, see [troubleshooting](https://github.com/towry/webpackrails/wiki/Troubleshooting) page. **Aug 16, 2015** 72 | * Support es6 (just use webpack babel loader) 73 | * Support react jsx syntax (just use webpack babel loader) 74 | 75 | ## Config 76 | 77 | See [source](https://raw.githubusercontent.com/towry/webpackrails/master/lib/webpackrails/railtie.rb) 78 | 79 | 80 | ## Development 81 | 82 | After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment. 83 | 84 | To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). 85 | 86 | ## Contributing 87 | 88 | Bug reports and pull requests are welcome on GitHub at https://github.com/towry/webpackrails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct. 89 | 90 | 91 | ## License 92 | 93 | The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). 94 | 95 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "webpackrails" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | 5 | bundle install 6 | 7 | # Do any other automated setup that you need to do here 8 | -------------------------------------------------------------------------------- /lib/webpackrails.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'sprockets' 4 | 5 | require 'webpackrails/webpack_processor' 6 | require 'webpackrails/webpack_logger' 7 | require 'webpackrails/webpack_error' 8 | require 'webpackrails/railtie' 9 | require 'webpackrails/version' 10 | 11 | module WebpackRails 12 | end 13 | -------------------------------------------------------------------------------- /lib/webpackrails/railtie.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module WebpackRails 4 | class Railtie < Rails::Engine 5 | config.webpackrails = ActiveSupport::OrderedOptions.new 6 | 7 | # Webpack config file location 8 | config.webpackrails.config_file = '' 9 | 10 | # Process every file? 11 | config.webpackrails.force = false 12 | 13 | # paths to be parse 14 | config.webpackrails.paths = [lambda { |p| p.start_with?(Rails.root.join("app").to_s) }, 15 | lambda { |p| p.start_with?(Rails.root.join('node_modules').to_s) }] 16 | 17 | config.webpackrails.node_bin = "node_modules/.bin/" 18 | 19 | # ignore node_modules 20 | config.webpackrails.ignore_node_modules = true 21 | 22 | # embed erb 23 | config.webpackrails.embed_erb = false 24 | 25 | # array of string to test if the file need to be process by this gem. 26 | # see `commonjs_module?` method 27 | config.webpackrails.force_condition = [] 28 | 29 | initializer :setup_webpack do |app| 30 | app.assets.register_postprocessor "application/javascript", WebpackRails::WebpackProcessor 31 | end 32 | 33 | rake_tasks do 34 | Dir[File.join(File.dirname(__FILE__), "tasks/*.rake")].each { |f| load f } 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/webpackrails/tasks/npm.rake: -------------------------------------------------------------------------------- 1 | namespace :npm do 2 | desc "Run npm install" 3 | task :install do 4 | sh "npm install && npm --save install railshot-webpack-plugin" do |ok, res| 5 | fail "Error running npm install." unless ok 6 | end 7 | end 8 | 9 | desc "Clean npm node_modules" 10 | task :clean do 11 | sh "rm -rf ./node_modules" do |ok, res| 12 | fail "Error cleaning npm node_modules." unless ok 13 | end 14 | end 15 | 16 | namespace :install do 17 | desc "Run a clean npm install" 18 | task :clean => ['npm:clean', 'npm:install'] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/webpackrails/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module WebpackRails 4 | VERSION = "2.0.0" 5 | end 6 | -------------------------------------------------------------------------------- /lib/webpackrails/webpack_error.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module WebpackRails 4 | class WebpackError < RuntimeError 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/webpackrails/webpack_logger.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | module WebpackRails 4 | module Logger 5 | def self.log(message) 6 | Rails.logger.debug message 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/webpackrails/webpack_processor.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'tilt' 4 | require 'open3' 5 | require 'tempfile' 6 | require 'fileutils' 7 | require 'shellwords' 8 | require 'digest/sha1' 9 | 10 | module WebpackRails 11 | class WebpackProcessor < Tilt::Template 12 | attr_accessor :config 13 | 14 | def initialize(template) 15 | self.config = Rails.application.config.webpackrails 16 | super(template) 17 | end 18 | 19 | def prepare 20 | ensure_tmp_dir_exists! 21 | ensure_commands_exists! 22 | ensure_webpack_config_exists! 23 | end 24 | 25 | def evaluate(context, locals, &block) 26 | # return if there is nothing to do 27 | return data unless should_webpack? 28 | 29 | if config.embed_erb 30 | run_webpack(context.pathname || context.logical_path, false) 31 | 32 | evaluate_dependencies(context.environment.paths).each do |path| 33 | context.depend_on(path.to_s) 34 | end 35 | 36 | if !@erb_deps.empty? 37 | evaluate_erb_deps(context, locals) 38 | end 39 | 40 | evaluated = run_webpack(context.pathname || context.logical_path) 41 | else 42 | evaluated = run_webpack(context.pathname || context.logical_path) 43 | evaluate_dependencies(context.environment.paths).each do |path| 44 | context.depend_on(path.to_s) 45 | end 46 | end 47 | 48 | evaluated 49 | end 50 | 51 | private 52 | 53 | def evaluate_erb_deps(context, locals) 54 | return if @erb_deps.empty? 55 | 56 | # dep is a path 57 | @erb_deps.each do |dep| 58 | begin 59 | shaname = sha1(dep) 60 | tmpfile = File.join(@erb_deps_root, shaname) 61 | template = Tilt::ERBTemplate.new(dep) 62 | template = template.render(context, locals) 63 | file = File.open(tmpfile, 'w') 64 | file.write(template) 65 | file.close() 66 | end 67 | end 68 | end 69 | 70 | def sha1(content) 71 | Digest::SHA1.hexdigest content 72 | end 73 | 74 | # Set the temp path 75 | def tmp_path 76 | @tmp_path ||= Rails.root.join('tmp', 'cache', 'webpackrails').freeze 77 | end 78 | 79 | # return the webpack command path 80 | def webpack_cmd 81 | @webpack_cmd ||= rails_path(config.node_bin, "webpack").freeze 82 | end 83 | 84 | # make sure the temp dir exists 85 | def ensure_tmp_dir_exists! 86 | FileUtils.mkdir_p(rails_path(tmp_path)) 87 | @deps_path ||= File.join(tmp_path, '_$webpackrails_dependencies'); 88 | @erb_deps_root ||= rails_path('tmp/cache/webpackrails/erbs').freeze 89 | 90 | FileUtils.mkdir_p(@erb_deps_root) 91 | end 92 | 93 | # Filter out node_module/ files and erb files. 94 | def evaluate_dependencies(asset_paths) 95 | return dependencies if !config.ignore_node_modules and !config.embed_erb 96 | 97 | @erb_deps = [] 98 | deps = dependencies.select do |path| 99 | if File.extname(path) == '.erb' 100 | @erb_deps << path 101 | end 102 | path.start_with?(*asset_paths) 103 | end 104 | end 105 | 106 | # return array 107 | def dependencies 108 | ret ||= begin 109 | if !File.exists?(@deps_path) 110 | return [] 111 | end 112 | 113 | output = '' 114 | begin 115 | file = File.open(@deps_path, 'r') 116 | output = file.read 117 | file.close 118 | rescue 119 | output = '' 120 | end 121 | 122 | if !output 123 | return [] 124 | end 125 | 126 | output.lines.map(&:strip).select do |path| 127 | File.exists?(path) 128 | end 129 | end 130 | end 131 | 132 | # make sure the webpack config file exists 133 | def ensure_webpack_config_exists! 134 | @config_file ||= config.config_file 135 | 136 | if @config_file.blank? 137 | @config_file = Rails.root.join('webpack.config.js').freeze 138 | end 139 | 140 | if !File.exists?(@config_file) 141 | raise WebpackRails::WebpackError.new("Webpack config file not found.") 142 | end 143 | end 144 | 145 | # make sure the `webpack` command exists 146 | def ensure_commands_exists! 147 | error = ->(cmd) { "Unable to run #{cmd}. Ensure you have installed it with npm." } 148 | 149 | if !File.exists?(rails_path(webpack_cmd)) 150 | raise WebpackRails::WebpackError.new(error.call(webpack_cmd)) 151 | end 152 | end 153 | 154 | # should we use webpack to parse that file? 155 | def should_webpack? 156 | config.force || (in_path? && !webpacked? && commonjs_module?) 157 | end 158 | 159 | def in_path? 160 | config.paths.any? { |p| p === file } 161 | end 162 | 163 | # Need check. 164 | def webpacked? 165 | data.to_s.include?("__webpack_require__") 166 | end 167 | 168 | def commonjs_module? 169 | return false if !data.present? 170 | 171 | condition = ["module.exports", "require", "import", "export", "exports", "export default", "React"] 172 | data_s = data.to_s 173 | 174 | (condition + config.force_condition).uniq.any? { |c| data_s.include?(c) } 175 | end 176 | 177 | def asset_paths 178 | @asset_paths ||= Rails.application.config.assets.paths.collect { |p| p.to_s }.join(":") || "" 179 | end 180 | 181 | def env 182 | env_hash = {} 183 | env_hash["NODE_PATH"] = asset_paths unless uses_exorcist 184 | env_hash["NODE_ENV"] = config.node_env || Rails.env 185 | env_hash["WR_TMP_FILE"] = @deps_path; 186 | env_hash['RAILS_ROOT'] = Rails.root.to_s 187 | env_hash 188 | end 189 | 190 | # Not supported yet. 191 | def uses_exorcist 192 | false 193 | end 194 | 195 | def run_webpack(logical_path=nil, bail= true) 196 | if bail 197 | command_options = "--colors --config #{@config_file} #{logical_path} --bail --output-filename" 198 | else 199 | command_options = "--colors --config #{@config_file} #{logical_path} --output-filename" 200 | end 201 | 202 | output_file = Tempfile.new("output", rails_path(tmp_path)) 203 | command_options << " #{output_file.path.inspect}" 204 | 205 | command = "#{Shellwords.escape(webpack_cmd)} #{command_options}" 206 | 207 | base_directory = File.dirname(file) 208 | 209 | Logger::log "\nWebpack: #{command}" 210 | 211 | mut_env = ENV.to_hash 212 | mut_env.merge!(env) 213 | ENV.replace(mut_env) 214 | stdout, stderr, status = Open3.capture3(env, command, stdin_data: data, chdir: base_directory) 215 | 216 | if !status.success? 217 | raise WebpackRails::WebpackError.new("Error while running `#{command}`:\n\n#{stderr}") 218 | end 219 | 220 | output = output_file.read 221 | 222 | output_file.close 223 | output_file.unlink 224 | 225 | output 226 | end 227 | 228 | def rails_path(*paths) 229 | Rails.root.join(*paths).to_s 230 | end 231 | end 232 | end 233 | -------------------------------------------------------------------------------- /webpackrails.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'webpackrails/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "webpackrails" 8 | spec.version = WebpackRails::VERSION 9 | spec.authors = ["towry"] 10 | spec.email = ["tovvry@gmail.com"] 11 | 12 | spec.summary = %q{Make Webpack work with Rails for you} 13 | spec.description = %q{Webpack + Rails ≠ CommonJS Heaven} 14 | spec.homepage = "https://github.com/towry/webpackrails" 15 | spec.license = "MIT" 16 | 17 | # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or 18 | # delete this section to allow pushing this gem to any host. 19 | if spec.respond_to?(:metadata) 20 | spec.metadata['allowed_push_host'] = "https://rubygems.org" 21 | else 22 | raise "RubyGems 2.0 or newer is required to protect against public gem pushes." 23 | end 24 | 25 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 26 | spec.bindir = "exe" 27 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 28 | spec.require_paths = ["lib"] 29 | 30 | spec.add_runtime_dependency "railties", ">= 3.0" 31 | spec.add_runtime_dependency "sprockets", "~> 2.0" 32 | 33 | spec.add_development_dependency "rake", "~> 10.0" 34 | spec.add_development_dependency "bundler", ">= 1.3" 35 | spec.add_development_dependency "rails" 36 | spec.add_development_dependency "pry" 37 | spec.add_development_dependency "tilt" 38 | end 39 | --------------------------------------------------------------------------------