├── .eslintignore ├── .eslintrc.js ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature-request.md ├── dependabot.yml └── workflows │ ├── jest.yml │ ├── js-lint.yml │ ├── rubocop.yml │ └── ruby.yml ├── .gitignore ├── .node-version ├── .rubocop.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── MIT-LICENSE ├── README.md ├── Rakefile ├── config ├── README.md └── webpacker.yml ├── docs ├── customizing_babel_config.md ├── deployment.md ├── developing_webpacker.md ├── troubleshooting.md └── v6_upgrade.md ├── gemfiles ├── Gemfile-rails-edge ├── Gemfile-rails.5.2.x ├── Gemfile-rails.6.0.x └── Gemfile-rails.6.1.x ├── lib ├── install │ ├── application.js │ ├── bin │ │ ├── webpacker │ │ ├── webpacker-dev-server │ │ └── yarn │ ├── binstubs.rb │ ├── config │ │ ├── webpack │ │ │ ├── base.js │ │ │ ├── development.js │ │ │ ├── production.js │ │ │ └── test.js │ │ └── webpacker.yml │ ├── package.json │ └── template.rb ├── tasks │ ├── webpacker.rake │ ├── webpacker │ │ ├── binstubs.rake │ │ ├── check_binstubs.rake │ │ ├── check_node.rake │ │ ├── check_yarn.rake │ │ ├── clean.rake │ │ ├── clobber.rake │ │ ├── compile.rake │ │ ├── info.rake │ │ ├── install.rake │ │ ├── verify_config.rake │ │ ├── verify_install.rake │ │ └── yarn_install.rake │ └── yarn.rake ├── webpacker.rb └── webpacker │ ├── commands.rb │ ├── compiler.rb │ ├── configuration.rb │ ├── dev_server.rb │ ├── dev_server_proxy.rb │ ├── dev_server_runner.rb │ ├── env.rb │ ├── helper.rb │ ├── instance.rb │ ├── manifest.rb │ ├── railtie.rb │ ├── runner.rb │ ├── version.rb │ └── webpack_runner.rb ├── package.json ├── package ├── __tests__ │ ├── config.js │ ├── dev_server.js │ ├── development.js │ ├── env.js │ ├── index.js │ ├── production.js │ ├── staging.js │ └── test.js ├── babel │ └── preset.js ├── config.js ├── configPath.js ├── dev_server.js ├── env.js ├── environments │ ├── __tests__ │ │ └── base.js │ ├── base.js │ ├── development.js │ ├── production.js │ └── test.js ├── index.js ├── inliningCss.js ├── rules │ ├── babel.js │ ├── coffee.js │ ├── css.js │ ├── erb.js │ ├── file.js │ ├── index.js │ ├── less.js │ ├── raw.js │ ├── sass.js │ └── stylus.js └── utils │ ├── get_style_rule.js │ └── helpers.js ├── test ├── command_test.rb ├── compiler_test.rb ├── configuration_test.rb ├── dev_server_runner_test.rb ├── dev_server_test.rb ├── engine_rake_tasks_test.rb ├── env_test.rb ├── helper_test.rb ├── manifest_test.rb ├── mounted_app │ ├── Rakefile │ └── test │ │ └── dummy │ │ ├── Rakefile │ │ ├── bin │ │ ├── rails │ │ └── rake │ │ ├── config.ru │ │ ├── config │ │ ├── application.rb │ │ ├── environment.rb │ │ └── webpacker.yml │ │ └── package.json ├── rake_tasks_test.rb ├── test_app │ ├── Rakefile │ ├── app │ │ └── packs │ │ │ └── entrypoints │ │ │ ├── application.js │ │ │ ├── multi_entry.css │ │ │ └── multi_entry.js │ ├── bin │ │ ├── webpacker │ │ └── webpacker-dev-server │ ├── config.ru │ ├── config │ │ ├── application.rb │ │ ├── environment.rb │ │ ├── initializers │ │ │ └── inspect_autoload_paths.rb │ │ ├── webpack │ │ │ └── development.js │ │ ├── webpacker.yml │ │ ├── webpacker_other_location.yml │ │ └── webpacker_public_root.yml │ ├── package.json │ ├── public │ │ └── packs │ │ │ └── manifest.json │ ├── some.config.js │ └── yarn.lock ├── test_helper.rb ├── webpack_runner_test.rb └── webpacker_test.rb ├── webpacker.gemspec └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | lib/* 2 | node_modules/* 3 | vendor/* 4 | **/__tests__/**/* 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['airbnb', 'prettier'], 3 | rules: { 4 | 'comma-dangle': ['error', 'never'], 5 | 'import/no-unresolved': 'off', 6 | 'import/no-extraneous-dependencies': 'off', 7 | 'import/extensions': 'off', 8 | semi: ['error', 'never'] 9 | }, 10 | env: { 11 | browser: true, 12 | node: true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report for a crash or unexpected behavior. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | A bug is a crash or incorrect behavior. If you have a debugging or troubleshooting question, please open a discussion on the [Rails forum, category webpacker](https://discuss.rubyonrails.org/c/webpacker/10) 11 | 12 | Ruby version: 13 | Rails version: 14 | Webpacker version: 15 | 16 | Expected behavior: 17 | 18 | Actual behavior: 19 | 20 | Small, reproducible repo: 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Create a request for new functionality 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | A feature request is describes a suggested improvement. If you have a debugging or troubleshooting question, please open a discussion on the [Rails forum, category webpacker](https://discuss.rubyonrails.org/c/webpacker/10) 11 | 12 | Ruby version: 13 | Rails version: 14 | Webpacker version: 15 | 16 | Desired behavior: 17 | 18 | Actual behavior: 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/jest.yml: -------------------------------------------------------------------------------- 1 | name: Jest specs 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | jest: 10 | name: Jest specs 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest] 14 | node: [12.x, 14.x, 16.x] 15 | 16 | runs-on: ${{ matrix.os }} 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Use Node.js ${{ matrix.node }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node }} 24 | cache: yarn 25 | 26 | - name: Install yarn maybe 27 | run: which yarn || npm install -g yarn 28 | 29 | - name: Install dependencies 30 | run: yarn --frozen-lockfile 31 | 32 | - name: Jest Specs 33 | run: yarn test 34 | -------------------------------------------------------------------------------- /.github/workflows/js-lint.yml: -------------------------------------------------------------------------------- 1 | name: JS lint 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | js-lint: 10 | name: JS Lint 11 | 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest] 15 | node: [14] 16 | 17 | runs-on: ${{ matrix.os }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Use Node.js ${{ matrix.node }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node }} 25 | cache: yarn 26 | 27 | - name: Install yarn maybe 28 | run: which yarn || npm install -g yarn 29 | 30 | - name: Install dependencies 31 | run: yarn --frozen-lockfile 32 | 33 | - name: Lint 34 | run: yarn lint 35 | -------------------------------------------------------------------------------- /.github/workflows/rubocop.yml: -------------------------------------------------------------------------------- 1 | name: Rubocop 2 | 3 | on: 4 | push: 5 | branches-ignore: 6 | - 'dependabot/**' 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | rubocop: 14 | name: Rubocop 15 | runs-on: ubuntu-latest 16 | env: 17 | BUNDLE_JOBS: 4 18 | BUNDLE_RETRY: 3 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - name: Set up Ruby 23 | uses: ruby/setup-ruby@v1 24 | with: 25 | ruby-version: 2.7 26 | bundler-cache: true 27 | 28 | - name: Ruby linter 29 | run: bundle exec rubocop 30 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: Ruby specs 2 | 3 | on: [push, pull_request] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | test: 10 | name: Ruby specs 11 | runs-on: ${{ matrix.os }} 12 | continue-on-error: ${{ endsWith(matrix.ruby, 'head') || matrix.ruby == 'debug' || matrix.experimental }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: [ubuntu-latest] 17 | ruby: 18 | - 2.7 19 | - "3.0" 20 | - 3.1 21 | - 3.2 22 | gemfile: 23 | - gemfiles/Gemfile-rails.5.2.x 24 | - gemfiles/Gemfile-rails.6.0.x 25 | - gemfiles/Gemfile-rails.6.1.x 26 | exclude: 27 | - ruby: 2.5 28 | gemfile: gemfiles/Gemfile-rails.6.1.x 29 | - ruby: "3.0" 30 | gemfile: gemfiles/Gemfile-rails.5.2.x 31 | - ruby: 3.1 32 | gemfile: gemfiles/Gemfile-rails.5.2.x 33 | - ruby: 3.2 34 | gemfile: gemfiles/Gemfile-rails.5.2.x 35 | experimental: [false] 36 | include: 37 | - ruby: 3.1 38 | os: ubuntu-latest 39 | gemfile: gemfiles/Gemfile-rails-edge 40 | experimental: true 41 | - ruby: 3.2 42 | os: ubuntu-latest 43 | gemfile: gemfiles/Gemfile-rails-edge 44 | experimental: true 45 | 46 | env: 47 | BUNDLE_GEMFILE: ${{ matrix.gemfile }} 48 | steps: 49 | - uses: actions/checkout@v4 50 | 51 | - uses: ruby/setup-ruby@v1 52 | with: 53 | ruby-version: ${{ matrix.ruby }} 54 | rubygems: latest 55 | bundler-cache: true # Run "bundle install", and cache the result automatically. 56 | 57 | - name: Ruby specs 58 | run: bundle exec rake 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle 2 | /pkg 3 | /test/mounted_app/test/dummy/log 4 | /test/test_app/log 5 | node_modules 6 | .byebug_history 7 | /test/test_app/tmp 8 | yarn-debug.log* 9 | yarn-error.log* 10 | .yarn-integrity 11 | /log 12 | gemfiles/*.lock 13 | .DS_Store 14 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16.7.0 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: rubocop-performance 2 | AllCops: 3 | TargetRubyVersion: 2.7 4 | # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop 5 | # to ignore them, so only the ones explicitly set in this file are enabled. 6 | DisabledByDefault: true 7 | Exclude: 8 | - "lib/install/templates/**" 9 | - "vendor/**/*" 10 | - "node_modules/**/*" 11 | - "_actions/**/*" 12 | 13 | # Prefer &&/|| over and/or. 14 | Style/AndOr: 15 | Enabled: true 16 | 17 | # Align `when` with `case`. 18 | Layout/CaseIndentation: 19 | Enabled: true 20 | 21 | # Align comments with method definitions. 22 | Layout/CommentIndentation: 23 | Enabled: true 24 | 25 | # No extra empty lines. 26 | Layout/EmptyLines: 27 | Enabled: true 28 | 29 | # In a regular class definition, no empty lines around the body. 30 | Layout/EmptyLinesAroundClassBody: 31 | Enabled: true 32 | 33 | # In a regular method definition, no empty lines around the body. 34 | Layout/EmptyLinesAroundMethodBody: 35 | Enabled: true 36 | 37 | # In a regular module definition, no empty lines around the body. 38 | Layout/EmptyLinesAroundModuleBody: 39 | Enabled: true 40 | 41 | # Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }. 42 | Style/HashSyntax: 43 | Enabled: true 44 | 45 | # Method definitions after `private` or `protected` isolated calls need one 46 | # extra level of indentation. 47 | Layout/IndentationConsistency: 48 | Enabled: true 49 | EnforcedStyle: indented_internal_methods 50 | 51 | # Detect hard tabs, no hard tabs. 52 | Layout/IndentationStyle: 53 | Enabled: true 54 | 55 | # Two spaces, no tabs (for indentation). 56 | Layout/IndentationWidth: 57 | Enabled: true 58 | 59 | Layout/SpaceAfterColon: 60 | Enabled: true 61 | 62 | Layout/SpaceAfterComma: 63 | Enabled: true 64 | 65 | Layout/SpaceAroundEqualsInParameterDefault: 66 | Enabled: true 67 | 68 | Layout/SpaceAroundKeyword: 69 | Enabled: true 70 | 71 | Layout/SpaceAroundOperators: 72 | Enabled: true 73 | 74 | Layout/SpaceBeforeFirstArg: 75 | Enabled: true 76 | 77 | # Defining a method with parameters needs parentheses. 78 | Style/MethodDefParentheses: 79 | Enabled: true 80 | 81 | # Use `foo {}` not `foo{}`. 82 | Layout/SpaceBeforeBlockBraces: 83 | Enabled: true 84 | 85 | # Use `foo { bar }` not `foo {bar}`. 86 | Layout/SpaceInsideBlockBraces: 87 | Enabled: true 88 | 89 | # Use `{ a: 1 }` not `{a:1}`. 90 | Layout/SpaceInsideHashLiteralBraces: 91 | Enabled: true 92 | 93 | Layout/SpaceInsideParens: 94 | Enabled: true 95 | 96 | # Check quotes usage according to lint rule below. 97 | Style/StringLiterals: 98 | Enabled: true 99 | EnforcedStyle: double_quotes 100 | 101 | # Blank lines should not have any spaces. 102 | Layout/TrailingEmptyLines: 103 | Enabled: true 104 | 105 | # No trailing whitespace. 106 | Layout/TrailingWhitespace: 107 | Enabled: true 108 | 109 | # Use quotes for string literals when they are enough. 110 | Style/RedundantPercentQ: 111 | Enabled: true 112 | 113 | Lint/DeprecatedClassMethods: 114 | Enabled: true 115 | 116 | # Align `end` with the matching keyword or starting expression except for 117 | # assignments, where it should be aligned with the LHS. 118 | Layout/EndAlignment: 119 | Enabled: true 120 | EnforcedStyleAlignWith: variable 121 | 122 | # Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg. 123 | Lint/RequireParentheses: 124 | Enabled: true 125 | 126 | # Use `bind_call(obj, args, ...)` instead of `bind(obj).call(args, ...)`. 127 | Performance/BindCall: 128 | Enabled: true 129 | 130 | # Use `caller(n..n)` instead of `caller`. 131 | Performance/Caller: 132 | Enabled: true 133 | 134 | # Use `casecmp` for case comparison. 135 | Performance/Casecmp: 136 | Enabled: true 137 | 138 | # Extract Array and Hash literals outside of loops into local variables or constants. 139 | Performance/CollectionLiteralInLoop: 140 | Enabled: true 141 | 142 | # Prefer `sort_by(&:foo)` instead of `sort { |a, b| a.foo <=> b.foo }`. 143 | Performance/CompareWithBlock: 144 | Enabled: true 145 | 146 | # Use `count` instead of `{select,find_all,filter,reject}...{size,count,length}`. 147 | Performance/Count: 148 | Enabled: true 149 | 150 | # Use `delete_prefix` instead of `gsub`. 151 | Performance/DeletePrefix: 152 | Enabled: true 153 | 154 | # Use `delete_suffix` instead of `gsub`. 155 | Performance/DeleteSuffix: 156 | Enabled: true 157 | 158 | # Use `detect` instead of `select.first`, `find_all.first`, `filter.first`, `select.last`, `find_all.last`, and `filter.last`. 159 | Performance/Detect: 160 | Enabled: true 161 | 162 | # Use `str.{start,end}_with?(x, ..., y, ...)` instead of `str.{start,end}_with?(x, ...) || str.{start,end}_with?(y, ...)`. 163 | Performance/DoubleStartEndWith: 164 | Enabled: true 165 | 166 | # Use `end_with?` instead of a regex match anchored to the end of a string. 167 | Performance/EndWith: 168 | Enabled: true 169 | 170 | # Do not compute the size of statically sized objects except in constants. 171 | Performance/FixedSize: 172 | Enabled: true 173 | 174 | # Use `Enumerable#flat_map` instead of `Enumerable#map...Array#flatten(1). 175 | Performance/FlatMap: 176 | Enabled: true 177 | 178 | # Use `key?` or `value?` instead of `keys.include?` or `values.include?`. 179 | Performance/InefficientHashSearch: 180 | Enabled: true 181 | 182 | # Use `Range#cover?` instead of `Range#include?` (or `Range#member?`). 183 | Performance/RangeInclude: 184 | Enabled: true 185 | 186 | # Use `yield` instead of `block.call`. 187 | Performance/RedundantBlockCall: 188 | Enabled: true 189 | 190 | # Use `=~` instead of `String#match` or `Regexp#match` in a context where the returned `MatchData` is not needed. 191 | Performance/RedundantMatch: 192 | Enabled: true 193 | 194 | # Use Hash#[]=, rather than Hash#merge! with a single key-value pair. 195 | Performance/RedundantMerge: 196 | Enabled: true 197 | 198 | # Use `match?` instead of `Regexp#match`, `String#match`, `Symbol#match`, `Regexp#===`, or `=~` when `MatchData` is not used. 199 | Performance/RegexpMatch: 200 | Enabled: true 201 | 202 | # Use `reverse_each` instead of `reverse.each`. 203 | Performance/ReverseEach: 204 | Enabled: true 205 | 206 | # Use `size` instead of `count` for counting the number of elements in `Array` and `Hash`. 207 | Performance/Size: 208 | Enabled: true 209 | 210 | # Use `start_with?` instead of a regex match anchored to the beginning of a string. 211 | Performance/StartWith: 212 | Enabled: true 213 | 214 | # Use `tr` instead of `gsub` when you are replacing the same number of characters. 215 | # Use `delete` instead of `gsub` when you are deleting characters. 216 | Performance/StringReplacement: 217 | Enabled: true 218 | 219 | # Checks for .times.map calls. 220 | Performance/TimesMap: 221 | Enabled: true 222 | 223 | # Use unary plus to get an unfrozen string literal. 224 | Performance/UnfreezeString: 225 | Enabled: true 226 | 227 | # Use `URI::DEFAULT_PARSER` instead of `URI::Parser.new`. 228 | Performance/UriDefaultParser: 229 | Enabled: true 230 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Setting Up a Development Environment 2 | 3 | 1. Install [Yarn](https://yarnpkg.com/) 4 | 5 | 2. Run the following commands to set up the development environment. 6 | 7 | ``` 8 | bundle install 9 | yarn 10 | ``` 11 | 12 | ## Making sure your changes pass all tests 13 | 14 | There are a number of automated checks which run on GitHub Actions when a pull request is created. 15 | 16 | You can run those checks on your own locally to make sure that your changes would not break the CI build. 17 | 18 | ### 1. Check the code for JavaScript style violations 19 | 20 | ``` 21 | yarn lint 22 | ``` 23 | 24 | ### 2. Check the code for Ruby style violations 25 | 26 | ``` 27 | bundle exec rubocop 28 | ``` 29 | 30 | ### 3. Run the JavaScript test suite 31 | 32 | ``` 33 | yarn test 34 | ``` 35 | 36 | ### 4. Run the Ruby test suite 37 | 38 | ``` 39 | bundle exec rake test 40 | ``` 41 | 42 | #### 4.1 Run a single ruby test file 43 | 44 | ``` 45 | bundle exec rake test TEST=test/rake_tasks_test.rb 46 | ``` 47 | 48 | #### 4.2 Run a single ruby test 49 | 50 | ``` 51 | bundle exec ruby -I test test/rake_tasks_test.rb -n test_rake_webpacker_install 52 | ``` 53 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "rails" 6 | gem "rake", ">= 11.1" 7 | gem "rack-proxy", require: false 8 | gem "semantic_range", require: false 9 | 10 | group :test do 11 | gem "minitest", "~> 5.0" 12 | gem "byebug" 13 | end 14 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | webpacker (6.0.0.rc.6) 5 | activesupport (>= 5.2) 6 | rack-proxy (>= 0.6.1) 7 | railties (>= 5.2) 8 | semantic_range (>= 2.3.0) 9 | 10 | GEM 11 | remote: https://rubygems.org/ 12 | specs: 13 | actioncable (6.1.4.1) 14 | actionpack (= 6.1.4.1) 15 | activesupport (= 6.1.4.1) 16 | nio4r (~> 2.0) 17 | websocket-driver (>= 0.6.1) 18 | actionmailbox (6.1.4.1) 19 | actionpack (= 6.1.4.1) 20 | activejob (= 6.1.4.1) 21 | activerecord (= 6.1.4.1) 22 | activestorage (= 6.1.4.1) 23 | activesupport (= 6.1.4.1) 24 | mail (>= 2.7.1) 25 | actionmailer (6.1.4.1) 26 | actionpack (= 6.1.4.1) 27 | actionview (= 6.1.4.1) 28 | activejob (= 6.1.4.1) 29 | activesupport (= 6.1.4.1) 30 | mail (~> 2.5, >= 2.5.4) 31 | rails-dom-testing (~> 2.0) 32 | actionpack (6.1.4.1) 33 | actionview (= 6.1.4.1) 34 | activesupport (= 6.1.4.1) 35 | rack (~> 2.0, >= 2.0.9) 36 | rack-test (>= 0.6.3) 37 | rails-dom-testing (~> 2.0) 38 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 39 | actiontext (6.1.4.1) 40 | actionpack (= 6.1.4.1) 41 | activerecord (= 6.1.4.1) 42 | activestorage (= 6.1.4.1) 43 | activesupport (= 6.1.4.1) 44 | nokogiri (>= 1.8.5) 45 | actionview (6.1.4.1) 46 | activesupport (= 6.1.4.1) 47 | builder (~> 3.1) 48 | erubi (~> 1.4) 49 | rails-dom-testing (~> 2.0) 50 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 51 | activejob (6.1.4.1) 52 | activesupport (= 6.1.4.1) 53 | globalid (>= 0.3.6) 54 | activemodel (6.1.4.1) 55 | activesupport (= 6.1.4.1) 56 | activerecord (6.1.4.1) 57 | activemodel (= 6.1.4.1) 58 | activesupport (= 6.1.4.1) 59 | activestorage (6.1.4.1) 60 | actionpack (= 6.1.4.1) 61 | activejob (= 6.1.4.1) 62 | activerecord (= 6.1.4.1) 63 | activesupport (= 6.1.4.1) 64 | marcel (~> 1.0.0) 65 | mini_mime (>= 1.1.0) 66 | activesupport (6.1.4.1) 67 | concurrent-ruby (~> 1.0, >= 1.0.2) 68 | i18n (>= 1.6, < 2) 69 | minitest (>= 5.1) 70 | tzinfo (~> 2.0) 71 | zeitwerk (~> 2.3) 72 | ast (2.4.2) 73 | builder (3.2.4) 74 | byebug (11.1.3) 75 | concurrent-ruby (1.1.10) 76 | crass (1.0.6) 77 | erubi (1.10.0) 78 | globalid (1.0.1) 79 | activesupport (>= 5.0) 80 | i18n (1.12.0) 81 | concurrent-ruby (~> 1.0) 82 | loofah (2.19.1) 83 | crass (~> 1.0.2) 84 | nokogiri (>= 1.5.9) 85 | mail (2.7.1) 86 | mini_mime (>= 0.1.1) 87 | marcel (1.0.2) 88 | method_source (1.0.0) 89 | mini_mime (1.1.1) 90 | mini_portile2 (2.8.1) 91 | minitest (5.14.4) 92 | nio4r (2.5.8) 93 | nokogiri (1.14.3) 94 | mini_portile2 (~> 2.8.0) 95 | racc (~> 1.4) 96 | parallel (1.21.0) 97 | parser (3.0.2.0) 98 | ast (~> 2.4.1) 99 | racc (1.6.2) 100 | rack (2.2.6.4) 101 | rack-proxy (0.7.0) 102 | rack 103 | rack-test (1.1.0) 104 | rack (>= 1.0, < 3) 105 | rails (6.1.4.1) 106 | actioncable (= 6.1.4.1) 107 | actionmailbox (= 6.1.4.1) 108 | actionmailer (= 6.1.4.1) 109 | actionpack (= 6.1.4.1) 110 | actiontext (= 6.1.4.1) 111 | actionview (= 6.1.4.1) 112 | activejob (= 6.1.4.1) 113 | activemodel (= 6.1.4.1) 114 | activerecord (= 6.1.4.1) 115 | activestorage (= 6.1.4.1) 116 | activesupport (= 6.1.4.1) 117 | bundler (>= 1.15.0) 118 | railties (= 6.1.4.1) 119 | sprockets-rails (>= 2.0.0) 120 | rails-dom-testing (2.0.3) 121 | activesupport (>= 4.2.0) 122 | nokogiri (>= 1.6) 123 | rails-html-sanitizer (1.4.4) 124 | loofah (~> 2.19, >= 2.19.1) 125 | railties (6.1.4.1) 126 | actionpack (= 6.1.4.1) 127 | activesupport (= 6.1.4.1) 128 | method_source 129 | rake (>= 0.13) 130 | thor (~> 1.0) 131 | rainbow (3.0.0) 132 | rake (13.0.6) 133 | regexp_parser (2.1.1) 134 | rexml (3.2.5) 135 | rubocop (1.21.0) 136 | parallel (~> 1.10) 137 | parser (>= 3.0.0.0) 138 | rainbow (>= 2.2.2, < 4.0) 139 | regexp_parser (>= 1.8, < 3.0) 140 | rexml 141 | rubocop-ast (>= 1.9.1, < 2.0) 142 | ruby-progressbar (~> 1.7) 143 | unicode-display_width (>= 1.4.0, < 3.0) 144 | rubocop-ast (1.11.0) 145 | parser (>= 3.0.1.1) 146 | rubocop-performance (1.11.5) 147 | rubocop (>= 1.7.0, < 2.0) 148 | rubocop-ast (>= 0.4.0) 149 | ruby-progressbar (1.11.0) 150 | semantic_range (3.0.0) 151 | sprockets (4.0.2) 152 | concurrent-ruby (~> 1.0) 153 | rack (> 1, < 3) 154 | sprockets-rails (3.2.2) 155 | actionpack (>= 4.0) 156 | activesupport (>= 4.0) 157 | sprockets (>= 3.0.0) 158 | thor (1.1.0) 159 | tzinfo (2.0.5) 160 | concurrent-ruby (~> 1.0) 161 | unicode-display_width (2.1.0) 162 | websocket-driver (0.7.5) 163 | websocket-extensions (>= 0.1.0) 164 | websocket-extensions (0.1.5) 165 | zeitwerk (2.6.6) 166 | 167 | PLATFORMS 168 | ruby 169 | 170 | DEPENDENCIES 171 | bundler (>= 1.3.0) 172 | byebug 173 | minitest (~> 5.0) 174 | rack-proxy 175 | rails 176 | rake (>= 11.1) 177 | rubocop 178 | rubocop-performance 179 | semantic_range 180 | webpacker! 181 | 182 | BUNDLED WITH 183 | 2.2.30 184 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2022 David Heinemeier Hansson, Basecamp 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 | # Webpacker has been retired 🌅 2 | 3 | Webpacker has served the Rails community for over five years as a bridge to compiled and bundled JavaScript. This bridge is no longer needed for most people in most situations following [the release of Rails 7](https://rubyonrails.org/2021/12/15/Rails-7-fulfilling-a-vision). We now have [three great default answers to JavaScript in 2021+](https://world.hey.com/dhh/rails-7-will-have-three-great-answers-to-javascript-in-2021-8d68191b), and thus we will no longer be evolving Webpacker in an official Rails capacity. 4 | 5 | For applications currently using Webpacker, the first recommendation is to switch to [jsbundling-rails with Webpack](https://github.com/rails/jsbundling-rails/) (or another bundler). You can follow [the switching guide](https://github.com/rails/jsbundling-rails/blob/main/docs/switch_from_webpacker.md), if you choose this option. 6 | 7 | Secondly, you may want to try making the jump all the way to [import maps](https://github.com/rails/importmap-rails/). That's the default setup for new Rails 7 applications, but depending on your JavaScript use, it may be a substantial jump. 8 | 9 | Finally, you can continue to use Webpacker as-is. We will continue to address security issues on the Ruby side of the gem according to [the normal maintenance schedule of Rails](https://guides.rubyonrails.org/maintenance_policy.html#security-issues). But we will not be updating the gem to include newer versions of the JavaScript libraries. This pertains to the v5 edition of this gem that was included by default with previous versions of Rails. 10 | 11 | The development of v6 will not result in an official gem released by the Rails team nor see any future support. But Justin Gordon is continuing that line of development – including a focus on hot-module reloading features etc – under a new gem called [Shakapacker](https://github.com/shakacode/shakapacker) that is based on the unreleased v6 work from this repository. 12 | 13 | Thank you to everyone who has contributed to Webpacker over the last five-plus years! 14 | 15 | _Please refer to the [5-x-stable](https://github.com/rails/webpacker/tree/5-x-stable) branch for 5.x documentation._ 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "bundler/gem_tasks" 3 | require "rake/testtask" 4 | 5 | Rake::TestTask.new do |t| 6 | t.libs << "test" 7 | t.test_files = FileList["test/**/*_test.rb"] 8 | t.verbose = true 9 | end 10 | 11 | task default: :test 12 | -------------------------------------------------------------------------------- /config/README.md: -------------------------------------------------------------------------------- 1 | # Note 2 | 3 | This directory exists for Jest specs that execute code expecting the Rails config directory at this path. 4 | -------------------------------------------------------------------------------- /config/webpacker.yml: -------------------------------------------------------------------------------- 1 | ../lib/install/config/webpacker.yml -------------------------------------------------------------------------------- /docs/customizing_babel_config.md: -------------------------------------------------------------------------------- 1 | # Customizing Babel Config 2 | 3 | ## Default Configuration 4 | The default configuration of babel is done by using `package.json` to use the file within the `@rails/webpacker` package. 5 | 6 | ```json 7 | { 8 | "babel": { 9 | "presets": [ 10 | "./node_modules/@rails/webpacker/package/babel/preset.js" 11 | ] 12 | } 13 | } 14 | ``` 15 | 16 | ## Customizing the Babel Config 17 | This example shows how you can create an object and apply _additional_ presets and plugins on top of the default. 18 | 19 | ### React Configuration 20 | To use this example file, 21 | 22 | ``` 23 | yarn add react react-dom @babel/preset-react 24 | yarn add --dev @pmmmwh/react-refresh-webpack-plugin react-refresh 25 | ``` 26 | 27 | ```js 28 | // babel.config.js 29 | module.exports = function (api) { 30 | const defaultConfigFunc = require('@rails/webpacker/package/babel/preset.js') 31 | const resultConfig = defaultConfigFunc(api) 32 | const isProductionEnv = api.env('production') 33 | 34 | const changesOnDefault = { 35 | presets: [ 36 | [ 37 | '@babel/preset-react', 38 | { 39 | development: isDevelopmentEnv || isTestEnv, 40 | useBuiltIns: true 41 | } 42 | ], 43 | isProductionEnv && ['babel-plugin-transform-react-remove-prop-types', 44 | { 45 | removeImport: true 46 | } 47 | ] 48 | ].filter(Boolean), 49 | plugins: [ 50 | process.env.WEBPACK_SERVE && 'react-refresh/babel' 51 | ].filter(Boolean), 52 | } 53 | 54 | resultConfig.presets = [...resultConfig.presets, ...changesOnDefault.presets] 55 | resultConfig.plugins = [...resultConfig.plugins, ...changesOnDefault.plugins ] 56 | 57 | return resultConfig 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/deployment.md: -------------------------------------------------------------------------------- 1 | # Deployment 2 | 3 | Webpacker hooks up a new `webpacker:compile` task to `assets:precompile`, which gets run whenever you run `assets:precompile`. 4 | If you are not using Sprockets `webpacker:compile` is automatically aliased to `assets:precompile`. 5 | 6 | ``` 7 | 8 | ## Heroku 9 | 10 | In order for your Webpacker app to run on Heroku, you'll need to do a bit of configuration before hand. 11 | 12 | ```bash 13 | heroku create my-webpacker-heroku-app 14 | heroku addons:create heroku-postgresql:hobby-dev 15 | heroku buildpacks:add heroku/nodejs 16 | heroku buildpacks:add heroku/ruby 17 | git push heroku master 18 | ``` 19 | 20 | We're essentially doing the following here: 21 | 22 | * Creating an app on Heroku 23 | * Creating a Postgres database for the app (this is assuming that you're using Heroku Postgres for your app) 24 | * Adding the Heroku NodeJS and Ruby buildpacks for your app. This allows the `npm` or `yarn` executables to properly function when compiling your app - as well as Ruby. 25 | * Pushing your code to Heroku and kicking off the deployment 26 | 27 | ## Nginx 28 | 29 | Webpacker doesn't serve anything in production. You’re expected to configure your web server to serve files in public/ directly. 30 | 31 | Some servers support sending precompressed versions of files when they're available. For example, nginx offers a `gzip_static` directive that serves files with the `.gz` extension to supported clients. With an optional module, nginx can also serve Brotli compressed files with the `.br` extension (see below for installation and configuration instructions). 32 | 33 | Here's a sample nginx site config for a Rails app using Webpacker: 34 | 35 | ```nginx 36 | upstream app { 37 | # server unix:///path/to/app/tmp/puma.sock; 38 | } 39 | 40 | server { 41 | listen 80; 42 | server_name www.example.com; 43 | root /path/to/app/public; 44 | 45 | location @app { 46 | proxy_pass http://app; 47 | proxy_redirect off; 48 | 49 | proxy_set_header Host $host; 50 | proxy_set_header X-Real-IP $remote_addr; 51 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 52 | proxy_set_header X-Forwarded-Proto $scheme; 53 | } 54 | 55 | location / { 56 | try_files $uri @app; 57 | } 58 | 59 | location = /favicon.ico { access_log off; log_not_found off; } 60 | location = /robots.txt { access_log off; log_not_found off; } 61 | 62 | location ~ /\.(?!well-known).* { 63 | deny all; 64 | } 65 | 66 | location ~ ^/(assets|packs)/ { 67 | gzip_static on; 68 | brotli_static on; # Optional, see below 69 | expires max; 70 | add_header Cache-Control public; 71 | } 72 | } 73 | ``` 74 | 75 | ### Installing the ngx_brotli module 76 | 77 | If you want to serve Brotli compressed files with nginx, you will need to install the `nginx_brotli` module. Installation instructions from source can be found in the official [google/ngx_brotli](https://github.com/google/ngx_brotli) git repository. Alternatively, depending on your platform, the module might be available via a pre-compiled package. 78 | 79 | Once installed, you need to load the module. As we want to serve the pre-compressed files, we only need the static module. Add the following line to your `nginx.conf` file and reload nginx: 80 | 81 | ``` 82 | load_module modules/ngx_http_brotli_static_module.so; 83 | ``` 84 | 85 | Now, you can set `brotli_static on;` in your nginx site config, as per the config in the last section above. 86 | 87 | ## CDN 88 | 89 | Webpacker out-of-the-box provides CDN support using your Rails app `config.action_controller.asset_host` setting. If you already have [CDN](http://guides.rubyonrails.org/asset_pipeline.html#cdns) added in your Rails app 90 | you don't need to do anything extra for Webpacker, it just works. 91 | 92 | ## Capistrano 93 | 94 | ### Assets compiling on every deployment even if JavaScript and CSS files are not changed 95 | 96 | Make sure you have `public/packs` and `node_modules` in `:linked_dirs` 97 | 98 | ```ruby 99 | append :linked_dirs, "log", "tmp/pids", "tmp/cache", "tmp/sockets", "public/packs", ".bundle", "node_modules" 100 | ``` 101 | 102 | If you have `node_modules` added to `:linked_dirs` you'll need to run yarn install before `deploy:assets:precompile`, so you can add this code snippet at the bottom deploy.rb 103 | 104 | ```ruby 105 | before "deploy:assets:precompile", "deploy:yarn_install" 106 | namespace :deploy do 107 | desc "Run rake yarn install" 108 | task :yarn_install do 109 | on roles(:web) do 110 | within release_path do 111 | execute("cd #{release_path} && yarn install --silent --no-progress --no-audit --no-optional") 112 | end 113 | end 114 | end 115 | end 116 | ``` 117 | -------------------------------------------------------------------------------- /docs/developing_webpacker.md: -------------------------------------------------------------------------------- 1 | # Developing Webpacker 2 | 3 | It's a little trickier for Rails developers to work on the JS code of a project like rails/webpacker. So here are some tips! 4 | 5 | ## Use some test app 6 | For example, for React on Rails Changes, I'm using [shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh](https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh). 7 | This directory is the `TEST_APP_DIR`. 8 | 9 | ## Fork rails/webpacker 10 | Let's call the rails/webpacker directory `WEBPACKER_DIR` which has rails/webpacker's `package.json`. 11 | 12 | ## Changing the Package 13 | ### Setup with Yalc 14 | Use [`yalc`](https://github.com/wclr/yalc) unless you like yak shaving weird errors. 15 | 1. In `WEBPACKER_DIR`, run `yalc publish` 16 | 2. In `TEST_APP_DIR`, run `yalc link @rails/webpacker` 17 | 18 | ## Update the Package Code 19 | 1. Make some JS change in WEBPACKER_DIR 20 | 2. Run `yalc push` and your changes will be pushed to your `TEST_APP_DIR`'s node_modules. 21 | 3. You may need to run `yarn` in `TEST_APP_DIR` if you added or removed dependencies of rails/webpacker. 22 | 23 | ## Updating the Ruby Code 24 | 25 | For the Ruby part, just change the gem reference `TEST_APP_DIR`, like: 26 | 27 | ```ruby 28 | gem "webpacker", path: "../../forks/webpacker" 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/troubleshooting.md: -------------------------------------------------------------------------------- 1 | # Troubleshooting 2 | 3 | ## Debugging your webpack config 4 | 5 | 1. Read the error message carefully. The error message will tell you the precise key value 6 | that is not matching what Webpack expects. 7 | 8 | 2. Put a `debugger` statement in your Webpack configuration and run `bin/webpacker --debug-webpacker`. 9 | If you have a node debugger installed, you'll see the Chrome debugger for your webpack 10 | config. For example, install the Chrome extension 11 | [NiM](https://chrome.google.com/webstore/detail/nodejs-v8-inspector-manag/gnhhdgbaldcilmgcpfddgdbkhjohddkj) and 12 | set the option for the dev tools to open automatically. Or put `chrome://inspect` in the URL bar. 13 | For more details on debugging, see the official 14 | [Webpack docs on debugging](https://webpack.js.org/contribute/debugging/#devtools) 15 | 16 | 3. Any arguments that you add to bin/webpacker get sent to webpack. For example, you can pass `--debug` to switch loaders to debug mode. See [webpack CLI debug options](https://webpack.js.org/api/cli/#debug-options) for more information on the available options. 17 | 18 | 4. You can also pass additional options to the command to run the webpack-dev-server and start the webpack-dev-server with the option `--debug-webpacker` 19 | 20 | ## ENOENT: no such file or directory - node-sass 21 | 22 | If you get the error `ENOENT: no such file or directory - node-sass` on deploy with 23 | `assets:precompile` or `bundle exec rails webpacker:compile` you may need to 24 | move Sass to production `dependencies`. 25 | 26 | Move any packages that related to Sass (e.g. `node-sass` or `sass-loader`) from 27 | `devDependencies` to `dependencies` in `package.json`. This is because 28 | webpacker is running on a production system with the Rails workflow to build 29 | the assets. Particularly on hosting providers that try to detect and do the right 30 | thing, like Heroku. 31 | 32 | However, if you get this on local development, or not during a deploy then you 33 | may need to rebuild `node-sass`. It's a bit of a weird error; basically, it 34 | can't find the `node-sass` binary. An easy solution is to create a postinstall 35 | hook to ensure `node-sass` is rebuilt whenever new modules are installed. 36 | 37 | In `package.json`: 38 | 39 | ```json 40 | "scripts": { 41 | "postinstall": "npm rebuild node-sass" 42 | } 43 | ``` 44 | 45 | ## Can't find hello_react.js in manifest.json 46 | 47 | * If you get this error `Can't find hello_react.js in manifest.json` 48 | when loading a view in the browser it's because webpack is still compiling packs. 49 | Webpacker uses a `manifest.json` file to keep track of packs in all environments, 50 | however since this file is generated after packs are compiled by webpack. So, 51 | if you load a view in browser whilst webpack is compiling you will get this error. 52 | Therefore, make sure webpack 53 | (i.e `./bin/webpacker-dev-server`) is running and has 54 | completed the compilation successfully before loading a view. 55 | 56 | 57 | ## throw er; // Unhandled 'error' event 58 | 59 | * If you get this error while trying to use Elm, try rebuilding Elm. You can do 60 | so with a postinstall hook in your `package.json`: 61 | 62 | ```json 63 | "scripts": { 64 | "postinstall": "npm rebuild elm" 65 | } 66 | ``` 67 | 68 | ## webpack or webpack-dev-server not found 69 | 70 | * This could happen if `webpacker:install` step is skipped. Please run `bundle exec rails webpacker:install` to fix the issue. 71 | 72 | * If you encounter the above error on heroku after upgrading from Rails 4.x to 5.1.x, then the problem might be related to missing `yarn` binstub. Please run following commands to update/add binstubs: 73 | 74 | ```bash 75 | bundle config --delete bin 76 | ./bin/rails app:update:bin # or rails app:update:bin 77 | ``` 78 | 79 | ## Running webpack on Windows 80 | 81 | If you are running webpack on Windows, your command shell may not be able to interpret the preferred interpreter 82 | for the scripts generated in `bin/webpacker` and `bin/webpacker-dev-server`. Instead you'll want to run the scripts 83 | manually with Ruby: 84 | 85 | ``` 86 | C:\path>ruby bin\webpack 87 | C:\path>ruby bin\webpack-dev-server 88 | ``` 89 | 90 | ## Invalid configuration object. webpack has been initialised using a configuration object that does not match the API schema. 91 | 92 | If you receive this error when running `$ ./bin/webpacker-dev-server` ensure your configuration is correct; most likely the path to your "packs" folder is incorrect if you modified from the original "source_path" defined in `config/webpacker.yml`. 93 | 94 | ## Running Elm on Continuous Integration (CI) services such as CircleCI, CodeShip, Travis CI 95 | 96 | If your tests are timing out or erroring on CI it is likely that you are experiencing the slow Elm compilation issue described here: [elm-compiler issue #1473](https://github.com/elm-lang/elm-compiler/issues/1473) 97 | 98 | The issue is related to CPU count exposed by the underlying service. The basic solution involves using [libsysconfcpus](https://github.com/obmarg/libsysconfcpus) to change the reported CPU count. 99 | 100 | Basic fix involves: 101 | 102 | ```bash 103 | # install sysconfcpus on CI 104 | 105 | git clone https://github.com/obmarg/libsysconfcpus.git $HOME/dependencies/libsysconfcpus 106 | cd libsysconfcpus 107 | .configure --prefix=$HOME/dependencies/sysconfcpus 108 | make && make install 109 | 110 | # use sysconfcpus with elm-make 111 | mv $HOME/your_rails_app/node_modules/.bin/elm-make $HOME/your_rails_app/node_modules/.bin/elm-make-old 112 | printf "#\041/bin/bash\n\necho \"Running elm-make with sysconfcpus -n 2\"\n\n$HOME/dependencies/sysconfcpus/bin/sysconfcpus -n 2 $HOME/your_rails_app/node_modules/.bin/elm-make-old \"\$@\"" > $HOME/your_rails_app/node_modules/.bin/elm-make 113 | chmod +x $HOME/your_rails_app/node_modules/.bin/elm-make 114 | ``` 115 | 116 | ## Rake assets:precompile fails. ExecJS::RuntimeError 117 | This error occurs because you are trying to minify by `terser` a pack that's already been minified by Webpacker. To avoid this conflict and prevent appearing of `ExecJS::RuntimeError` error, you will need to disable uglifier from Rails config: 118 | 119 | ```ruby 120 | # In production.rb 121 | 122 | # From 123 | Rails.application.config.assets.js_compressor = :uglifier 124 | 125 | # To 126 | Rails.application.config.assets.js_compressor = Uglifier.new(harmony: true) 127 | ``` 128 | 129 | ### Angular: WARNING in ./node_modules/@angular/core/esm5/core.js, Critical dependency: the request of a dependency is an expression 130 | 131 | To silent these warnings, please update `config/webpack/base.js`: 132 | ```js 133 | const webpack = require('webpack') 134 | const { resolve } = require('path') 135 | const { webpackConfig, merge } = require('@rails/webpacker') 136 | 137 | module.exports = merge(webpackConfig, { 138 | plugins: [ 139 | new webpack.ContextReplacementPlugin( 140 | /angular(\\|\/)core(\\|\/)(@angular|esm5)/, 141 | resolve(config.source_path) 142 | ) 143 | ] 144 | }) 145 | ``` 146 | 147 | ### Compilation Fails Silently 148 | 149 | If compiling is not producing output files and there are no error messages to help troubleshoot. Setting the `webpack_compile_output` configuration variable to `true` in webpacker.yml may add some helpful error information to your log file (Rails `log/development.log` or `log/production.log`) 150 | 151 | ```yml 152 | # webpacker.yml 153 | default: &default 154 | source_path: app/javascript 155 | source_entry_path: packs 156 | public_root_path: public 157 | public_output_path: complaints_packs 158 | webpack_compile_output: true 159 | ``` 160 | 161 | ### Using global variables for dependencies 162 | 163 | If you want to access any dependency without importing it everywhere or use it directly in your dev tools, please check: [https://webpack.js.org/plugins/provide-plugin/](https://webpack.js.org/plugins/provide-plugin/) and the [webpack docs on shimming globals](https://webpack.js.org/guides/shimming/#shimming-globals). 164 | 165 | Note, if you are exposing globals, like jQuery, to non-webpack dependencies (like an inline script) via the [expose-loader](https://webpack.js.org/loaders/expose-loader/), you will need to override the default of `defer: true` to be `defer:false` your call to the `javascript_pack_tag` so that the browser will load your bundle to setup the global variable before other code depends on it. However, you really should try to remove the dependendency on such globals. 166 | 167 | Thus ProvidePlugin manages build-time dependencies to global symbols whereas the expose-loader manages runtime dependencies to global symbols. 168 | 169 | **You don't need to assign dependencies on `window`.** 170 | 171 | For instance, with [jQuery](https://jquery.com/): 172 | ```diff 173 | // app/packs/entrypoints/application.js 174 | 175 | - import jQuery from 'jquery' 176 | - window.jQuery = jQuery 177 | ``` 178 | 179 | Instead do: 180 | ```js 181 | // config/webpack/base.js 182 | 183 | const webpack = require('webpack') 184 | const { webpackConfig, merge } = require('@rails/webpacker') 185 | 186 | module.exports = merge(webpackConfig, { 187 | plugins: [ 188 | new webpack.ProvidePlugin({ 189 | $: 'jquery', 190 | jQuery: 'jquery', 191 | }) 192 | ], 193 | }) 194 | ``` 195 | 196 | ## Wrong CDN src from javascript_pack_tag 197 | 198 | If your deployment doesn't rebuild assets between environments (such as when 199 | using Heroku's Pipeline promote feature). You might find that your production 200 | application is using your staging `config.asset_host` host when using 201 | `javascript_pack_tag`. 202 | 203 | This can be fixed by setting the environment variable `WEBPACKER_ASSET_HOST` to 204 | an empty string where your assets are compiled. On Heroku this is done under 205 | *Settings* -> *Config Vars*. 206 | 207 | This way webpacker won't hard-code the CDN host into the manifest file used by 208 | `javascript_pack_tag`, but instead fetch the CDN host at runtime, resolving the 209 | issue. 210 | 211 | See [this issue](https://github.com/rails/webpacker/issues/3005) for more 212 | details. 213 | -------------------------------------------------------------------------------- /docs/v6_upgrade.md: -------------------------------------------------------------------------------- 1 | # Upgrading from Webpacker 5 to 6 2 | 3 | There are several substantial changes in Webpacker 6 that you need to manually account for when coming from Webpacker 5. This guide will help you through it. 4 | 5 | ## Webpacker has become a slimmer wrapper around Webpack 6 | 7 | By default, Webpacker 6 is focused on compiling and bundling JavaScript. This pairs with the existing asset pipeline in Rails that's setup to transpile CSS and static images using [Sprockets](https://github.com/rails/sprockets). For most developers, that's the recommended combination. But if you'd like to use Webpacker for CSS and static assets as well, please see [integrations](https://github.com/rails/webpacker#integrations) for more information. 8 | 9 | Webpacker used to configure Webpack indirectly, which lead to a [complicated secondary configuration process](https://github.com/rails/webpacker/blob/5-x-stable/docs/webpack.md). This was done in order to provide default configurations for the most popular frameworks, but ended up creating more complexity than it cured. So now Webpacker delegates all configuration directly to Webpack's default configuration setup. 10 | 11 | This means you have to configure integration with frameworks yourself, but webpack-merge helps with this. See this example for [Vue](https://github.com/rails/webpacker#other-frameworks) and scroll to the bottom for [more examples](#examples-of-v5-to-v6). 12 | 13 | ## How to upgrade to Webpacker v6 from v5 14 | 15 | 1. Consider changing from the v5 default for `source_entry_path`. 16 | ```yml 17 | source_path: app/javascript 18 | source_entry_path: packs 19 | ``` 20 | consider changing to the v6 default: 21 | ```yml 22 | source_path: app/javascript 23 | source_entry_path: / 24 | ``` 25 | Then consider moving your `app/javascript/packs/*` (including `application.js`) to `app/javascript/` and updating the configuration file. 26 | 27 | Note, moving your files is optional, as you can stil keep your entries in a separate directory, called something like `packs`, or `entries`. This directory is defined within the source_path. 28 | 29 | 2. **Ensure no nested directories in your `source_entry_path`.** Check if you had any entry point files in child directories of your `source_entry_path`. Files for entry points in child directories are not supported by rails/webpacker v6. Move those files to the top level, adjusting any imports in those files. 30 | 31 | The new v6 configuration does not allow nesting, so as to allow placing the entry points at in the root directory of JavaScript. You can find this change [here](https://github.com/rails/webpacker/commit/5de0fbc1e16d3db0c93202fb39f5b4d80582c682#diff-7af8667a3e36201db57c02b68dd8651883d7bfc00dc9653661be11cd31feeccdL19). 32 | 33 | 3. Rename `config/webpack` to `config/webpack_old` 34 | 35 | 4. Rename `config/webpacker.yml` to `config/webpacker_old.yml` 36 | 37 | 5. Update `webpack-dev-server` to the current version, greater than 4.2, updating `package.json`. 38 | 39 | 6. Upgrade the Webpacker Ruby gem and NPM package 40 | 41 | Note: [Check the releases page to verify the latest version](https://github.com/rails/webpacker/releases), and make sure to install identical version numbers of webpacker gem and `@rails/webpacker` npm package. (Gems use a period and packages use a dot between the main version number and the beta version.) 42 | 43 | Example going to a specific version: 44 | 45 | ```ruby 46 | # Gemfile 47 | gem 'webpacker', '6.0.0.rc.5' 48 | ``` 49 | 50 | ```bash 51 | bundle install 52 | ``` 53 | 54 | ```bash 55 | yarn add @rails/webpacker@6.0.0-rc.5 --exact 56 | ``` 57 | 58 | ```bash 59 | bundle exec rails webpacker:install 60 | ``` 61 | 62 | 7. Update API usage of the view helpers by changing `javascript_packs_with_chunks_tag` and `stylesheet_packs_with_chunks_tag` to `javascript_pack_tag` and `stylesheet_pack_tag`. Ensure that your layouts and views will only have **at most one call** to `javascript_pack_tag` and **at most one call** to `stylesheet_pack_tag`. You can now pass multiple bundles to these view helper methods. If you fail to changes this, you may experience performance issues, and other bugs related to multiple copies of React, like [issue 2932](https://github.com/rails/webpacker/issues/2932). If you expose jquery globally with `expose-loader,` by using `import $ from "expose-loader?exposes=$,jQuery!jquery"` in your `app/javascript/application.js`, pass the option `defer: false` to your `javascript_pack_tag`. 63 | 64 | 8. If you are using any integrations like `css`, `postcss`, `React` or `TypeScript`. Please see https://github.com/rails/webpacker#integrations section on how they work in v6. 65 | 66 | 9. Copy over any custom webpack config from `config/webpack_old`. Common code previously called 'environment' should be changed to 'base', and import `environment` changed to `webpackConfig`. 67 | 68 | ```js 69 | // config/webpack/base.js 70 | const { webpackConfig, merge } = require('@rails/webpacker') 71 | const customConfig = require('./custom') 72 | 73 | module.exports = merge(webpackConfig, customConfig) 74 | ``` 75 | 76 | 10. Copy over custom browserlist config from `.browserslistrc` if it exists into the `"browserslist"` key in `package.json` and remove `.browserslistrc`. 77 | 78 | 11. Remove `babel.config.js` if you never changed it. Be sure to have this config in your `package.json`: 79 | 80 | ```json 81 | "babel": { 82 | "presets": [ 83 | "./node_modules/@rails/webpacker/package/babel/preset.js" 84 | ] 85 | } 86 | ``` 87 | See customization example the [Customizing Babel Config](./docs/customizing_babel_config.md) for React configuration. 88 | 89 | 12. `extensions` was removed from the `webpacker.yml` file. Move custom extensions to your configuration by merging an object like this. For more details, see docs for [Webpack Configuration](https://github.com/rails/webpacker/blob/master/README.md#webpack-configuration) 90 | 91 | ```js 92 | { 93 | resolve: { 94 | extensions: ['.ts', '.tsx', '.vue', '.css'] 95 | } 96 | } 97 | ``` 98 | 99 | 13. Some dependencies were removed in [PR 3056](https://github.com/rails/webpacker/pull/3056). If you see the error: `Error: Cannot find module 'babel-plugin-macros'`, or similar, then you need to `yarn add ` where might include: `babel-plugin-macros`, `case-sensitive-paths-webpack-plugin`, `core-js`, `regenerator-runtime`. Or you might want to remove your dependency on those. 100 | 101 | 14. If `bin/yarn` does not exist, create an executable [yarn](https://github.com/rails/webpacker/blob/master/lib/install/bin/yarn) file in your `/bin` directory. 102 | 103 | 15. Remove overlapping dependencies from your `package.json` and rails/webpacker's `package.json`. For example, don't include `webpack` directly as that's a dependency of rails/webpacker. 104 | 105 | 16. Review the new default's changes to `webpacker.yml` and `config/webpack`. Consider each suggested change carefully, especially the change to have your `source_entry_path` be at the top level of your `source_path`. 106 | 107 | 17. Make sure that you can run `bin/webpacker` without errors. 108 | 109 | 18. Try running `RAILS_ENV=production bin/rails assets:precompile`. If all goes well, don't forget to clean the generated assets with `bin/rails assets:clobber`. 110 | 111 | 19. Run `yarn add webpack-dev-server` if those are not already in your dev dependencies. Make sure you're using v4+. 112 | 113 | 20. Try your app! 114 | 115 | 21. Update any scripts that called `/bin/webpack` or `bin/webpack-dev-server` to `/bin/webpacker` or `bin/webpacker-dev-server` 116 | 117 | ## Examples of v5 to v6 118 | 119 | 1. [React on Rails Project with HMR and SSR](https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/compare/webpacker-5.x...master) 120 | 2. [Vue and Sass Example](https://github.com/guillaumebriday/upgrade-webpacker-5-to-6) 121 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails-edge: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 4 | 5 | gemspec path: "../" 6 | 7 | gem "rails", github: "rails/rails", branch: "main" 8 | gem "arel", github: "rails/arel" 9 | gem "rake", ">= 11.1" 10 | gem "rack-proxy", require: false 11 | gem "minitest", "~> 5.0" 12 | gem "byebug" 13 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails.5.2.x: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec path: "../" 4 | 5 | gem "rails", "~> 5.2.0" 6 | gem "rake", ">= 11.1" 7 | gem "rack-proxy", require: false 8 | gem "minitest", "~> 5.0" 9 | gem "byebug" 10 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails.6.0.x: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec path: "../" 4 | 5 | gem "rails", "~> 6.0.0.rc2" 6 | gem "rake", ">= 11.1" 7 | gem "rack-proxy", require: false 8 | gem "minitest", "~> 5.0" 9 | gem "byebug" 10 | -------------------------------------------------------------------------------- /gemfiles/Gemfile-rails.6.1.x: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 4 | 5 | gemspec path: "../" 6 | 7 | gem "rails", '~>6.1.0' 8 | gem "arel", github: "rails/arel" 9 | gem "rake", ">= 11.1" 10 | gem "rack-proxy", require: false 11 | gem "minitest", "~> 5.0" 12 | gem "byebug" 13 | -------------------------------------------------------------------------------- /lib/install/application.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | // This file is automatically compiled by Webpack, along with any other files 3 | // present in this directory. You're encouraged to place your actual application logic in 4 | // a relevant structure within app/javascript and only use these pack files to reference 5 | // that code so it'll be compiled. 6 | // 7 | // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate 8 | // layout file, like app/views/layouts/application.html.erb 9 | 10 | // Uncomment to copy all static images under ./images to the output folder and reference 11 | // them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>) 12 | // or the `imagePath` JavaScript helper below. 13 | // 14 | // const images = require.context('./images', true) 15 | // const imagePath = (name) => images(name, true) 16 | -------------------------------------------------------------------------------- /lib/install/bin/webpacker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "pathname" 4 | require "bundler/setup" 5 | require "webpacker" 6 | require "webpacker/webpack_runner" 7 | 8 | ENV["RAILS_ENV"] ||= "development" 9 | ENV["NODE_ENV"] ||= ENV["RAILS_ENV"] 10 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) 11 | 12 | APP_ROOT = File.expand_path("..", __dir__) 13 | Dir.chdir(APP_ROOT) do 14 | Webpacker::WebpackRunner.run(ARGV) 15 | end 16 | -------------------------------------------------------------------------------- /lib/install/bin/webpacker-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= "development" 4 | ENV["NODE_ENV"] ||= ENV["RAILS_ENV"] 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | 15 | APP_ROOT = File.expand_path("..", __dir__) 16 | Dir.chdir(APP_ROOT) do 17 | Webpacker::DevServerRunner.run(ARGV) 18 | end 19 | -------------------------------------------------------------------------------- /lib/install/bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | APP_ROOT = File.expand_path("..", __dir__) 4 | Dir.chdir(APP_ROOT) do 5 | yarn = ENV["PATH"].split(File::PATH_SEPARATOR). 6 | select { |dir| File.expand_path(dir) != __dir__ }. 7 | product(["yarn", "yarnpkg", "yarn.cmd", "yarn.ps1"]). 8 | map { |dir, file| File.expand_path(file, dir) }. 9 | find { |file| File.executable?(file) } 10 | 11 | if yarn 12 | exec yarn, *ARGV 13 | else 14 | $stderr.puts "Yarn executable was not detected in the system." 15 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 16 | exit 1 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/install/binstubs.rb: -------------------------------------------------------------------------------- 1 | say "Copying binstubs" 2 | directory "#{__dir__}/bin", "bin" 3 | 4 | chmod "bin", 0755 & ~File.umask, verbose: false 5 | -------------------------------------------------------------------------------- /lib/install/config/webpack/base.js: -------------------------------------------------------------------------------- 1 | const { webpackConfig } = require('@rails/webpacker') 2 | 3 | module.exports = webpackConfig 4 | -------------------------------------------------------------------------------- /lib/install/config/webpack/development.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const webpackConfig = require('./base') 4 | 5 | module.exports = webpackConfig 6 | -------------------------------------------------------------------------------- /lib/install/config/webpack/production.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'production' 2 | 3 | const webpackConfig = require('./base') 4 | 5 | module.exports = webpackConfig 6 | -------------------------------------------------------------------------------- /lib/install/config/webpack/test.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_ENV = process.env.NODE_ENV || 'development' 2 | 3 | const webpackConfig = require('./base') 4 | 5 | module.exports = webpackConfig 6 | -------------------------------------------------------------------------------- /lib/install/config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpacker-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/javascript 5 | source_entry_path: / 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/webpacker 9 | webpack_compile_output: true 10 | 11 | # Additional paths webpack should look up modules 12 | # ['app/assets', 'engine/foo/app/assets'] 13 | additional_paths: [] 14 | 15 | # Reload manifest.json on all requests so we reload latest compiled packs 16 | cache_manifest: false 17 | 18 | development: 19 | <<: *default 20 | compile: true 21 | 22 | # Reference: https://webpack.js.org/configuration/dev-server/ 23 | dev_server: 24 | https: false 25 | host: localhost 26 | port: 3035 27 | # Hot Module Replacement updates modules while the application is running without a full reload 28 | hmr: false 29 | # Defaults to the inverse of hmr. Uncomment to manually set this. 30 | # live_reload: true 31 | client: 32 | # Should we show a full-screen overlay in the browser when there are compiler errors or warnings? 33 | overlay: true 34 | # May also be a string 35 | # webSocketURL: 36 | # hostname: "0.0.0.0" 37 | # pathname: "/ws" 38 | # port: 8080 39 | # Should we use gzip compression? 40 | compress: true 41 | # Note that apps that do not check the host are vulnerable to DNS rebinding attacks 42 | allowed_hosts: "all" 43 | pretty: true 44 | headers: 45 | 'Access-Control-Allow-Origin': '*' 46 | static: 47 | watch: 48 | ignored: '**/node_modules/**' 49 | 50 | test: 51 | <<: *default 52 | compile: true 53 | 54 | # Compile test packs to a separate directory 55 | public_output_path: packs-test 56 | 57 | production: 58 | <<: *default 59 | 60 | # Production depends on precompilation of packs prior to booting for performance. 61 | compile: false 62 | 63 | # Cache manifest.json for performance 64 | cache_manifest: true 65 | -------------------------------------------------------------------------------- /lib/install/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "private": true, 4 | "dependencies": { 5 | }, 6 | "version": "0.1.0", 7 | "babel": { 8 | "presets": [ 9 | "./node_modules/@rails/webpacker/package/babel/preset.js" 10 | ] 11 | }, 12 | "browserslist": [ 13 | "defaults" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /lib/install/template.rb: -------------------------------------------------------------------------------- 1 | # Install Webpacker 2 | copy_file "#{__dir__}/config/webpacker.yml", "config/webpacker.yml" 3 | copy_file "#{__dir__}/package.json", "package.json" 4 | 5 | say "Copying webpack core config" 6 | directory "#{__dir__}/config/webpack", "config/webpack" 7 | 8 | if Dir.exist?(Webpacker.config.source_path) 9 | say "The packs app source directory already exists" 10 | else 11 | say "Creating packs app source directory" 12 | empty_directory "app/javascript" 13 | copy_file "#{__dir__}/application.js", "app/javascript/application.js" 14 | end 15 | 16 | apply "#{__dir__}/binstubs.rb" 17 | 18 | git_ignore_path = Rails.root.join(".gitignore") 19 | if File.exist?(git_ignore_path) 20 | append_to_file git_ignore_path do 21 | "\n" + 22 | "/public/packs\n" + 23 | "/public/packs-test\n" + 24 | "/node_modules\n" + 25 | "/yarn-error.log\n" + 26 | "yarn-debug.log*\n" + 27 | ".yarn-integrity\n" 28 | end 29 | end 30 | 31 | if (app_layout_path = Rails.root.join("app/views/layouts/application.html.erb")).exist? 32 | say "Add JavaScript include tag in application layout" 33 | insert_into_file app_layout_path.to_s, %(\n <%= javascript_pack_tag "application" %>), before: /\s*<\/head>/ 34 | else 35 | say "Default application.html.erb is missing!", :red 36 | say %( Add <%= javascript_pack_tag "application" %> within the tag in your custom layout.) 37 | end 38 | 39 | if (setup_path = Rails.root.join("bin/setup")).exist? 40 | say "Run bin/yarn during bin/setup" 41 | insert_into_file setup_path.to_s, <<-RUBY, after: %( system("bundle check") || system!("bundle install")\n) 42 | 43 | # Install JavaScript dependencies 44 | system! "bin/yarn" 45 | RUBY 46 | end 47 | 48 | if (asset_config_path = Rails.root.join("config/initializers/assets.rb")).exist? 49 | say "Add node_modules to the asset load path" 50 | append_to_file asset_config_path, <<-RUBY 51 | 52 | # Add node_modules folder to the asset load path. 53 | Rails.application.config.assets.paths << Rails.root.join("node_modules") 54 | RUBY 55 | end 56 | 57 | if (csp_config_path = Rails.root.join("config/initializers/content_security_policy.rb")).exist? 58 | say "Make note of webpack-dev-server exemption needed to csp" 59 | insert_into_file csp_config_path, <<-RUBY, after: %(# Rails.application.config.content_security_policy do |policy|) 60 | # # If you are using webpack-dev-server then specify webpack-dev-server host 61 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? 62 | RUBY 63 | end 64 | 65 | results = [] 66 | 67 | Dir.chdir(Rails.root) do 68 | if Webpacker::VERSION.match?(/^[0-9]+\.[0-9]+\.[0-9]+$/) 69 | say "Installing all JavaScript dependencies [#{Webpacker::VERSION}]" 70 | results << run("yarn add @rails/webpacker@#{Webpacker::VERSION}") 71 | else 72 | say "Installing all JavaScript dependencies [from prerelease rails/webpacker]" 73 | results << run("yarn add @rails/webpacker@next") 74 | end 75 | 76 | package_json = File.read("#{__dir__}/../../package.json") 77 | webpack_version = package_json.match(/"webpack": "(.*)"/)[1] 78 | webpack_cli_version = package_json.match(/"webpack-cli": "(.*)"/)[1] 79 | 80 | # needed for experimental Yarn 2 support and should not harm Yarn 1 81 | say "Installing webpack and webpack-cli as direct dependencies" 82 | results << run("yarn add webpack@#{webpack_version} webpack-cli@#{webpack_cli_version}") 83 | 84 | say "Installing dev server for live reloading" 85 | results << run("yarn add --dev webpack-dev-server @webpack-cli/serve") 86 | end 87 | 88 | if Rails::VERSION::MAJOR == 5 && Rails::VERSION::MINOR > 1 89 | say "You need to allow webpack-dev-server host as allowed origin for connect-src.", :yellow 90 | say "This can be done in Rails 5.2+ for development environment in the CSP initializer", :yellow 91 | say "config/initializers/content_security_policy.rb with a snippet like this:", :yellow 92 | say "policy.connect_src :self, :https, \"http://localhost:3035\", \"ws://localhost:3035\" if Rails.env.development?", :yellow 93 | end 94 | 95 | unless results.all? 96 | say "Webpacker installation failed 😭 See above for details.", :red 97 | exit 1 98 | end 99 | -------------------------------------------------------------------------------- /lib/tasks/webpacker.rake: -------------------------------------------------------------------------------- 1 | tasks = { 2 | "webpacker:info" => "Provides information on Webpacker's environment", 3 | "webpacker:install" => "Installs and setup webpack with Yarn", 4 | "webpacker:compile" => "Compiles webpack bundles based on environment", 5 | "webpacker:clean" => "Remove old compiled webpacks", 6 | "webpacker:clobber" => "Removes the webpack compiled output directory", 7 | "webpacker:check_node" => "Verifies if Node.js is installed", 8 | "webpacker:check_yarn" => "Verifies if Yarn is installed", 9 | "webpacker:check_binstubs" => "Verifies that bin/webpacker is present", 10 | "webpacker:binstubs" => "Installs Webpacker binstubs in this application", 11 | "webpacker:verify_install" => "Verifies if Webpacker is installed", 12 | "webpacker:yarn_install" => "Support for older Rails versions. Install all JavaScript dependencies as specified via Yarn" 13 | }.freeze 14 | 15 | desc "Lists all available tasks in Webpacker" 16 | task :webpacker do 17 | puts "Available Webpacker tasks are:" 18 | tasks.each { |task, message| puts task.ljust(30) + message } 19 | end 20 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/binstubs.rake: -------------------------------------------------------------------------------- 1 | binstubs_template_path = File.expand_path("../../install/binstubs.rb", __dir__).freeze 2 | bin_path = ENV["BUNDLE_BIN"] || Rails.root.join("bin") 3 | 4 | namespace :webpacker do 5 | desc "Installs Webpacker binstubs in this application" 6 | task binstubs: [:check_node, :check_yarn] do |task| 7 | prefix = task.name.split(/#|webpacker:binstubs/).first 8 | 9 | if Rails::VERSION::MAJOR >= 5 10 | exec "#{RbConfig.ruby} #{bin_path}/rails #{prefix}app:template LOCATION='#{binstubs_template_path}'" 11 | else 12 | exec "#{RbConfig.ruby} #{bin_path}/rake #{prefix}rails:template LOCATION='#{binstubs_template_path}'" 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/check_binstubs.rake: -------------------------------------------------------------------------------- 1 | namespace :webpacker do 2 | desc "Verifies that bin/webpacker is present" 3 | task :check_binstubs do 4 | unless File.exist?(Rails.root.join("bin/webpacker")) 5 | $stderr.puts "webpacker binstub not found.\n"\ 6 | "Have you run rails webpacker:install ?\n"\ 7 | "Make sure the bin directory and bin/webpacker are not included in .gitignore\n"\ 8 | "Exiting!" 9 | exit! 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/check_node.rake: -------------------------------------------------------------------------------- 1 | require "semantic_range" 2 | namespace :webpacker do 3 | desc "Verifies if Node.js is installed" 4 | task :check_node do 5 | begin 6 | node_version = `node -v || nodejs -v`.strip 7 | raise Errno::ENOENT if node_version.blank? 8 | 9 | pkg_path = Pathname.new("#{__dir__}/../../../package.json").realpath 10 | node_range = JSON.parse(pkg_path.read)["engines"]["node"] 11 | is_valid = SemanticRange.satisfies?(node_version, node_range) rescue false 12 | semver_major = node_version[/\d+/] rescue nil 13 | is_unstable = semver_major.to_i.odd? rescue false 14 | 15 | if is_unstable 16 | $stderr.puts "Warning: you are using an unstable release of Node.js (#{node_version}). If you encounter issues with Node.js, consider switching to an Active LTS release. More info: https://docs.npmjs.com/try-the-latest-stable-version-of-node" 17 | end 18 | 19 | unless is_valid 20 | $stderr.puts "Webpacker requires Node.js \"#{node_range}\" and you are using #{node_version}" 21 | $stderr.puts "Please upgrade Node.js https://nodejs.org/en/download/" 22 | $stderr.puts "Exiting!" 23 | exit! 24 | end 25 | rescue Errno::ENOENT 26 | $stderr.puts "Node.js not installed. Please download and install Node.js https://nodejs.org/en/download/" 27 | $stderr.puts "Exiting!" 28 | exit! 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/check_yarn.rake: -------------------------------------------------------------------------------- 1 | require "semantic_range" 2 | namespace :webpacker do 3 | desc "Verifies if Yarn is installed" 4 | task :check_yarn do 5 | begin 6 | which_command = Gem.win_platform? ? "where" : "which" 7 | raise Errno::ENOENT if `#{which_command} yarn`.strip.empty? 8 | 9 | yarn_version = `yarn --version`.strip 10 | raise Errno::ENOENT if yarn_version.blank? 11 | 12 | pkg_path = Pathname.new("#{__dir__}/../../../package.json").realpath 13 | yarn_range = JSON.parse(pkg_path.read)["engines"]["yarn"] 14 | is_valid = SemanticRange.satisfies?(yarn_version, yarn_range) rescue false 15 | is_unsupported = SemanticRange.satisfies?(yarn_version, ">=4.0.0") rescue false 16 | 17 | unless is_valid 18 | $stderr.puts "Webpacker requires Yarn \"#{yarn_range}\" and you are using #{yarn_version}" 19 | if is_unsupported 20 | $stderr.puts "This version of Webpacker does not support Yarn #{yarn_version}. Please downgrade to a supported version of Yarn https://yarnpkg.com/lang/en/docs/install/" 21 | else 22 | $stderr.puts "Please upgrade Yarn https://yarnpkg.com/lang/en/docs/install/" 23 | end 24 | $stderr.puts "Exiting!" 25 | exit! 26 | end 27 | rescue Errno::ENOENT 28 | $stderr.puts "Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/" 29 | $stderr.puts "Exiting!" 30 | exit! 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/clean.rake: -------------------------------------------------------------------------------- 1 | $stdout.sync = true 2 | 3 | require "webpacker/configuration" 4 | 5 | namespace :webpacker do 6 | desc "Remove old compiled webpacks" 7 | task :clean, [:keep, :age] => ["webpacker:verify_install", :environment] do |_, args| 8 | Webpacker.ensure_log_goes_to_stdout do 9 | Webpacker.clean(Integer(args.keep || 2), Integer(args.age || 3600)) 10 | end 11 | end 12 | end 13 | 14 | skip_webpacker_clean = %w(no false n f).include?(ENV["WEBPACKER_PRECOMPILE"]) 15 | 16 | unless skip_webpacker_clean 17 | # Run clean if the assets:clean is run 18 | if Rake::Task.task_defined?("assets:clean") 19 | Rake::Task["assets:clean"].enhance do 20 | Rake::Task["webpacker:clean"].invoke 21 | end 22 | else 23 | Rake::Task.define_task("assets:clean" => "webpacker:clean") 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/clobber.rake: -------------------------------------------------------------------------------- 1 | require "webpacker/configuration" 2 | 3 | namespace :webpacker do 4 | desc "Remove the webpack compiled output directory" 5 | task clobber: ["webpacker:verify_config", :environment] do 6 | Webpacker.clobber 7 | $stdout.puts "Removed webpack output path directory #{Webpacker.config.public_output_path}" 8 | end 9 | end 10 | 11 | skip_webpacker_clobber = %w(no false n f).include?(ENV["WEBPACKER_PRECOMPILE"]) 12 | 13 | unless skip_webpacker_clobber 14 | # Run clobber if the assets:clobber is run 15 | if Rake::Task.task_defined?("assets:clobber") 16 | Rake::Task["assets:clobber"].enhance do 17 | Rake::Task["webpacker:clobber"].invoke 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/compile.rake: -------------------------------------------------------------------------------- 1 | $stdout.sync = true 2 | 3 | def yarn_install_available? 4 | rails_major = Rails::VERSION::MAJOR 5 | rails_minor = Rails::VERSION::MINOR 6 | 7 | rails_major > 5 || (rails_major == 5 && rails_minor >= 1) 8 | end 9 | 10 | def enhance_assets_precompile 11 | # yarn:install was added in Rails 5.1 12 | deps = yarn_install_available? ? [] : ["webpacker:yarn_install"] 13 | Rake::Task["assets:precompile"].enhance(deps) do |task| 14 | prefix = task.name.split(/#|assets:precompile/).first 15 | 16 | Rake::Task["#{prefix}webpacker:compile"].invoke 17 | end 18 | end 19 | 20 | namespace :webpacker do 21 | desc "Compile JavaScript packs using webpack for production with digests" 22 | task compile: ["webpacker:verify_install", :environment] do 23 | Webpacker.with_node_env(ENV.fetch("NODE_ENV", "production")) do 24 | Webpacker.ensure_log_goes_to_stdout do 25 | if Webpacker.compile 26 | # Successful compilation! 27 | else 28 | # Failed compilation 29 | exit! 30 | end 31 | end 32 | end 33 | end 34 | end 35 | 36 | # Compile packs after we've compiled all other assets during precompilation 37 | skip_webpacker_precompile = %w(no false n f).include?(ENV["WEBPACKER_PRECOMPILE"]) 38 | 39 | unless skip_webpacker_precompile 40 | if Rake::Task.task_defined?("assets:precompile") 41 | enhance_assets_precompile 42 | else 43 | Rake::Task.define_task("assets:precompile" => ["webpacker:yarn_install", "webpacker:compile"]) 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/info.rake: -------------------------------------------------------------------------------- 1 | require "webpacker/version" 2 | 3 | namespace :webpacker do 4 | desc "Provide information on Webpacker's environment" 5 | task :info do 6 | Dir.chdir(Rails.root) do 7 | $stdout.puts "Ruby: #{`ruby --version`}" 8 | $stdout.puts "Rails: #{Rails.version}" 9 | $stdout.puts "Webpacker: #{Webpacker::VERSION}" 10 | $stdout.puts "Node: #{`node --version`}" 11 | $stdout.puts "Yarn: #{`yarn --version`}" 12 | 13 | $stdout.puts "\n" 14 | $stdout.puts "@rails/webpacker: \n#{`npm list @rails/webpacker version`}" 15 | 16 | $stdout.puts "Is bin/webpacker present?: #{File.exist? 'bin/webpacker'}" 17 | $stdout.puts "Is bin/webpacker-dev-server present?: #{File.exist? 'bin/webpacker-dev-server'}" 18 | $stdout.puts "Is bin/yarn present?: #{File.exist? 'bin/yarn'}" 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/install.rake: -------------------------------------------------------------------------------- 1 | install_template_path = File.expand_path("../../install/template.rb", __dir__).freeze 2 | bin_path = ENV["BUNDLE_BIN"] || Rails.root.join("bin") 3 | 4 | namespace :webpacker do 5 | desc "Install Webpacker in this application" 6 | task install: [:check_node, :check_yarn] do |task| 7 | Webpacker::Configuration.installing = true 8 | 9 | prefix = task.name.split(/#|webpacker:install/).first 10 | 11 | if Rails::VERSION::MAJOR >= 5 12 | exec "#{RbConfig.ruby} #{bin_path}/rails #{prefix}app:template LOCATION='#{install_template_path}'" 13 | else 14 | exec "#{RbConfig.ruby} #{bin_path}/rake #{prefix}rails:template LOCATION='#{install_template_path}'" 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/verify_config.rake: -------------------------------------------------------------------------------- 1 | require "webpacker/configuration" 2 | 3 | namespace :webpacker do 4 | desc "Verifies if the Webpacker config is present" 5 | task :verify_config do 6 | unless Webpacker.config.config_path.exist? 7 | path = Webpacker.config.config_path.relative_path_from(Pathname.new(pwd)).to_s 8 | $stderr.puts "Configuration #{path} file not found. \n"\ 9 | "Make sure webpacker:install is run successfully before " \ 10 | "running dependent tasks" 11 | exit! 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/verify_install.rake: -------------------------------------------------------------------------------- 1 | namespace :webpacker do 2 | desc "Verifies if Webpacker is installed" 3 | task verify_install: [:verify_config, :check_node, :check_yarn, :check_binstubs] 4 | end 5 | -------------------------------------------------------------------------------- /lib/tasks/webpacker/yarn_install.rake: -------------------------------------------------------------------------------- 1 | namespace :webpacker do 2 | desc "Support for older Rails versions. Install all JavaScript dependencies as specified via Yarn" 3 | task :yarn_install do 4 | valid_node_envs = %w[test development production] 5 | node_env = ENV.fetch("NODE_ENV") do 6 | valid_node_envs.include?(Rails.env) ? Rails.env : "production" 7 | end 8 | Dir.chdir(Rails.root) do 9 | yarn_flags = 10 | if `yarn --version`.start_with?("1") 11 | "--no-progress --frozen-lockfile" 12 | else 13 | "--immutable" 14 | end 15 | system({ "NODE_ENV" => node_env }, "yarn install #{yarn_flags}") 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/tasks/yarn.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Duplicate of the yarn tasks still present in Rails until Webpacker <5 have been deprecated 4 | 5 | namespace :yarn do 6 | desc "Install all JavaScript dependencies as specified via Yarn" 7 | task :install do 8 | begin 9 | # Install only production deps when for not usual envs. 10 | valid_node_envs = %w[test development production] 11 | node_env = ENV.fetch("NODE_ENV") do 12 | valid_node_envs.include?(Rails.env) ? Rails.env : "production" 13 | end 14 | 15 | yarn_flags = 16 | if `#{RbConfig.ruby} "#{Rails.root}/bin/yarn" --version`.start_with?("1") 17 | "--no-progress --frozen-lockfile" 18 | else 19 | "--immutable" 20 | end 21 | 22 | system( 23 | { "NODE_ENV" => node_env }, 24 | "#{RbConfig.ruby} \"#{Rails.root}/bin/yarn\" install #{yarn_flags}", 25 | exception: true 26 | ) 27 | rescue Errno::ENOENT 28 | $stderr.puts "bin/yarn was not found." 29 | $stderr.puts "Please run `bundle exec rails app:update:bin` to create it." 30 | exit 1 31 | end 32 | end 33 | end 34 | 35 | # Run Yarn prior to Sprockets assets precompilation, so dependencies are available for use. 36 | if Rake::Task.task_defined?("assets:precompile") && File.exist?(Rails.root.join("bin", "yarn")) 37 | Rake::Task["assets:precompile"].enhance [ "yarn:install" ] 38 | end 39 | -------------------------------------------------------------------------------- /lib/webpacker.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/module/attribute_accessors" 2 | require "active_support/core_ext/string/inquiry" 3 | require "active_support/logger" 4 | require "active_support/tagged_logging" 5 | 6 | module Webpacker 7 | extend self 8 | 9 | def instance=(instance) 10 | @instance = instance 11 | end 12 | 13 | def instance 14 | @instance ||= Webpacker::Instance.new 15 | end 16 | 17 | def with_node_env(env) 18 | original = ENV["NODE_ENV"] 19 | ENV["NODE_ENV"] = env 20 | yield 21 | ensure 22 | ENV["NODE_ENV"] = original 23 | end 24 | 25 | def ensure_log_goes_to_stdout 26 | old_logger = Webpacker.logger 27 | Webpacker.logger = Logger.new(STDOUT) 28 | yield 29 | ensure 30 | Webpacker.logger = old_logger 31 | end 32 | 33 | delegate :logger, :logger=, :env, :inlining_css?, to: :instance 34 | delegate :config, :compiler, :manifest, :commands, :dev_server, to: :instance 35 | delegate :bootstrap, :clean, :clobber, :compile, to: :commands 36 | end 37 | 38 | require "webpacker/instance" 39 | require "webpacker/env" 40 | require "webpacker/configuration" 41 | require "webpacker/manifest" 42 | require "webpacker/compiler" 43 | require "webpacker/commands" 44 | require "webpacker/dev_server" 45 | 46 | require "webpacker/railtie" if defined?(Rails) 47 | -------------------------------------------------------------------------------- /lib/webpacker/commands.rb: -------------------------------------------------------------------------------- 1 | class Webpacker::Commands 2 | delegate :config, :compiler, :manifest, :logger, to: :@webpacker 3 | 4 | def initialize(webpacker) 5 | @webpacker = webpacker 6 | end 7 | 8 | # Cleanup old assets in the compile directory. By default it will 9 | # keep the latest version, 2 backups created within the past hour. 10 | # 11 | # Examples 12 | # 13 | # To force only 1 backup to be kept, set count=1 and age=0. 14 | # 15 | # To only keep files created within the last 10 minutes, set count=0 and 16 | # age=600. 17 | # 18 | def clean(count = 2, age = 3600) 19 | if config.public_output_path.exist? && config.public_manifest_path.exist? 20 | packs 21 | .map do |paths| 22 | paths.map { |path| [Time.now - File.mtime(path), path] } 23 | .sort 24 | .reject.with_index do |(file_age, _), index| 25 | file_age < age || index < count 26 | end 27 | .map { |_, path| path } 28 | end 29 | .flatten 30 | .compact 31 | .each do |file| 32 | if File.file?(file) 33 | File.delete(file) 34 | logger.info "Removed #{file}" 35 | end 36 | end 37 | end 38 | 39 | true 40 | end 41 | 42 | def clobber 43 | config.public_output_path.rmtree if config.public_output_path.exist? 44 | config.cache_path.rmtree if config.cache_path.exist? 45 | end 46 | 47 | def bootstrap 48 | manifest.refresh 49 | end 50 | 51 | def compile 52 | compiler.compile.tap do |success| 53 | manifest.refresh if success 54 | end 55 | end 56 | 57 | private 58 | def packs 59 | all_files = Dir.glob("#{config.public_output_path}/**/*") 60 | manifest_config = Dir.glob("#{config.public_manifest_path}*") 61 | 62 | packs = all_files - manifest_config - current_version 63 | packs.reject { |file| File.directory?(file) }.group_by do |path| 64 | base, _, ext = File.basename(path).scan(/(.*)(-[\da-f]+)(\.\w+)/).flatten 65 | "#{File.dirname(path)}/#{base}#{ext}" 66 | end.values 67 | end 68 | 69 | def current_version 70 | packs = manifest.refresh.values.map do |value| 71 | value = value["src"] if value.is_a?(Hash) 72 | next unless value.is_a?(String) 73 | 74 | File.join(config.root_path, "public", "#{value}*") 75 | end.compact 76 | 77 | Dir.glob(packs).uniq 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /lib/webpacker/compiler.rb: -------------------------------------------------------------------------------- 1 | require "open3" 2 | require "digest/sha1" 3 | 4 | class Webpacker::Compiler 5 | # Additional paths that test compiler needs to watch 6 | # Webpacker::Compiler.watched_paths << 'bower_components' 7 | # 8 | # Deprecated. Use additional_paths in the YAML configuration instead. 9 | cattr_accessor(:watched_paths) { [] } 10 | 11 | # Additional environment variables that the compiler is being run with 12 | # Webpacker::Compiler.env['FRONTEND_API_KEY'] = 'your_secret_key' 13 | cattr_accessor(:env) { {} } 14 | 15 | delegate :config, :logger, to: :webpacker 16 | 17 | def initialize(webpacker) 18 | @webpacker = webpacker 19 | end 20 | 21 | def compile 22 | if stale? 23 | run_webpack.tap do |success| 24 | # We used to only record the digest on success 25 | # However, the output file is still written on error, meaning that the digest should still be updated. 26 | # If it's not, you can end up in a situation where a recompile doesn't take place when it should. 27 | # See https://github.com/rails/webpacker/issues/2113 28 | record_compilation_digest 29 | end 30 | else 31 | logger.debug "Everything's up-to-date. Nothing to do" 32 | true 33 | end 34 | end 35 | 36 | # Returns true if all the compiled packs are up to date with the underlying asset files. 37 | def fresh? 38 | last_compilation_digest&.== watched_files_digest 39 | end 40 | 41 | # Returns true if the compiled packs are out of date with the underlying asset files. 42 | def stale? 43 | !fresh? 44 | end 45 | 46 | private 47 | attr_reader :webpacker 48 | 49 | def last_compilation_digest 50 | compilation_digest_path.read if compilation_digest_path.exist? && config.public_manifest_path.exist? 51 | rescue Errno::ENOENT, Errno::ENOTDIR 52 | end 53 | 54 | def watched_files_digest 55 | warn "Webpacker::Compiler.watched_paths has been deprecated. Set additional_paths in webpacker.yml instead." unless watched_paths.empty? 56 | Dir.chdir File.expand_path(config.root_path) do 57 | files = Dir[*default_watched_paths, *watched_paths].reject { |f| File.directory?(f) } 58 | file_ids = files.sort.map { |f| "#{File.basename(f)}/#{Digest::SHA1.file(f).hexdigest}" } 59 | Digest::SHA1.hexdigest(file_ids.join("/")) 60 | end 61 | end 62 | 63 | def record_compilation_digest 64 | config.cache_path.mkpath 65 | compilation_digest_path.write(watched_files_digest) 66 | end 67 | 68 | def optionalRubyRunner 69 | bin_webpack_path = config.root_path.join("bin/webpacker") 70 | first_line = File.readlines(bin_webpack_path).first.chomp 71 | /ruby/.match?(first_line) ? RbConfig.ruby : "" 72 | end 73 | 74 | def run_webpack 75 | logger.info "Compiling..." 76 | 77 | stdout, stderr, status = Open3.capture3( 78 | webpack_env, 79 | "#{optionalRubyRunner} ./bin/webpacker", 80 | chdir: File.expand_path(config.root_path) 81 | ) 82 | 83 | if status.success? 84 | logger.info "Compiled all packs in #{config.public_output_path}" 85 | logger.error "#{stderr}" unless stderr.empty? 86 | 87 | if config.webpack_compile_output? 88 | logger.info stdout 89 | end 90 | else 91 | non_empty_streams = [stdout, stderr].delete_if(&:empty?) 92 | logger.error "\nCOMPILATION FAILED:\nEXIT STATUS: #{status}\nOUTPUTS:\n#{non_empty_streams.join("\n\n")}" 93 | end 94 | 95 | status.success? 96 | end 97 | 98 | def default_watched_paths 99 | [ 100 | *config.additional_paths, 101 | "#{config.source_path}/**/*", 102 | "yarn.lock", "package.json", 103 | "config/webpack/**/*" 104 | ].freeze 105 | end 106 | 107 | def compilation_digest_path 108 | config.cache_path.join("last-compilation-digest-#{webpacker.env}") 109 | end 110 | 111 | def webpack_env 112 | return env unless defined?(ActionController::Base) 113 | 114 | env.merge("WEBPACKER_ASSET_HOST" => ENV.fetch("WEBPACKER_ASSET_HOST", ActionController::Base.helpers.compute_asset_host), 115 | "WEBPACKER_RELATIVE_URL_ROOT" => ENV.fetch("WEBPACKER_RELATIVE_URL_ROOT", ActionController::Base.relative_url_root), 116 | "WEBPACKER_CONFIG" => webpacker.config_path.to_s) 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /lib/webpacker/configuration.rb: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | require "active_support/core_ext/hash/keys" 3 | require "active_support/core_ext/hash/indifferent_access" 4 | 5 | class Webpacker::Configuration 6 | class << self 7 | attr_accessor :installing 8 | end 9 | 10 | attr_reader :root_path, :config_path, :env 11 | 12 | def initialize(root_path:, config_path:, env:) 13 | @root_path = root_path 14 | @config_path = config_path 15 | @env = env 16 | end 17 | 18 | def dev_server 19 | fetch(:dev_server) 20 | end 21 | 22 | def compile? 23 | fetch(:compile) 24 | end 25 | 26 | def source_path 27 | root_path.join(fetch(:source_path)) 28 | end 29 | 30 | def additional_paths 31 | fetch(:additional_paths) 32 | end 33 | 34 | def source_entry_path 35 | source_path.join(fetch(:source_entry_path)) 36 | end 37 | 38 | def public_path 39 | root_path.join(fetch(:public_root_path)) 40 | end 41 | 42 | def public_output_path 43 | public_path.join(fetch(:public_output_path)) 44 | end 45 | 46 | def public_manifest_path 47 | public_output_path.join("manifest.json") 48 | end 49 | 50 | def cache_manifest? 51 | fetch(:cache_manifest) 52 | end 53 | 54 | def cache_path 55 | root_path.join(fetch(:cache_path)) 56 | end 57 | 58 | def check_yarn_integrity=(value) 59 | warn <<~EOS 60 | Webpacker::Configuration#check_yarn_integrity=(value) is obsolete. The integrity 61 | check has been removed from Webpacker (https://github.com/rails/webpacker/pull/2518) 62 | so changing this setting will have no effect. 63 | EOS 64 | end 65 | 66 | def webpack_compile_output? 67 | fetch(:webpack_compile_output) 68 | end 69 | 70 | def fetch(key) 71 | data.fetch(key, defaults[key]) 72 | end 73 | 74 | private 75 | def data 76 | @data ||= load 77 | end 78 | 79 | def load 80 | config = begin 81 | YAML.load_file(config_path.to_s, aliases: true) 82 | rescue ArgumentError 83 | YAML.load_file(config_path.to_s) 84 | end 85 | config[env].deep_symbolize_keys 86 | rescue Errno::ENOENT => e 87 | if self.class.installing 88 | {} 89 | else 90 | raise "Webpacker configuration file not found #{config_path}. " \ 91 | "Please run rails webpacker:install " \ 92 | "Error: #{e.message}" 93 | end 94 | rescue Psych::SyntaxError => e 95 | raise "YAML syntax error occurred while parsing #{config_path}. " \ 96 | "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ 97 | "Error: #{e.message}" 98 | end 99 | 100 | def defaults 101 | @defaults ||= begin 102 | path = File.expand_path("../../install/config/webpacker.yml", __FILE__) 103 | config = begin 104 | YAML.load_file(path, aliases: true) 105 | rescue ArgumentError 106 | YAML.load_file(path) 107 | end 108 | HashWithIndifferentAccess.new(config[env]) 109 | end 110 | end 111 | end 112 | -------------------------------------------------------------------------------- /lib/webpacker/dev_server.rb: -------------------------------------------------------------------------------- 1 | class Webpacker::DevServer 2 | DEFAULT_ENV_PREFIX = "WEBPACKER_DEV_SERVER".freeze 3 | 4 | # Configure dev server connection timeout (in seconds), default: 0.01 5 | # Webpacker.dev_server.connect_timeout = 1 6 | cattr_accessor(:connect_timeout) { 0.01 } 7 | 8 | attr_reader :config 9 | 10 | def initialize(config) 11 | @config = config 12 | end 13 | 14 | def running? 15 | if config.dev_server.present? 16 | Socket.tcp(host, port, connect_timeout: connect_timeout).close 17 | true 18 | else 19 | false 20 | end 21 | rescue 22 | false 23 | end 24 | 25 | def host 26 | fetch(:host) 27 | end 28 | 29 | def port 30 | fetch(:port) 31 | end 32 | 33 | def https? 34 | case fetch(:https) 35 | when true, "true", Hash 36 | true 37 | else 38 | false 39 | end 40 | end 41 | 42 | def protocol 43 | https? ? "https" : "http" 44 | end 45 | 46 | def host_with_port 47 | "#{host}:#{port}" 48 | end 49 | 50 | def pretty? 51 | fetch(:pretty) 52 | end 53 | 54 | def hmr? 55 | fetch(:hmr) 56 | end 57 | 58 | def env_prefix 59 | config.dev_server.fetch(:env_prefix, DEFAULT_ENV_PREFIX) 60 | end 61 | 62 | private 63 | def fetch(key) 64 | return nil unless config.dev_server.present? 65 | 66 | ENV["#{env_prefix}_#{key.upcase}"] || config.dev_server.fetch(key, defaults[key]) 67 | end 68 | 69 | def defaults 70 | config.send(:defaults)[:dev_server] || {} 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/webpacker/dev_server_proxy.rb: -------------------------------------------------------------------------------- 1 | require "rack/proxy" 2 | 3 | class Webpacker::DevServerProxy < Rack::Proxy 4 | delegate :config, :dev_server, to: :@webpacker 5 | 6 | def initialize(app = nil, opts = {}) 7 | @webpacker = opts.delete(:webpacker) || Webpacker.instance 8 | opts[:streaming] = false if Rails.env.test? && !opts.key?(:streaming) 9 | super 10 | end 11 | 12 | def perform_request(env) 13 | if env["PATH_INFO"].start_with?("/#{public_output_uri_path}") && dev_server.running? 14 | env["HTTP_HOST"] = env["HTTP_X_FORWARDED_HOST"] = dev_server.host 15 | env["HTTP_X_FORWARDED_SERVER"] = dev_server.host_with_port 16 | env["HTTP_PORT"] = env["HTTP_X_FORWARDED_PORT"] = dev_server.port.to_s 17 | env["HTTP_X_FORWARDED_PROTO"] = env["HTTP_X_FORWARDED_SCHEME"] = dev_server.protocol 18 | unless dev_server.https? 19 | env["HTTPS"] = env["HTTP_X_FORWARDED_SSL"] = "off" 20 | end 21 | env["SCRIPT_NAME"] = "" 22 | 23 | super(env) 24 | else 25 | @app.call(env) 26 | end 27 | end 28 | 29 | private 30 | def public_output_uri_path 31 | config.public_output_path.relative_path_from(config.public_path).to_s + "/" 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/webpacker/dev_server_runner.rb: -------------------------------------------------------------------------------- 1 | require "shellwords" 2 | require "socket" 3 | require "webpacker/configuration" 4 | require "webpacker/dev_server" 5 | require "webpacker/runner" 6 | 7 | module Webpacker 8 | class DevServerRunner < Webpacker::Runner 9 | def run 10 | load_config 11 | detect_unsupported_switches! 12 | detect_port! 13 | execute_cmd 14 | end 15 | 16 | private 17 | 18 | def load_config 19 | app_root = Pathname.new(@app_path) 20 | 21 | @config = Configuration.new( 22 | root_path: app_root, 23 | config_path: Pathname.new(@webpacker_config), 24 | env: ENV["RAILS_ENV"] 25 | ) 26 | 27 | dev_server = DevServer.new(@config) 28 | 29 | @hostname = dev_server.host 30 | @port = dev_server.port 31 | @pretty = dev_server.pretty? 32 | @https = dev_server.https? 33 | @hot = dev_server.hmr? 34 | 35 | rescue Errno::ENOENT, NoMethodError 36 | $stdout.puts "webpack dev_server configuration not found in #{@config.config_path}[#{ENV["RAILS_ENV"]}]." 37 | $stdout.puts "Please run bundle exec rails webpacker:install to install Webpacker" 38 | exit! 39 | end 40 | 41 | UNSUPPORTED_SWITCHES = %w[--host --port] 42 | private_constant :UNSUPPORTED_SWITCHES 43 | def detect_unsupported_switches! 44 | unsupported_switches = UNSUPPORTED_SWITCHES & @argv 45 | if unsupported_switches.any? 46 | $stdout.puts "The following CLI switches are not supported by Webpacker: #{unsupported_switches.join(' ')}. Please edit your command and try again." 47 | exit! 48 | end 49 | 50 | if @argv.include?("--https") && !@https 51 | $stdout.puts "Please set https: true in webpacker.yml to use the --https command line flag." 52 | exit! 53 | end 54 | end 55 | 56 | def detect_port! 57 | server = TCPServer.new(@hostname, @port) 58 | server.close 59 | 60 | rescue Errno::EADDRINUSE 61 | $stdout.puts "Another program is running on port #{@port}. Set a new port in #{@config.config_path} for dev_server" 62 | exit! 63 | end 64 | 65 | def execute_cmd 66 | env = Webpacker::Compiler.env 67 | env["WEBPACKER_CONFIG"] = @webpacker_config 68 | env["WEBPACK_SERVE"] = "true" 69 | 70 | cmd = if node_modules_bin_exist? 71 | ["#{@node_modules_bin_path}/webpack", "serve"] 72 | else 73 | ["yarn", "webpack", "serve"] 74 | end 75 | 76 | if @argv.include?("--debug-webpacker") 77 | cmd = [ "node", "--inspect-brk", "--trace-warnings" ] + cmd 78 | @argv.delete "--debug-webpacker" 79 | end 80 | 81 | cmd += ["--config", @webpack_config] 82 | cmd += ["--progress", "--color"] if @pretty 83 | 84 | cmd += ["--hot"] if @hot 85 | cmd += @argv 86 | 87 | Dir.chdir(@app_path) do 88 | Kernel.exec env, *cmd 89 | end 90 | end 91 | 92 | def node_modules_bin_exist? 93 | File.exist?("#{@node_modules_bin_path}/webpack-dev-server") 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/webpacker/env.rb: -------------------------------------------------------------------------------- 1 | class Webpacker::Env 2 | DEFAULT = "production".freeze 3 | 4 | delegate :config_path, :logger, to: :@webpacker 5 | 6 | def self.inquire(webpacker) 7 | new(webpacker).inquire 8 | end 9 | 10 | def initialize(webpacker) 11 | @webpacker = webpacker 12 | end 13 | 14 | def inquire 15 | fallback_env_warning if config_path.exist? && !current 16 | current || DEFAULT.inquiry 17 | end 18 | 19 | private 20 | def current 21 | Rails.env.presence_in(available_environments) 22 | end 23 | 24 | def fallback_env_warning 25 | logger.info "RAILS_ENV=#{Rails.env} environment is not defined in config/webpacker.yml, falling back to #{DEFAULT} environment" 26 | end 27 | 28 | def available_environments 29 | if config_path.exist? 30 | begin 31 | YAML.load_file(config_path.to_s, aliases: true) 32 | rescue ArgumentError 33 | YAML.load_file(config_path.to_s) 34 | end 35 | else 36 | [].freeze 37 | end 38 | rescue Psych::SyntaxError => e 39 | raise "YAML syntax error occurred while parsing #{config_path}. " \ 40 | "Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \ 41 | "Error: #{e.message}" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/webpacker/helper.rb: -------------------------------------------------------------------------------- 1 | module Webpacker::Helper 2 | # Returns the current Webpacker instance. 3 | # Could be overridden to use multiple Webpacker 4 | # configurations within the same app (e.g. with engines). 5 | def current_webpacker_instance 6 | Webpacker.instance 7 | end 8 | 9 | # Computes the relative path for a given Webpacker asset. 10 | # Returns the relative path using manifest.json and passes it to path_to_asset helper. 11 | # This will use path_to_asset internally, so most of their behaviors will be the same. 12 | # 13 | # Example: 14 | # 15 | # <%= asset_pack_path 'calendar.css' %> # => "/packs/calendar-1016838bab065ae1e122.css" 16 | def asset_pack_path(name, **options) 17 | path_to_asset(current_webpacker_instance.manifest.lookup!(name), options) 18 | end 19 | 20 | # Computes the absolute path for a given Webpacker asset. 21 | # Returns the absolute path using manifest.json and passes it to url_to_asset helper. 22 | # This will use url_to_asset internally, so most of their behaviors will be the same. 23 | # 24 | # Example: 25 | # 26 | # <%= asset_pack_url 'calendar.css' %> # => "http://example.com/packs/calendar-1016838bab065ae1e122.css" 27 | def asset_pack_url(name, **options) 28 | url_to_asset(current_webpacker_instance.manifest.lookup!(name), options) 29 | end 30 | 31 | # Computes the relative path for a given Webpacker image with the same automated processing as image_pack_tag. 32 | # Returns the relative path using manifest.json and passes it to path_to_asset helper. 33 | # This will use path_to_asset internally, so most of their behaviors will be the same. 34 | def image_pack_path(name, **options) 35 | resolve_path_to_image(name, **options) 36 | end 37 | 38 | # Computes the absolute path for a given Webpacker image with the same automated 39 | # processing as image_pack_tag. Returns the relative path using manifest.json 40 | # and passes it to path_to_asset helper. This will use path_to_asset internally, 41 | # so most of their behaviors will be the same. 42 | def image_pack_url(name, **options) 43 | resolve_path_to_image(name, **options.merge(protocol: :request)) 44 | end 45 | 46 | # Creates an image tag that references the named pack file. 47 | # 48 | # Example: 49 | # 50 | # <%= image_pack_tag 'application.png', size: '16x10', alt: 'Edit Entry' %> 51 | # Edit Entry 52 | # 53 | # <%= image_pack_tag 'picture.png', srcset: { 'picture-2x.png' => '2x' } %> 54 | # 55 | def image_pack_tag(name, **options) 56 | if options[:srcset] && !options[:srcset].is_a?(String) 57 | options[:srcset] = options[:srcset].map do |src_name, size| 58 | "#{resolve_path_to_image(src_name)} #{size}" 59 | end.join(", ") 60 | end 61 | 62 | image_tag(resolve_path_to_image(name), options) 63 | end 64 | 65 | # Creates a link tag for a favicon that references the named pack file. 66 | # 67 | # Example: 68 | # 69 | # <%= favicon_pack_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' %> 70 | # 71 | def favicon_pack_tag(name, **options) 72 | favicon_link_tag(resolve_path_to_image(name), options) 73 | end 74 | 75 | # Creates script tags that reference the js chunks from entrypoints when using split chunks API, 76 | # as compiled by webpack per the entries list in package/environments/base.js. 77 | # By default, this list is auto-generated to match everything in 78 | # app/packs/entrypoints/*.js and all the dependent chunks. In production mode, the digested reference is automatically looked up. 79 | # See: https://webpack.js.org/plugins/split-chunks-plugin/ 80 | # 81 | # Example: 82 | # 83 | # <%= javascript_pack_tag 'calendar', 'map', 'data-turbolinks-track': 'reload' %> # => 84 | # 85 | # 86 | # 87 | # 88 | # 89 | # 90 | # DO: 91 | # 92 | # <%= javascript_pack_tag 'calendar', 'map' %> 93 | # 94 | # DON'T: 95 | # 96 | # <%= javascript_pack_tag 'calendar' %> 97 | # <%= javascript_pack_tag 'map' %> 98 | def javascript_pack_tag(*names, defer: true, **options) 99 | javascript_include_tag(*sources_from_manifest_entrypoints(names, type: :javascript), **options.tap { |o| o[:defer] = defer }) 100 | end 101 | 102 | # Creates a link tag, for preloading, that references a given Webpacker asset. 103 | # In production mode, the digested reference is automatically looked up. 104 | # See: https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content 105 | # 106 | # Example: 107 | # 108 | # <%= preload_pack_asset 'fonts/fa-regular-400.woff2' %> # => 109 | # 110 | def preload_pack_asset(name, **options) 111 | if self.class.method_defined?(:preload_link_tag) 112 | preload_link_tag(current_webpacker_instance.manifest.lookup!(name), options) 113 | else 114 | raise "You need Rails >= 5.2 to use this tag." 115 | end 116 | end 117 | 118 | # Creates link tags that reference the css chunks from entrypoints when using split chunks API, 119 | # as compiled by webpack per the entries list in package/environments/base.js. 120 | # By default, this list is auto-generated to match everything in 121 | # app/packs/entrypoints/*.js and all the dependent chunks. In production mode, the digested reference is automatically looked up. 122 | # See: https://webpack.js.org/plugins/split-chunks-plugin/ 123 | # 124 | # Examples: 125 | # 126 | # <%= stylesheet_pack_tag 'calendar', 'map' %> # => 127 | # 128 | # 129 | # 130 | # 131 | # When using the webpack-dev-server, CSS is inlined so HMR can be turned on for CSS, 132 | # including CSS modules 133 | # <%= stylesheet_pack_tag 'calendar', 'map' %> # => nil 134 | # 135 | # DO: 136 | # 137 | # <%= stylesheet_pack_tag 'calendar', 'map' %> 138 | # 139 | # DON'T: 140 | # 141 | # <%= stylesheet_pack_tag 'calendar' %> 142 | # <%= stylesheet_pack_tag 'map' %> 143 | def stylesheet_pack_tag(*names, **options) 144 | return "" if Webpacker.inlining_css? 145 | 146 | stylesheet_link_tag(*sources_from_manifest_entrypoints(names, type: :stylesheet), **options) 147 | end 148 | 149 | private 150 | 151 | def sources_from_manifest_entrypoints(names, type:) 152 | names.map { |name| current_webpacker_instance.manifest.lookup_pack_with_chunks!(name.to_s, type: type) }.flatten.uniq 153 | end 154 | 155 | def resolve_path_to_image(name, **options) 156 | path = name.starts_with?("static/") ? name : "static/#{name}" 157 | path_to_asset(current_webpacker_instance.manifest.lookup!(path), options) 158 | rescue 159 | path_to_asset(current_webpacker_instance.manifest.lookup!(name), options) 160 | end 161 | end 162 | -------------------------------------------------------------------------------- /lib/webpacker/instance.rb: -------------------------------------------------------------------------------- 1 | class Webpacker::Instance 2 | cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) } 3 | 4 | attr_reader :root_path, :config_path 5 | 6 | def initialize(root_path: Rails.root, config_path: Rails.root.join("config/webpacker.yml")) 7 | @root_path, @config_path = root_path, config_path 8 | end 9 | 10 | def env 11 | @env ||= Webpacker::Env.inquire self 12 | end 13 | 14 | def config 15 | @config ||= Webpacker::Configuration.new( 16 | root_path: root_path, 17 | config_path: config_path, 18 | env: env 19 | ) 20 | end 21 | 22 | def compiler 23 | @compiler ||= Webpacker::Compiler.new self 24 | end 25 | 26 | def dev_server 27 | @dev_server ||= Webpacker::DevServer.new config 28 | end 29 | 30 | def manifest 31 | @manifest ||= Webpacker::Manifest.new self 32 | end 33 | 34 | def commands 35 | @commands ||= Webpacker::Commands.new self 36 | end 37 | 38 | def inlining_css? 39 | dev_server.hmr? && dev_server.running? 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/webpacker/manifest.rb: -------------------------------------------------------------------------------- 1 | # Singleton registry for accessing the packs path using a generated manifest. 2 | # This allows javascript_pack_tag, stylesheet_pack_tag, asset_pack_path to take a reference to, 3 | # say, "calendar.js" or "calendar.css" and turn it into "/packs/calendar-1016838bab065ae1e314.js" or 4 | # "/packs/calendar-1016838bab065ae1e314.css". 5 | # 6 | # When the configuration is set to on-demand compilation, with the `compile: true` option in 7 | # the webpacker.yml file, any lookups will be preceded by a compilation if one is needed. 8 | class Webpacker::Manifest 9 | class MissingEntryError < StandardError; end 10 | 11 | delegate :config, :compiler, :dev_server, to: :@webpacker 12 | 13 | def initialize(webpacker) 14 | @webpacker = webpacker 15 | end 16 | 17 | def refresh 18 | @data = load 19 | end 20 | 21 | def lookup_pack_with_chunks(name, pack_type = {}) 22 | compile if compiling? 23 | 24 | manifest_pack_type = manifest_type(pack_type[:type]) 25 | manifest_pack_name = manifest_name(name, manifest_pack_type) 26 | find("entrypoints")[manifest_pack_name]["assets"][manifest_pack_type] 27 | rescue NoMethodError 28 | nil 29 | end 30 | 31 | def lookup_pack_with_chunks!(name, pack_type = {}) 32 | lookup_pack_with_chunks(name, pack_type) || handle_missing_entry(name, pack_type) 33 | end 34 | 35 | # Computes the relative path for a given Webpacker asset using manifest.json. 36 | # If no asset is found, returns nil. 37 | # 38 | # Example: 39 | # 40 | # Webpacker.manifest.lookup('calendar.js') # => "/packs/calendar-1016838bab065ae1e122.js" 41 | def lookup(name, pack_type = {}) 42 | compile if compiling? 43 | 44 | find(full_pack_name(name, pack_type[:type])) 45 | end 46 | 47 | # Like lookup, except that if no asset is found, raises a Webpacker::Manifest::MissingEntryError. 48 | def lookup!(name, pack_type = {}) 49 | lookup(name, pack_type) || handle_missing_entry(name, pack_type) 50 | end 51 | 52 | private 53 | def compiling? 54 | config.compile? && !dev_server.running? 55 | end 56 | 57 | def compile 58 | Webpacker.logger.tagged("Webpacker") { compiler.compile } 59 | end 60 | 61 | def data 62 | if config.cache_manifest? 63 | @data ||= load 64 | else 65 | refresh 66 | end 67 | end 68 | 69 | def find(name) 70 | data[name.to_s].presence 71 | end 72 | 73 | def full_pack_name(name, pack_type) 74 | return name unless File.extname(name.to_s).empty? 75 | "#{name}.#{manifest_type(pack_type)}" 76 | end 77 | 78 | def handle_missing_entry(name, pack_type) 79 | raise Webpacker::Manifest::MissingEntryError, missing_file_from_manifest_error(full_pack_name(name, pack_type[:type])) 80 | end 81 | 82 | def load 83 | if config.public_manifest_path.exist? 84 | JSON.parse config.public_manifest_path.read 85 | else 86 | {} 87 | end 88 | end 89 | 90 | # The `manifest_name` method strips of the file extension of the name, because in the 91 | # manifest hash the entrypoints are defined by their pack name without the extension. 92 | # When the user provides a name with a file extension, we want to try to strip it off. 93 | def manifest_name(name, pack_type) 94 | name.chomp(".#{pack_type}") 95 | end 96 | 97 | def manifest_type(pack_type) 98 | case pack_type 99 | when :javascript then "js" 100 | when :stylesheet then "css" 101 | else pack_type.to_s 102 | end 103 | end 104 | 105 | def missing_file_from_manifest_error(bundle_name) 106 | <<-MSG 107 | Webpacker can't find #{bundle_name} in #{config.public_manifest_path}. Possible causes: 108 | 1. You forgot to install node packages (try `yarn install`) or are running an incompatible version of Node 109 | 2. Your app has code with a non-standard extension (like a `.jsx` file) but the extension is not in the `extensions` config in `config/webpacker.yml` 110 | 3. You have set compile: false (see `config/webpacker.yml`) for this environment 111 | (unless you are using the `bin/webpacker -w` or the `bin/webpacker-dev-server`, in which case maybe you aren't running the dev server in the background?) 112 | 4. webpack has not yet re-run to reflect updates. 113 | 5. You have misconfigured Webpacker's `config/webpacker.yml` file. 114 | 6. Your webpack configuration is not creating a manifest. 115 | 116 | Your manifest contains: 117 | #{JSON.pretty_generate(@data)} 118 | MSG 119 | end 120 | end 121 | -------------------------------------------------------------------------------- /lib/webpacker/railtie.rb: -------------------------------------------------------------------------------- 1 | require "rails/railtie" 2 | 3 | require "webpacker/helper" 4 | require "webpacker/dev_server_proxy" 5 | 6 | class Webpacker::Engine < ::Rails::Engine 7 | # Allows Webpacker config values to be set via Rails env config files 8 | config.webpacker = ActiveSupport::OrderedOptions.new 9 | 10 | initializer "webpacker.proxy" do |app| 11 | if (Webpacker.config.dev_server.present? rescue nil) 12 | app.middleware.insert_before 0, 13 | Rails::VERSION::MAJOR >= 5 ? 14 | Webpacker::DevServerProxy : "Webpacker::DevServerProxy", ssl_verify_none: true 15 | end 16 | end 17 | 18 | initializer "webpacker.helper" do 19 | ActiveSupport.on_load :action_controller do 20 | ActionController::Base.helper Webpacker::Helper 21 | end 22 | 23 | ActiveSupport.on_load :action_view do 24 | include Webpacker::Helper 25 | end 26 | end 27 | 28 | initializer "webpacker.logger" do 29 | config.after_initialize do 30 | if ::Rails.logger.respond_to?(:tagged) 31 | Webpacker.logger = ::Rails.logger 32 | else 33 | Webpacker.logger = ActiveSupport::TaggedLogging.new(::Rails.logger) 34 | end 35 | end 36 | end 37 | 38 | initializer "webpacker.bootstrap" do 39 | if defined?(Rails::Server) || defined?(Rails::Console) 40 | Webpacker.bootstrap 41 | if defined?(Spring) 42 | require "spring/watcher" 43 | Spring.after_fork { Webpacker.bootstrap } 44 | Spring.watch(Webpacker.config.config_path) 45 | end 46 | end 47 | end 48 | 49 | initializer "webpacker.set_source" do |app| 50 | if Webpacker.config.config_path.exist? 51 | app.config.javascript_path = Webpacker.config.source_path.relative_path_from(Rails.root.join("app")).to_s 52 | end 53 | end 54 | 55 | initializer "webpacker.remove_app_packs_from_the_autoload_paths" do 56 | Rails.application.config.before_initialize do 57 | if Webpacker.config.config_path.exist? 58 | source_path = Webpacker.config.source_path.to_s 59 | ActiveSupport::Dependencies.autoload_paths.delete(source_path) 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/webpacker/runner.rb: -------------------------------------------------------------------------------- 1 | module Webpacker 2 | class Runner 3 | def self.run(argv) 4 | $stdout.sync = true 5 | 6 | new(argv).run 7 | end 8 | 9 | def initialize(argv) 10 | @argv = argv 11 | 12 | @app_path = File.expand_path(".", Dir.pwd) 13 | @node_modules_bin_path = ENV["WEBPACKER_NODE_MODULES_BIN_PATH"] || `yarn bin`.chomp 14 | @webpack_config = File.join(@app_path, "config/webpack/#{ENV["NODE_ENV"]}.js") 15 | @webpacker_config = ENV["WEBPACKER_CONFIG"] || File.join(@app_path, "config/webpacker.yml") 16 | 17 | unless File.exist?(@webpack_config) 18 | $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." 19 | exit! 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/webpacker/version.rb: -------------------------------------------------------------------------------- 1 | module Webpacker 2 | # Change the version in package.json too, please! 3 | VERSION = "6.0.0.rc.6".freeze 4 | end 5 | -------------------------------------------------------------------------------- /lib/webpacker/webpack_runner.rb: -------------------------------------------------------------------------------- 1 | require "shellwords" 2 | require "webpacker/runner" 3 | 4 | module Webpacker 5 | class WebpackRunner < Webpacker::Runner 6 | WEBPACK_COMMANDS = [ 7 | "help", 8 | "h", 9 | "--help", 10 | "-h", 11 | "version", 12 | "v", 13 | "--version", 14 | "-v", 15 | "info", 16 | "i" 17 | ].freeze 18 | 19 | def run 20 | env = Webpacker::Compiler.env 21 | env["WEBPACKER_CONFIG"] = @webpacker_config 22 | 23 | cmd = if node_modules_bin_exist? 24 | ["#{@node_modules_bin_path}/webpack"] 25 | else 26 | ["yarn", "webpack"] 27 | end 28 | 29 | if @argv.delete "--debug-webpacker" 30 | cmd = ["node", "--inspect-brk"] + cmd 31 | end 32 | 33 | if @argv.delete "--trace-deprecation" 34 | cmd = ["node", "--trace-deprecation"] + cmd 35 | end 36 | 37 | if @argv.delete "--no-deprecation" 38 | cmd = ["node", "--no-deprecation"] + cmd 39 | end 40 | 41 | # Webpack commands are not compatible with --config option. 42 | if (@argv & WEBPACK_COMMANDS).empty? 43 | cmd += ["--config", @webpack_config] 44 | end 45 | 46 | cmd += @argv 47 | 48 | Dir.chdir(@app_path) do 49 | Kernel.exec env, *cmd 50 | end 51 | end 52 | 53 | private 54 | def node_modules_bin_exist? 55 | File.exist?("#{@node_modules_bin_path}/webpack") 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rails/webpacker", 3 | "version": "6.0.0-rc.6", 4 | "description": "Use webpack to manage app-like JavaScript modules in Rails", 5 | "main": "package/index.js", 6 | "files": [ 7 | "package", 8 | "lib/install/config/webpacker.yml" 9 | ], 10 | "engines": { 11 | "node": "^12.13.0 || ^14 || >=16", 12 | "yarn": ">=1 <4" 13 | }, 14 | "dependencies": { 15 | "@babel/core": "^7.15.5", 16 | "@babel/plugin-transform-runtime": "^7.15.0", 17 | "@babel/preset-env": "^7.15.6", 18 | "@babel/runtime": "^7.15.4", 19 | "babel-loader": "^8.2.2", 20 | "compression-webpack-plugin": "^9.0.0", 21 | "glob": "^7.2.0", 22 | "js-yaml": "^4.1.0", 23 | "path-complete-extname": "^1.0.0", 24 | "pnp-webpack-plugin": "^1.7.0", 25 | "terser-webpack-plugin": "^5.2.4", 26 | "webpack": "^5.53.0", 27 | "webpack-assets-manifest": "^5.0.6", 28 | "webpack-cli": "^4.8.0", 29 | "webpack-merge": "^5.8.0", 30 | "webpack-sources": "^3.2.1" 31 | }, 32 | "devDependencies": { 33 | "eslint": "^7.32.0", 34 | "eslint-config-airbnb": "^18.2.1", 35 | "eslint-config-prettier": "^8.3.0", 36 | "eslint-plugin-import": "^2.24.2", 37 | "eslint-plugin-jsx-a11y": "^6.4.1", 38 | "eslint-plugin-react": "^7.26.0", 39 | "jest": "^27.2.1" 40 | }, 41 | "jest": { 42 | "testRegex": "(/__tests__/.*|(\\.|/))\\.jsx?$", 43 | "roots": [ 44 | "/package" 45 | ] 46 | }, 47 | "scripts": { 48 | "test": "jest", 49 | "lint": "eslint package/" 50 | }, 51 | "repository": { 52 | "type": "git", 53 | "url": "git+https://github.com/rails/webpacker.git" 54 | }, 55 | "author": "David Heinemeier Hansson ", 56 | "license": "MIT", 57 | "bugs": { 58 | "url": "https://github.com/rails/webpacker/issues" 59 | }, 60 | "homepage": "https://github.com/rails/webpacker" 61 | } 62 | -------------------------------------------------------------------------------- /package/__tests__/config.js: -------------------------------------------------------------------------------- 1 | /* global test expect, describe */ 2 | 3 | const { chdirCwd, chdirTestApp, resetEnv } = require('../utils/helpers') 4 | 5 | chdirTestApp() 6 | 7 | const config = require('../config') 8 | 9 | describe('Config', () => { 10 | beforeEach(() => jest.resetModules() && resetEnv()) 11 | afterAll(chdirCwd) 12 | 13 | test('public path', () => { 14 | process.env.RAILS_ENV = 'development' 15 | const config = require('../config') 16 | expect(config.publicPath).toEqual('/packs/') 17 | }) 18 | 19 | test('public path with asset host', () => { 20 | process.env.RAILS_ENV = 'development' 21 | process.env.WEBPACKER_ASSET_HOST = 'http://foo.com/' 22 | const config = require('../config') 23 | expect(config.publicPath).toEqual('http://foo.com/packs/') 24 | }) 25 | 26 | test('should return additional paths as listed in app config, with resolved paths', () => { 27 | expect(config.additional_paths).toEqual([ 28 | 'app/assets', 29 | '/etc/yarn', 30 | 'some.config.js', 31 | 'app/elm' 32 | ]) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /package/__tests__/dev_server.js: -------------------------------------------------------------------------------- 1 | /* global test expect, describe */ 2 | 3 | const { chdirTestApp, chdirCwd } = require('../utils/helpers') 4 | 5 | chdirTestApp() 6 | 7 | describe('DevServer', () => { 8 | beforeEach(() => jest.resetModules()) 9 | afterAll(chdirCwd) 10 | 11 | test('with NODE_ENV and RAILS_ENV set to development', () => { 12 | process.env.NODE_ENV = 'development' 13 | process.env.RAILS_ENV = 'development' 14 | process.env.WEBPACKER_DEV_SERVER_HOST = '0.0.0.0' 15 | process.env.WEBPACKER_DEV_SERVER_PORT = 5000 16 | process.env.WEBPACKER_DEV_SERVER_DISABLE_HOST_CHECK = false 17 | 18 | const devServer = require('../dev_server') 19 | expect(devServer).toBeDefined() 20 | expect(devServer.host).toEqual('0.0.0.0') 21 | expect(devServer.port).toEqual('5000') 22 | expect(devServer.disable_host_check).toBe(false) 23 | }) 24 | 25 | test('with custom env prefix', () => { 26 | const config = require('../config') 27 | config.dev_server.env_prefix = 'TEST_WEBPACKER_DEV_SERVER' 28 | 29 | process.env.NODE_ENV = 'development' 30 | process.env.RAILS_ENV = 'development' 31 | process.env.TEST_WEBPACKER_DEV_SERVER_HOST = '0.0.0.0' 32 | process.env.TEST_WEBPACKER_DEV_SERVER_PORT = 5000 33 | 34 | const devServer = require('../dev_server') 35 | expect(devServer).toBeDefined() 36 | expect(devServer.host).toEqual('0.0.0.0') 37 | expect(devServer.port).toEqual('5000') 38 | }) 39 | 40 | test('with NODE_ENV and RAILS_ENV set to production', () => { 41 | process.env.RAILS_ENV = 'production' 42 | process.env.NODE_ENV = 'production' 43 | expect(require('../dev_server')).toEqual({}) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /package/__tests__/development.js: -------------------------------------------------------------------------------- 1 | /* test expect, describe, afterAll, beforeEach */ 2 | 3 | const { resolve } = require('path') 4 | const { chdirTestApp, chdirCwd } = require('../utils/helpers') 5 | 6 | chdirTestApp() 7 | 8 | describe('Development environment', () => { 9 | afterAll(chdirCwd) 10 | 11 | describe('webpackConfig', () => { 12 | beforeEach(() => jest.resetModules()) 13 | 14 | test('should use development config and environment including devServer if WEBPACK_SERVE', () => { 15 | process.env.RAILS_ENV = 'development' 16 | process.env.NODE_ENV = 'development' 17 | process.env.WEBPACK_SERVE = 'true' 18 | const { webpackConfig } = require('../index') 19 | 20 | expect(webpackConfig.output.path).toEqual(resolve('public', 'packs')) 21 | expect(webpackConfig.output.publicPath).toEqual('/packs/') 22 | }) 23 | 24 | test('should use development config and environment if WEBPACK_SERVE', () => { 25 | process.env.RAILS_ENV = 'development' 26 | process.env.NODE_ENV = 'development' 27 | process.env.WEBPACK_SERVE = undefined 28 | const { webpackConfig } = require('../index') 29 | 30 | expect(webpackConfig.output.path).toEqual(resolve('public', 'packs')) 31 | expect(webpackConfig.output.publicPath).toEqual('/packs/') 32 | expect(webpackConfig.devServer).toEqual(undefined) 33 | }) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /package/__tests__/env.js: -------------------------------------------------------------------------------- 1 | /* global test expect, describe */ 2 | 3 | const { chdirTestApp, chdirCwd } = require('../utils/helpers') 4 | 5 | chdirTestApp() 6 | 7 | describe('Env', () => { 8 | beforeEach(() => jest.resetModules()) 9 | afterAll(chdirCwd) 10 | 11 | test('with NODE_ENV and RAILS_ENV set to development', () => { 12 | process.env.RAILS_ENV = 'development' 13 | process.env.NODE_ENV = 'development' 14 | expect(require('../env')).toEqual({ 15 | railsEnv: 'development', 16 | nodeEnv: 'development', 17 | isProduction: false, 18 | isDevelopment: true, 19 | runningWebpackDevServer: false 20 | }) 21 | }) 22 | 23 | test('with undefined NODE_ENV and RAILS_ENV set to development', () => { 24 | process.env.RAILS_ENV = 'development' 25 | delete process.env.NODE_ENV 26 | expect(require('../env')).toEqual({ 27 | railsEnv: 'development', 28 | nodeEnv: 'production', 29 | isProduction: true, 30 | isDevelopment: false, 31 | runningWebpackDevServer: false 32 | }) 33 | }) 34 | 35 | test('with undefined NODE_ENV and RAILS_ENV', () => { 36 | delete process.env.NODE_ENV 37 | delete process.env.RAILS_ENV 38 | expect(require('../env')).toEqual({ 39 | railsEnv: 'production', 40 | nodeEnv: 'production', 41 | isProduction: true, 42 | isDevelopment: false, 43 | runningWebpackDevServer: false 44 | }) 45 | }) 46 | 47 | test('with a non-standard environment', () => { 48 | process.env.RAILS_ENV = 'staging' 49 | process.env.NODE_ENV = 'staging' 50 | expect(require('../env')).toEqual({ 51 | railsEnv: 'staging', 52 | nodeEnv: 'production', 53 | isProduction: true, 54 | isDevelopment: false, 55 | runningWebpackDevServer: false 56 | }) 57 | }) 58 | }) 59 | -------------------------------------------------------------------------------- /package/__tests__/index.js: -------------------------------------------------------------------------------- 1 | const index = require('../index') 2 | 3 | describe('index', () => { 4 | test('exports webpack-merge v5 functions', () => { 5 | expect(index.merge).toBeInstanceOf(Function) 6 | expect(index.mergeWithRules).toBeInstanceOf(Function) 7 | expect(index.mergeWithCustomize).toBeInstanceOf(Function) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /package/__tests__/production.js: -------------------------------------------------------------------------------- 1 | /* test expect, describe, afterAll, beforeEach */ 2 | 3 | const { resolve } = require('path') 4 | const { chdirTestApp, chdirCwd } = require('../utils/helpers') 5 | 6 | chdirTestApp() 7 | 8 | describe('Production environment', () => { 9 | afterAll(chdirCwd) 10 | 11 | describe('webpackConfig', () => { 12 | beforeEach(() => jest.resetModules()) 13 | 14 | test('should use production config and environment', () => { 15 | process.env.RAILS_ENV = 'production' 16 | process.env.NODE_ENV = 'production' 17 | 18 | const { webpackConfig } = require('../index') 19 | 20 | expect(webpackConfig.output.path).toEqual(resolve('public', 'packs')) 21 | expect(webpackConfig.output.publicPath).toEqual('/packs/') 22 | 23 | expect(webpackConfig).toMatchObject({ 24 | devtool: 'source-map', 25 | stats: 'normal' 26 | }) 27 | }) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /package/__tests__/staging.js: -------------------------------------------------------------------------------- 1 | /* test expect, describe, afterAll, beforeEach */ 2 | 3 | const { resolve } = require('path') 4 | const { chdirTestApp, chdirCwd } = require('../utils/helpers') 5 | 6 | chdirTestApp() 7 | 8 | describe('Custom environment', () => { 9 | afterAll(chdirCwd) 10 | 11 | describe('webpackConfig', () => { 12 | beforeEach(() => jest.resetModules()) 13 | 14 | test('should use staging config and default production environment', () => { 15 | process.env.RAILS_ENV = 'staging' 16 | delete process.env.NODE_ENV 17 | 18 | const { webpackConfig } = require('../index') 19 | 20 | expect(webpackConfig.output.path).toEqual( 21 | resolve('public', 'packs-staging') 22 | ) 23 | expect(webpackConfig.output.publicPath).toEqual('/packs-staging/') 24 | expect(webpackConfig).toMatchObject({ 25 | devtool: 'source-map', 26 | stats: 'normal' 27 | }) 28 | }) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /package/__tests__/test.js: -------------------------------------------------------------------------------- 1 | /* test expect, describe, afterAll, beforeEach */ 2 | 3 | const { resolve } = require('path') 4 | const { chdirTestApp, chdirCwd } = require('../utils/helpers') 5 | 6 | chdirTestApp() 7 | 8 | describe('Test environment', () => { 9 | afterAll(chdirCwd) 10 | 11 | describe('toWebpackConfig', () => { 12 | beforeEach(() => jest.resetModules()) 13 | 14 | test('should use test config and production environment', () => { 15 | process.env.RAILS_ENV = 'test' 16 | process.env.NODE_ENV = 'test' 17 | 18 | const { webpackConfig } = require('../index') 19 | 20 | expect(webpackConfig.output.path).toEqual(resolve('public', 'packs-test')) 21 | expect(webpackConfig.output.publicPath).toEqual('/packs-test/') 22 | expect(webpackConfig.devServer).toEqual(undefined) 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /package/babel/preset.js: -------------------------------------------------------------------------------- 1 | const { moduleExists } = require('@rails/webpacker') 2 | 3 | module.exports = function config(api) { 4 | const validEnv = ['development', 'test', 'production'] 5 | const currentEnv = api.env() 6 | const isDevelopmentEnv = api.env('development') 7 | const isProductionEnv = api.env('production') 8 | const isTestEnv = api.env('test') 9 | 10 | if (!validEnv.includes(currentEnv)) { 11 | throw new Error( 12 | `Please specify a valid NODE_ENV or BABEL_ENV environment variable. Valid values are "development", "test", and "production". Instead, received: "${JSON.stringify( 13 | currentEnv 14 | )}".` 15 | ) 16 | } 17 | 18 | return { 19 | presets: [ 20 | isTestEnv && ['@babel/preset-env', { targets: { node: 'current' } }], 21 | (isProductionEnv || isDevelopmentEnv) && [ 22 | '@babel/preset-env', 23 | { 24 | useBuiltIns: 'entry', 25 | corejs: '3.8', 26 | modules: 'auto', 27 | bugfixes: true, 28 | loose: true, 29 | exclude: ['transform-typeof-symbol'] 30 | } 31 | ], 32 | moduleExists('@babel/preset-typescript') && [ 33 | '@babel/preset-typescript', 34 | { allExtensions: true, isTSX: true } 35 | ] 36 | ].filter(Boolean), 37 | plugins: [ 38 | ['@babel/plugin-transform-runtime', { helpers: false }] 39 | ].filter(Boolean) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package/config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const { load } = require('js-yaml') 3 | const { readFileSync } = require('fs') 4 | const { merge } = require('webpack-merge') 5 | const { ensureTrailingSlash } = require('./utils/helpers') 6 | const { railsEnv } = require('./env') 7 | const configPath = require('./configPath') 8 | 9 | const defaultConfigPath = require.resolve('../lib/install/config/webpacker.yml') 10 | 11 | const getDefaultConfig = () => { 12 | const defaultConfig = load(readFileSync(defaultConfigPath), 'utf8') 13 | return defaultConfig[railsEnv] || defaultConfig.production 14 | } 15 | 16 | const defaults = getDefaultConfig() 17 | const app = load(readFileSync(configPath), 'utf8')[railsEnv] 18 | 19 | const config = merge(defaults, app) 20 | config.outputPath = resolve(config.public_root_path, config.public_output_path) 21 | 22 | // Ensure that the publicPath includes our asset host so dynamic imports 23 | // (code-splitting chunks and static assets) load from the CDN instead of a relative path. 24 | const getPublicPath = () => { 25 | const rootUrl = ensureTrailingSlash(process.env.WEBPACKER_ASSET_HOST || '/') 26 | return `${rootUrl}${config.public_output_path}/` 27 | } 28 | 29 | config.publicPath = getPublicPath() 30 | config.publicPathWithoutCDN = `/${config.public_output_path}/` 31 | 32 | module.exports = config 33 | -------------------------------------------------------------------------------- /package/configPath.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | 3 | module.exports = process.env.WEBPACKER_CONFIG || resolve('config', 'webpacker.yml') 4 | -------------------------------------------------------------------------------- /package/dev_server.js: -------------------------------------------------------------------------------- 1 | const { isBoolean } = require('./utils/helpers') 2 | const config = require('./config') 3 | 4 | const fetch = (key) => { 5 | const value = process.env[key] 6 | return isBoolean(value) ? JSON.parse(value) : value 7 | } 8 | 9 | const devServerConfig = config.dev_server 10 | 11 | if (devServerConfig) { 12 | const envPrefix = config.dev_server.env_prefix || 'WEBPACKER_DEV_SERVER' 13 | 14 | Object.keys(devServerConfig).forEach((key) => { 15 | const envValue = fetch(`${envPrefix}_${key.toUpperCase()}`) 16 | if (envValue !== undefined) devServerConfig[key] = envValue 17 | }) 18 | } 19 | 20 | module.exports = devServerConfig || {} 21 | -------------------------------------------------------------------------------- /package/env.js: -------------------------------------------------------------------------------- 1 | const { load } = require('js-yaml') 2 | const { readFileSync } = require('fs') 3 | 4 | const NODE_ENVIRONMENTS = ['development', 'production', 'test'] 5 | const DEFAULT = 'production' 6 | const configPath = require('./configPath') 7 | 8 | const railsEnv = process.env.RAILS_ENV 9 | const rawNodeEnv = process.env.NODE_ENV 10 | const nodeEnv 11 | = rawNodeEnv && NODE_ENVIRONMENTS.includes(rawNodeEnv) ? rawNodeEnv : DEFAULT 12 | const isProduction = nodeEnv === 'production' 13 | const isDevelopment = nodeEnv === 'development' 14 | 15 | const config = load(readFileSync(configPath), 'utf8') 16 | const availableEnvironments = Object.keys(config).join('|') 17 | const regex = new RegExp(`^(${availableEnvironments})$`, 'g') 18 | 19 | const runningWebpackDevServer = process.env.WEBPACK_SERVE === 'true' 20 | 21 | module.exports = { 22 | railsEnv: railsEnv && railsEnv.match(regex) ? railsEnv : DEFAULT, 23 | nodeEnv, 24 | isProduction, 25 | isDevelopment, 26 | runningWebpackDevServer 27 | } 28 | -------------------------------------------------------------------------------- /package/environments/__tests__/base.js: -------------------------------------------------------------------------------- 1 | /* global test expect, describe, afterAll, beforeEach */ 2 | 3 | // environment.js expects to find config/webpacker.yml and resolved modules from 4 | // the root of a Rails project 5 | 6 | const { chdirTestApp, chdirCwd } = require('../../utils/helpers') 7 | 8 | chdirTestApp() 9 | 10 | const { resolve } = require('path') 11 | const rules = require('../../rules') 12 | const baseConfig = require('../base') 13 | 14 | describe('Base config', () => { 15 | afterAll(chdirCwd) 16 | 17 | describe('config', () => { 18 | test('should return entry', () => { 19 | expect(baseConfig.entry.application).toEqual( 20 | resolve('app', 'packs', 'entrypoints', 'application.js') 21 | ) 22 | }) 23 | 24 | test('should return multi file entry points', () => { 25 | expect(baseConfig.entry.multi_entry.sort()).toEqual([ 26 | resolve('app', 'packs', 'entrypoints', 'multi_entry.css'), 27 | resolve('app', 'packs', 'entrypoints', 'multi_entry.js') 28 | ]) 29 | }) 30 | 31 | test('should return output', () => { 32 | expect(baseConfig.output.filename).toEqual('js/[name].js') 33 | expect(baseConfig.output.chunkFilename).toEqual( 34 | 'js/[name].chunk.js' 35 | ) 36 | }) 37 | 38 | test('should return default loader rules for each file in config/loaders', () => { 39 | const defaultRules = Object.keys(rules) 40 | const configRules = baseConfig.module.rules 41 | 42 | expect(defaultRules.length).toEqual(3) 43 | expect(configRules.length).toEqual(3) 44 | }) 45 | 46 | test('should return default plugins', () => { 47 | expect(baseConfig.plugins.length).toEqual(2) 48 | }) 49 | 50 | test('should return default resolveLoader', () => { 51 | expect(baseConfig.resolveLoader.modules).toEqual(['node_modules']) 52 | }) 53 | 54 | test('should return default resolve.modules with additions', () => { 55 | expect(baseConfig.resolve.modules).toEqual([ 56 | resolve('app', 'packs'), 57 | resolve('app/assets'), 58 | resolve('/etc/yarn'), 59 | resolve('some.config.js'), 60 | resolve('app/elm'), 61 | 'node_modules' 62 | ]) 63 | }) 64 | 65 | test('returns plugins property as Array', () => { 66 | expect(baseConfig.plugins).toBeInstanceOf(Array) 67 | }) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /package/environments/base.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /* eslint import/no-dynamic-require: 0 */ 3 | 4 | const { basename, dirname, join, relative, resolve } = require('path') 5 | const extname = require('path-complete-extname') 6 | const PnpWebpackPlugin = require('pnp-webpack-plugin') 7 | const { sync: globSync } = require('glob') 8 | const WebpackAssetsManifest = require('webpack-assets-manifest') 9 | const webpack = require('webpack') 10 | const rules = require('../rules') 11 | const { isProduction } = require('../env') 12 | const config = require('../config') 13 | const { moduleExists } = require('../utils/helpers') 14 | 15 | const getEntryObject = () => { 16 | const entries = {} 17 | const rootPath = join(config.source_path, config.source_entry_path) 18 | 19 | globSync(`${rootPath}/*.*`).forEach((path) => { 20 | const namespace = relative(join(rootPath), dirname(path)) 21 | const name = join(namespace, basename(path, extname(path))) 22 | let assetPaths = resolve(path) 23 | 24 | // Allows for multiple filetypes per entry (https://webpack.js.org/guides/entry-advanced/) 25 | // Transforms the config object value to an array with all values under the same name 26 | let previousPaths = entries[name] 27 | if (previousPaths) { 28 | previousPaths = Array.isArray(previousPaths) 29 | ? previousPaths 30 | : [previousPaths] 31 | previousPaths.push(assetPaths) 32 | assetPaths = previousPaths 33 | } 34 | 35 | entries[name] = assetPaths 36 | }) 37 | 38 | return entries 39 | } 40 | 41 | const getModulePaths = () => { 42 | const result = [resolve(config.source_path)] 43 | 44 | if (config.additional_paths) { 45 | config.additional_paths.forEach((path) => result.push(resolve(path))) 46 | } 47 | result.push('node_modules') 48 | 49 | return result 50 | } 51 | 52 | const getPlugins = () => { 53 | const plugins = [ 54 | new webpack.EnvironmentPlugin(process.env), 55 | new WebpackAssetsManifest({ 56 | entrypoints: true, 57 | writeToDisk: true, 58 | output: 'manifest.json', 59 | entrypointsUseAssets: true, 60 | publicPath: true 61 | }) 62 | ] 63 | 64 | if (moduleExists('css-loader') && moduleExists('mini-css-extract-plugin')) { 65 | const hash = isProduction ? '-[contenthash:8]' : '' 66 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 67 | plugins.push( 68 | new MiniCssExtractPlugin({ 69 | filename: `css/[name]${hash}.css`, 70 | chunkFilename: `css/[id]${hash}.css` 71 | }) 72 | ) 73 | } 74 | 75 | return plugins 76 | } 77 | 78 | // Don't use contentHash except for production for performance 79 | // https://webpack.js.org/guides/build-performance/#avoid-production-specific-tooling 80 | const hash = isProduction ? '-[contenthash]' : '' 81 | module.exports = { 82 | mode: 'production', 83 | output: { 84 | filename: `js/[name]${hash}.js`, 85 | chunkFilename: `js/[name]${hash}.chunk.js`, 86 | 87 | // https://webpack.js.org/configuration/output/#outputhotupdatechunkfilename 88 | hotUpdateChunkFilename: 'js/[id].[fullhash].hot-update.js', 89 | path: config.outputPath, 90 | publicPath: config.publicPath 91 | }, 92 | entry: getEntryObject(), 93 | resolve: { 94 | extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx', '.coffee'], 95 | modules: getModulePaths(), 96 | plugins: [PnpWebpackPlugin] 97 | }, 98 | 99 | plugins: getPlugins(), 100 | 101 | resolveLoader: { 102 | modules: ['node_modules'], 103 | plugins: [PnpWebpackPlugin.moduleLoader(module)] 104 | }, 105 | 106 | optimization: { 107 | splitChunks: { chunks: 'all' }, 108 | 109 | runtimeChunk: 'single' 110 | }, 111 | 112 | module: { 113 | strictExportPresence: true, 114 | rules 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /package/environments/development.js: -------------------------------------------------------------------------------- 1 | const { merge } = require('webpack-merge') 2 | 3 | const baseConfig = require('./base') 4 | const devServer = require('../dev_server') 5 | const { runningWebpackDevServer } = require('../env') 6 | 7 | const { outputPath: contentBase, publicPath } = require('../config') 8 | 9 | let devConfig = { 10 | mode: 'development', 11 | devtool: 'cheap-module-source-map' 12 | } 13 | 14 | if (runningWebpackDevServer) { 15 | const liveReload = devServer.live_reload !== undefined ? devServer.live_reload : !devServer.hmr 16 | 17 | const devServerConfig = { 18 | devMiddleware: { 19 | publicPath 20 | }, 21 | compress: devServer.compress, 22 | allowedHosts: devServer.allowed_hosts, 23 | host: devServer.host, 24 | port: devServer.port, 25 | https: devServer.https, 26 | hot: devServer.hmr, 27 | liveReload, 28 | historyApiFallback: { disableDotRule: true }, 29 | headers: devServer.headers, 30 | static: { 31 | publicPath: contentBase 32 | } 33 | } 34 | 35 | if (devServer.static) { 36 | devServerConfig.static = { ...devServerConfig.static, ...devServer.static } 37 | } 38 | 39 | if (devServer.client) { 40 | devServerConfig.client = devServer.client 41 | } 42 | 43 | devConfig = merge(devConfig, { 44 | stats: { 45 | colors: true, 46 | entrypoints: false, 47 | errorDetails: true, 48 | modules: false, 49 | moduleTrace: false 50 | }, 51 | devServer: devServerConfig 52 | }) 53 | } 54 | 55 | module.exports = merge(baseConfig, devConfig) 56 | -------------------------------------------------------------------------------- /package/environments/production.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /* eslint import/no-dynamic-require: 0 */ 3 | 4 | const { merge } = require('webpack-merge') 5 | const CompressionPlugin = require('compression-webpack-plugin') 6 | const TerserPlugin = require('terser-webpack-plugin') 7 | const baseConfig = require('./base') 8 | const { moduleExists } = require('../utils/helpers') 9 | 10 | const getPlugins = () => { 11 | const plugins = [] 12 | 13 | plugins.push( 14 | new CompressionPlugin({ 15 | filename: '[path][base].gz[query]', 16 | algorithm: 'gzip', 17 | test: /\.(js|css|html|json|ico|svg|eot|otf|ttf|map)$/ 18 | }) 19 | ) 20 | 21 | if ('brotli' in process.versions) { 22 | plugins.push( 23 | new CompressionPlugin({ 24 | filename: '[path][base].br[query]', 25 | algorithm: 'brotliCompress', 26 | test: /\.(js|css|html|json|ico|svg|eot|otf|ttf|map)$/ 27 | }) 28 | ) 29 | } 30 | 31 | return plugins 32 | } 33 | 34 | const tryCssMinimizer = () => { 35 | if ( 36 | moduleExists('css-loader') && 37 | moduleExists('css-minimizer-webpack-plugin') 38 | ) { 39 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') 40 | return new CssMinimizerPlugin() 41 | } 42 | 43 | return null 44 | } 45 | 46 | const productionConfig = { 47 | devtool: 'source-map', 48 | stats: 'normal', 49 | bail: true, 50 | plugins: getPlugins(), 51 | optimization: { 52 | minimizer: [ 53 | tryCssMinimizer(), 54 | new TerserPlugin({ 55 | parallel: Number.parseInt(process.env.WEBPACKER_PARALLEL, 10) || true, 56 | terserOptions: { 57 | parse: { 58 | // Let terser parse ecma 8 code but always output 59 | // ES5 compliant code for older browsers 60 | ecma: 8 61 | }, 62 | compress: { 63 | ecma: 5, 64 | warnings: false, 65 | comparisons: false 66 | }, 67 | mangle: { safari10: true }, 68 | output: { 69 | ecma: 5, 70 | comments: false, 71 | ascii_only: true 72 | } 73 | } 74 | }) 75 | ].filter(Boolean) 76 | } 77 | } 78 | 79 | module.exports = merge(baseConfig, productionConfig) 80 | -------------------------------------------------------------------------------- /package/environments/test.js: -------------------------------------------------------------------------------- 1 | const baseConfig = require('./base') 2 | 3 | module.exports = baseConfig 4 | -------------------------------------------------------------------------------- /package/index.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /* eslint import/no-dynamic-require: 0 */ 3 | 4 | const webpackMerge = require('webpack-merge') 5 | const { resolve } = require('path') 6 | const { existsSync } = require('fs') 7 | const baseConfig = require('./environments/base') 8 | const rules = require('./rules') 9 | const config = require('./config') 10 | const devServer = require('./dev_server') 11 | const { nodeEnv } = require('./env') 12 | const { moduleExists, canProcess } = require('./utils/helpers') 13 | const inliningCss = require('./inliningCss') 14 | 15 | const webpackConfig = () => { 16 | const path = resolve(__dirname, 'environments', `${nodeEnv}.js`) 17 | const environmentConfig = existsSync(path) ? require(path) : baseConfig 18 | return environmentConfig 19 | } 20 | 21 | module.exports = { 22 | config, 23 | devServer, 24 | webpackConfig: webpackConfig(), 25 | baseConfig, 26 | rules, 27 | moduleExists, 28 | canProcess, 29 | inliningCss, 30 | ...webpackMerge 31 | } 32 | -------------------------------------------------------------------------------- /package/inliningCss.js: -------------------------------------------------------------------------------- 1 | const { runningWebpackDevServer } = require('./env') 2 | const devServer = require('./dev_server') 3 | 4 | // This logic is tied to lib/webpacker/instance.rb 5 | const inliningCss = runningWebpackDevServer && devServer.hmr 6 | 7 | module.exports = inliningCss 8 | -------------------------------------------------------------------------------- /package/rules/babel.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path') 2 | const { realpathSync } = require('fs') 3 | 4 | const { 5 | source_path: sourcePath, 6 | additional_paths: additionalPaths 7 | } = require('../config') 8 | const { isProduction } = require('../env') 9 | 10 | module.exports = { 11 | test: /\.(js|jsx|mjs|ts|tsx|coffee)?(\.erb)?$/, 12 | include: [sourcePath, ...additionalPaths].map((p) => { 13 | try { 14 | return realpathSync(p) 15 | } catch (e) { 16 | return resolve(p) 17 | } 18 | }), 19 | exclude: /node_modules/, 20 | use: [ 21 | { 22 | loader: require.resolve('babel-loader'), 23 | options: { 24 | cacheDirectory: true, 25 | cacheCompression: isProduction, 26 | compact: isProduction 27 | } 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /package/rules/coffee.js: -------------------------------------------------------------------------------- 1 | const { canProcess } = require('../utils/helpers') 2 | 3 | module.exports = canProcess('coffee-loader', (resolvedPath) => ({ 4 | test: /\.coffee(\.erb)?$/, 5 | use: [{ loader: resolvedPath }] 6 | })) 7 | -------------------------------------------------------------------------------- /package/rules/css.js: -------------------------------------------------------------------------------- 1 | const getStyleRule = require('../utils/get_style_rule') 2 | 3 | module.exports = getStyleRule(/\.(css)$/i) 4 | -------------------------------------------------------------------------------- /package/rules/erb.js: -------------------------------------------------------------------------------- 1 | const { canProcess } = require('../utils/helpers') 2 | 3 | const runner = /^win/.test(process.platform) ? 'ruby ' : '' 4 | 5 | module.exports = canProcess('rails-erb-loader', (resolvedPath) => ({ 6 | test: /\.erb$/, 7 | enforce: 'pre', 8 | exclude: /node_modules/, 9 | use: [ 10 | { 11 | loader: resolvedPath, 12 | options: { runner: `${runner}bin/rails runner` } 13 | } 14 | ] 15 | })) 16 | -------------------------------------------------------------------------------- /package/rules/file.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test: [ 3 | /\.bmp$/, 4 | /\.gif$/, 5 | /\.jpe?g$/, 6 | /\.png$/, 7 | /\.tiff$/, 8 | /\.ico$/, 9 | /\.avif$/, 10 | /\.webp$/, 11 | /\.eot$/, 12 | /\.otf$/, 13 | /\.ttf$/, 14 | /\.woff$/, 15 | /\.woff2$/, 16 | /\.svg$/ 17 | ], 18 | exclude: [/\.(js|mjs|jsx|ts|tsx)$/], 19 | type: 'asset/resource', 20 | generator: { 21 | filename: 'static/[name]-[hash][ext][query]' 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package/rules/index.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | /* eslint import/no-dynamic-require: 0 */ 3 | 4 | const rules = { 5 | raw: require('./raw'), 6 | file: require('./file'), 7 | css: require('./css'), 8 | sass: require('./sass'), 9 | babel: require('./babel'), 10 | erb: require('./erb'), 11 | coffee: require('./coffee'), 12 | less: require('./less'), 13 | stylus: require('./stylus') 14 | } 15 | 16 | module.exports = Object.keys(rules) 17 | .filter((key) => !!rules[key]) 18 | .map((key) => rules[key]) 19 | -------------------------------------------------------------------------------- /package/rules/less.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { canProcess } = require('../utils/helpers') 3 | const getStyleRule = require('../utils/get_style_rule') 4 | 5 | const { 6 | additional_paths: paths, 7 | source_path: sourcePath 8 | } = require('../config') 9 | 10 | module.exports = canProcess('less-loader', (resolvedPath) => 11 | getStyleRule(/\.(less)(\.erb)?$/i, [ 12 | { 13 | loader: resolvedPath, 14 | options: { 15 | lessOptions: { 16 | paths: [path.resolve(__dirname, 'node_modules'), sourcePath, ...paths] 17 | }, 18 | sourceMap: true 19 | } 20 | } 21 | ]) 22 | ) 23 | -------------------------------------------------------------------------------- /package/rules/raw.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test: [/\.html$/], 3 | exclude: [/\.(js|mjs|jsx|ts|tsx)$/], 4 | type: 'asset/source' 5 | } 6 | -------------------------------------------------------------------------------- /package/rules/sass.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | 3 | const getStyleRule = require('../utils/get_style_rule') 4 | const { canProcess } = require('../utils/helpers') 5 | const { additional_paths: includePaths } = require('../config') 6 | 7 | module.exports = canProcess('sass-loader', (resolvedPath) => 8 | getStyleRule(/\.(scss|sass)(\.erb)?$/i, [ 9 | { 10 | loader: resolvedPath, 11 | options: { 12 | sassOptions: { includePaths } 13 | } 14 | } 15 | ]) 16 | ) 17 | -------------------------------------------------------------------------------- /package/rules/stylus.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const { canProcess } = require('../utils/helpers') 3 | const getStyleRule = require('../utils/get_style_rule') 4 | 5 | const { 6 | additional_paths: paths, 7 | source_path: sourcePath 8 | } = require('../config') 9 | 10 | module.exports = canProcess('stylus-loader', (resolvedPath) => 11 | getStyleRule(/\.(styl(us)?)(\.erb)?$/i, [ 12 | { 13 | loader: resolvedPath, 14 | options: { 15 | stylusOptions: { 16 | include: [ 17 | path.resolve(__dirname, 'node_modules'), 18 | sourcePath, 19 | ...paths 20 | ] 21 | }, 22 | sourceMap: true 23 | } 24 | } 25 | ]) 26 | ) 27 | -------------------------------------------------------------------------------- /package/utils/get_style_rule.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0 */ 2 | const { canProcess, moduleExists } = require('./helpers') 3 | const inliningCss = require('../inliningCss') 4 | 5 | const getStyleRule = (test, preprocessors = []) => { 6 | if (moduleExists('css-loader')) { 7 | const tryPostcss = () => 8 | canProcess('postcss-loader', (loaderPath) => ({ 9 | loader: loaderPath, 10 | options: { sourceMap: true } 11 | })) 12 | 13 | // style-loader is required when using css modules with HMR on the webpack-dev-server 14 | 15 | const use = [ 16 | inliningCss ? 'style-loader' : require('mini-css-extract-plugin').loader, 17 | { 18 | loader: require.resolve('css-loader'), 19 | options: { 20 | sourceMap: true, 21 | importLoaders: 2 22 | } 23 | }, 24 | tryPostcss(), 25 | ...preprocessors 26 | ].filter(Boolean) 27 | 28 | return { 29 | test, 30 | use 31 | } 32 | } 33 | 34 | return null 35 | } 36 | 37 | module.exports = getStyleRule 38 | -------------------------------------------------------------------------------- /package/utils/helpers.js: -------------------------------------------------------------------------------- 1 | const isArray = (value) => Array.isArray(value) 2 | const isBoolean = (str) => /^true/.test(str) || /^false/.test(str) 3 | const chdirTestApp = () => { 4 | try { 5 | return process.chdir('test/test_app') 6 | } catch (e) { 7 | return null 8 | } 9 | } 10 | 11 | const chdirCwd = () => process.chdir(process.cwd()) 12 | 13 | const resetEnv = () => { 14 | process.env = {} 15 | } 16 | 17 | const ensureTrailingSlash = (path) => (path.endsWith('/') ? path : `${path}/`) 18 | 19 | const resolvedPath = (packageName) => { 20 | try { 21 | return require.resolve(packageName) 22 | } catch (e) { 23 | if (e.code !== 'MODULE_NOT_FOUND') { 24 | throw e 25 | } 26 | return null 27 | } 28 | } 29 | 30 | const moduleExists = (packageName) => (!!resolvedPath(packageName)) 31 | 32 | const canProcess = (rule, fn) => { 33 | const modulePath = resolvedPath(rule) 34 | 35 | if (modulePath) { 36 | return fn(modulePath) 37 | } 38 | 39 | return null 40 | } 41 | 42 | module.exports = { 43 | chdirTestApp, 44 | chdirCwd, 45 | isArray, 46 | isBoolean, 47 | ensureTrailingSlash, 48 | canProcess, 49 | moduleExists, 50 | resetEnv 51 | } 52 | -------------------------------------------------------------------------------- /test/command_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CommandTest < Minitest::Test 4 | def test_compile_command_returns_success_status_when_stale 5 | Webpacker.compiler.stub :stale?, true do 6 | Webpacker.compiler.stub :run_webpack, true do 7 | assert_equal true, Webpacker.commands.compile 8 | end 9 | end 10 | end 11 | 12 | def test_compile_command_returns_success_status_when_fresh 13 | Webpacker.compiler.stub :stale?, false do 14 | Webpacker.compiler.stub :run_webpack, true do 15 | assert_equal true, Webpacker.commands.compile 16 | end 17 | end 18 | end 19 | 20 | def test_compile_command_returns_failure_status_when_stale 21 | Webpacker.compiler.stub :stale?, true do 22 | Webpacker.compiler.stub :run_webpack, false do 23 | assert_equal false, Webpacker.commands.compile 24 | end 25 | end 26 | end 27 | 28 | def test_clean_command_works_with_nested_hashes_and_without_any_compiled_files 29 | File.stub :delete, true do 30 | assert Webpacker.commands.clean 31 | end 32 | end 33 | end 34 | 35 | class ClearCommandVersioningTest < Minitest::Test 36 | def setup 37 | @now = Time.parse("2021-01-01 12:34:56 UTC") 38 | # Test assets to be kept and deleted, path and mtime 39 | @prev_files = { 40 | # recent versions to be kept with Webpacker.commands.clean(count = 2) 41 | "js/application-deadbeef.js" => @now - 4000, 42 | "js/common-deadbeee.js" => @now - 4002, 43 | "css/common-deadbeed.css" => @now - 4004, 44 | "media/images/logo-deadbeeb.css" => @now - 4006, 45 | "js/application-1eadbeef.js" => @now - 8000, 46 | "js/common-1eadbeee.js" => @now - 8002, 47 | "css/common-1eadbeed.css" => @now - 8004, 48 | "media/images/logo-1eadbeeb.css" => @now - 8006, 49 | # new files to be kept with Webpacker.commands.clean(age = 3600) 50 | "js/brandnew-0001.js" => @now, 51 | "js/brandnew-0002.js" => @now - 10, 52 | "js/brandnew-0003.js" => @now - 20, 53 | "js/brandnew-0004.js" => @now - 40, 54 | }.transform_keys { |path| "#{Webpacker.config.public_output_path}/#{path}" } 55 | @expired_files = { 56 | # old files that are outside count = 2 or age = 3600 and to be deleted 57 | "js/application-0eadbeef.js" => @now - 9000, 58 | "js/common-0eadbeee.js" => @now - 9002, 59 | "css/common-0eadbeed.css" => @now - 9004, 60 | "js/brandnew-0005.js" => @now - 3640, 61 | }.transform_keys { |path| "#{Webpacker.config.public_output_path}/#{path}" } 62 | @all_files = @prev_files.merge(@expired_files) 63 | @dir_glob_stub = Proc.new { |arg| 64 | case arg 65 | when "#{Webpacker.config.public_output_path}/**/*" 66 | @all_files.keys 67 | else 68 | [] 69 | end 70 | } 71 | @file_mtime_stub = Proc.new { |longpath| 72 | @all_files[longpath] 73 | } 74 | @file_delete_mock = Minitest::Mock.new 75 | @expired_files.keys.each do |longpath| 76 | @file_delete_mock.expect(:delete, 1, [longpath]) 77 | end 78 | @file_delete_stub = Proc.new { |longpath| 79 | if @prev_files.has_key?(longpath) 80 | flunk "#{longpath} should not be deleted" 81 | else 82 | @file_delete_mock.delete(longpath) 83 | end 84 | } 85 | end 86 | 87 | def time_and_files_stub(&proc) 88 | Time.stub :now, @now do 89 | Dir.stub :glob, @dir_glob_stub do 90 | File.stub :directory?, false do 91 | File.stub :file?, true do 92 | File.stub :mtime, @file_mtime_stub do 93 | File.stub :delete, @file_delete_stub do 94 | yield proc 95 | end 96 | end 97 | end 98 | end 99 | end 100 | end 101 | @file_delete_mock.verify 102 | end 103 | 104 | def test_clean_command_with_versioned_files 105 | time_and_files_stub do 106 | assert Webpacker.commands.clean 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /test/compiler_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CompilerTest < Minitest::Test 4 | def remove_compilation_digest_path 5 | Webpacker.compiler.send(:compilation_digest_path).tap do |path| 6 | path.delete if path.exist? 7 | end 8 | end 9 | 10 | def setup 11 | remove_compilation_digest_path 12 | end 13 | 14 | def teardown 15 | remove_compilation_digest_path 16 | end 17 | 18 | def test_custom_environment_variables 19 | assert_nil Webpacker.compiler.send(:webpack_env)["FOO"] 20 | Webpacker.compiler.env["FOO"] = "BAR" 21 | assert Webpacker.compiler.send(:webpack_env)["FOO"] == "BAR" 22 | ensure 23 | Webpacker.compiler.env = {} 24 | end 25 | 26 | def test_freshness 27 | assert Webpacker.compiler.stale? 28 | assert !Webpacker.compiler.fresh? 29 | end 30 | 31 | def test_compile 32 | assert !Webpacker.compiler.compile 33 | end 34 | 35 | def test_freshness_on_compile_success 36 | status = OpenStruct.new(success?: true) 37 | 38 | assert Webpacker.compiler.stale? 39 | Open3.stub :capture3, [:sterr, :stdout, status] do 40 | Webpacker.compiler.compile 41 | assert Webpacker.compiler.fresh? 42 | end 43 | end 44 | 45 | def test_freshness_on_compile_fail 46 | status = OpenStruct.new(success?: false) 47 | 48 | assert Webpacker.compiler.stale? 49 | Open3.stub :capture3, [:sterr, :stdout, status] do 50 | Webpacker.compiler.compile 51 | assert Webpacker.compiler.fresh? 52 | end 53 | end 54 | 55 | def test_compilation_digest_path 56 | assert_equal Webpacker.compiler.send(:compilation_digest_path).basename.to_s, "last-compilation-digest-#{Webpacker.env}" 57 | end 58 | 59 | def test_external_env_variables 60 | assert_nil Webpacker.compiler.send(:webpack_env)["WEBPACKER_ASSET_HOST"] 61 | assert_nil Webpacker.compiler.send(:webpack_env)["WEBPACKER_RELATIVE_URL_ROOT"] 62 | 63 | ENV["WEBPACKER_ASSET_HOST"] = "foo.bar" 64 | ENV["WEBPACKER_RELATIVE_URL_ROOT"] = "/baz" 65 | assert_equal Webpacker.compiler.send(:webpack_env)["WEBPACKER_ASSET_HOST"], "foo.bar" 66 | assert_equal Webpacker.compiler.send(:webpack_env)["WEBPACKER_RELATIVE_URL_ROOT"], "/baz" 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/configuration_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ConfigurationTest < Webpacker::Test 4 | def setup 5 | @config = Webpacker::Configuration.new( 6 | root_path: Pathname.new(File.expand_path("test_app", __dir__)), 7 | config_path: Pathname.new(File.expand_path("./test_app/config/webpacker.yml", __dir__)), 8 | env: "production" 9 | ) 10 | end 11 | 12 | def test_source_path 13 | source_path = File.expand_path File.join(File.dirname(__FILE__), "test_app/app/packs").to_s 14 | assert_equal source_path, @config.source_path.to_s 15 | end 16 | 17 | def test_source_entry_path 18 | source_entry_path = File.expand_path File.join(File.dirname(__FILE__), "test_app/app/packs", "entrypoints").to_s 19 | assert_equal @config.source_entry_path.to_s, source_entry_path 20 | end 21 | 22 | def test_public_root_path 23 | public_root_path = File.expand_path File.join(File.dirname(__FILE__), "test_app/public").to_s 24 | assert_equal @config.public_path.to_s, public_root_path 25 | end 26 | 27 | def test_public_output_path 28 | public_output_path = File.expand_path File.join(File.dirname(__FILE__), "test_app/public/packs").to_s 29 | assert_equal @config.public_output_path.to_s, public_output_path 30 | 31 | @config = Webpacker::Configuration.new( 32 | root_path: @config.root_path, 33 | config_path: Pathname.new(File.expand_path("./test_app/config/webpacker_public_root.yml", __dir__)), 34 | env: "production" 35 | ) 36 | 37 | public_output_path = File.expand_path File.join(File.dirname(__FILE__), "public/packs").to_s 38 | assert_equal @config.public_output_path.to_s, public_output_path 39 | end 40 | 41 | def test_public_manifest_path 42 | public_manifest_path = File.expand_path File.join(File.dirname(__FILE__), "test_app/public/packs", "manifest.json").to_s 43 | assert_equal @config.public_manifest_path.to_s, public_manifest_path 44 | end 45 | 46 | def test_cache_path 47 | cache_path = File.expand_path File.join(File.dirname(__FILE__), "test_app/tmp/webpacker").to_s 48 | assert_equal @config.cache_path.to_s, cache_path 49 | end 50 | 51 | def test_additional_paths 52 | assert_equal @config.additional_paths, ["app/assets", "/etc/yarn", "some.config.js", "app/elm"] 53 | end 54 | 55 | def test_cache_manifest? 56 | assert @config.cache_manifest? 57 | 58 | with_rails_env("development") do 59 | refute Webpacker.config.cache_manifest? 60 | end 61 | 62 | with_rails_env("test") do 63 | refute Webpacker.config.cache_manifest? 64 | end 65 | end 66 | 67 | def test_compile? 68 | refute @config.compile? 69 | 70 | with_rails_env("development") do 71 | assert Webpacker.config.compile? 72 | end 73 | 74 | with_rails_env("test") do 75 | assert Webpacker.config.compile? 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /test/dev_server_runner_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "webpacker/dev_server_runner" 3 | 4 | class DevServerRunnerTest < Webpacker::Test 5 | def setup 6 | @original_node_env, ENV["NODE_ENV"] = ENV["NODE_ENV"], "development" 7 | @original_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "development" 8 | @original_webpacker_config = ENV["WEBPACKER_CONFIG"] 9 | end 10 | 11 | def teardown 12 | ENV["NODE_ENV"] = @original_node_env 13 | ENV["RAILS_ENV"] = @original_rails_env 14 | ENV["WEBPACKER_CONFIG"] = @original_webpacker_config 15 | end 16 | 17 | def test_run_cmd_via_node_modules 18 | cmd = ["#{test_app_path}/node_modules/.bin/webpack", "serve", "--config", "#{test_app_path}/config/webpack/development.js"] 19 | 20 | verify_command(cmd, use_node_modules: true) 21 | end 22 | 23 | def test_run_cmd_via_yarn 24 | cmd = ["yarn", "webpack", "serve", "--config", "#{test_app_path}/config/webpack/development.js"] 25 | 26 | verify_command(cmd, use_node_modules: false) 27 | end 28 | 29 | def test_run_cmd_argv 30 | cmd = ["#{test_app_path}/node_modules/.bin/webpack", "serve", "--config", "#{test_app_path}/config/webpack/development.js", "--quiet"] 31 | 32 | verify_command(cmd, argv: ["--quiet"]) 33 | end 34 | 35 | def test_run_cmd_argv_with_https 36 | cmd = ["#{test_app_path}/node_modules/.bin/webpack", "serve", "--config", "#{test_app_path}/config/webpack/development.js", "--https"] 37 | 38 | dev_server = Webpacker::DevServer.new({}) 39 | def dev_server.host; "localhost"; end 40 | def dev_server.port; "3035"; end 41 | def dev_server.pretty?; false; end 42 | def dev_server.https?; true; end 43 | def dev_server.hmr?; false; end 44 | Webpacker::DevServer.stub(:new, dev_server) do 45 | verify_command(cmd, argv: ["--https"]) 46 | end 47 | end 48 | 49 | def test_environment_variables 50 | cmd = ["#{test_app_path}/node_modules/.bin/webpack", "serve", "--config", "#{test_app_path}/config/webpack/development.js"] 51 | env = Webpacker::Compiler.env.dup 52 | ENV["WEBPACKER_CONFIG"] = env["WEBPACKER_CONFIG"] = "#{test_app_path}/config/webpacker_other_location.yml" 53 | env["WEBPACK_SERVE"] = "true" 54 | verify_command(cmd, env: env) 55 | end 56 | 57 | private 58 | def test_app_path 59 | File.expand_path("test_app", __dir__) 60 | end 61 | 62 | def verify_command(cmd, use_node_modules: true, argv: [], env: Webpacker::Compiler.env) 63 | cwd = Dir.pwd 64 | Dir.chdir(test_app_path) 65 | 66 | klass = Webpacker::DevServerRunner 67 | instance = klass.new(argv) 68 | mock = Minitest::Mock.new 69 | mock.expect(:call, nil, [env, *cmd]) 70 | 71 | klass.stub(:new, instance) do 72 | instance.stub(:node_modules_bin_exist?, use_node_modules) do 73 | Kernel.stub(:exec, mock) { klass.run(argv) } 74 | end 75 | end 76 | 77 | mock.verify 78 | ensure 79 | Dir.chdir(cwd) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/dev_server_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class DevServerTest < Webpacker::Test 4 | def test_running? 5 | refute Webpacker.dev_server.running? 6 | end 7 | 8 | def test_host 9 | with_rails_env("development") do 10 | assert_equal Webpacker.dev_server.host, "localhost" 11 | end 12 | end 13 | 14 | def test_port 15 | with_rails_env("development") do 16 | assert_equal Webpacker.dev_server.port, 3035 17 | end 18 | end 19 | 20 | def test_https? 21 | with_rails_env("development") do 22 | assert_equal Webpacker.dev_server.https?, false 23 | end 24 | end 25 | 26 | def test_protocol 27 | with_rails_env("development") do 28 | assert_equal Webpacker.dev_server.protocol, "http" 29 | end 30 | end 31 | 32 | def test_host_with_port 33 | with_rails_env("development") do 34 | assert_equal Webpacker.dev_server.host_with_port, "localhost:3035" 35 | end 36 | end 37 | 38 | def test_pretty? 39 | with_rails_env("development") do 40 | refute Webpacker.dev_server.pretty? 41 | end 42 | end 43 | 44 | def test_default_env_prefix 45 | assert_equal Webpacker::DevServer::DEFAULT_ENV_PREFIX, "WEBPACKER_DEV_SERVER" 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/engine_rake_tasks_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class EngineRakeTasksTest < Minitest::Test 4 | def setup 5 | remove_webpack_binstubs 6 | end 7 | 8 | def teardown 9 | remove_webpack_binstubs 10 | end 11 | 12 | def test_task_mounted 13 | output = Dir.chdir(mounted_app_path) { `rake -T` } 14 | assert_includes output, "app:webpacker" 15 | end 16 | 17 | def test_binstubs 18 | Dir.chdir(mounted_app_path) { `bundle exec rake app:webpacker:binstubs` } 19 | webpack_binstub_paths.each { |path| assert File.exist?(path) } 20 | end 21 | 22 | private 23 | def mounted_app_path 24 | File.expand_path("mounted_app", __dir__) 25 | end 26 | 27 | def webpack_binstub_paths 28 | [ 29 | "#{mounted_app_path}/test/dummy/bin/webpacker", 30 | "#{mounted_app_path}/test/dummy/bin/webpacker-dev-server", 31 | ] 32 | end 33 | 34 | def remove_webpack_binstubs 35 | webpack_binstub_paths.each do |path| 36 | File.delete(path) if File.exist?(path) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /test/env_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class EnvTest < Webpacker::Test 4 | def test_current 5 | assert_equal Webpacker.env, Rails.env 6 | end 7 | 8 | def test_custom_without_config 9 | with_rails_env("foo") do 10 | assert_equal Webpacker.env, "production" 11 | end 12 | end 13 | 14 | def test_custom_with_config 15 | with_rails_env("staging") do 16 | assert_equal Webpacker.env, "staging" 17 | end 18 | end 19 | 20 | def test_default 21 | assert_equal Webpacker::Env::DEFAULT, "production" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/helper_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class HelperTest < ActionView::TestCase 4 | tests Webpacker::Helper 5 | 6 | attr_reader :request 7 | 8 | def setup 9 | @request = Class.new do 10 | def send_early_hints(links) end 11 | def base_url 12 | "https://example.com" 13 | end 14 | end.new 15 | end 16 | 17 | def test_asset_pack_path 18 | assert_equal "/packs/bootstrap-300631c4f0e0f9c865bc.js", asset_pack_path("bootstrap.js") 19 | assert_equal "/packs/bootstrap-c38deda30895059837cf.css", asset_pack_path("bootstrap.css") 20 | end 21 | 22 | def test_asset_pack_url 23 | assert_equal "https://example.com/packs/bootstrap-300631c4f0e0f9c865bc.js", asset_pack_url("bootstrap.js") 24 | assert_equal "https://example.com/packs/bootstrap-c38deda30895059837cf.css", asset_pack_url("bootstrap.css") 25 | end 26 | 27 | def test_image_pack_path 28 | assert_equal "/packs/application-k344a6d59eef8632c9d1.png", image_pack_path("application.png") 29 | assert_equal "/packs/static/image-c38deda30895059837cf.jpg", image_pack_path("image.jpg") 30 | assert_equal "/packs/static/image-c38deda30895059837cf.jpg", image_pack_path("static/image.jpg") 31 | assert_equal "/packs/static/nested/image-c38deda30895059837cf.jpg", image_pack_path("nested/image.jpg") 32 | assert_equal "/packs/static/nested/image-c38deda30895059837cf.jpg", image_pack_path("static/nested/image.jpg") 33 | end 34 | 35 | def test_image_pack_url 36 | assert_equal "https://example.com/packs/application-k344a6d59eef8632c9d1.png", image_pack_url("application.png") 37 | assert_equal "https://example.com/packs/static/image-c38deda30895059837cf.jpg", image_pack_url("image.jpg") 38 | assert_equal "https://example.com/packs/static/image-c38deda30895059837cf.jpg", image_pack_url("static/image.jpg") 39 | assert_equal "https://example.com/packs/static/nested/image-c38deda30895059837cf.jpg", image_pack_url("nested/image.jpg") 40 | assert_equal "https://example.com/packs/static/nested/image-c38deda30895059837cf.jpg", image_pack_url("static/nested/image.jpg") 41 | end 42 | 43 | def test_image_pack_tag 44 | assert_equal \ 45 | "\"Edit", 46 | image_pack_tag("application.png", size: "16x10", alt: "Edit Entry") 47 | assert_equal \ 48 | "\"Edit", 49 | image_pack_tag("image.jpg", size: "16x10", alt: "Edit Entry") 50 | assert_equal \ 51 | "\"Edit", 52 | image_pack_tag("static/image.jpg", size: "16x10", alt: "Edit Entry") 53 | assert_equal \ 54 | "\"Edit", 55 | image_pack_tag("nested/image.jpg", size: "16x10", alt: "Edit Entry") 56 | assert_equal \ 57 | "\"Edit", 58 | image_pack_tag("static/nested/image.jpg", size: "16x10", alt: "Edit Entry") 59 | assert_equal \ 60 | "", 61 | image_pack_tag("static/image.jpg", srcset: { "static/image-2x.jpg" => "2x" }) 62 | end 63 | 64 | def test_favicon_pack_tag 65 | assert_equal \ 66 | "", 67 | favicon_pack_tag("application.png", rel: "apple-touch-icon", type: "image/png") 68 | assert_equal \ 69 | "", 70 | favicon_pack_tag("mb-icon.png", rel: "apple-touch-icon", type: "image/png") 71 | assert_equal \ 72 | "", 73 | favicon_pack_tag("static/mb-icon.png", rel: "apple-touch-icon", type: "image/png") 74 | assert_equal \ 75 | "", 76 | favicon_pack_tag("nested/mb-icon.png", rel: "apple-touch-icon", type: "image/png") 77 | assert_equal \ 78 | "", 79 | favicon_pack_tag("static/nested/mb-icon.png", rel: "apple-touch-icon", type: "image/png") 80 | end 81 | 82 | def test_preload_pack_asset 83 | if self.class.method_defined?(:preload_link_tag) 84 | assert_equal \ 85 | %(), 86 | preload_pack_asset("fonts/fa-regular-400.woff2") 87 | else 88 | error = assert_raises do 89 | preload_pack_asset("fonts/fa-regular-400.woff2") 90 | end 91 | 92 | assert_equal \ 93 | "You need Rails >= 5.2 to use this tag.", 94 | error.message 95 | end 96 | end 97 | 98 | def test_javascript_pack_tag 99 | assert_equal \ 100 | %(\n) + 101 | %(\n) + 102 | %(\n) + 103 | %(), 104 | javascript_pack_tag("application", "bootstrap") 105 | end 106 | 107 | def test_javascript_pack_with_no_defer_tag 108 | assert_equal \ 109 | %(\n) + 110 | %(\n) + 111 | %(\n) + 112 | %(), 113 | javascript_pack_tag("application", "bootstrap", defer: false) 114 | end 115 | 116 | def test_javascript_pack_tag_splat 117 | assert_equal \ 118 | %(\n) + 119 | %(\n) + 120 | %(), 121 | javascript_pack_tag("application", defer: true) 122 | end 123 | 124 | def test_javascript_pack_tag_symbol 125 | assert_equal \ 126 | %(\n) + 127 | %(\n) + 128 | %(), 129 | javascript_pack_tag(:application) 130 | end 131 | 132 | def application_stylesheet_chunks 133 | %w[/packs/1-c20632e7baf2c81200d3.chunk.css /packs/application-k344a6d59eef8632c9d1.chunk.css] 134 | end 135 | 136 | def hello_stimulus_stylesheet_chunks 137 | %w[/packs/hello_stimulus-k344a6d59eef8632c9d1.chunk.css] 138 | end 139 | 140 | def test_stylesheet_pack_tag 141 | assert_equal \ 142 | (application_stylesheet_chunks + hello_stimulus_stylesheet_chunks) 143 | .map { |chunk| stylesheet_link_tag(chunk) }.join("\n"), 144 | stylesheet_pack_tag("application", "hello_stimulus") 145 | end 146 | 147 | def test_stylesheet_pack_tag_symbol 148 | assert_equal \ 149 | (application_stylesheet_chunks + hello_stimulus_stylesheet_chunks) 150 | .map { |chunk| stylesheet_link_tag(chunk) }.join("\n"), 151 | stylesheet_pack_tag(:application, :hello_stimulus) 152 | end 153 | 154 | def test_stylesheet_pack_tag_splat 155 | assert_equal \ 156 | (application_stylesheet_chunks).map { |chunk| stylesheet_link_tag(chunk, media: "all") }.join("\n"), 157 | stylesheet_pack_tag("application", media: "all") 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /test/manifest_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ManifestTest < Minitest::Test 4 | def test_lookup_exception! 5 | asset_file = "calendar.js" 6 | 7 | error = assert_raises_manifest_missing_entry_error do 8 | Webpacker.manifest.lookup!(asset_file) 9 | end 10 | 11 | assert_match "Webpacker can't find #{asset_file} in #{manifest_path}", error.message 12 | end 13 | 14 | def test_lookup_with_type_exception! 15 | asset_file = "calendar" 16 | 17 | error = assert_raises_manifest_missing_entry_error do 18 | Webpacker.manifest.lookup!(asset_file, type: :javascript) 19 | end 20 | 21 | assert_match "Webpacker can't find #{asset_file}.js in #{manifest_path}", error.message 22 | end 23 | 24 | def test_lookup_success! 25 | assert_equal Webpacker.manifest.lookup!("bootstrap.js"), "/packs/bootstrap-300631c4f0e0f9c865bc.js" 26 | end 27 | 28 | def test_lookup_with_chunks_without_extension_success! 29 | assert_equal ["/packs/bootstrap-300631c4f0e0f9c865bc.js"], Webpacker.manifest.lookup_pack_with_chunks!("bootstrap", type: :javascript) 30 | end 31 | 32 | def test_lookup_with_chunks_with_extension_success! 33 | assert_equal ["/packs/bootstrap-300631c4f0e0f9c865bc.js"], Webpacker.manifest.lookup_pack_with_chunks!("bootstrap.js", type: :javascript) 34 | end 35 | 36 | def test_lookup_with_chunks_without_extension_subdir_success! 37 | assert_equal ["/packs/print/application-983b6c164a47f7ed49cd.css"], Webpacker.manifest.lookup_pack_with_chunks!("print/application", type: :css) 38 | end 39 | 40 | def test_lookup_with_chunks_with_extension_subdir_success! 41 | assert_equal ["/packs/print/application-983b6c164a47f7ed49cd.css"], Webpacker.manifest.lookup_pack_with_chunks!("print/application.css", type: :css) 42 | end 43 | 44 | def test_lookup_nil 45 | assert_nil Webpacker.manifest.lookup("foo.js") 46 | end 47 | 48 | def test_lookup_chunks_nil 49 | assert_nil Webpacker.manifest.lookup_pack_with_chunks("foo.js") 50 | end 51 | 52 | def test_lookup_success 53 | assert_equal Webpacker.manifest.lookup("bootstrap.js"), "/packs/bootstrap-300631c4f0e0f9c865bc.js" 54 | end 55 | 56 | def test_lookup_entrypoint_exception! 57 | asset_file = "calendar" 58 | 59 | error = assert_raises_manifest_missing_entry_error do 60 | Webpacker.manifest.lookup_pack_with_chunks!(asset_file, type: :javascript) 61 | end 62 | 63 | assert_match "Webpacker can't find #{asset_file}.js in #{manifest_path}", error.message 64 | end 65 | 66 | def test_lookup_entrypoint 67 | application_entrypoints = [ 68 | "/packs/vendors~application~bootstrap-c20632e7baf2c81200d3.chunk.js", 69 | "/packs/vendors~application-e55f2aae30c07fb6d82a.chunk.js", 70 | "/packs/application-k344a6d59eef8632c9d1.js" 71 | ] 72 | 73 | assert_equal Webpacker.manifest.lookup_pack_with_chunks!("application", type: :javascript), application_entrypoints 74 | end 75 | 76 | private 77 | 78 | def assert_raises_manifest_missing_entry_error(&block) 79 | error = nil 80 | Webpacker.config.stub :compile?, false do 81 | error = assert_raises Webpacker::Manifest::MissingEntryError, &block 82 | end 83 | error 84 | end 85 | 86 | def manifest_path 87 | File.expand_path File.join(File.dirname(__FILE__), "test_app/public/packs", "manifest.json").to_s 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /test/mounted_app/Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | 3 | APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__) 4 | load "rails/tasks/engine.rake" 5 | -------------------------------------------------------------------------------- /test/mounted_app/test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | require_relative "config/application" 2 | 3 | Rails.application.load_tasks 4 | -------------------------------------------------------------------------------- /test/mounted_app/test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require "rails/commands" 4 | -------------------------------------------------------------------------------- /test/mounted_app/test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rake" 3 | Rake.application.run 4 | -------------------------------------------------------------------------------- /test/mounted_app/test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file allows the `Rails.root` to be correctly determined. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /test/mounted_app/test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require "action_controller/railtie" 2 | require "action_view/railtie" 3 | require "webpacker" 4 | 5 | module TestDummyApp 6 | class Application < Rails::Application 7 | config.secret_key_base = "abcdef" 8 | config.eager_load = true 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/mounted_app/test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | require_relative "application" 2 | 3 | Rails.application.initialize! 4 | -------------------------------------------------------------------------------- /test/mounted_app/test/dummy/config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpacker-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/packs 5 | source_entry_path: entrypoints 6 | public_output_path: packs 7 | cache_path: tmp/webpacker 8 | 9 | # Additional paths webpack should look up modules 10 | # ['app/assets', 'engine/foo/app/assets'] 11 | additional_paths: 12 | - app/assets 13 | - /etc/yarn 14 | 15 | # Reload manifest.json on all requests so we reload latest compiled packs 16 | cache_manifest: false 17 | 18 | extensions: 19 | - .js 20 | - .sass 21 | - .scss 22 | - .css 23 | - .module.sass 24 | - .module.scss 25 | - .module.css 26 | - .png 27 | - .svg 28 | - .gif 29 | - .jpeg 30 | - .jpg 31 | 32 | development: 33 | <<: *default 34 | compile: true 35 | 36 | # Reference: https://webpack.js.org/configuration/dev-server/ 37 | dev_server: 38 | https: false 39 | host: localhost 40 | port: 3035 41 | public: localhost:3035 42 | hmr: false 43 | # Inline should be set to true if using HMR 44 | inline: true 45 | overlay: true 46 | disable_host_check: true 47 | use_local_ip: false 48 | 49 | test: 50 | <<: *default 51 | compile: true 52 | 53 | # Compile test packs to a separate directory 54 | public_output_path: packs-test 55 | 56 | production: 57 | <<: *default 58 | 59 | # Production depends on precompilation of packs prior to booting for performance. 60 | compile: false 61 | 62 | # Cache manifest.json for performance 63 | cache_manifest: true 64 | 65 | staging: 66 | <<: *default 67 | 68 | # Production depends on precompilation of packs prior to booting for performance. 69 | compile: false 70 | 71 | # Cache manifest.json for performance 72 | cache_manifest: true 73 | 74 | # Compile staging packs to a separate directory 75 | public_output_path: packs-staging 76 | -------------------------------------------------------------------------------- /test/mounted_app/test/dummy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "dependencies": { 4 | "@rails/webpacker": "file:../../../../" 5 | }, 6 | "license": "MIT" 7 | } 8 | -------------------------------------------------------------------------------- /test/rake_tasks_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class RakeTasksTest < Minitest::Test 4 | def test_rake_tasks 5 | output = Dir.chdir(test_app_path) { `rake -T` } 6 | assert_includes output, "webpacker" 7 | assert_includes output, "webpacker:check_binstubs" 8 | assert_includes output, "webpacker:check_node" 9 | assert_includes output, "webpacker:check_yarn" 10 | assert_includes output, "webpacker:clean" 11 | assert_includes output, "webpacker:clobber" 12 | assert_includes output, "webpacker:compile" 13 | assert_includes output, "webpacker:install" 14 | assert_includes output, "webpacker:verify_install" 15 | end 16 | 17 | def test_rake_task_webpacker_check_binstubs 18 | output = Dir.chdir(test_app_path) { `rake webpacker:check_binstubs 2>&1` } 19 | refute_includes output, "webpack binstub not found." 20 | end 21 | 22 | def test_check_node_version 23 | output = Dir.chdir(test_app_path) { `rake webpacker:check_node 2>&1` } 24 | refute_includes output, "Webpacker requires Node.js" 25 | end 26 | 27 | def test_check_yarn_version 28 | output = Dir.chdir(test_app_path) { `rake webpacker:check_yarn 2>&1` } 29 | refute_includes output, "Yarn not installed" 30 | refute_includes output, "Webpacker requires Yarn" 31 | end 32 | 33 | def test_rake_webpacker_yarn_install_in_non_production_environments 34 | assert_includes test_app_dev_dependencies, "right-pad" 35 | 36 | Webpacker.with_node_env("test") do 37 | Dir.chdir(test_app_path) do 38 | `bundle exec rake webpacker:yarn_install` 39 | end 40 | end 41 | 42 | assert_includes installed_node_module_names, "right-pad", 43 | "Expected dev dependencies to be installed" 44 | end 45 | 46 | def test_rake_webpacker_yarn_install_in_production_environment 47 | Webpacker.with_node_env("production") do 48 | Dir.chdir(test_app_path) do 49 | `bundle exec rake webpacker:yarn_install` 50 | end 51 | end 52 | 53 | refute_includes installed_node_module_names, "right-pad", 54 | "Expected only production dependencies to be installed" 55 | end 56 | 57 | private 58 | def test_app_path 59 | File.expand_path("test_app", __dir__) 60 | end 61 | 62 | def test_app_dev_dependencies 63 | package_json = File.expand_path("package.json", test_app_path) 64 | JSON.parse(File.read(package_json))["devDependencies"] 65 | end 66 | 67 | def installed_node_module_names 68 | node_modules_path = File.expand_path("node_modules", test_app_path) 69 | Dir.chdir(node_modules_path) { Dir.glob("*") } 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/test_app/Rakefile: -------------------------------------------------------------------------------- 1 | require_relative "config/application" 2 | 3 | Rails.application.load_tasks 4 | -------------------------------------------------------------------------------- /test/test_app/app/packs/entrypoints/application.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:0 */ 2 | // This file is automatically compiled by Webpack, along with any other files 3 | // present in this directory. You're encouraged to place your actual application logic in 4 | // a relevant structure within app/packs and only use these pack files to reference 5 | // that code so it'll be compiled. 6 | // 7 | // To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate 8 | // layout file, like app/views/layouts/application.html.erb 9 | 10 | console.log('Hello World from Webpacker') 11 | -------------------------------------------------------------------------------- /test/test_app/app/packs/entrypoints/multi_entry.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Dummy file #1 for Multi File Entry points: https://webpack.js.org/guides/entry-advanced/ 3 | * This file must be named the same 4 | */ -------------------------------------------------------------------------------- /test/test_app/app/packs/entrypoints/multi_entry.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Dummy file #2 for Multi File Entry points: https://webpack.js.org/guides/entry-advanced/ 3 | * This file must be named the same 4 | */ 5 | -------------------------------------------------------------------------------- /test/test_app/bin/webpacker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= "development" 4 | ENV["NODE_ENV"] ||= ENV["RAILS_ENV"] 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/webpack_runner" 14 | Webpacker::WebpackRunner.run(ARGV) 15 | -------------------------------------------------------------------------------- /test/test_app/bin/webpacker-dev-server: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | ENV["RAILS_ENV"] ||= "development" 4 | ENV["NODE_ENV"] ||= ENV["RAILS_ENV"] 5 | 6 | require "pathname" 7 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 8 | Pathname.new(__FILE__).realpath) 9 | 10 | require "bundler/setup" 11 | 12 | require "webpacker" 13 | require "webpacker/dev_server_runner" 14 | Webpacker::DevServerRunner.run(ARGV) 15 | -------------------------------------------------------------------------------- /test/test_app/config.ru: -------------------------------------------------------------------------------- 1 | # This file allows the `Rails.root` to be correctly determined. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /test/test_app/config/application.rb: -------------------------------------------------------------------------------- 1 | require "action_controller/railtie" 2 | require "action_view/railtie" 3 | require "webpacker" 4 | 5 | module TestApp 6 | class Application < ::Rails::Application 7 | config.secret_key_base = "abcdef" 8 | config.eager_load = true 9 | config.active_support.test_order = :sorted 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/test_app/config/environment.rb: -------------------------------------------------------------------------------- 1 | require_relative "application" 2 | 3 | Rails.backtrace_cleaner.remove_silencers! 4 | Rails.application.initialize! 5 | -------------------------------------------------------------------------------- /test/test_app/config/initializers/inspect_autoload_paths.rb: -------------------------------------------------------------------------------- 1 | $test_app_autoload_paths_in_initializer = ActiveSupport::Dependencies.autoload_paths 2 | -------------------------------------------------------------------------------- /test/test_app/config/webpack/development.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/webpacker/a715e055d937748c7cfd34b0450295348ca13ee6/test/test_app/config/webpack/development.js -------------------------------------------------------------------------------- /test/test_app/config/webpacker.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpacker-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/packs 5 | source_entry_path: entrypoints 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/webpacker 9 | webpack_compile_output: false 10 | 11 | # Additional paths webpack should look up modules 12 | # ['app/assets', 'engine/foo/app/assets'] 13 | additional_paths: 14 | - app/assets 15 | - /etc/yarn 16 | - some.config.js 17 | - app/elm 18 | 19 | # Reload manifest.json on all requests so we reload latest compiled packs 20 | cache_manifest: false 21 | 22 | static_assets_extensions: 23 | - .jpg 24 | - .jpeg 25 | - .png 26 | - .gif 27 | - .tiff 28 | - .ico 29 | - .svg 30 | 31 | extensions: 32 | - .mjs 33 | - .js 34 | 35 | development: 36 | <<: *default 37 | compile: true 38 | 39 | # Reference: https://webpack.js.org/configuration/dev-server/ 40 | dev_server: 41 | https: false 42 | host: localhost 43 | port: 3035 44 | public: localhost:3035 45 | hmr: false 46 | overlay: true 47 | disable_host_check: true 48 | use_local_ip: false 49 | pretty: false 50 | 51 | test: 52 | <<: *default 53 | compile: true 54 | 55 | # Compile test packs to a separate directory 56 | public_output_path: packs-test 57 | 58 | production: 59 | <<: *default 60 | 61 | # Production depends on precompilation of packs prior to booting for performance. 62 | compile: false 63 | 64 | # Cache manifest.json for performance 65 | cache_manifest: true 66 | 67 | staging: 68 | <<: *default 69 | 70 | # Production depends on precompilation of packs prior to booting for performance. 71 | compile: false 72 | 73 | # Cache manifest.json for performance 74 | cache_manifest: true 75 | 76 | # Compile staging packs to a separate directory 77 | public_output_path: packs-staging 78 | -------------------------------------------------------------------------------- /test/test_app/config/webpacker_other_location.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpacker-dev-server for changes to take effect 2 | 3 | default: &default 4 | source_path: app/packs 5 | source_entry_path: entrypoints 6 | public_root_path: public 7 | public_output_path: packs 8 | cache_path: tmp/cache/webpacker 9 | webpack_compile_output: false 10 | 11 | # Additional paths webpack should look up modules 12 | # ['app/assets', 'engine/foo/app/assets'] 13 | additional_paths: 14 | - app/assets 15 | - /etc/yarn 16 | - some.config.js 17 | - app/elm 18 | 19 | # Reload manifest.json on all requests so we reload latest compiled packs 20 | cache_manifest: false 21 | 22 | static_assets_extensions: 23 | - .jpg 24 | - .jpeg 25 | - .png 26 | - .gif 27 | - .tiff 28 | - .ico 29 | - .svg 30 | 31 | extensions: 32 | - .mjs 33 | - .js 34 | 35 | development: 36 | <<: *default 37 | compile: true 38 | 39 | # Reference: https://webpack.js.org/configuration/dev-server/ 40 | dev_server: 41 | https: false 42 | host: localhost 43 | port: 3035 44 | public: localhost:3035 45 | hmr: false 46 | # Inline should be set to true if using HMR 47 | inline: true 48 | overlay: true 49 | disable_host_check: true 50 | use_local_ip: false 51 | pretty: false 52 | 53 | test: 54 | <<: *default 55 | compile: true 56 | 57 | # Compile test packs to a separate directory 58 | public_output_path: packs-test 59 | 60 | production: 61 | <<: *default 62 | 63 | # Production depends on precompilation of packs prior to booting for performance. 64 | compile: false 65 | 66 | # Cache manifest.json for performance 67 | cache_manifest: true 68 | 69 | staging: 70 | <<: *default 71 | 72 | # Production depends on precompilation of packs prior to booting for performance. 73 | compile: false 74 | 75 | # Cache manifest.json for performance 76 | cache_manifest: true 77 | 78 | # Compile staging packs to a separate directory 79 | public_output_path: packs-staging 80 | -------------------------------------------------------------------------------- /test/test_app/config/webpacker_public_root.yml: -------------------------------------------------------------------------------- 1 | # Note: You must restart bin/webpacker-dev-server for changes to take effect 2 | 3 | default: &default 4 | public_root_path: ../public 5 | 6 | development: 7 | <<: *default 8 | compile: true 9 | 10 | test: 11 | <<: *default 12 | compile: true 13 | public_output_path: packs-test 14 | 15 | production: 16 | <<: *default 17 | compile: false 18 | cache_manifest: true 19 | -------------------------------------------------------------------------------- /test/test_app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test_app", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "dependencies": { 8 | "left-pad": "^1.2.0" 9 | }, 10 | "devDependencies": { 11 | "right-pad": "^1.0.1" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/test_app/public/packs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "bootstrap.css": "/packs/bootstrap-c38deda30895059837cf.css", 3 | "application.css": "/packs/application-dd6b1cd38bfa093df600.css", 4 | "bootstrap.js": "/packs/bootstrap-300631c4f0e0f9c865bc.js", 5 | "application.js": "/packs/application-k344a6d59eef8632c9d1.js", 6 | "application.png": "/packs/application-k344a6d59eef8632c9d1.png", 7 | "fonts/fa-regular-400.woff2": "/packs/fonts/fa-regular-400-944fb546bd7018b07190a32244f67dc9.woff2", 8 | "static/image.jpg": "/packs/static/image-c38deda30895059837cf.jpg", 9 | "static/image-2x.jpg": "/packs/static/image-2x-7cca48e6cae66ec07b8e.jpg", 10 | "static/nested/image.jpg": "/packs/static/nested/image-c38deda30895059837cf.jpg", 11 | "static/mb-icon.png": "/packs/static/mb-icon-c38deda30895059837cf.png", 12 | "static/nested/mb-icon.png": "/packs/static/nested/mb-icon-c38deda30895059837cf.png", 13 | "entrypoints": { 14 | "application": { 15 | "assets": { 16 | "js": [ 17 | "/packs/vendors~application~bootstrap-c20632e7baf2c81200d3.chunk.js", 18 | "/packs/vendors~application-e55f2aae30c07fb6d82a.chunk.js", 19 | "/packs/application-k344a6d59eef8632c9d1.js" 20 | ], 21 | "css": [ 22 | "/packs/1-c20632e7baf2c81200d3.chunk.css", 23 | "/packs/application-k344a6d59eef8632c9d1.chunk.css" 24 | ] 25 | } 26 | }, 27 | "bootstrap": { 28 | "assets": { 29 | "js": [ 30 | "/packs/bootstrap-300631c4f0e0f9c865bc.js" 31 | ] 32 | } 33 | }, 34 | "hello_stimulus": { 35 | "assets": { 36 | "css": [ 37 | "/packs/1-c20632e7baf2c81200d3.chunk.css", 38 | "/packs/hello_stimulus-k344a6d59eef8632c9d1.chunk.css" 39 | ] 40 | } 41 | }, 42 | "print/application": { 43 | "assets": { 44 | "css": [ 45 | "/packs/print/application-983b6c164a47f7ed49cd.css" 46 | ] 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/test_app/some.config.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rails/webpacker/a715e055d937748c7cfd34b0450295348ca13ee6/test/test_app/some.config.js -------------------------------------------------------------------------------- /test/test_app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | left-pad@^1.2.0: 6 | version "1.2.0" 7 | resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee" 8 | 9 | right-pad@^1.0.1: 10 | version "1.0.1" 11 | resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0" 12 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require "minitest/autorun" 3 | require "rails" 4 | require "rails/test_help" 5 | require "byebug" 6 | 7 | require_relative "test_app/config/environment" 8 | 9 | Rails.env = "production" 10 | 11 | Webpacker.instance = ::Webpacker::Instance.new 12 | 13 | class Webpacker::Test < Minitest::Test 14 | private 15 | def reloaded_config 16 | Webpacker.instance.instance_variable_set(:@env, nil) 17 | Webpacker.instance.instance_variable_set(:@config, nil) 18 | Webpacker.instance.instance_variable_set(:@dev_server, nil) 19 | Webpacker.env 20 | Webpacker.config 21 | Webpacker.dev_server 22 | end 23 | 24 | def with_rails_env(env) 25 | original = Rails.env 26 | Rails.env = ActiveSupport::StringInquirer.new(env) 27 | reloaded_config 28 | yield 29 | ensure 30 | Rails.env = ActiveSupport::StringInquirer.new(original) 31 | reloaded_config 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /test/webpack_runner_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "webpacker/webpack_runner" 3 | 4 | class WebpackRunnerTest < Webpacker::Test 5 | def setup 6 | @original_node_env, ENV["NODE_ENV"] = ENV["NODE_ENV"], "development" 7 | @original_rails_env, ENV["RAILS_ENV"] = ENV["RAILS_ENV"], "development" 8 | end 9 | 10 | def teardown 11 | ENV["NODE_ENV"] = @original_node_env 12 | ENV["RAILS_ENV"] = @original_rails_env 13 | end 14 | 15 | def test_run_cmd_via_node_modules 16 | cmd = ["#{test_app_path}/node_modules/.bin/webpack", "--config", "#{test_app_path}/config/webpack/development.js"] 17 | 18 | verify_command(cmd, use_node_modules: true) 19 | end 20 | 21 | def test_run_cmd_via_yarn 22 | cmd = ["yarn", "webpack", "--config", "#{test_app_path}/config/webpack/development.js"] 23 | 24 | verify_command(cmd, use_node_modules: false) 25 | end 26 | 27 | def test_run_cmd_argv 28 | cmd = ["#{test_app_path}/node_modules/.bin/webpack", "--config", "#{test_app_path}/config/webpack/development.js", "--watch"] 29 | 30 | verify_command(cmd, argv: ["--watch"]) 31 | end 32 | 33 | private 34 | def test_app_path 35 | File.expand_path("test_app", __dir__) 36 | end 37 | 38 | def verify_command(cmd, use_node_modules: true, argv: []) 39 | cwd = Dir.pwd 40 | Dir.chdir(test_app_path) 41 | 42 | klass = Webpacker::WebpackRunner 43 | instance = klass.new(argv) 44 | mock = Minitest::Mock.new 45 | mock.expect(:call, nil, [Webpacker::Compiler.env, *cmd]) 46 | 47 | klass.stub(:new, instance) do 48 | instance.stub(:node_modules_bin_exist?, use_node_modules) do 49 | Kernel.stub(:exec, mock) { klass.run(argv) } 50 | end 51 | end 52 | 53 | mock.verify 54 | ensure 55 | Dir.chdir(cwd) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/webpacker_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class WebpackerTest < Webpacker::Test 4 | def test_config_params 5 | assert_equal Rails.env, Webpacker.config.env 6 | assert_equal Webpacker.instance.root_path, Webpacker.config.root_path 7 | assert_equal Webpacker.instance.config_path, Webpacker.config.config_path 8 | 9 | with_rails_env("test") do 10 | assert_equal "test", Webpacker.config.env 11 | end 12 | end 13 | 14 | def test_inline_css_no_dev_server 15 | assert !Webpacker.inlining_css? 16 | end 17 | 18 | def test_inline_css_with_hmr 19 | dev_server = Webpacker::DevServer.new({}) 20 | def dev_server.host; "localhost"; end 21 | def dev_server.port; "3035"; end 22 | def dev_server.pretty?; false; end 23 | def dev_server.https?; true; end 24 | def dev_server.hmr?; true; end 25 | def dev_server.running?; true; end 26 | Webpacker.instance.stub(:dev_server, dev_server) do 27 | assert Webpacker.inlining_css? 28 | end 29 | end 30 | 31 | def test_app_autoload_paths_cleanup 32 | assert_empty $test_app_autoload_paths_in_initializer 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /webpacker.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | require "webpacker/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "webpacker" 6 | s.version = Webpacker::VERSION 7 | s.authors = [ "David Heinemeier Hansson", "Gaurav Tiwari" ] 8 | s.email = [ "david@basecamp.com", "gaurav@gauravtiwari.co.uk" ] 9 | s.summary = "Use webpack to manage app-like JavaScript modules in Rails" 10 | s.homepage = "https://github.com/rails/webpacker" 11 | s.license = "MIT" 12 | 13 | s.metadata = { 14 | "source_code_uri" => "https://github.com/rails/webpacker/tree/v#{Webpacker::VERSION}", 15 | "changelog_uri" => "https://github.com/rails/webpacker/blob/v#{Webpacker::VERSION}/CHANGELOG.md" 16 | } 17 | 18 | s.required_ruby_version = ">= 2.7.0" 19 | 20 | s.add_dependency "activesupport", ">= 5.2" 21 | s.add_dependency "railties", ">= 5.2" 22 | s.add_dependency "rack-proxy", ">= 0.6.1" 23 | s.add_dependency "semantic_range", ">= 2.3.0" 24 | 25 | s.add_development_dependency "bundler", ">= 1.3.0" 26 | s.add_development_dependency "rubocop" 27 | s.add_development_dependency "rubocop-performance" 28 | 29 | s.files = `git ls-files`.split("\n") 30 | s.test_files = `git ls-files -- test/*`.split("\n") 31 | end 32 | --------------------------------------------------------------------------------