minipack 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f8946e76b71b48eccb31e56408075341276eddbc8c1d8a3bc75a4c1969f7a327
4
+ data.tar.gz: 26418d01d9e406d37330eae60558ebe3b0a304d90c5fa312ab4b3b2924075193
5
+ SHA512:
6
+ metadata.gz: b3bbac2192c9098b951b16f5f48541b0aeb9a9fdd1aa16a26016fcd20c4fe02134e062277e474f0156348395966e56932d7503c0894a78aa38cf842e191b25e3
7
+ data.tar.gz: c7ea1f6a21823b101e91c11266819141824af89d819ea73bb63e9c52c0f8ca5c9406a7bfa880b255144dbd5cc4cfc2ccd24ffe5394869507de4c16341a15ee36
data/CHANGELOG.md ADDED
@@ -0,0 +1,46 @@
1
+ # 0.3.0 / 2019-06-01
2
+
3
+ The project was renamed to Minipack from WebpackManifest since v0.3.0. Please refer to [the migration guide](docs/migrate_from_webpack_manifest.md') from WebpackManifest.
4
+
5
+ ## Breaking changes
6
+
7
+ * Rename the project as Minipack.
8
+
9
+ ## Enhancements:
10
+
11
+ * pre-build in test (#11)
12
+ * Enable to set default value through config_attr (#16)
13
+
14
+ # 0.2.4 / 2019-03-25
15
+
16
+ ## Enhancements:
17
+
18
+ * Added `javascript_bundles_with_chunks_tag` and `stylesheet_bundles_with_chunks_tag` helpers, which creates html tags for a pack and all the dependent chunks, when using splitChunks. (#7)
19
+
20
+ # 0.2.3 / 2018-11-18
21
+
22
+ * Support passing symbols to helper methods (#5) by @jmortlock
23
+
24
+ # 0.2.2 / 2018-11-05
25
+
26
+ ## Enhancements
27
+
28
+ * Support reading manifest.json from an uri (#4)
29
+
30
+ # 0.2.1 / 2018-10-18
31
+
32
+ * Improve exceptional case handling
33
+
34
+ # 0.2.0 / 2018-10-18
35
+
36
+ ## Enhancements
37
+
38
+ * Multiple manifest files support
39
+
40
+ ## Breaking changes
41
+
42
+ * Changed internal and public APIs
43
+
44
+ # 0.1.0 / 2018-10-17
45
+
46
+ Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Nobuhiro Nikushi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,312 @@
1
+ # Minipack [![Build Status](https://travis-ci.org/nikushi/minipack.svg?branch=master)](https://travis-ci.org/nikushi/minipack) [![Gem Version](https://badge.fury.io/rb/minipack.svg)](https://badge.fury.io/rb/minipack)
2
+
3
+ Minipack, a gem for minimalists, which can integrates Rails with [webpack](https://webpack.js.org/). It is an alternative to [Webpacker](https://github.com/rails/).
4
+
5
+ Minipack provides view helpers through a manifest, which resolve paths of assets build by a webpack configured by you as you like.
6
+
7
+ **Note:** Before Minipack v0.3.0, it was called WebpackManifest. Please refer to [the migration guide](docs/migrate_from_webpack_manifest.md') from WebpackManifest.
8
+
9
+ ## Features
10
+
11
+ * Rails view helpers to resolve paths to assets which are built by webpack according to a manifest file.
12
+ * Multiple manifest files support
13
+ * Pre-build assets before running tests
14
+
15
+ ## Pre configuration
16
+
17
+ Unlike Webpacker, Minipack itself does not offer generating webpack configuration, DSL for setup, or scaffolding. So first you need to set up webpack in your favorite way.
18
+
19
+ Also, Minipack expects that webpack emits a manifest file. So please install [webpack-manifest-plugin](https://www.npmjs.com/package/webpack-manifest-plugin) and set it up accordingly.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'minipack'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install minipack
36
+
37
+ ## Configuration
38
+
39
+ After installed, configure your Rails app below as a new file `config/initializers/minipack.rb`.
40
+
41
+ ```rb
42
+ Minipack.configuration do |c|
43
+ # By default c.cache is set to `false`, which means an application always parses a
44
+ # manifest.json. In development, you should set cache false usually.
45
+ # Instead, setting it `true` which caches the manifest in memory is recommended basically.
46
+ c.cache = !Rails.env.development?
47
+
48
+ # Register a path to a manifest file here. Right now you have to specify an absolute path.
49
+ c.manifest = Rails.root.join('public', 'assets', 'manifest.json')
50
+
51
+ # The base directory for the frontend system. By default, it will be
52
+ # `Rails.root`.
53
+ # c.base_path = Rails.root
54
+ #
55
+ # Suppose you want to change the root directory for the frontend system such as `frontend`.
56
+ # Note that a base_path can be a relative path from `Rails.root`.
57
+ # c.base_path = 'frontend'
58
+
59
+ # You can invokes a command to build assets in node from Minipack.
60
+ #
61
+ # When running tests, the lazy compilation is cached until a cache key, based
62
+ # on file checksum under your tracked paths, is changed. You can configure
63
+ # which paths are tracked by adding new paths to `build_cache_key`. Each path
64
+ # can be a relative path from the `base_dir`.
65
+ #
66
+ # The value will be as follows by default:
67
+ # c.build_cache_key = [
68
+ # 'package.json', 'package-lock.json', 'yarn.lock', 'webpack.config.js',
69
+ # 'webpackfile.js', 'config/webpack.config.js', 'config/webpackfile.js',
70
+ # 'app/javascripts/**/*',
71
+ # ]
72
+ #
73
+ # You can override it.
74
+ # c.build_cache_key = ['package.json', 'package-lock.json', 'config/webpack.config.js', 'src/**/*']
75
+ #
76
+ # Or you can add files in addition to the defaults:
77
+ # c.build_cache_key << 'src/**/*'
78
+
79
+ # A command to to build assets. The command you specify is executed under the `base_dir`.
80
+ # c.build_command = 'node_modules/.bin/webpack'
81
+ #
82
+ # You may want to customize it with options:
83
+ # c.build_command = 'node_modules/.bin/webpack --config config/webpack.config.js --mode production'
84
+ #
85
+ # You are also able to specify npm run script.
86
+ # c.build_command = 'npm run build'
87
+
88
+ # A full package installation command, with it's arguments and options. The command is executed under the `base_path`.
89
+ # c.pkg_install_command = 'npm install'
90
+ #
91
+ # If you prefer `yarn`:
92
+ # c.pkg_install_command = 'yarn install'
93
+ end
94
+ ```
95
+
96
+ ## Usage
97
+
98
+ ### Rails view helpers
99
+
100
+ #### `asset_bundle_path`
101
+
102
+ This is a wrapper of [asset_path](https://api.rubyonrails.org/classes/ActionView/Helpers/AssetUrlHelper.html#method-i-asset_path). You can set any options of `asset_path`.
103
+
104
+ A given entry point name is resolved according to definition of manifest.
105
+
106
+ ```ruby
107
+ asset_bundle_path 'calendar.css'
108
+ # => "/assets/web/pack/calendar-1016838bab065ae1e122.css"
109
+
110
+ asset_bundle_path 'icon/favicon.ico'
111
+ # => "/assets/web/pack/icon/favicon-1016838bab065ae1e122.ico"
112
+ ```
113
+
114
+ #### `javascript_bundle_tag`
115
+
116
+ This is a wrapper of [javascript_include_tag](https://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#method-i-javascript_include_tag). You can set any options of `javascript_include_tag`.
117
+
118
+ Given entry point name is resolved according to definition of manifest.
119
+
120
+ ```ruby
121
+ javascript_bundle_tag 'calendar', 'data-turbolinks-track': 'reload'
122
+ # => <script src="/assets/web/pack/calendar-1016838bab065ae1e314.js"
123
+ # data-turbolinks-track="reload"></script>
124
+
125
+ javascript_bundle_tag 'orders/app'
126
+ # => <script src="/assets/web/pack/orders/app-1016838bab065ae1e314.js"></script>
127
+ ```
128
+
129
+ #### `stylesheet_bundle_tag`
130
+
131
+ This is a wrapper of [stylesheet_link_tag](https://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#method-i-stylesheet_link_tag). You can set any options of `stylesheet_link_tag`.
132
+
133
+ A given entry point name is resolved according to definition of manifest.
134
+
135
+ ```ruby
136
+ stylesheet_bundle_tag 'calendar', 'data-turbolinks-track': 'reload'
137
+ # => <link rel="stylesheet" media="screen"
138
+ # href="/assets/web/pack/calendar-1016838bab065ae1e122.css"
139
+ # data-turbolinks-track="reload" />
140
+
141
+ stylesheet_bundle_tag 'orders/style'
142
+ # => <link rel="stylesheet" media="screen"
143
+ # href="/assets/web/pack/orders/style-1016838bab065ae1e122.css" />
144
+ ```
145
+
146
+ #### `image_bundle_tag`
147
+
148
+ This is a wrapper of [image_tag](https://api.rubyonrails.org/classes/ActionView/Helpers/AssetTagHelper.html#method-i-image_tag). You can set any options of `image_tag`.
149
+
150
+ A given entry point name is resolved according to definition of manifest.
151
+
152
+ ```ruby
153
+ image_bundle_tag 'icon.png'
154
+ # => <img src="/assets/pack/icon-1016838bab065ae1e314.png" />
155
+
156
+ image_bundle_tag "icon.png", size: "16x10", alt: "Edit Entry"
157
+ # => <img src="/assets/pack/icon-1016838bab065ae1e314.png" width="16"
158
+ height="10" alt="Edit Entry" />
159
+ ```
160
+
161
+
162
+ #### `javascript_bundles_with_chunks_tag` and `stylesheet_bundles_with_chunks_tag`
163
+
164
+ **Experimental** These are the helpers, which are similar to Webpacker, to support `splitChunks` feature introduced since Webpack 4.
165
+
166
+ For the full configuration options of splitChunks, see the Webpack's [documentation](https://webpack.js.org/plugins/split-chunks-plugin/).
167
+
168
+ Then use the `javascript_bundles_with_chunks_tag` and `stylesheet_bundles_with_chunks_tag` helpers to include all
169
+ the transpiled packs with the chunks in your view, which creates html tags for all the chunks.
170
+
171
+ ```
172
+ <%= javascript_bundles_with_chunks_tag 'calendar', 'map', 'data-turbolinks-track': 'reload' %>
173
+
174
+ <!-- Creates the following: -->
175
+ <script src="/packs/vendor-16838bab065ae1e314.js" data-turbolinks-track="reload"></script>
176
+ <script src="/packs/calendar~runtime-16838bab065ae1e314.js" data-turbolinks-track="reload"></script>
177
+ <script src="/packs/calendar-1016838bab065ae1e314.js" data-turbolinks-track="reload"></script>
178
+ <script src="/packs/map~runtime-16838bab065ae1e314.js" data-turbolinks-track="reload"></script>
179
+ <script src="/packs/map-16838bab065ae1e314.js" data-turbolinks-track="reload"></script>
180
+ ```
181
+
182
+ **Important:** Pass all your pack names to the helper otherwise you will get duplicated chunks on the page.
183
+
184
+ ```
185
+ <%# DO %>
186
+ <%= javascript_bundles_with_chunks_tag 'calendar', 'map' %>
187
+
188
+ <%# DON'T %>
189
+ <%= javascript_bundles_with_chunks_tag 'calendar' %>
190
+ <%= javascript_bundles_with_chunks_tag 'map' %>
191
+ ```
192
+
193
+ **Important:** Also, these helpers do not work with `webpack-manifest-plugin` npm because it has no support to generate a manifest with a set of of chunk entries https://github.com/danethurber/webpack-manifest-plugin/issues/133. Instead, [webpack-assets-manifest](https://github.com/webdeveric/webpack-assets-manifest) npm supports. Please change the plugin for manifest file generation if you wish to enable `splitChunks` feature.
194
+
195
+ ```
196
+ const WebpackAssetsManifest = require('webpack-assets-manifest');
197
+
198
+ module.exports = {
199
+ // ...
200
+ plugins: [
201
+ new WebpackAssetsManifest({
202
+ entrypoints: true, // Please set this as true
203
+ })
204
+ ],
205
+ // ...
206
+ }
207
+ ```
208
+
209
+ ## Advanced Configuration
210
+
211
+ ### Hot Module Replacement in development
212
+
213
+ Optionally you can integrate the gem with [webpack-dev-server](https://github.com/webpack/webpack-dev-server) to enable live reloading by setting an manifest url served by webpack-dev-server, instead of a local file path. This should be used for development only.
214
+
215
+ Note that Minipack itself does not launches webpack-dev-server, so it must be started along with Rails server by yourself.
216
+
217
+ ```rb
218
+ Minipack.configuration do |c|
219
+ c.cache = !Rails.env.development?
220
+
221
+ c.manifest = if Rails.env.development?
222
+ 'http://localhost:8080/packs/manifest.json'
223
+ else
224
+ Rails.root.join('public', 'assets', 'manifest.json')
225
+ end
226
+ end
227
+ ```
228
+
229
+ ### Multiple manifest files support
230
+
231
+ This is optional. You can register multiple manifest files for the view helpers. This feature must be useful if your Rails project serves for several sites, then asset bundling process is isolated every site.
232
+
233
+ For example, your project serve for two sites, `shop` and `admin` from each individual manifest file. You can register each as
234
+
235
+ ```rb
236
+ # In config/initializers/minipack.rb
237
+
238
+ Minipack.configuration do |c|
239
+ c.cache = !Rails.env.development?
240
+
241
+ # In order for Raild to handle multiple manifests, you must call `c.add` instead
242
+ # of `c.manifest=`. Note that the first registered one(e.g. `shop` in this
243
+ # example) is recognized as a default manifest.
244
+ c.add :shop do |co|
245
+ co.manifest = Rails.root.join('public', 'assets', 'manifest-shop.json')
246
+ co.base_path = Rails.root.join('frontend/shop')
247
+ end
248
+
249
+ c.add :admin do |co|
250
+ co.manifest = Rails.root.join('public', 'assets', 'manifest-admin.json')
251
+ co.base_path = Rails.root.join('frontend/admin')
252
+ # You can customize all configurable parameters per site.
253
+ co.build_cache_key << 'javascripts/**/*'
254
+ co.build_command = 'yarn install'
255
+ end
256
+ end
257
+ ```
258
+
259
+ Then you can resolve a path with view helpers by passing `manifest:` option.
260
+
261
+ ```
262
+ # This resolves a path by shop's manifest json.
263
+ javascript_bundle_tag('item_group_editor', manifest: :shop)
264
+
265
+ # This resolves a path by admin's manifest json.
266
+ asset_bundle_tag('favicon.ico', manifest: :admin)
267
+
268
+ # This resolves a path by shop's manifest json implicitly because the first one is marked as a default.
269
+ javascript_bundle_tag('item_group_editor')
270
+ ```
271
+
272
+ ## Building assets before running tests
273
+
274
+ To pre-build assets before runngin tests, add the following line (typically to your spec_helper.rb file):
275
+
276
+ ```rb
277
+ require 'minipack/rspec'
278
+ ```
279
+
280
+ ## TODO
281
+
282
+ * Provides configuration generator for Rails initializers
283
+
284
+ ## Acknowledgement
285
+
286
+ Special thanks to [@f_subal](https://twitter.com/f_subal) and his awesome blog [post](https://inside.pixiv.blog/subal/4615)(japanese).
287
+
288
+ ## Alternatives
289
+
290
+ * [ed-mare/minipack_plugin](https://github.com/ed-mare/minipack_plugin)
291
+
292
+ ## Development
293
+
294
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
295
+
296
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
297
+
298
+ **v0.3.x-trunk**
299
+
300
+ `v0.3.x-trunk` is the current active development branch. Please send pull requests to v0.3.x-trunk if you want any features.
301
+
302
+ **v0.2.x-trunk**
303
+
304
+ v0.2.x-trunk is made from the latest released version v0.2.4. I will not intend to actively maintain v0.2.x anymore, except security fixes or bug fixes.
305
+
306
+ ## Contributing
307
+
308
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nikushi/minipack.
309
+
310
+ ## License
311
+
312
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'open3'
5
+
6
+ module Minipack
7
+ class CommandRunner
8
+ class UnsuccessfulError < StandardError; end
9
+
10
+ def initialize(env, command, chdir: '.', logger: nil, watcher: nil)
11
+ @env = env
12
+ @command = command
13
+ @chdir = chdir
14
+ @logger = logger || Logger.new(nil)
15
+ @watcher = watcher
16
+ end
17
+
18
+ def run
19
+ run!
20
+ rescue UnsuccessfulError
21
+ false
22
+ end
23
+
24
+ def run!
25
+ @logger.info "Start executing #{@command}, within #{@chdir}"
26
+
27
+ return run_command if @watcher.nil?
28
+
29
+ if @watcher.stale?
30
+ run_command.tap do |success|
31
+ @watcher.record_digest if success
32
+ end
33
+ else
34
+ @logger.info 'Skipped because no file changes'
35
+ true
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ def run_command
42
+ stdout, stderr, status = Open3.capture3(
43
+ @env,
44
+ @command,
45
+ chdir: @chdir,
46
+ )
47
+
48
+ if status.success?
49
+ @logger.info "Executed successfully"
50
+ @logger.error "#{stderr}" unless stderr.empty?
51
+ else
52
+ @logger.error "Failed to execute:\n#{stderr}"
53
+ end
54
+
55
+ status.success? || raise(UnsuccessfulError, "Failed to execute #{@command}, exit:#{status.exitstatus}, stdout:#{stdout}, stderr:#{stderr}")
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minipack
4
+ module Commands
5
+ class Base
6
+ class << self
7
+ def call(*args)
8
+ new(*args).call
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minipack
4
+ module Commands
5
+ class Build < Base
6
+ def initialize(logger: nil)
7
+ @logger = logger
8
+ end
9
+
10
+ def call
11
+ Minipack.configuration.leaves.each do |c|
12
+ # Note: someone wants pre_build hook?
13
+ build(c)
14
+ # Note: someone wants post_build hook?
15
+ end
16
+ true
17
+ end
18
+
19
+ private
20
+
21
+ def build(c)
22
+ watcher = FileChangeWatcher.new(c.resolved_build_cache_key, File.join(c.cache_path, "last-build-digest-#{c.id}-#{::Rails.env}"))
23
+ CommandRunner.new(
24
+ {},
25
+ c.build_command,
26
+ chdir: c.resolved_base_path,
27
+ logger: @logger,
28
+ watcher: watcher,
29
+ ).run!
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minipack
4
+ module Commands
5
+ class PkgInstall < Base
6
+ PKG_INSTALL_CACHE_KEY = ['package.json', 'package-lock.json', 'yarn.lock'].freeze
7
+
8
+ def initialize(logger: nil)
9
+ @logger = logger
10
+ end
11
+
12
+ def call
13
+ Minipack.configuration.leaves.each do |c|
14
+ # Note: someone wants pre_pkg_install hook?
15
+ pkg_install(c)
16
+ # Note: someone wants post_pkg_install hook?
17
+ end
18
+ true
19
+ end
20
+
21
+ private
22
+
23
+ def pkg_install(c)
24
+ pkg_install_cache_key = PKG_INSTALL_CACHE_KEY.map { |f| File.expand_path(f, c.resolved_base_path) }
25
+ watcher = FileChangeWatcher.new(pkg_install_cache_key, File.join(c.cache_path, "last-installation-digest-#{c.id}-#{::Rails.env}"))
26
+ CommandRunner.new(
27
+ {},
28
+ c.pkg_install_command,
29
+ chdir: c.resolved_base_path,
30
+ logger: @logger,
31
+ watcher: watcher,
32
+ ).run!
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minipack
4
+ # 1-level or 2-levels configuration system. With the typical single site usecase,
5
+ # only the root instance exists as a singleton. If you manage more then one site,
6
+ # each configuration is stored at the 2nd level of the configuration tree.
7
+ class Configuration
8
+ class Collection
9
+ include Enumerable
10
+ class NotFoundError < StandardError; end
11
+
12
+ def initialize(configs = [])
13
+ @configs = configs.map(&:id).zip(configs).to_h
14
+ end
15
+
16
+ def find(id)
17
+ @configs[id] || raise(NotFoundError, "collection not found by #{id}")
18
+ end
19
+
20
+ def each
21
+ @configs.values.each { |c| yield c }
22
+ end
23
+ end
24
+
25
+ class Error < StandardError; end
26
+
27
+ ROOT_DEFAULT_ID = :''
28
+ BUILD_CACHE_KEY_DEFAULT = [
29
+ 'package.json',
30
+ 'package-lock.json',
31
+ 'yarn.lock',
32
+ 'webpack.config.js',
33
+ 'webpackfile.js',
34
+ 'config/webpack.config.js',
35
+ 'config/webpackfile.js',
36
+ 'app/javascripts/**/*',
37
+ ].freeze
38
+
39
+ class << self
40
+ def config_attr(prop, default: nil)
41
+ define_method(prop) do
42
+ @config.fetch(prop) do
43
+ @parent ? @parent.public_send(prop) : default
44
+ end
45
+ end
46
+
47
+ define_method("#{prop}=".to_sym) do |v|
48
+ @config[prop] = v
49
+ end
50
+ end
51
+ end
52
+
53
+ # Private
54
+ config_attr :root_path
55
+ config_attr :id, default: ROOT_DEFAULT_ID
56
+
57
+ config_attr :cache, default: false
58
+
59
+ # The base directory of the frontend.
60
+ config_attr :base_path
61
+
62
+ config_attr :manifest
63
+
64
+ # The lazy compilation is cached until a file is change under the tracked paths.
65
+ config_attr :build_cache_key, default: BUILD_CACHE_KEY_DEFAULT.dup
66
+
67
+ # Let me leave this line for remember the indea of pre build hooks
68
+ # config_attr :pre_build_hooks, default: []
69
+
70
+ # The command for bundling assets
71
+ config_attr :build_command, default: 'node_modules/.bin/webpack'
72
+
73
+ # Let me leave this line to remember the indea of post build hooks
74
+ # config_attr :post_build_hooks, default: []
75
+
76
+ # Let me leave this line to remember the indea of pre pkg install hooks
77
+ # config_attr :pre_pkg_install_hooks, default: []
78
+
79
+ # The command for installation of npm packages
80
+ config_attr :pkg_install_command, default: 'npm install'
81
+
82
+ # Let me leave this line for remember the indea of post pkg install hooks
83
+ # config_attr :post_install_hooks, default: []
84
+
85
+ # Initializes a new instance of Configuration class.
86
+ #
87
+ # @param [Configuration,nil] parent refenrece to the parent configuration instance.
88
+ def initialize(parent = nil)
89
+ @parent = parent
90
+ # Only a root instance can have children, which are sub configurations each site.
91
+ @children = {}
92
+ @config = {}
93
+ end
94
+
95
+ # Register a sub configuration with a site name, with a manifest file
96
+ # optionally. You can configure per site.
97
+ #
98
+ # @param [Symbol] id uniq name of the site
99
+ # @param [String] path path of the manifest file
100
+ # @yieldparam [Configuration] config a sub configuration instance is sent to the block
101
+ def add(id, path = nil)
102
+ raise Error, 'Defining a sub configuration under a sub is not allowed' if leaf?
103
+
104
+ id = id.to_sym
105
+ config = self.class.new(self)
106
+ config.id = id
107
+ config.manifest = path unless path.nil?
108
+
109
+ # Link the root to the child
110
+ @children[id] = config
111
+
112
+ # The sub configuration can be configured within a block
113
+ yield config if block_given?
114
+
115
+ config
116
+ end
117
+
118
+ def children
119
+ Collection.new(@children.values)
120
+ end
121
+
122
+ # Return scoped leaf nodes in self and children. This method is useful
123
+ # to get the concrete(enabled, or active) configuration instances.
124
+ # Each leaf inherit parameters from parent, so leaves always become
125
+ # active.
126
+ def leaves
127
+ col = @children.empty? ? [self] : @children.values
128
+ Collection.new(col)
129
+ end
130
+
131
+ # TODO: This will be moved to Minipack.manifests in the future.
132
+ def manifests
133
+ raise Error, 'Calling #manifests is only allowed from a root' unless root?
134
+
135
+ repo = ManifestRepository.new
136
+ # Determine if a single manifest mode or multiple manifests(multiple site) mode
137
+ targets = @children.empty? ? [self] : @children.values
138
+ targets.each do |config|
139
+ repo.add(config.id, config.manifest, cache: config.cache)
140
+ end
141
+ repo
142
+ end
143
+
144
+ # Resolve base_path as an absolute path
145
+ #
146
+ # @return [String]
147
+ def resolved_base_path
148
+ File.expand_path(base_path || '.', root_path)
149
+ end
150
+
151
+ # Resolve build_cache_key as absolute paths
152
+ #
153
+ # @return [Array<String>]
154
+ def resolved_build_cache_key
155
+ base = resolved_base_path
156
+ build_cache_key.map { |path| File.expand_path(path, base) }
157
+ end
158
+
159
+ # @return [String]
160
+ def cache_path
161
+ File.join(root_path, 'tmp', 'cache', 'minipack')
162
+ end
163
+
164
+ private
165
+
166
+ def root?
167
+ @parent.nil?
168
+ end
169
+
170
+ def leaf?
171
+ !root?
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest/sha1"
4
+ require 'pathname'
5
+
6
+ module Minipack
7
+ class FileChangeWatcher
8
+ # @param [Array<Pathname>] watched_paths
9
+ # @param [Pathname] digest_store_path
10
+ def initialize(watched_paths, digest_store_path)
11
+ @watched_paths = watched_paths
12
+ @digest_store_path = Pathname.new(digest_store_path)
13
+ end
14
+
15
+ # Returns true if all watched files are up to date
16
+ def fresh?
17
+ watched_files_digest == last_stored_digest
18
+ end
19
+
20
+ # Returns true if the watched files are out of date
21
+ def stale?
22
+ !fresh?
23
+ end
24
+
25
+ def record_digest
26
+ @digest_store_path.dirname.mkpath
27
+ @digest_store_path.write(watched_files_digest)
28
+ end
29
+
30
+ private
31
+
32
+ def last_stored_digest
33
+ @digest_store_path.read if @digest_store_path.exist?
34
+ rescue Errno::ENOENT, Errno::ENOTDIR
35
+ end
36
+
37
+ def watched_files_digest
38
+ files = Dir[*@watched_paths].reject { |f| File.directory?(f) }
39
+ file_ids = files.sort.map { |f| "#{File.basename(f)}/#{Digest::SHA1.file(f).hexdigest}" }
40
+ Digest::SHA1.hexdigest(file_ids.join("/"))
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_view'
4
+
5
+ module Minipack::Helper
6
+ # Example:
7
+ #
8
+ # <%= asset_bundle_path 'calendar.css' %> # => "/assets/web/pack/calendar-1016838bab065ae1e122.css"
9
+ # <%= asset_bundle_path 'icon/favicon.ico' %> # => "/assets/web/pack/icon/favicon-1016838bab065ae1e122.ico"
10
+ def asset_bundle_path(name, manifest: nil, **options)
11
+ manifest = get_manifest_by_key(manifest)
12
+ asset_path(manifest.lookup!(name.to_s), **options)
13
+ end
14
+
15
+ # Example:
16
+ #
17
+ # <%= javascript_bundle_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
18
+ # <script src="/assets/web/pack/calendar-1016838bab065ae1e314.js" data-turbolinks-track="reload"></script>
19
+ #
20
+ # <%= javascript_bundle_tag 'orders/app' %> # =>
21
+ # <script src="/assets/web/pack/orders/app-1016838bab065ae1e314.js"></script>
22
+ def javascript_bundle_tag(*names, manifest: nil, **options)
23
+ javascript_include_tag(*sources_from_manifest(names, 'js', key: manifest), **options)
24
+ end
25
+
26
+ # Creates script tags that references the js chunks from entrypoints when using split chunks API.
27
+ # See: https://webpack.js.org/plugins/split-chunks-plugin/
28
+ # Example:
29
+ #
30
+ # <%= javascript_bundles_with_chunks_tag 'calendar', 'map', 'data-turbolinks-track': 'reload' %> # =>
31
+ # <script src="/packs/vendor-16838bab065ae1e314.chunk.js" data-turbolinks-track="reload"></script>
32
+ # <script src="/packs/calendar~runtime-16838bab065ae1e314.chunk.js" data-turbolinks-track="reload"></script>
33
+ # <script src="/packs/calendar-1016838bab065ae1e314.chunk.js" data-turbolinks-track="reload"></script>
34
+ # <script src="/packs/map~runtime-16838bab065ae1e314.chunk.js" data-turbolinks-track="reload"></script>
35
+ # <script src="/packs/map-16838bab065ae1e314.chunk.js" data-turbolinks-track="reload"></script>
36
+ # DO:
37
+ # <%= javascript_bundles_with_chunks_tag 'calendar', 'map' %>
38
+ # DON'T:
39
+ # <%= javascript_bundles_with_chunks_tag 'calendar' %>
40
+ # <%= javascript_bundles_with_chunks_tag 'map' %>
41
+ def javascript_bundles_with_chunks_tag(*names, manifest: nil, **options)
42
+ javascript_include_tag(*sources_from_manifest_entrypoints(names, 'js', key: manifest), **options)
43
+ end
44
+
45
+ # Examples:
46
+ #
47
+ # <%= stylesheet_bundle_tag 'calendar', 'data-turbolinks-track': 'reload' %> # =>
48
+ # <link rel="stylesheet" media="screen"
49
+ # href="/assets/web/pack/calendar-1016838bab065ae1e122.css" data-turbolinks-track="reload" />
50
+ #
51
+ # <%= stylesheet_bundle_tag 'orders/style' %> # =>
52
+ # <link rel="stylesheet" media="screen"
53
+ # href="/assets/web/pack/orders/style-1016838bab065ae1e122.css" />
54
+ def stylesheet_bundle_tag(*names, manifest: nil, **options)
55
+ stylesheet_link_tag(*sources_from_manifest(names, 'css', key: manifest), **options)
56
+ end
57
+
58
+ # Creates link tags that references the css chunks from entrypoints when using split chunks API.
59
+ # See: https://webpack.js.org/plugins/split-chunks-plugin/
60
+ # Example:
61
+ #
62
+ # <%= stylesheet_bundles_with_chunks_tag 'calendar', 'map' %> # =>
63
+ # <link rel="stylesheet" media="screen" href="/packs/3-8c7ce31a.chunk.css" />
64
+ # <link rel="stylesheet" media="screen" href="/packs/calendar-8c7ce31a.chunk.css" />
65
+ # <link rel="stylesheet" media="screen" href="/packs/map-8c7ce31a.chunk.css" />
66
+ # DO:
67
+ # <%= stylesheet_bundles_with_chunks_tag 'calendar', 'map' %>
68
+ # DON'T:
69
+ # <%= stylesheet_bundles_with_chunks_tag 'calendar' %>
70
+ # <%= stylesheet_bundles_with_chunks_tag 'map' %>
71
+ def stylesheet_bundles_with_chunks_tag(*names, manifest: nil, **options)
72
+ stylesheet_link_tag(*sources_from_manifest_entrypoints(names, 'css', key: manifest), **options)
73
+ end
74
+
75
+ # Examples:
76
+ #
77
+ # <%= image_bundle_tag 'icon.png'
78
+ # <img src="/assets/pack/icon-1016838bab065ae1e314.png" />
79
+ #
80
+ # <%= image_bundle_tag "icon.png", size: "16x10", alt: "Edit Entry"
81
+ # <img src="/assets/pack/icon-1016838bab065ae1e314.png" width="16" height="10" alt="Edit Entry" />
82
+ def image_bundle_tag(name, manifest: nil, **options)
83
+ manifest = get_manifest_by_key(manifest)
84
+ image_tag(manifest.lookup!(name.to_s), **options)
85
+ end
86
+
87
+ private
88
+
89
+ def sources_from_manifest(names, ext, key: nil)
90
+ manifest = get_manifest_by_key(key)
91
+ names.map { |name| manifest.lookup!(name.to_s + '.' + ext) }
92
+ end
93
+
94
+ def sources_from_manifest_entrypoints(names, type, key: nil)
95
+ manifest = get_manifest_by_key(key)
96
+ names.map { |name| manifest.lookup_pack_with_chunks!(name, type: type) }.flatten.uniq
97
+ end
98
+
99
+ def get_manifest_by_key(key = nil)
100
+ repository = Minipack.configuration.manifests
101
+ key.nil? ? repository.default : repository.get(key)
102
+ end
103
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open-uri'
5
+ require 'uri'
6
+
7
+ module Minipack
8
+ class Manifest
9
+ class MissingEntryError < StandardError; end
10
+ class FileNotFoundError < StandardError; end
11
+
12
+ attr_reader :path
13
+ attr_writer :cache
14
+
15
+ def initialize(path, cache: false)
16
+ @path = path.to_s
17
+ @cache = cache
18
+ end
19
+
20
+ def lookup_pack_with_chunks!(name, type: nil)
21
+ manifest_pack_type = manifest_type(name, type)
22
+ manifest_pack_name = manifest_name(name, manifest_pack_type)
23
+ find('entrypoints')&.dig(manifest_pack_name, manifest_pack_type) || handle_missing_entry(name)
24
+ end
25
+
26
+ def lookup!(name)
27
+ find(name) || handle_missing_entry(name)
28
+ end
29
+
30
+ def find(name)
31
+ data[name.to_s]
32
+ end
33
+
34
+ def assets
35
+ data.values
36
+ end
37
+
38
+ def cache_enabled?
39
+ @cache
40
+ end
41
+
42
+ private
43
+
44
+ def data
45
+ if cache_enabled?
46
+ @data ||= load_data
47
+ else
48
+ load_data
49
+ end
50
+ end
51
+
52
+ def load_data
53
+ u = URI.parse(@path)
54
+ data = nil
55
+ if u.scheme == 'file' || u.path == @path # file path
56
+ raise(FileNotFoundError, "#{@path}: no such manifest found") unless File.exist?(@path)
57
+ data = File.read(@path)
58
+ else
59
+ # http url
60
+ data = u.read
61
+ end
62
+ JSON.parse(data)
63
+ end
64
+
65
+ # The `manifest_name` method strips of the file extension of the name, because in the
66
+ # manifest hash the entrypoints are defined by their pack name without the extension.
67
+ # When the user provides a name with a file extension, we want to try to strip it off.
68
+ def manifest_name(name, pack_type)
69
+ return name if File.extname(name.to_s).empty?
70
+ File.basename(name, '.' + pack_type)
71
+ end
72
+
73
+ def manifest_type(name, pack_type)
74
+ return File.extname(name)[1..-1] if pack_type.nil?
75
+ pack_type.to_s
76
+ end
77
+
78
+ def handle_missing_entry(name)
79
+ raise MissingEntryError, <<~MSG
80
+ Can not find #{name} in #{@path}.
81
+ Your manifest contains:
82
+ #{JSON.pretty_generate(@data)}
83
+ MSG
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minipack
4
+ class ManifestRepository
5
+ class NotFoundError < StandardError; end
6
+
7
+ attr_accessor :default
8
+
9
+ def initialize
10
+ @manifests = {}
11
+ @default = nil # a pointer to a default manifest
12
+ end
13
+
14
+ def all_manifests
15
+ @manifests.values
16
+ end
17
+
18
+ # @private
19
+ def add(key, path, **options)
20
+ manifest = Minipack::Manifest.new(path, options)
21
+ # Mark a first one as a default
22
+ @default = manifest if @manifests.empty?
23
+ @manifests[key.to_sym] = manifest
24
+ end
25
+
26
+ def get(key)
27
+ @manifests[key.to_sym] || raise(NotFoundError, "manifest associated with #{key} not found")
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails'
4
+
5
+ module Minipack
6
+ class Railtie < ::Rails::Railtie
7
+ initializer "minipack.set_defaults" do
8
+ Minipack.configuration.root_path = ::Rails.root.to_s
9
+ Minipack.configuration.base_path = ::Rails.root.to_s
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.configure do |c|
4
+ c.before :suite do
5
+ logger = Logger.new(STDOUT).tap do |l|
6
+ l.formatter = proc { |severity, datetime, progname, msg| "#{msg}\n" }
7
+ end
8
+ Minipack::Commands::PkgInstall.call(logger: logger)
9
+ Minipack::Commands::Build.call(logger: logger)
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Minipack
4
+ VERSION = "0.3.0"
5
+ end
data/lib/minipack.rb ADDED
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Minipack
6
+ require 'minipack/manifest'
7
+ require 'minipack/configuration'
8
+ require 'minipack/helper'
9
+ require 'minipack/manifest_repository'
10
+ require 'minipack/file_change_watcher'
11
+ require 'minipack/command_runner'
12
+ require 'minipack/railtie'
13
+ require 'minipack/commands/base'
14
+ require 'minipack/commands/build'
15
+ require 'minipack/commands/pkg_install'
16
+ require "minipack/version"
17
+
18
+ class << self
19
+ def configuration(&block)
20
+ @configuration ||= Configuration.new
21
+ yield @configuration if block_given?
22
+ @configuration
23
+ end
24
+ attr_writer :configuration
25
+ end
26
+ end
27
+
28
+ require 'active_support/lazy_load_hooks'
29
+ ActiveSupport.on_load :action_view do
30
+ ::ActionView::Base.send :include, Minipack::Helper
31
+ end
32
+
33
+ # To keep backward compatibility
34
+ require_relative 'webpack_manifest'
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @deprecated Use Minipack instead
4
+ module WebpackManifest
5
+ module Rails
6
+ class << self
7
+ # @deprecated Use Minipack.configuration API instead
8
+ def configuration(&block)
9
+ ActiveSupport::Deprecation.warn('WebpackManifest::Rails.configuration was deprecated. Use Minipack.configuration instead.')
10
+ Minipack.configuration(&block)
11
+ end
12
+
13
+ # @deprecated Use Minipack.configuration= API instead
14
+ def configuration=(c)
15
+ ActiveSupport::Deprecation.warn('WebpackManifest::Rails.configuration= was deprecated. Use Minipack.configuration= instead.')
16
+ Minipack.configuration = c
17
+ end
18
+ end
19
+ end
20
+ end
data/minipack.gemspec ADDED
@@ -0,0 +1,32 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "minipack/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "minipack"
8
+ spec.version = Minipack::VERSION
9
+ spec.authors = ["Nobuhiro Nikushi"]
10
+ spec.email = ["deneb.ge@gmail.com"]
11
+
12
+ spec.summary = "Minipack is a gem for minimalists that integrates Rails and webpack without Webpcker"
13
+ spec.description = spec.summary
14
+ spec.homepage = "https://github.com/nikushi/minipack"
15
+ spec.license = "MIT"
16
+ spec.files = Dir['lib/**/*.rb'] + %w[
17
+ CHANGELOG.md
18
+ LICENSE.txt
19
+ README.md
20
+ Rakefile
21
+ minipack.gemspec
22
+ ]
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.required_ruby_version = '>= 2.0.0'
26
+
27
+ spec.add_dependency 'actionview'
28
+ spec.add_dependency "railties", ">= 4.2"
29
+ spec.add_development_dependency "bundler", "~> 2.0"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "rspec", "~> 3.0"
32
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: minipack
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Nobuhiro Nikushi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-06-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionview
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: railties
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '4.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '4.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: Minipack is a gem for minimalists that integrates Rails and webpack without
84
+ Webpcker
85
+ email:
86
+ - deneb.ge@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - CHANGELOG.md
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - lib/minipack.rb
96
+ - lib/minipack/command_runner.rb
97
+ - lib/minipack/commands/base.rb
98
+ - lib/minipack/commands/build.rb
99
+ - lib/minipack/commands/pkg_install.rb
100
+ - lib/minipack/configuration.rb
101
+ - lib/minipack/file_change_watcher.rb
102
+ - lib/minipack/helper.rb
103
+ - lib/minipack/manifest.rb
104
+ - lib/minipack/manifest_repository.rb
105
+ - lib/minipack/railtie.rb
106
+ - lib/minipack/rspec.rb
107
+ - lib/minipack/version.rb
108
+ - lib/webpack_manifest.rb
109
+ - minipack.gemspec
110
+ homepage: https://github.com/nikushi/minipack
111
+ licenses:
112
+ - MIT
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 2.0.0
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.0.3
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: Minipack is a gem for minimalists that integrates Rails and webpack without
133
+ Webpcker
134
+ test_files: []