antoinette 0.1.1 → 0.1.2
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +31 -7
- data/lib/antoinette/cli/commands.rb +11 -2
- data/lib/antoinette/services/clear_script_tag.rb +2 -2
- data/lib/antoinette/services/elm_app_usage_analyzer.rb +9 -11
- data/lib/antoinette/services/inject_script_tag.rb +4 -12
- data/lib/antoinette/services/layout_resolver.rb +47 -0
- data/lib/antoinette/services/weaver.rb +13 -10
- data/lib/antoinette/version.rb +1 -1
- data/lib/antoinette.rb +1 -0
- data/lib/generators/antoinette/templates/BundleGraph.elm +17 -7
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 993d24ce2af00ab04f836fefdecc77510733c8f4ca905b396f63eaa4bb578bbc
|
|
4
|
+
data.tar.gz: ff2cb5009b0a07f05afb5055ff3a7547368effe0a0577913d6394bfbe16f9303
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 611a4278897c3ee934884453547a96dfeec11855732f5596dcb71c7028fd70e32caf588af64c6746a6f5f89e08a5b69f8c04a63ccfdd5f14a1310b853b3bb861
|
|
7
|
+
data.tar.gz: 18bb190a09f1ef55effc1c8b79597e89fb50bd84c9e8d81cc66a7d9cc170c8b80ab57069ebd3bee868fe44bfd3f5dea3bb2030d37ae2e245d59249507888b8a5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.0] - 2026
|
|
4
|
+
|
|
5
|
+
- Layout-aware bundling for SitePress YAML frontmatter
|
|
6
|
+
- Remove SHA1 digests from template marker comments
|
|
7
|
+
- Optional `--width` and `--height` flags for BundleGraph
|
|
8
|
+
|
|
3
9
|
## [0.1.0] - 2024
|
|
4
10
|
|
|
5
11
|
- Initial extraction from Gridular Mod
|
data/README.md
CHANGED
|
@@ -18,7 +18,19 @@ This is the problem Antoinette solves.
|
|
|
18
18
|
Antoinette is a lightweight JS bundler which weaves Elm apps into JavaScript
|
|
19
19
|
bundles, and weaves JavaScript bundles into Rails templates.
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
It looks at `app/views` to make a hash which maps every Elm app referenced in
|
|
22
|
+
the templates to every template which references them. It uses that mapping to
|
|
23
|
+
create a set of JS bundles such that every Rails template will download all of
|
|
24
|
+
the Elm apps it needs, but no Rails template will download any Elm app it
|
|
25
|
+
doesn't need. Antoinette also creates a visualizer, as an Elm app in a Rails
|
|
26
|
+
view, which you can use to understand its output.
|
|
27
|
+
|
|
28
|
+
Here's an example screenshot. In the actual Elm app, you can hover over any
|
|
29
|
+
given bundle to see which Elm apps it places within which Rails templates.
|
|
30
|
+
|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
The name was inspired by mansion weave, a style of flooring based on woven elm,
|
|
22
34
|
which was popular in French mansions from the 16th century onwards.
|
|
23
35
|
|
|
24
36
|
## Installation
|
|
@@ -49,7 +61,8 @@ It also adds a route for `/antoinette` admin page, and adds
|
|
|
49
61
|
|
|
50
62
|
### Configuration
|
|
51
63
|
|
|
52
|
-
Generate bundle configuration by analyzing which Elm
|
|
64
|
+
Generate bundle configuration by analyzing which Elm programs your Rails
|
|
65
|
+
views use:
|
|
53
66
|
|
|
54
67
|
```bash
|
|
55
68
|
bin/antoinette config
|
|
@@ -94,23 +107,26 @@ flow into bundles and then into Rails templates.
|
|
|
94
107
|
|
|
95
108
|
1. **Analysis**: Scans Rails views for `Elm.AppName.init` patterns
|
|
96
109
|
2. **Grouping**: Groups templates that use the same combination of Elm apps
|
|
97
|
-
3. **Bundling**: Compiles each group into a single JavaScript bundle (with a
|
|
98
|
-
|
|
110
|
+
3. **Bundling**: Compiles each group into a single JavaScript bundle (with a
|
|
111
|
+
haiku-styled name like `holy-waterfall-8432`)
|
|
112
|
+
4. **Injection**: Adds `javascript_include_tag` to templates with a marker
|
|
113
|
+
comment for idempotent updates
|
|
99
114
|
|
|
100
115
|
### Script Tag Format
|
|
101
116
|
|
|
102
117
|
Antoinette injects script tags like:
|
|
103
118
|
|
|
104
119
|
```erb
|
|
105
|
-
<%= javascript_include_tag "antoinette/holy-waterfall-8432" %> <!-- antoinette
|
|
120
|
+
<%= javascript_include_tag "antoinette/holy-waterfall-8432" %> <!-- antoinette -->
|
|
106
121
|
```
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
The `<!-- antoinette -->` comment marks the line so it can be found and replaced
|
|
124
|
+
on subsequent builds.
|
|
109
125
|
|
|
110
126
|
## Requirements
|
|
111
127
|
|
|
112
128
|
- Rails 7.0+
|
|
113
|
-
- Elm (
|
|
129
|
+
- Elm (customizable via `elm_path` in `config/antoinette.json`)
|
|
114
130
|
|
|
115
131
|
## Configuration
|
|
116
132
|
|
|
@@ -130,6 +146,14 @@ The `config/antoinette.json` file structure:
|
|
|
130
146
|
}
|
|
131
147
|
```
|
|
132
148
|
|
|
149
|
+
## SitePress Support and Layout Awareness
|
|
150
|
+
|
|
151
|
+
`custom_view_paths` exists because Antoinette has a limited layout awareness
|
|
152
|
+
tied into SitePress. Antoinette looks for layouts in `app/views/layouts` and in
|
|
153
|
+
the YAML frontmatter in SitePress content templates. It can't currently tell
|
|
154
|
+
which Rails templates use which Rails layouts, but it can tell which SitePress
|
|
155
|
+
templates use which SitePress layouts.
|
|
156
|
+
|
|
133
157
|
## Rake Integration
|
|
134
158
|
|
|
135
159
|
The `antoinette:build` task runs automatically before `assets:precompile`:
|
|
@@ -17,8 +17,9 @@ module Antoinette
|
|
|
17
17
|
|
|
18
18
|
option :stdout, type: :boolean, default: false, desc: "Output to stdout instead of file"
|
|
19
19
|
option :custom_views, type: :array, default: [], desc: "Additional view directories to scan"
|
|
20
|
+
option :layout_dirs, type: :array, default: [], desc: "Additional layout directories to scan"
|
|
20
21
|
|
|
21
|
-
def call(stdout:, custom_views: [], **)
|
|
22
|
+
def call(stdout:, custom_views: [], layout_dirs: [], **)
|
|
22
23
|
out = Antoinette::CLI.output
|
|
23
24
|
config_path = Rails.root.join("config", "antoinette.json")
|
|
24
25
|
|
|
@@ -29,19 +30,27 @@ module Antoinette
|
|
|
29
30
|
end
|
|
30
31
|
existing_custom = existing_config["custom_view_paths"] || []
|
|
31
32
|
existing_elm_path = existing_config["elm_path"] || "elm"
|
|
33
|
+
existing_layout_dirs = existing_config["layout_dirs"] || []
|
|
32
34
|
all_custom_views = (existing_custom + custom_views).uniq
|
|
35
|
+
all_layout_dirs = (["app/views/layouts"] + existing_layout_dirs + layout_dirs).uniq
|
|
33
36
|
|
|
34
37
|
analyzer = Antoinette::ElmAppUsageAnalyzer.new(
|
|
35
|
-
|
|
38
|
+
layout_dirs: all_layout_dirs,
|
|
36
39
|
custom_view_paths: all_custom_views
|
|
37
40
|
)
|
|
41
|
+
layout_resolver = Antoinette::LayoutResolver.new(
|
|
42
|
+
layout_dirs: all_layout_dirs
|
|
43
|
+
)
|
|
38
44
|
weaver = Antoinette::Weaver.new(
|
|
39
45
|
elm_analyzer: analyzer,
|
|
46
|
+
layout_resolver: layout_resolver,
|
|
40
47
|
custom_view_paths: all_custom_views
|
|
41
48
|
)
|
|
42
49
|
|
|
43
50
|
output = JSON.parse(weaver.generate_json)
|
|
44
51
|
output["elm_path"] = existing_elm_path
|
|
52
|
+
extra_layout_dirs = all_layout_dirs - ["app/views/layouts"]
|
|
53
|
+
output["layout_dirs"] = extra_layout_dirs if extra_layout_dirs.any?
|
|
45
54
|
json_output = JSON.pretty_generate(output)
|
|
46
55
|
|
|
47
56
|
if stdout
|
|
@@ -10,9 +10,9 @@ module Antoinette
|
|
|
10
10
|
full_path = resolve_template_path(template_path)
|
|
11
11
|
content = File.read(full_path)
|
|
12
12
|
|
|
13
|
-
return unless content.match?(
|
|
13
|
+
return unless content.match?(InjectScriptTag::MARKER)
|
|
14
14
|
|
|
15
|
-
updated_content = content.gsub(
|
|
15
|
+
updated_content = content.gsub(/^.*#{InjectScriptTag::MARKER}.*\n?/o, "")
|
|
16
16
|
|
|
17
17
|
File.write(full_path, updated_content)
|
|
18
18
|
end
|
|
@@ -8,8 +8,8 @@ module Antoinette
|
|
|
8
8
|
ElmApp = Struct.new(:name)
|
|
9
9
|
class Matrix < Hash; end
|
|
10
10
|
|
|
11
|
-
def initialize(
|
|
12
|
-
@
|
|
11
|
+
def initialize(layout_dirs: ["app/views/layouts"], custom_view_paths: [])
|
|
12
|
+
@layout_dirs = layout_dirs
|
|
13
13
|
@custom_view_paths = custom_view_paths
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -21,7 +21,7 @@ module Antoinette
|
|
|
21
21
|
next if apps.empty?
|
|
22
22
|
|
|
23
23
|
relative_path = file_path.sub("#{Rails.root}/", "")
|
|
24
|
-
next if
|
|
24
|
+
next if in_layout_dir?(relative_path)
|
|
25
25
|
|
|
26
26
|
app_names = apps.map(&:name)
|
|
27
27
|
result << ViewFile.new(relative_path, app_names)
|
|
@@ -60,14 +60,6 @@ module Antoinette
|
|
|
60
60
|
@all_app_names ||= views.flat_map(&:elm_apps).uniq.sort
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
-
def layout_apps
|
|
64
|
-
@layout_apps ||= Dir.glob(Rails.root.join("app", "views", "layouts", "*.html.erb"))
|
|
65
|
-
.flat_map do |file_path|
|
|
66
|
-
content = File.read(file_path)
|
|
67
|
-
elm_apps(content).map(&:name)
|
|
68
|
-
end.uniq
|
|
69
|
-
end
|
|
70
|
-
|
|
71
63
|
def per_file
|
|
72
64
|
@per_file ||= views.sort_by { |vf| -vf.elm_apps.count }
|
|
73
65
|
.each_with_object({}) do |view_file, result|
|
|
@@ -96,5 +88,11 @@ module Antoinette
|
|
|
96
88
|
end
|
|
97
89
|
end
|
|
98
90
|
end
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
def in_layout_dir?(relative_path)
|
|
95
|
+
@layout_dirs.any? { |dir| relative_path.start_with?(dir) }
|
|
96
|
+
end
|
|
99
97
|
end
|
|
100
98
|
end
|
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "digest"
|
|
4
|
-
|
|
5
3
|
module Antoinette
|
|
6
4
|
class InjectScriptTag
|
|
7
|
-
|
|
8
|
-
assets_path: Rails.root.join("app", "assets", "javascripts", "antoinette")
|
|
9
|
-
)
|
|
10
|
-
@assets_path = assets_path
|
|
11
|
-
end
|
|
5
|
+
MARKER = /<!-- antoinette( [a-f0-9]+)? -->/
|
|
12
6
|
|
|
13
7
|
def inject(template_path:, bundle_name:)
|
|
14
8
|
full_path = Rails.root.join(template_path)
|
|
15
9
|
content = File.read(full_path)
|
|
16
10
|
|
|
17
|
-
|
|
18
|
-
digest = Digest::SHA1.hexdigest(File.read(bundle_path))
|
|
19
|
-
script_tag = "<%= javascript_include_tag \"antoinette/#{bundle_name}\" %> <!-- antoinette #{digest} -->"
|
|
11
|
+
script_tag = "<%= javascript_include_tag \"antoinette/#{bundle_name}\" %> <!-- antoinette -->"
|
|
20
12
|
|
|
21
|
-
updated_content = if content.match?(
|
|
22
|
-
content.gsub(
|
|
13
|
+
updated_content = if content.match?(MARKER)
|
|
14
|
+
content.gsub(/^.*#{MARKER}.*$/o, script_tag)
|
|
23
15
|
else
|
|
24
16
|
content + "\n" + script_tag
|
|
25
17
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
|
|
5
|
+
module Antoinette
|
|
6
|
+
class LayoutResolver
|
|
7
|
+
def initialize(layout_dirs: ["app/views/layouts"], default_layout: "application")
|
|
8
|
+
@layout_dirs = layout_dirs
|
|
9
|
+
@default_layout = default_layout
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def layout_apps_map
|
|
13
|
+
@layout_apps_map ||= @layout_dirs.each_with_object({}) do |dir, result|
|
|
14
|
+
Dir.glob(Rails.root.join(dir, "*.html.erb")).each do |file_path|
|
|
15
|
+
content = File.read(file_path)
|
|
16
|
+
apps = content.scan(/Elm\.(\w+)\.init/).flatten.uniq
|
|
17
|
+
name = File.basename(file_path, ".html.erb")
|
|
18
|
+
result[name] = ((result[name] || []) + apps).uniq
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def layout_for(template_path)
|
|
24
|
+
full_path = if Pathname.new(template_path).absolute?
|
|
25
|
+
template_path
|
|
26
|
+
else
|
|
27
|
+
Rails.root.join(template_path).to_s
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
return @default_layout unless File.exist?(full_path)
|
|
31
|
+
|
|
32
|
+
content = File.read(full_path)
|
|
33
|
+
return @default_layout unless content.start_with?("---")
|
|
34
|
+
|
|
35
|
+
frontmatter = content.match(/\A---\s*\n(.*?\n)---/m)
|
|
36
|
+
return @default_layout unless frontmatter
|
|
37
|
+
|
|
38
|
+
yaml = YAML.safe_load(frontmatter[1])
|
|
39
|
+
yaml&.fetch("layout", @default_layout) || @default_layout
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def apps_for(template_path)
|
|
43
|
+
layout = layout_for(template_path)
|
|
44
|
+
layout_apps_map[layout] || []
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -10,24 +10,27 @@ module Antoinette
|
|
|
10
10
|
def initialize(
|
|
11
11
|
elm_analyzer: ElmAppUsageAnalyzer.new,
|
|
12
12
|
partial_resolver: PartialResolver.new,
|
|
13
|
+
layout_resolver: LayoutResolver.new,
|
|
13
14
|
custom_view_paths: []
|
|
14
15
|
)
|
|
15
16
|
@elm_analyzer = elm_analyzer
|
|
16
17
|
@partial_resolver = partial_resolver
|
|
18
|
+
@layout_resolver = layout_resolver
|
|
17
19
|
@custom_view_paths = custom_view_paths
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def bundles
|
|
21
|
-
@bundles ||=
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
23
|
+
@bundles ||= begin
|
|
24
|
+
result = []
|
|
25
|
+
@elm_analyzer.mappings.each do |page_apps, templates|
|
|
26
|
+
resolved = resolve_templates_from_partials(templates)
|
|
27
|
+
groups = resolved.group_by { |t| @layout_resolver.apps_for(t) }
|
|
28
|
+
groups.each do |layout_apps, group_templates|
|
|
29
|
+
merged = (page_apps + layout_apps).uniq.sort
|
|
30
|
+
result << Bundle.new(Haikunator.haikunate, merged, group_templates.sort)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
result.sort_by { |b| -b.elm_apps.count }
|
|
31
34
|
end
|
|
32
35
|
end
|
|
33
36
|
|
data/lib/antoinette/version.rb
CHANGED
data/lib/antoinette.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "antoinette/version"
|
|
4
4
|
require_relative "antoinette/services/partial_resolver"
|
|
5
|
+
require_relative "antoinette/services/layout_resolver"
|
|
5
6
|
require_relative "antoinette/services/elm_app_usage_analyzer"
|
|
6
7
|
require_relative "antoinette/services/weaver"
|
|
7
8
|
require_relative "antoinette/services/compile_elm"
|
|
@@ -13,6 +13,8 @@ type alias Model =
|
|
|
13
13
|
{ bundles : List Bundle
|
|
14
14
|
, selectedNodeId : Maybe String
|
|
15
15
|
, lockedNodeIds : List String
|
|
16
|
+
, width : Maybe Int
|
|
17
|
+
, height : Maybe Int
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
|
|
@@ -25,6 +27,8 @@ type alias Bundle =
|
|
|
25
27
|
|
|
26
28
|
type alias Flags =
|
|
27
29
|
{ bundles : List Bundle
|
|
30
|
+
, width : Maybe Int
|
|
31
|
+
, height : Maybe Int
|
|
28
32
|
}
|
|
29
33
|
|
|
30
34
|
|
|
@@ -59,6 +63,8 @@ init flags =
|
|
|
59
63
|
( { bundles = decoded.bundles
|
|
60
64
|
, selectedNodeId = Nothing
|
|
61
65
|
, lockedNodeIds = []
|
|
66
|
+
, width = decoded.width
|
|
67
|
+
, height = decoded.height
|
|
62
68
|
}
|
|
63
69
|
, Cmd.none
|
|
64
70
|
)
|
|
@@ -67,6 +73,8 @@ init flags =
|
|
|
67
73
|
( { bundles = []
|
|
68
74
|
, selectedNodeId = Nothing
|
|
69
75
|
, lockedNodeIds = []
|
|
76
|
+
, width = Nothing
|
|
77
|
+
, height = Nothing
|
|
70
78
|
}
|
|
71
79
|
, Cmd.none
|
|
72
80
|
)
|
|
@@ -125,8 +133,8 @@ view model =
|
|
|
125
133
|
model.lockedNodeIds
|
|
126
134
|
|
|
127
135
|
sankeyModel =
|
|
128
|
-
{ svgWidth = 1100
|
|
129
|
-
, svgHeight =
|
|
136
|
+
{ svgWidth = toFloat (Maybe.withDefault 1100 model.width)
|
|
137
|
+
, svgHeight = toFloat (Maybe.withDefault (totalHeightInt model.bundles) model.height)
|
|
130
138
|
, columnWidths = columnWidths
|
|
131
139
|
, nodePadding = 8
|
|
132
140
|
, columnSpacing = 350
|
|
@@ -162,7 +170,7 @@ view model =
|
|
|
162
170
|
, style "border" "1px solid #d1d5db"
|
|
163
171
|
]
|
|
164
172
|
in
|
|
165
|
-
div []
|
|
173
|
+
div [ Html.Attributes.class "bundle-graph" ]
|
|
166
174
|
[ div [ style "display" "flex", style "align-items" "center", style "gap" "16px" ]
|
|
167
175
|
[ h1
|
|
168
176
|
[ style "font-family" "system-ui, sans-serif"
|
|
@@ -187,8 +195,8 @@ view model =
|
|
|
187
195
|
]
|
|
188
196
|
|
|
189
197
|
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
totalHeightInt : List Bundle -> Int
|
|
199
|
+
totalHeightInt bundles =
|
|
192
200
|
let
|
|
193
201
|
elmApps =
|
|
194
202
|
uniqueElmApps bundles
|
|
@@ -202,7 +210,7 @@ totalHeight bundles =
|
|
|
202
210
|
rowHeight =
|
|
203
211
|
27
|
|
204
212
|
in
|
|
205
|
-
|
|
213
|
+
maxItems * rowHeight + 100
|
|
206
214
|
|
|
207
215
|
|
|
208
216
|
buildColumnWidths : List LabelNode -> Dict.Dict Int Float
|
|
@@ -332,8 +340,10 @@ unique list =
|
|
|
332
340
|
|
|
333
341
|
flagsDecoder : Decoder Flags
|
|
334
342
|
flagsDecoder =
|
|
335
|
-
Decode.
|
|
343
|
+
Decode.map3 Flags
|
|
336
344
|
(Decode.field "bundles" (Decode.list bundleDecoder))
|
|
345
|
+
(Decode.maybe (Decode.field "width" Decode.int))
|
|
346
|
+
(Decode.maybe (Decode.field "height" Decode.int))
|
|
337
347
|
|
|
338
348
|
|
|
339
349
|
bundleDecoder : Decoder Bundle
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: antoinette
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Giles Bowkett
|
|
@@ -99,6 +99,7 @@ files:
|
|
|
99
99
|
- lib/antoinette/services/concat_bundle.rb
|
|
100
100
|
- lib/antoinette/services/elm_app_usage_analyzer.rb
|
|
101
101
|
- lib/antoinette/services/inject_script_tag.rb
|
|
102
|
+
- lib/antoinette/services/layout_resolver.rb
|
|
102
103
|
- lib/antoinette/services/partial_resolver.rb
|
|
103
104
|
- lib/antoinette/services/weaver.rb
|
|
104
105
|
- lib/antoinette/version.rb
|