shakapacker 6.3.0.pre.rc.1 → 6.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -5
- data/Gemfile +2 -1
- data/Gemfile.lock +94 -84
- data/README.md +152 -60
- data/docs/react.md +262 -0
- data/docs/sprockets.md +10 -0
- data/lib/install/config/webpacker.yml +12 -0
- data/lib/tasks/webpacker/yarn_install.rake +6 -0
- data/lib/tasks/webpacker.rake +0 -1
- data/lib/tasks/yarn.rake +6 -0
- data/lib/webpacker/base_strategy.rb +24 -0
- data/lib/webpacker/compiler.rb +6 -51
- data/lib/webpacker/compiler_strategy.rb +20 -0
- data/lib/webpacker/configuration.rb +8 -1
- data/lib/webpacker/digest_strategy.rb +59 -0
- data/lib/webpacker/instance.rb +4 -0
- data/lib/webpacker/mtime_strategy.rb +40 -0
- data/lib/webpacker/version.rb +1 -1
- data/package/environments/__tests__/base.js +30 -3
- data/package/environments/base.js +8 -1
- data/package/rules/erb.js +7 -1
- data/package/rules/file.js +2 -2
- data/package.json +4 -4
- data/test/compiler_strategy_test.rb +27 -0
- data/test/compiler_test.rb +27 -30
- data/test/digest_strategy_test.rb +33 -0
- data/test/mtime_strategy_test.rb +42 -0
- data/test/rake_tasks_test.rb +0 -34
- data/test/test_app/app/packs/entrypoints/generated/something.js +2 -0
- data/test/test_app/config/webpacker.yml +1 -0
- data/test/test_app/config/webpacker_nested_entries.yml +83 -0
- metadata +21 -5
data/docs/react.md
ADDED
@@ -0,0 +1,262 @@
|
|
1
|
+
# React Integration
|
2
|
+
|
3
|
+
These steps describe how to create a Rails/React app, using Shakapacker as the bundler.
|
4
|
+
|
5
|
+
Before starting, ensure that you have Yarn installed, for example:
|
6
|
+
|
7
|
+
```shell
|
8
|
+
npm i -g yarn
|
9
|
+
```
|
10
|
+
|
11
|
+
## Basic Setup
|
12
|
+
|
13
|
+
Create a new Rails app as per the [installation instructions in the README](https://github.com/shakacode/shakapacker#installation).
|
14
|
+
|
15
|
+
Add React, as well as the necessary libraries to enable CSS support in your application:
|
16
|
+
|
17
|
+
```shell
|
18
|
+
yarn add react react-dom @babel/preset-react
|
19
|
+
yarn add css-loader style-loader mini-css-extract-plugin css-minimizer-webpack-plugin
|
20
|
+
```
|
21
|
+
|
22
|
+
Update the Babel configuration in the `package.json` file:
|
23
|
+
|
24
|
+
```diff
|
25
|
+
"babel": {
|
26
|
+
"presets": [
|
27
|
+
"./node_modules/shakapacker/package/babel/preset.js",
|
28
|
+
+ "@babel/preset-react"
|
29
|
+
]
|
30
|
+
},
|
31
|
+
```
|
32
|
+
|
33
|
+
And that's it. You can now create a React app using `app/javascript/application.js` as your entry point.
|
34
|
+
|
35
|
+
## Enabling Hot Module Replacement (HMR)
|
36
|
+
|
37
|
+
With HMR enabled, Shakapacker will automatically update only that part of the page that changed when it detects changes in your project files. This has the nice advantage of preserving your app’s state.
|
38
|
+
|
39
|
+
To enable HMR in a React app, proceed as follows:.
|
40
|
+
|
41
|
+
In `config/webpacker.yml` set `hmr` is set to `true`.
|
42
|
+
|
43
|
+
Install the [react-refresh](https://www.npmjs.com/package/react-refresh) package, as well as [@pmmmwh/react-refresh-webpack-plugin](https://www.npmjs.com/package/@pmmmwh/react-refresh-webpack-plugin):
|
44
|
+
|
45
|
+
```shell
|
46
|
+
yarn add --dev react-refresh @pmmmwh/react-refresh-webpack-plugin
|
47
|
+
```
|
48
|
+
|
49
|
+
Alter `config/webpack/webpack.config.js` like so:
|
50
|
+
|
51
|
+
```js
|
52
|
+
const { webpackConfig, inliningCss } = require('shakapacker');
|
53
|
+
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
54
|
+
const isDevelopment = process.env.NODE_ENV !== 'production';
|
55
|
+
|
56
|
+
if (isDevelopment && inliningCss) {
|
57
|
+
webpackConfig.plugins.push(
|
58
|
+
new ReactRefreshWebpackPlugin({
|
59
|
+
overlay: {
|
60
|
+
sockPort: webpackConfig.devServer.port,
|
61
|
+
},
|
62
|
+
})
|
63
|
+
);
|
64
|
+
}
|
65
|
+
|
66
|
+
module.exports = webpackConfig;
|
67
|
+
```
|
68
|
+
|
69
|
+
This applies the plugin to the webpack configuration.
|
70
|
+
|
71
|
+
Delete the Babel configuration from `package.json`:
|
72
|
+
|
73
|
+
```diff
|
74
|
+
- "babel": {
|
75
|
+
- "presets": [
|
76
|
+
- "./node_modules/shakapacker/package/babel/preset.js",
|
77
|
+
- "@babel/preset-react"
|
78
|
+
- ]
|
79
|
+
- },
|
80
|
+
```
|
81
|
+
|
82
|
+
Then create a `babel.config.js` file in the root of project and add the following:
|
83
|
+
|
84
|
+
```js
|
85
|
+
module.exports = function (api) {
|
86
|
+
const defaultConfigFunc = require('shakapacker/package/babel/preset.js')
|
87
|
+
const resultConfig = defaultConfigFunc(api)
|
88
|
+
const isDevelopmentEnv = api.env('development')
|
89
|
+
const isProductionEnv = api.env('production')
|
90
|
+
const isTestEnv = api.env('test')
|
91
|
+
|
92
|
+
const changesOnDefault = {
|
93
|
+
presets: [
|
94
|
+
[
|
95
|
+
'@babel/preset-react',
|
96
|
+
{
|
97
|
+
development: isDevelopmentEnv || isTestEnv,
|
98
|
+
useBuiltIns: true
|
99
|
+
}
|
100
|
+
]
|
101
|
+
].filter(Boolean),
|
102
|
+
plugins: [
|
103
|
+
isProductionEnv && ['babel-plugin-transform-react-remove-prop-types',
|
104
|
+
{
|
105
|
+
removeImport: true
|
106
|
+
}
|
107
|
+
],
|
108
|
+
process.env.WEBPACK_SERVE && 'react-refresh/babel'
|
109
|
+
].filter(Boolean),
|
110
|
+
}
|
111
|
+
|
112
|
+
resultConfig.presets = [...resultConfig.presets, ...changesOnDefault.presets]
|
113
|
+
resultConfig.plugins = [...resultConfig.plugins, ...changesOnDefault.plugins ]
|
114
|
+
|
115
|
+
return resultConfig
|
116
|
+
}
|
117
|
+
```
|
118
|
+
|
119
|
+
This is taken from the [sample React Babel config](https://github.com/jameshibbard/shakapacker/blob/master/docs/customizing_babel_config.md#react-configuration).
|
120
|
+
|
121
|
+
HMR for your React app is now enabled. 🚀
|
122
|
+
|
123
|
+
## A Basic Demo App
|
124
|
+
|
125
|
+
To test that all of the above is working, you can follow these instructions to create a basic React app using Shakapacker.
|
126
|
+
|
127
|
+
1. Create a new Rails app:
|
128
|
+
```shell
|
129
|
+
rails new myapp --skip-javascript
|
130
|
+
cd myapp
|
131
|
+
bundle add shakapacker --strict
|
132
|
+
./bin/bundle install
|
133
|
+
./bin/rails webpacker:install
|
134
|
+
yarn add react react-dom @babel/preset-react
|
135
|
+
yarn add css-loader style-loader mini-css-extract-plugin css-minimizer-webpack-plugin
|
136
|
+
```
|
137
|
+
|
138
|
+
2. Generate a controller
|
139
|
+
```shell
|
140
|
+
rails g controller site index
|
141
|
+
echo '<div id="root"></div>' > app/views/site/index.html.erb
|
142
|
+
```
|
143
|
+
|
144
|
+
3. Create a CSS file and a React component:
|
145
|
+
```shell
|
146
|
+
touch app/javascript/App.css app/javascript/App.js
|
147
|
+
```
|
148
|
+
|
149
|
+
4. Edit `app/javascript/application.js` like so:
|
150
|
+
```jsx
|
151
|
+
import React from 'react';
|
152
|
+
import { createRoot } from 'react-dom/client';
|
153
|
+
import HelloMessage from './App';
|
154
|
+
|
155
|
+
const container = document.getElementById('root');
|
156
|
+
const root = createRoot(container);
|
157
|
+
|
158
|
+
document.addEventListener('DOMContentLoaded', () => {
|
159
|
+
root.render(<HelloMessage name="World" />);
|
160
|
+
});
|
161
|
+
```
|
162
|
+
|
163
|
+
5. Add the following to `app/javascript/App.js`:
|
164
|
+
```jsx
|
165
|
+
import React from 'react';
|
166
|
+
import 'App.css';
|
167
|
+
const HelloMessage = ({ name }) => <h1>Hello, {name}!</h1>;
|
168
|
+
export default HelloMessage;
|
169
|
+
```
|
170
|
+
|
171
|
+
6. Add the following to `app/javascript/App.css`:
|
172
|
+
```css
|
173
|
+
h1 { color: blue; }
|
174
|
+
```
|
175
|
+
|
176
|
+
7. Enable HMR in config/webpacker.yml:
|
177
|
+
```shell
|
178
|
+
hmr: true
|
179
|
+
```
|
180
|
+
|
181
|
+
8. Install the [react-refresh](https://www.npmjs.com/package/react-refresh) package, as well as [@pmmmwh/react-refresh-webpack-plugin](https://www.npmjs.com/package/@pmmmwh/react-refresh-webpack-plugin):
|
182
|
+
|
183
|
+
```shell
|
184
|
+
yarn add --dev react-refresh @pmmmwh/react-refresh-webpack-plugin
|
185
|
+
```
|
186
|
+
|
187
|
+
9. Alter `config/webpack/webpack.config.js` like so:
|
188
|
+
|
189
|
+
```js
|
190
|
+
const { webpackConfig, inliningCss } = require('shakapacker');
|
191
|
+
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
192
|
+
const isDevelopment = process.env.NODE_ENV !== 'production';
|
193
|
+
|
194
|
+
if (isDevelopment && inliningCss) {
|
195
|
+
webpackConfig.plugins.push(
|
196
|
+
new ReactRefreshWebpackPlugin({
|
197
|
+
overlay: {
|
198
|
+
sockPort: webpackConfig.devServer.port,
|
199
|
+
},
|
200
|
+
})
|
201
|
+
);
|
202
|
+
}
|
203
|
+
|
204
|
+
module.exports = webpackConfig;
|
205
|
+
```
|
206
|
+
|
207
|
+
10. Remove the Babel configuration from `package.json`
|
208
|
+
```diff
|
209
|
+
- "babel": {
|
210
|
+
- "presets": [
|
211
|
+
- "./node_modules/shakapacker/package/babel/preset.js"
|
212
|
+
- ]
|
213
|
+
- },
|
214
|
+
```
|
215
|
+
|
216
|
+
11. Create a `babel.config.js` file in the project root and add the following [sample code](https://github.com/shakacode/shakapacker/blob/master/docs/customizing_babel_config.md#react-configuration):
|
217
|
+
```js
|
218
|
+
module.exports = function (api) {
|
219
|
+
const defaultConfigFunc = require('shakapacker/package/babel/preset.js')
|
220
|
+
const resultConfig = defaultConfigFunc(api)
|
221
|
+
const isDevelopmentEnv = api.env('development')
|
222
|
+
const isProductionEnv = api.env('production')
|
223
|
+
const isTestEnv = api.env('test')
|
224
|
+
|
225
|
+
const changesOnDefault = {
|
226
|
+
presets: [
|
227
|
+
[
|
228
|
+
'@babel/preset-react',
|
229
|
+
{
|
230
|
+
development: isDevelopmentEnv || isTestEnv,
|
231
|
+
useBuiltIns: true
|
232
|
+
}
|
233
|
+
]
|
234
|
+
].filter(Boolean),
|
235
|
+
plugins: [
|
236
|
+
isProductionEnv && ['babel-plugin-transform-react-remove-prop-types',
|
237
|
+
{
|
238
|
+
removeImport: true
|
239
|
+
}
|
240
|
+
],
|
241
|
+
process.env.WEBPACK_SERVE && 'react-refresh/babel'
|
242
|
+
].filter(Boolean),
|
243
|
+
}
|
244
|
+
|
245
|
+
resultConfig.presets = [...resultConfig.presets, ...changesOnDefault.presets]
|
246
|
+
resultConfig.plugins = [...resultConfig.plugins, ...changesOnDefault.plugins ]
|
247
|
+
|
248
|
+
return resultConfig
|
249
|
+
}
|
250
|
+
```
|
251
|
+
|
252
|
+
9. Start the Rails server and the webpack-dev-server in separate console windows:
|
253
|
+
```shell
|
254
|
+
rails s
|
255
|
+
./bin/webpacker-dev-server
|
256
|
+
```
|
257
|
+
|
258
|
+
10. Hit: <http://localhost:3000/site/index>
|
259
|
+
|
260
|
+
11. Edit either the React component at `app/javascript/App.js` or the CSS file at `app/javascript/App.css` and observe the HMR goodness.
|
261
|
+
|
262
|
+
Note that HMR will not work if you edit `app/javascript/application.js` and you experience a full refresh with a warning in the console. For more info on this, see here: https://github.com/pmmmwh/react-refresh-webpack-plugin/issues/177
|
data/docs/sprockets.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Sprockets
|
2
|
+
|
3
|
+
### Note for Sprockets usage
|
4
|
+
|
5
|
+
If you are still using Sprockets for some of your assets, you might want to include files from `node_modules` directory in your asset pipeline. This is useful, for example, if you want to reference a stylesheet from a node package in your `.scss` stylesheet.
|
6
|
+
|
7
|
+
In order to enable this, make sure you add `node_modules` to the asset load path by adding the following in an initializer (for example `config/initializers/assets.rb`)
|
8
|
+
```ruby
|
9
|
+
Rails.application.config.assets.paths << Rails.root.join('node_modules')
|
10
|
+
```
|
@@ -2,7 +2,15 @@
|
|
2
2
|
|
3
3
|
default: &default
|
4
4
|
source_path: app/javascript
|
5
|
+
|
6
|
+
# You can have a subdirectory of the source_path, like 'packs' (recommended).
|
7
|
+
# Alternatively, you can use '/' to use the whole source_path directory.
|
5
8
|
source_entry_path: /
|
9
|
+
|
10
|
+
# If nested_entries is true, then we'll pick up subdirectories within the source_entry_path.
|
11
|
+
# You cannot set this option to true if you set source_entry_path to '/'
|
12
|
+
nested_entries: false
|
13
|
+
|
6
14
|
public_root_path: public
|
7
15
|
public_output_path: packs
|
8
16
|
cache_path: tmp/webpacker
|
@@ -26,9 +34,13 @@ default: &default
|
|
26
34
|
# Set to true to enable check for matching versions of shakapacker gem and NPM package - will raise an error if there is a mismatch or wildcard versioning is used
|
27
35
|
ensure_consistent_versioning: false
|
28
36
|
|
37
|
+
# Select whether the compiler will use SHA digest ('digest' option) or most most recent modified timestamp ('mtime') to determine freshness
|
38
|
+
compiler_strategy: digest
|
39
|
+
|
29
40
|
development:
|
30
41
|
<<: *default
|
31
42
|
compile: true
|
43
|
+
compiler_strategy: mtime
|
32
44
|
|
33
45
|
# Reference: https://webpack.js.org/configuration/dev-server/
|
34
46
|
dev_server:
|
@@ -1,6 +1,12 @@
|
|
1
1
|
namespace :webpacker do
|
2
2
|
desc "Support for older Rails versions. Install all JavaScript dependencies as specified via Yarn"
|
3
3
|
task :yarn_install do
|
4
|
+
warn <<~MSG.strip
|
5
|
+
Shakapacker - Automatic installation of yarn packages is deprecated
|
6
|
+
Automatic installation of yarn packages when assets are precompiled is deprecated and will be removed in Shakapacker v7.
|
7
|
+
Please ensure you are installing yarn packages explicitly before the asset compilation.
|
8
|
+
MSG
|
9
|
+
|
4
10
|
valid_node_envs = %w[test development production]
|
5
11
|
node_env = ENV.fetch("NODE_ENV") do
|
6
12
|
valid_node_envs.include?(Rails.env) ? Rails.env : "production"
|
data/lib/tasks/webpacker.rake
CHANGED
@@ -9,7 +9,6 @@ tasks = {
|
|
9
9
|
"webpacker:check_binstubs" => "Verifies that bin/webpacker is present",
|
10
10
|
"webpacker:binstubs" => "Installs Webpacker binstubs in this application",
|
11
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
12
|
}.freeze
|
14
13
|
|
15
14
|
desc "Lists all available tasks in Webpacker"
|
data/lib/tasks/yarn.rake
CHANGED
@@ -5,6 +5,12 @@
|
|
5
5
|
namespace :yarn do
|
6
6
|
desc "Install all JavaScript dependencies as specified via Yarn"
|
7
7
|
task :install do
|
8
|
+
warn <<~MSG.strip
|
9
|
+
Shakapacker - Automatic installation of yarn packages is deprecated
|
10
|
+
Automatic installation of yarn packages when assets are precompiled is deprecated and will be removed in Shakapacker v7.
|
11
|
+
Please ensure you are installing yarn packages explicitly before the asset compilation.
|
12
|
+
MSG
|
13
|
+
|
8
14
|
begin
|
9
15
|
# Install only production deps when for not usual envs.
|
10
16
|
valid_node_envs = %w[test development production]
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Webpacker
|
2
|
+
class BaseStrategy
|
3
|
+
def initialize
|
4
|
+
@config = Webpacker.config
|
5
|
+
end
|
6
|
+
|
7
|
+
def after_compile_hook
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :config
|
14
|
+
|
15
|
+
def default_watched_paths
|
16
|
+
[
|
17
|
+
*config.additional_paths.map { |path| "#{path}{,/**/*}" },
|
18
|
+
"#{config.source_path}{,/**/*}",
|
19
|
+
"yarn.lock", "package.json",
|
20
|
+
"config/webpack{,/**/*}"
|
21
|
+
].freeze
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/webpacker/compiler.rb
CHANGED
@@ -1,17 +1,13 @@
|
|
1
1
|
require "open3"
|
2
|
+
require "webpacker/compiler_strategy"
|
2
3
|
|
3
4
|
class Webpacker::Compiler
|
4
|
-
# Additional paths that test compiler needs to watch
|
5
|
-
# Webpacker::Compiler.watched_paths << 'bower_components'
|
6
|
-
#
|
7
|
-
# Deprecated. Use additional_paths in the YAML configuration instead.
|
8
|
-
cattr_accessor(:watched_paths) { [] }
|
9
|
-
|
10
5
|
# Additional environment variables that the compiler is being run with
|
11
6
|
# Webpacker::Compiler.env['FRONTEND_API_KEY'] = 'your_secret_key'
|
12
7
|
cattr_accessor(:env) { {} }
|
13
8
|
|
14
|
-
delegate :config, :logger, to: :webpacker
|
9
|
+
delegate :config, :logger, :strategy, to: :webpacker
|
10
|
+
delegate :fresh?, :stale?, :after_compile_hook, to: :strategy
|
15
11
|
|
16
12
|
def initialize(webpacker)
|
17
13
|
@webpacker = webpacker
|
@@ -19,50 +15,18 @@ class Webpacker::Compiler
|
|
19
15
|
|
20
16
|
def compile
|
21
17
|
if stale?
|
22
|
-
run_webpack
|
18
|
+
run_webpack.tap do |success|
|
19
|
+
after_compile_hook
|
20
|
+
end
|
23
21
|
else
|
24
22
|
logger.debug "Everything's up-to-date. Nothing to do"
|
25
23
|
true
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
29
|
-
# Returns true if manifest file mtime is newer than the timestamp of the last modified watched file
|
30
|
-
def fresh?
|
31
|
-
manifest_mtime > latest_modified_timestamp
|
32
|
-
end
|
33
|
-
|
34
|
-
# Returns true if manifest file mtime is older than the timestamp of the last modified watched file
|
35
|
-
def stale?
|
36
|
-
!fresh?
|
37
|
-
end
|
38
|
-
|
39
27
|
private
|
40
28
|
attr_reader :webpacker
|
41
29
|
|
42
|
-
def manifest_mtime
|
43
|
-
config.manifest_path.exist? ? File.mtime(config.manifest_path).to_i : 0
|
44
|
-
end
|
45
|
-
|
46
|
-
def latest_modified_timestamp
|
47
|
-
if Rails.env.development?
|
48
|
-
warn <<~MSG.strip
|
49
|
-
Webpacker::Compiler - Slow setup for development
|
50
|
-
|
51
|
-
Prepare JS assets with either:
|
52
|
-
1. Running `bin/webpacker-dev-server`
|
53
|
-
2. Set `compile` to false in webpacker.yml and run `bin/webpacker -w`
|
54
|
-
MSG
|
55
|
-
end
|
56
|
-
|
57
|
-
warn "Webpacker::Compiler.watched_paths has been deprecated. Set additional_paths in webpacker.yml instead." unless watched_paths.empty?
|
58
|
-
root_path = Pathname.new(File.expand_path(config.root_path))
|
59
|
-
expanded_paths = [*default_watched_paths, *watched_paths].map do |path|
|
60
|
-
root_path.join(path)
|
61
|
-
end
|
62
|
-
latest_modified = Dir[*expanded_paths].max_by { |f| File.mtime(f) }
|
63
|
-
File.mtime(latest_modified).to_i
|
64
|
-
end
|
65
|
-
|
66
30
|
def optionalRubyRunner
|
67
31
|
bin_webpack_path = config.root_path.join("bin/webpacker")
|
68
32
|
first_line = File.readlines(bin_webpack_path).first.chomp
|
@@ -93,15 +57,6 @@ class Webpacker::Compiler
|
|
93
57
|
status.success?
|
94
58
|
end
|
95
59
|
|
96
|
-
def default_watched_paths
|
97
|
-
[
|
98
|
-
*config.additional_paths.map { |path| "#{path}{,/**/*}" },
|
99
|
-
"#{config.source_path}{,/**/*}",
|
100
|
-
"yarn.lock", "package.json",
|
101
|
-
"config/webpack{,/**/*}"
|
102
|
-
].freeze
|
103
|
-
end
|
104
|
-
|
105
60
|
def webpack_env
|
106
61
|
return env unless defined?(ActionController::Base)
|
107
62
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "webpacker/mtime_strategy"
|
2
|
+
require "webpacker/digest_strategy"
|
3
|
+
|
4
|
+
module Webpacker
|
5
|
+
class CompilerStrategy
|
6
|
+
def self.from_config
|
7
|
+
strategy_from_config = Webpacker.config.compiler_strategy
|
8
|
+
|
9
|
+
case strategy_from_config
|
10
|
+
when "mtime"
|
11
|
+
Webpacker::MtimeStrategy.new
|
12
|
+
when "digest"
|
13
|
+
Webpacker::DigestStrategy.new
|
14
|
+
else
|
15
|
+
raise "Unknown strategy '#{strategy_from_config}'. " \
|
16
|
+
"Available options are 'mtime' and 'digest'."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -30,7 +30,10 @@ class Webpacker::Configuration
|
|
30
30
|
def webpacker_precompile?
|
31
31
|
# ENV of false takes precedence
|
32
32
|
return false if %w(no false n f).include?(ENV["WEBPACKER_PRECOMPILE"])
|
33
|
-
return %w(yes true t).include?(ENV["WEBPACKER_PRECOMPILE"])
|
33
|
+
return true if %w(yes true t).include?(ENV["WEBPACKER_PRECOMPILE"])
|
34
|
+
|
35
|
+
return false unless config_path.exist?
|
36
|
+
fetch(:webpacker_precompile)
|
34
37
|
end
|
35
38
|
|
36
39
|
def source_path
|
@@ -85,6 +88,10 @@ class Webpacker::Configuration
|
|
85
88
|
fetch(:webpack_compile_output)
|
86
89
|
end
|
87
90
|
|
91
|
+
def compiler_strategy
|
92
|
+
fetch(:compiler_strategy)
|
93
|
+
end
|
94
|
+
|
88
95
|
def fetch(key)
|
89
96
|
data.fetch(key, defaults[key])
|
90
97
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
require "webpacker/base_strategy"
|
3
|
+
|
4
|
+
module Webpacker
|
5
|
+
class DigestStrategy < BaseStrategy
|
6
|
+
# Returns true if all the compiled packs are up to date with the underlying asset files.
|
7
|
+
def fresh?
|
8
|
+
last_compilation_digest&.== watched_files_digest
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns true if the compiled packs are out of date with the underlying asset files.
|
12
|
+
def stale?
|
13
|
+
!fresh?
|
14
|
+
end
|
15
|
+
|
16
|
+
def after_compile_hook
|
17
|
+
# We used to only record the digest on success
|
18
|
+
# However, the output file is still written on error, meaning that the digest should still be updated.
|
19
|
+
# If it's not, you can end up in a situation where a recompile doesn't take place when it should.
|
20
|
+
# See https://github.com/rails/webpacker/issues/2113
|
21
|
+
record_compilation_digest
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def last_compilation_digest
|
27
|
+
compilation_digest_path.read if compilation_digest_path.exist? && config.manifest_path.exist?
|
28
|
+
rescue Errno::ENOENT, Errno::ENOTDIR
|
29
|
+
end
|
30
|
+
|
31
|
+
def watched_files_digest
|
32
|
+
if Rails.env.development?
|
33
|
+
warn <<~MSG.strip
|
34
|
+
Webpacker::Compiler - Slow setup for development
|
35
|
+
Prepare JS assets with either:
|
36
|
+
1. Running `bin/webpacker-dev-server`
|
37
|
+
2. Set `compile` to false in webpacker.yml and run `bin/webpacker -w`
|
38
|
+
MSG
|
39
|
+
end
|
40
|
+
|
41
|
+
root_path = Pathname.new(File.expand_path(config.root_path))
|
42
|
+
expanded_paths = [*default_watched_paths].map do |path|
|
43
|
+
root_path.join(path)
|
44
|
+
end
|
45
|
+
files = Dir[*expanded_paths].reject { |f| File.directory?(f) }
|
46
|
+
file_ids = files.sort.map { |f| "#{File.basename(f)}/#{Digest::SHA1.file(f).hexdigest}" }
|
47
|
+
Digest::SHA1.hexdigest(file_ids.join("/"))
|
48
|
+
end
|
49
|
+
|
50
|
+
def record_compilation_digest
|
51
|
+
config.cache_path.mkpath
|
52
|
+
compilation_digest_path.write(watched_files_digest)
|
53
|
+
end
|
54
|
+
|
55
|
+
def compilation_digest_path
|
56
|
+
config.cache_path.join("last-compilation-digest-#{Webpacker.env}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/webpacker/instance.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "webpacker/base_strategy"
|
2
|
+
|
3
|
+
module Webpacker
|
4
|
+
class MtimeStrategy < BaseStrategy
|
5
|
+
# Returns true if manifest file mtime is newer than the timestamp of the last modified watched file
|
6
|
+
def fresh?
|
7
|
+
manifest_mtime > latest_modified_timestamp
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns true if manifest file mtime is older than the timestamp of the last modified watched file
|
11
|
+
def stale?
|
12
|
+
!fresh?
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def manifest_mtime
|
18
|
+
config.manifest_path.exist? ? File.mtime(config.manifest_path).to_i : 0
|
19
|
+
end
|
20
|
+
|
21
|
+
def latest_modified_timestamp
|
22
|
+
if Rails.env.development?
|
23
|
+
warn <<~MSG.strip
|
24
|
+
Webpacker::Compiler - Slow setup for development
|
25
|
+
|
26
|
+
Prepare JS assets with either:
|
27
|
+
1. Running `bin/webpacker-dev-server`
|
28
|
+
2. Set `compile` to false in webpacker.yml and run `bin/webpacker -w`
|
29
|
+
MSG
|
30
|
+
end
|
31
|
+
|
32
|
+
root_path = Pathname.new(File.expand_path(config.root_path))
|
33
|
+
expanded_paths = [*default_watched_paths].map do |path|
|
34
|
+
root_path.join(path)
|
35
|
+
end
|
36
|
+
latest_modified = Dir[*expanded_paths].max_by { |f| File.mtime(f) }
|
37
|
+
File.mtime(latest_modified).to_i
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/webpacker/version.rb
CHANGED
@@ -3,15 +3,18 @@
|
|
3
3
|
// environment.js expects to find config/webpacker.yml and resolved modules from
|
4
4
|
// the root of a Rails project
|
5
5
|
|
6
|
-
const { chdirTestApp, chdirCwd } = require('../../utils/helpers')
|
6
|
+
const { chdirTestApp, chdirCwd, resetEnv } = require('../../utils/helpers')
|
7
7
|
|
8
8
|
chdirTestApp()
|
9
9
|
|
10
10
|
const { resolve } = require('path')
|
11
|
-
|
11
|
+
|
12
12
|
const baseConfig = require('../base')
|
13
|
+
const config = require("../../config");
|
13
14
|
|
14
15
|
describe('Base config', () => {
|
16
|
+
beforeEach(() => jest.resetModules() && resetEnv())
|
17
|
+
|
15
18
|
afterAll(chdirCwd)
|
16
19
|
|
17
20
|
describe('config', () => {
|
@@ -21,11 +24,33 @@ describe('Base config', () => {
|
|
21
24
|
)
|
22
25
|
})
|
23
26
|
|
24
|
-
test('should return
|
27
|
+
test('should return only 2 entry points with config.nested_entries == false', () => {
|
28
|
+
expect(config.nested_entries).toEqual(false)
|
29
|
+
|
30
|
+
expect(baseConfig.entry.multi_entry.sort()).toEqual([
|
31
|
+
resolve('app', 'packs', 'entrypoints', 'multi_entry.css'),
|
32
|
+
resolve('app', 'packs', 'entrypoints', 'multi_entry.js')
|
33
|
+
])
|
34
|
+
expect(baseConfig.entry['generated/something']).toEqual(undefined)
|
35
|
+
})
|
36
|
+
|
37
|
+
test('should returns top level and nested entry points with config.nested_entries == true', () => {
|
38
|
+
process.env.WEBPACKER_CONFIG = 'config/webpacker_nested_entries.yml'
|
39
|
+
const config = require("../../config");
|
40
|
+
const baseConfig = require('../base')
|
41
|
+
|
42
|
+
expect(config.nested_entries).toEqual(true)
|
43
|
+
|
44
|
+
expect(baseConfig.entry.application).toEqual(
|
45
|
+
resolve('app', 'packs', 'entrypoints', 'application.js')
|
46
|
+
)
|
25
47
|
expect(baseConfig.entry.multi_entry.sort()).toEqual([
|
26
48
|
resolve('app', 'packs', 'entrypoints', 'multi_entry.css'),
|
27
49
|
resolve('app', 'packs', 'entrypoints', 'multi_entry.js')
|
28
50
|
])
|
51
|
+
expect(baseConfig.entry['generated/something']).toEqual(
|
52
|
+
resolve('app', 'packs', 'entrypoints', 'generated', 'something.js')
|
53
|
+
)
|
29
54
|
})
|
30
55
|
|
31
56
|
test('should return output', () => {
|
@@ -36,6 +61,8 @@ describe('Base config', () => {
|
|
36
61
|
})
|
37
62
|
|
38
63
|
test('should return default loader rules for each file in config/loaders', () => {
|
64
|
+
const rules = require('../../rules')
|
65
|
+
|
39
66
|
const defaultRules = Object.keys(rules)
|
40
67
|
const configRules = baseConfig.module.rules
|
41
68
|
|