rails-minify 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +89 -0
- data/lib/rails-minify.rb +3 -0
- data/lib/rails_minify/patches/action_text_plain_text.rb +102 -0
- data/lib/rails_minify/patches/omniauth_passthrough.rb +53 -0
- data/lib/rails_minify/patches/request_host.rb +32 -0
- data/lib/rails_minify/railtie.rb +85 -0
- data/lib/rails_minify/version.rb +5 -0
- data/lib/rails_minify.rb +30 -0
- metadata +97 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 32a43a0b86ec6d79152580e59ed32f5468ddd3d1a4e0865716f404bd542b566c
|
|
4
|
+
data.tar.gz: fa640778b9324a8737ff1e809af7a101ff2e10b910677611de081b033678c983
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d078aaa82950d4407b91e82fcde444264496d72551db08177d629cdb61d5928ff23381462f23388cbf5db91c138cca78d1f79a870f0eeca794d178b0e8427af9
|
|
7
|
+
data.tar.gz: e6ee0e7f39335eaba0d2adf4718a89dbdf52e8d1a8b993539ddfa3081631714bac16c5fc899b6f8dea5fd3cf12d667875843a1d73ccc524ecc72735c9e42834d
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 (2026-10-18)
|
|
4
|
+
|
|
5
|
+
First release.
|
|
6
|
+
|
|
7
|
+
- `config.rails_minify.enabled` turns on activerecord-, activesupport- and actionpack-minimizer.
|
|
8
|
+
- Patches: `:action_text_plain_text` (cached plain text, including blob attachments), `:request_host` (rails/rails#59104 for Rails 8.0/8.1), `:omniauth` (strategies skip requests that can't be theirs).
|
|
9
|
+
- `minify:census` and `minify:check` tasks.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sampo Kuokkanen
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# rails-minify
|
|
2
|
+
|
|
3
|
+
Cuts what Rails (and common gems) build or redo on every request.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
# Gemfile
|
|
7
|
+
gem "rails-minify"
|
|
8
|
+
|
|
9
|
+
# config/environments/production.rb
|
|
10
|
+
config.rails_minify.enabled = true
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`enabled` turns on every part below; each can still be set on its own
|
|
14
|
+
(`config.ar_minimizer.enums = false`, `config.rails_minify.patches = [:omniauth]`).
|
|
15
|
+
Ruby 3.4+, Rails 8.0, 8.1 and main.
|
|
16
|
+
|
|
17
|
+
## Strict by default
|
|
18
|
+
|
|
19
|
+
Active Record defines, for every column, a reader, a writer and 19 more
|
|
20
|
+
methods (`name?`, `name_changed?`, `saved_change_to_name?`, ...). With
|
|
21
|
+
rails-minify they're gone: only readers and writers are generated, which
|
|
22
|
+
on a 191-model app is 42,210 → ~4,500 methods, ~10 MB less memory and ~150
|
|
23
|
+
ms less boot. Calling a removed method raises NoMethodError naming its
|
|
24
|
+
replacement:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
name_changed? was removed by activerecord-minimizer's strict mode; use attribute_changed?(:name, ...)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
rubocop-minify rewrites those calls in your code. For calls in gems
|
|
31
|
+
(Devise's `will_save_change_to_email?`, ...), list them in
|
|
32
|
+
`config/ar_minimizer_keep.txt`; `bin/rails ar_minimizer:profile` finds them.
|
|
33
|
+
`config.ar_minimizer.strict = false` keeps the removed methods working
|
|
34
|
+
instead, generated the first time each is called.
|
|
35
|
+
|
|
36
|
+
## What's in it
|
|
37
|
+
|
|
38
|
+
| part | does | gem |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| attribute methods | readers and writers only (strict) | activerecord-minimizer |
|
|
41
|
+
| enums | predicates/bang methods as plain methods (1.3-1.6x) | activerecord-minimizer |
|
|
42
|
+
| small records | no Hashes for records that never fill them (-22% per record) | activerecord-minimizer |
|
|
43
|
+
| callbacks | chains as generated methods (`valid?` -33..45%) | activesupport-minimizer |
|
|
44
|
+
| route helpers | `post_path(post)` as interpolation (3-14x) | actionpack-minimizer |
|
|
45
|
+
| views | `render`/`link_to` without their machinery (2x per page of partials) | actionpack-minimizer |
|
|
46
|
+
| patches | below | rails-minify |
|
|
47
|
+
| rewrites | removed-method calls in Ruby and ERB, as RuboCop autocorrections | rubocop-minify (development) |
|
|
48
|
+
|
|
49
|
+
## Patches
|
|
50
|
+
|
|
51
|
+
For parts of Rails and common gems that redo work on every request.
|
|
52
|
+
`config.rails_minify.patches = [...]` picks them; all by default.
|
|
53
|
+
|
|
54
|
+
* `:action_text_plain_text`: `ActionText::Content#to_plain_text` parses the
|
|
55
|
+
HTML with Nokogiri, and looks up every attachment's record, on every call.
|
|
56
|
+
It's computed once per distinct HTML (cache capped at 8 MB of HTML).
|
|
57
|
+
Attachments that are Active Storage blobs, remote images or inline content
|
|
58
|
+
are cached too: a blob without a caption contributes its filename, which a
|
|
59
|
+
hit checks in one query (renamed or deleted: computed again). Other
|
|
60
|
+
attachables aren't cached. In golf-buddy, 13% of all allocations on its
|
|
61
|
+
pages came from it, and guides with images another 4.5%.
|
|
62
|
+
* `:request_host`: `ActionDispatch::Request#host` runs a regexp substitution
|
|
63
|
+
on every call, port or not, and Rails reads it several times per request.
|
|
64
|
+
rails/rails#59104 (Rails 8.2) for 8.1: no substitution without a port, and
|
|
65
|
+
the host kept while the raw host is the same String. Not installed on 8.2.
|
|
66
|
+
* `:omniauth`: every OmniAuth strategy middleware dups itself and its
|
|
67
|
+
options on every request before looking at the path. Requests that can't
|
|
68
|
+
be OmniAuth's go straight to the app. Only when that's certain up front:
|
|
69
|
+
a session is present, not in test mode, no `other_phase` or callable
|
|
70
|
+
paths, and the strategy doesn't override how paths are matched.
|
|
71
|
+
|
|
72
|
+
## Numbers
|
|
73
|
+
|
|
74
|
+
golf-buddy, 132 production pages, with everything on (strict mode after
|
|
75
|
+
rubocop-minify's rewrites, all patches): identical HTML, and
|
|
76
|
+
1,561,283 → 1,224,141 allocations per pass (−21.6%). The machine was too
|
|
77
|
+
loaded for wall-clock times to mean much.
|
|
78
|
+
|
|
79
|
+
## Checks
|
|
80
|
+
|
|
81
|
+
* `bin/rails minify:census`: how much of the app runs compiled, and why the rest doesn't.
|
|
82
|
+
* `bin/rails minify:check`: callbacks naming methods that don't exist (for CI).
|
|
83
|
+
|
|
84
|
+
## Things found that are app config, not patches
|
|
85
|
+
|
|
86
|
+
* sentry-rails' `breadcrumbs_logger = [:active_support_logger]` subscribes to
|
|
87
|
+
every notification (each SQL query, render, instantiation) and takes
|
|
88
|
+
`Time.now` twice per event, Sentry enabled or not. `:monotonic_active_support_logger`
|
|
89
|
+
avoids the clock calls; dropping it avoids the rest.
|
data/lib/rails-minify.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsMinify
|
|
4
|
+
module Patches
|
|
5
|
+
# ActionText::Content#to_plain_text walks the whole HTML tree with
|
|
6
|
+
# Nokogiri on every call, and looks up the record behind every attachment:
|
|
7
|
+
# in golf-buddy, 13% of all allocations on its pages came from it
|
|
8
|
+
# (excerpts, meta descriptions, cards). The text is computed once per
|
|
9
|
+
# distinct HTML and kept, up to MAX_BYTES of HTML.
|
|
10
|
+
#
|
|
11
|
+
# Without attachments the text depends on the HTML alone. An attachment
|
|
12
|
+
# adds its record's text: for Active Storage blobs "[caption]" (from the
|
|
13
|
+
# HTML) or, without a caption, "[filename]". So content whose attachments
|
|
14
|
+
# are all blobs, remote images or inline content is cached too, with the
|
|
15
|
+
# filenames it used: a hit with caption-less blobs checks them in one
|
|
16
|
+
# query (renamed or deleted: computed again). Attachments of other
|
|
17
|
+
# records (an app's own attachables, whose text can be anything) aren't
|
|
18
|
+
# cached.
|
|
19
|
+
module ActionTextPlainText
|
|
20
|
+
MAX_BYTES = 8 * 1024 * 1024
|
|
21
|
+
ATTACHMENT = /<action-text-attachment|data-trix-attachment/
|
|
22
|
+
|
|
23
|
+
@cache = {} # [html, canonicalize] => text, or [text, { blob id => filename }] with attachments
|
|
24
|
+
@bytes = 0
|
|
25
|
+
@mutex = Mutex.new
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
def install!
|
|
29
|
+
ActionText::Content.prepend(Content) unless ActionText::Content < Content
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fetch(key)
|
|
33
|
+
@cache.fetch(key) { store(key, yield.freeze) }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The text, from the cache when the blobs it named are unchanged.
|
|
37
|
+
def fetch_with_attachments(key, content)
|
|
38
|
+
if (entry = @cache[key]) && filenames_unchanged?(entry[1])
|
|
39
|
+
return entry[0]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
text = yield.freeze
|
|
43
|
+
filenames = blob_filenames(content)
|
|
44
|
+
store(key, [text, filenames]) if filenames
|
|
45
|
+
text
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def clear! = @mutex.synchronize { @cache = {}; @bytes = 0 }
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def store(key, value)
|
|
53
|
+
@mutex.synchronize do
|
|
54
|
+
if @bytes + key[0].bytesize > MAX_BYTES
|
|
55
|
+
@cache = {}
|
|
56
|
+
@bytes = 0
|
|
57
|
+
end
|
|
58
|
+
@bytes += key[0].bytesize unless @cache.key?(key)
|
|
59
|
+
@cache[key] = value
|
|
60
|
+
end
|
|
61
|
+
value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# { blob id => filename } for the caption-less blobs, or nil when an
|
|
65
|
+
# attachment's text could depend on more than that.
|
|
66
|
+
def blob_filenames(content)
|
|
67
|
+
content.attachments.each_with_object({}) do |attachment, filenames|
|
|
68
|
+
case attachable = attachment.attachable
|
|
69
|
+
when ActiveStorage::Blob
|
|
70
|
+
filenames[attachable.id] = attachable[:filename] unless attachment.caption
|
|
71
|
+
when ActionText::Attachables::RemoteImage, ActionText::Attachables::ContentAttachment
|
|
72
|
+
# the text comes from the HTML
|
|
73
|
+
else
|
|
74
|
+
return nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def filenames_unchanged?(filenames)
|
|
80
|
+
filenames.empty? || ActiveStorage::Blob.where(id: filenames.keys).pluck(:id, :filename).to_h == filenames
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
module Content
|
|
85
|
+
def initialize(content = nil, options = {})
|
|
86
|
+
super
|
|
87
|
+
return unless content.is_a?(String)
|
|
88
|
+
|
|
89
|
+
@plain_text_key = [content.frozen? ? content : content.dup.freeze, options.fetch(:canonicalize, true)]
|
|
90
|
+
@plain_text_attachments = content.match?(ATTACHMENT)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def to_plain_text
|
|
94
|
+
return super unless @plain_text_key
|
|
95
|
+
return ActionTextPlainText.fetch(@plain_text_key) { super }.dup unless @plain_text_attachments
|
|
96
|
+
|
|
97
|
+
ActionTextPlainText.fetch_with_attachments(@plain_text_key, self) { super }.dup
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsMinify
|
|
4
|
+
module Patches
|
|
5
|
+
# Every OmniAuth strategy is a middleware that, on every request, dups
|
|
6
|
+
# itself (and its Hashie::Mash options) before looking at the path: in
|
|
7
|
+
# golf-buddy about 35k allocations per pass over its pages, none of them
|
|
8
|
+
# on a login path. Requests that can't be OmniAuth's go straight to the
|
|
9
|
+
# app; anything that might be (or that the strategy decides at runtime:
|
|
10
|
+
# callable paths, other_phase, overridden path methods, test mode, a
|
|
11
|
+
# missing session, which OmniAuth reports) runs OmniAuth as before.
|
|
12
|
+
module OmniAuthPassthrough
|
|
13
|
+
# What decides whether a request is the strategy's. A strategy that
|
|
14
|
+
# overrides any of these decides for itself: always run it.
|
|
15
|
+
PATH_METHODS = %i[call! on_auth_path? on_request_path? on_callback_path? on_path? request_path
|
|
16
|
+
callback_path current_path path_prefix script_name].freeze
|
|
17
|
+
|
|
18
|
+
def self.install!
|
|
19
|
+
OmniAuth::Strategy.prepend(self) unless OmniAuth::Strategy < self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Per strategy class: are all of PATH_METHODS OmniAuth's own?
|
|
23
|
+
def self.standard_paths?(klass)
|
|
24
|
+
@standard ||= {}.compare_by_identity
|
|
25
|
+
@standard.fetch(klass) do
|
|
26
|
+
@standard[klass] = PATH_METHODS.all? { |name| [OmniAuth::Strategy, self].include?(klass.instance_method(name).owner) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def call(env)
|
|
31
|
+
return super unless passthrough?(env)
|
|
32
|
+
|
|
33
|
+
@app.call(env)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def passthrough?(env)
|
|
39
|
+
return false if OmniAuth.config.test_mode || !env["rack.session"] || respond_to?(:other_phase)
|
|
40
|
+
return false unless OmniAuthPassthrough.standard_paths?(self.class)
|
|
41
|
+
|
|
42
|
+
request_path = options[:request_path]
|
|
43
|
+
callback_path = options[:callback_path]
|
|
44
|
+
return false if request_path.respond_to?(:call) || callback_path.respond_to?(:call)
|
|
45
|
+
|
|
46
|
+
path_info = env["PATH_INFO"].to_s.downcase
|
|
47
|
+
full_path = "#{env["SCRIPT_NAME"]}#{path_info}".downcase
|
|
48
|
+
prefixes = [options[:path_prefix] || OmniAuth.config.path_prefix, request_path, callback_path]
|
|
49
|
+
prefixes.none? { |prefix| prefix.is_a?(String) && (path_info.start_with?(prefix.downcase) || full_path.start_with?(prefix.downcase)) }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsMinify
|
|
4
|
+
module Patches
|
|
5
|
+
# ActionDispatch::Request#host runs a regexp substitution on every call,
|
|
6
|
+
# allocating a String and a MatchData even when the host has no port
|
|
7
|
+
# (behind a proxy, the usual case), and Rails reads it several times per
|
|
8
|
+
# request: url_options, routes constrained by host, cookies, redirects.
|
|
9
|
+
#
|
|
10
|
+
# The same change as rails/rails#59104 (Rails 8.2) for earlier versions:
|
|
11
|
+
# no substitution without a colon, and the host kept for as long as the
|
|
12
|
+
# raw host is the same String (the env's HTTP_HOST; a test's host= or
|
|
13
|
+
# X-Forwarded-Host make a new one, so nothing goes stale).
|
|
14
|
+
module RequestHost
|
|
15
|
+
PORT = /:\d+$/
|
|
16
|
+
|
|
17
|
+
def self.install!
|
|
18
|
+
return if ActionPack.gem_version >= Gem::Version.new("8.2.0.alpha") # upstream already
|
|
19
|
+
|
|
20
|
+
ActionDispatch::Request.prepend(self) unless ActionDispatch::Request < self
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def host
|
|
24
|
+
raw = raw_host_with_port
|
|
25
|
+
return @__rm_host if @__rm_host_raw.equal?(raw)
|
|
26
|
+
|
|
27
|
+
@__rm_host_raw = raw
|
|
28
|
+
@__rm_host = raw.include?(":") ? raw.sub(PORT, "") : raw
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RailsMinify
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
config.rails_minify = ActiveSupport::OrderedOptions.new
|
|
6
|
+
|
|
7
|
+
# After config/environments/*.rb, before any initializer reads the
|
|
8
|
+
# minimizers' options.
|
|
9
|
+
config.before_initialize do |app|
|
|
10
|
+
next unless app.config.rails_minify.enabled
|
|
11
|
+
|
|
12
|
+
ar = app.config.ar_minimizer
|
|
13
|
+
ar.slim = true if ar.slim.nil?
|
|
14
|
+
ar.views = true if ar.views.nil?
|
|
15
|
+
ar.enums = true if ar.enums.nil?
|
|
16
|
+
ar.small_records = true if ar.small_records.nil?
|
|
17
|
+
ar.load_time = app.root.join("config/ar_minimizer.json").exist? if ar.load_time.nil?
|
|
18
|
+
as = app.config.as_minimizer
|
|
19
|
+
as.unroll_callbacks = true if as.unroll_callbacks.nil?
|
|
20
|
+
ap = app.config.ap_minimizer
|
|
21
|
+
ap.route_helpers = true if ap.route_helpers.nil?
|
|
22
|
+
ap.views = true if ap.views.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
PATCHES = {
|
|
26
|
+
action_text_plain_text: ["ActionText::Content", :action_text_content, -> { Patches::ActionTextPlainText.install! }],
|
|
27
|
+
omniauth: ["OmniAuth::Strategy", nil, -> { Patches::OmniAuthPassthrough.install! }],
|
|
28
|
+
request_host: ["ActionDispatch::Request", nil, -> { Patches::RequestHost.install! }],
|
|
29
|
+
}.freeze
|
|
30
|
+
|
|
31
|
+
initializer "rails_minify.patches" do |app|
|
|
32
|
+
options = app.config.rails_minify
|
|
33
|
+
next unless options.enabled || options.patches
|
|
34
|
+
|
|
35
|
+
Array(options.patches || PATCHES.keys).each do |name|
|
|
36
|
+
constant, hook, install = PATCHES.fetch(name.to_sym)
|
|
37
|
+
if hook
|
|
38
|
+
ActiveSupport.on_load(hook) { install.call }
|
|
39
|
+
else
|
|
40
|
+
config.after_initialize { install.call if Object.const_defined?(constant) }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
rake_tasks do
|
|
46
|
+
namespace :minify do
|
|
47
|
+
prepare = lambda do
|
|
48
|
+
app = Rails.application
|
|
49
|
+
app.eager_load!
|
|
50
|
+
# Routes define methods too (Devise's authenticate_user!); Rails 8 loads them lazily.
|
|
51
|
+
app.respond_to?(:reload_routes_unless_loaded) ? app.reload_routes_unless_loaded : app.reload_routes!
|
|
52
|
+
app
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc "How much of the app runs compiled, and why the rest doesn't (VERBOSE=1 for every item)"
|
|
56
|
+
task census: :environment do
|
|
57
|
+
app = prepare.call
|
|
58
|
+
verbose = ENV["VERBOSE"] == "1"
|
|
59
|
+
routed = AsMinimizer::Check.routed_actions(app.routes)
|
|
60
|
+
sections = {
|
|
61
|
+
"Active Record (activerecord-minimizer)" => ArMinimizer::Census.report(root: app.root),
|
|
62
|
+
"Callbacks (activesupport-minimizer)" => AsMinimizer::Census.report(routed: routed),
|
|
63
|
+
"Routes and views (actionpack-minimizer)" => ApMinimizer::Census.report(app.routes, app.paths["app/views"].existent),
|
|
64
|
+
}
|
|
65
|
+
sections.each do |title, result|
|
|
66
|
+
puts title
|
|
67
|
+
result.lines(verbose: verbose).each { puts " #{it}" }
|
|
68
|
+
puts
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc "Fail on what would break at runtime or compile stale: missing callback targets, an out-of-date manifest"
|
|
73
|
+
task check: :environment do
|
|
74
|
+
app = prepare.call
|
|
75
|
+
problems = AsMinimizer::Check.callback_targets(routed: AsMinimizer::Check.routed_actions(app.routes))
|
|
76
|
+
missing = problems.select { it.status == :missing }
|
|
77
|
+
missing.each { puts " #{it}" }
|
|
78
|
+
manifest = ArMinimizer::Census.manifest_state(app.root, ArMinimizer::Index.from_models)
|
|
79
|
+
puts "callback targets: #{missing.size} missing; manifest: #{manifest}"
|
|
80
|
+
abort if missing.any? || manifest.start_with?("out of date")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
data/lib/rails_minify.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails_minify/version"
|
|
4
|
+
require "as_minimizer"
|
|
5
|
+
require "ar_minimizer"
|
|
6
|
+
require "ap_minimizer"
|
|
7
|
+
|
|
8
|
+
# One switch for the minimizers:
|
|
9
|
+
#
|
|
10
|
+
# # config/environments/production.rb
|
|
11
|
+
# config.rails_minify.enabled = true
|
|
12
|
+
#
|
|
13
|
+
# turns on, unless set otherwise on their own:
|
|
14
|
+
# config.ar_minimizer.slim, .views, .load_time (load_time only with config/ar_minimizer.json)
|
|
15
|
+
# config.as_minimizer.unroll_callbacks
|
|
16
|
+
# config.ap_minimizer.route_helpers, .views
|
|
17
|
+
#
|
|
18
|
+
# and these patches, for parts of Rails and common gems that redo work on
|
|
19
|
+
# every request (config.rails_minify.patches = [...] to choose):
|
|
20
|
+
# :action_text_plain_text ActionText::Content#to_plain_text, once per distinct HTML
|
|
21
|
+
# :omniauth OmniAuth strategies skip their per-request dup off their paths
|
|
22
|
+
module RailsMinify
|
|
23
|
+
module Patches
|
|
24
|
+
autoload :ActionTextPlainText, "rails_minify/patches/action_text_plain_text"
|
|
25
|
+
autoload :OmniAuthPassthrough, "rails_minify/patches/omniauth_passthrough"
|
|
26
|
+
autoload :RequestHost, "rails_minify/patches/request_host"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
require "rails_minify/railtie" if defined?(Rails::Railtie)
|
metadata
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: rails-minify
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sampo Kuokkanen
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activerecord-minimizer
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: actionpack-minimizer
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: activesupport-minimizer
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.1'
|
|
54
|
+
description: Turns on activerecord-, activesupport- and actionpack-minimizer, and
|
|
55
|
+
patches parts of Rails and common gems that redo work on every request (Action Text
|
|
56
|
+
plain text, Request#host, OmniAuth strategies).
|
|
57
|
+
executables: []
|
|
58
|
+
extensions: []
|
|
59
|
+
extra_rdoc_files: []
|
|
60
|
+
files:
|
|
61
|
+
- CHANGELOG.md
|
|
62
|
+
- LICENSE.txt
|
|
63
|
+
- README.md
|
|
64
|
+
- lib/rails-minify.rb
|
|
65
|
+
- lib/rails_minify.rb
|
|
66
|
+
- lib/rails_minify/patches/action_text_plain_text.rb
|
|
67
|
+
- lib/rails_minify/patches/omniauth_passthrough.rb
|
|
68
|
+
- lib/rails_minify/patches/request_host.rb
|
|
69
|
+
- lib/rails_minify/railtie.rb
|
|
70
|
+
- lib/rails_minify/version.rb
|
|
71
|
+
homepage: https://github.com/sampokuokkanen/rails-minify/tree/main/rails-minify
|
|
72
|
+
licenses:
|
|
73
|
+
- MIT
|
|
74
|
+
metadata:
|
|
75
|
+
source_code_uri: https://github.com/sampokuokkanen/rails-minify/tree/main/rails-minify
|
|
76
|
+
changelog_uri: https://github.com/sampokuokkanen/rails-minify/blob/main/rails-minify/CHANGELOG.md
|
|
77
|
+
bug_tracker_uri: https://github.com/sampokuokkanen/rails-minify/issues
|
|
78
|
+
rubygems_mfa_required: 'true'
|
|
79
|
+
rdoc_options: []
|
|
80
|
+
require_paths:
|
|
81
|
+
- lib
|
|
82
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '3.4'
|
|
87
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - ">="
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: '0'
|
|
92
|
+
requirements: []
|
|
93
|
+
rubygems_version: 4.0.20
|
|
94
|
+
specification_version: 4
|
|
95
|
+
summary: 'Precompile what Rails works out at runtime: attribute methods, callback
|
|
96
|
+
chains'
|
|
97
|
+
test_files: []
|