phlex 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of phlex might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.editorconfig +5 -0
- data/.rubocop.yml +6 -0
- data/CONTRIBUTING.md +23 -0
- data/README.md +2 -2
- data/Rakefile +2 -2
- data/SECURITY.md +5 -0
- data/bench.rb +1 -1
- data/docs/assets/application.css +2 -0
- data/docs/build.rb +4 -4
- data/docs/components/callout.rb +5 -5
- data/docs/components/code_block.rb +18 -18
- data/docs/components/example.rb +23 -23
- data/docs/components/heading.rb +5 -5
- data/docs/components/layout.rb +42 -41
- data/docs/components/markdown.rb +19 -19
- data/docs/components/tabs/tab.rb +18 -18
- data/docs/components/tabs.rb +21 -21
- data/docs/components/title.rb +5 -5
- data/docs/page_builder.rb +32 -32
- data/docs/pages/application_page.rb +3 -3
- data/docs/pages/index.rb +23 -25
- data/docs/pages/rails_integration.rb +58 -0
- data/docs/pages/templates.rb +238 -238
- data/docs/pages/views.rb +175 -0
- data/fixtures/compilation/vcall.rb +38 -0
- data/fixtures/dummy/app/views/articles/form.rb +9 -9
- data/fixtures/dummy/app/views/articles/index.html.erb +3 -0
- data/fixtures/dummy/app/views/card.rb +8 -8
- data/fixtures/dummy/app/views/heading.rb +9 -0
- data/fixtures/dummy/config/routes.rb +1 -1
- data/fixtures/dummy/db/schema.rb +2 -2
- data/fixtures/layout.rb +24 -24
- data/fixtures/page.rb +34 -34
- data/fixtures/test_helper.rb +2 -2
- data/fixtures/view_helper.rb +16 -0
- data/lib/generators/phlex/component/USAGE +1 -1
- data/lib/generators/phlex/component/component_generator.rb +8 -8
- data/lib/generators/phlex/component/templates/{component.rb.erb → view.rb.erb} +1 -1
- data/lib/install/phlex.rb +18 -0
- data/lib/overrides/symbol/name.rb +1 -1
- data/lib/phlex/block.rb +12 -12
- data/lib/phlex/buffered.rb +13 -13
- data/lib/phlex/compiler/formatter.rb +91 -0
- data/lib/phlex/compiler/generators/standard_element.rb +30 -0
- data/lib/phlex/compiler/generators/void_element.rb +29 -0
- data/lib/phlex/compiler/optimizers/base_optimizer.rb +34 -0
- data/lib/phlex/compiler/optimizers/vcall.rb +29 -0
- data/lib/phlex/compiler/visitors/base_visitor.rb +19 -0
- data/lib/phlex/compiler/visitors/component.rb +28 -0
- data/lib/phlex/compiler/visitors/component_method.rb +28 -0
- data/lib/phlex/compiler/visitors/file.rb +17 -0
- data/lib/phlex/compiler.rb +50 -0
- data/lib/phlex/configuration.rb +3 -3
- data/lib/phlex/engine.rb +11 -0
- data/lib/phlex/html.rb +136 -18
- data/lib/phlex/rails/tag_helpers.rb +23 -23
- data/lib/phlex/renderable.rb +33 -27
- data/lib/phlex/version.rb +1 -1
- data/lib/phlex/view.rb +223 -0
- data/lib/phlex.rb +27 -12
- data/lib/tasks/phlex_tasks.rake +10 -0
- metadata +41 -10
- data/CHANGELOG.md +0 -5
- data/docs/pages/components.rb +0 -175
- data/fixtures/component_helper.rb +0 -16
- data/lib/phlex/component.rb +0 -191
- data/lib/phlex/rails.rb +0 -8
data/docs/pages/index.rb
CHANGED
@@ -1,40 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pages
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
class Index < ApplicationPage
|
5
|
+
def template
|
6
|
+
render Layout.new(title: "Introduction to Phlex") do
|
7
|
+
render Markdown.new(<<~MD)
|
8
|
+
# Introduction
|
9
9
|
|
10
|
-
|
10
|
+
Phlex is a framework for building fast, reusable, testable views in pure Ruby.
|
11
11
|
|
12
|
-
|
12
|
+
Each view object is an instance of a specific class of view. The nav-bar, for example, might contain three different nav-bar-items, but they’re all instances of the nav-bar-item class. This class, then, manifests everything there is to know about nav bar items in general. It models:
|
13
13
|
|
14
|
-
|
14
|
+
1. the **data** attributes being represented — perhaps url and label;
|
15
|
+
2. a **template**, which dictates how the data should be represented with HTML markup and CSS classes; and
|
16
|
+
3. **logic**, conditions, or calculations on the data — perhaps a predicate method to determine whether the link is active or not.
|
15
17
|
|
16
|
-
|
17
|
-
2. a **template**, which dictates how the data should be represented with HTML markup and CSS classes; and
|
18
|
-
3. **logic**, conditions, or calculations on the data — perhaps a predicate method to determine whether the link is active or not.
|
18
|
+
# Why use Phlex?
|
19
19
|
|
20
|
-
|
20
|
+
## Better developer experience 🎉
|
21
21
|
|
22
|
-
|
22
|
+
You don’t need to introduce a new language like Slim, HAML, or ERB. Phlex views are plain old Ruby objects: view classes are just Ruby classes, templates are just methods, and HTML tags are just method calls. If you know how to define a method that calls another method, you pretty much already know how to use Phlex.
|
23
23
|
|
24
|
-
|
24
|
+
## Better safety 🥽
|
25
|
+
Rails partials can implicitly depend on instance variables from a controller or view. This happens more often than you might think when copying code into a new partial extraction. If the partial is then rendered in a different context or the instance variable’s meaning changes, things can break quite severely without warning.
|
25
26
|
|
26
|
-
|
27
|
-
Rails partials can implicitly depend on instance variables from a controller or view. This happens more often than you might think when copying code into a new partial extraction. If the partial is then rendered in a different context or the instance variable’s meaning changes, things can break quite severely without warning.
|
27
|
+
Conversely, Phlex view templates render in an isolated execution context where only the instance variables and methods for the specific view are exposed.
|
28
28
|
|
29
|
-
|
29
|
+
## Better performance 🚀
|
30
30
|
|
31
|
-
|
31
|
+
Phlex is ~4.35× faster than ActionView and ~2× faster than ViewComponent. Phlex views are also streamable.
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
39
|
-
end
|
33
|
+
Rails apps typically spend 40-80% of the response time rendering views, so this could be a significant factor in overall app performance.
|
34
|
+
MD
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
40
38
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Pages
|
4
|
+
class RailsIntegration < ApplicationPage
|
5
|
+
def template
|
6
|
+
render Layout.new(title: "Ruby on Rails integration") do
|
7
|
+
render Markdown.new(<<~MD)
|
8
|
+
# Ruby on Rails integration
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
To install Phlex into your Rails application, you can run the `bin/rails phlex:install` command.
|
13
|
+
|
14
|
+
## Component generator
|
15
|
+
|
16
|
+
You can generate new views with the `rails g phlex:view` command.
|
17
|
+
|
18
|
+
For example, running `rails g phlex:view Card` will create the following file:
|
19
|
+
MD
|
20
|
+
|
21
|
+
render CodeBlock.new(<<~RUBY, syntax: :ruby)
|
22
|
+
# app/views/card.rb
|
23
|
+
|
24
|
+
module Views
|
25
|
+
class Card < Phlex::View
|
26
|
+
def template
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
RUBY
|
31
|
+
|
32
|
+
render Markdown.new(<<~MD)
|
33
|
+
## Helpers
|
34
|
+
|
35
|
+
You can use the `helpers` proxy to access helpers within a `Phlex::View`.
|
36
|
+
|
37
|
+
For example, you can use the `#t` helper for translations:
|
38
|
+
MD
|
39
|
+
|
40
|
+
render CodeBlock.new(<<~RUBY, syntax: :ruby)
|
41
|
+
# app/views/hello.rb
|
42
|
+
|
43
|
+
module Views
|
44
|
+
class Hello < Phlex::View
|
45
|
+
delegate :t, to: :helpers
|
46
|
+
|
47
|
+
def template
|
48
|
+
h1 do
|
49
|
+
t "hello"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
RUBY
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/docs/pages/templates.rb
CHANGED
@@ -1,242 +1,242 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Pages
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
4
|
+
class Templates < ApplicationPage
|
5
|
+
def template
|
6
|
+
render Layout.new(title: "Templates in Phlex") do
|
7
|
+
render Markdown.new(<<~MD)
|
8
|
+
# Templates
|
9
|
+
|
10
|
+
Rather than use another language like ERB, HAML or Slim, Phlex provides a Ruby DSL for defining HTML templates.
|
11
|
+
|
12
|
+
You can create a view class by subclassing `Phlex::View` and defining a method called `template`. Within the `template` method, you can compose HTML markup by calling the name of any [HTML element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element).
|
13
|
+
|
14
|
+
The first argument to an HTML element method is the _text content_ for that element. For example, here’s a view with an `<h1>` element that says “Hello World!”
|
15
|
+
MD
|
16
|
+
|
17
|
+
render Example.new do |e|
|
18
|
+
e.tab "heading.rb", <<~RUBY
|
19
|
+
class Heading < Phlex::View
|
20
|
+
def template
|
21
|
+
h1 "Hello World!"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
RUBY
|
25
|
+
|
26
|
+
e.execute "Heading.new.call"
|
27
|
+
end
|
28
|
+
|
29
|
+
render Markdown.new(<<~MD)
|
30
|
+
The text content is always HTML-escaped, so it’s safe to use with user input.
|
31
|
+
|
32
|
+
## Attributes
|
33
|
+
|
34
|
+
You can add attributes to HTML elements by passing keyword arguments to the tag method. Underscores (`_`) in attribute names are automatically converted to dashes (`-`).
|
35
|
+
MD
|
36
|
+
|
37
|
+
render Example.new do |e|
|
38
|
+
e.tab "heading.rb", <<~RUBY
|
39
|
+
class Heading < Phlex::View
|
40
|
+
def template
|
41
|
+
h1 "Hello World!",
|
42
|
+
class: "text-xl font-bold",
|
43
|
+
aria_details: "details"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
RUBY
|
47
|
+
|
48
|
+
e.execute "Heading.new.call"
|
49
|
+
end
|
50
|
+
|
51
|
+
render Markdown.new(<<~MD)
|
52
|
+
You can also use *boolean* attributes. When set to `true`, the attribute will be rendered without a value, when _falsy_, the attribute isn’t rendered at all.
|
53
|
+
MD
|
54
|
+
|
55
|
+
render Example.new do |e|
|
56
|
+
e.tab "example.rb", <<~RUBY
|
57
|
+
class Example < Phlex::View
|
58
|
+
def template
|
59
|
+
input type: "radio", name: "channel", id: "1", checked: true
|
60
|
+
input type: "radio", name: "channel", id: "2", checked: false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
RUBY
|
64
|
+
|
65
|
+
e.execute "Example.new.call"
|
66
|
+
end
|
67
|
+
|
68
|
+
render Markdown.new(<<~MD)
|
69
|
+
## Nesting
|
70
|
+
|
71
|
+
Pass a block to an element method to nest other elements inside it.
|
72
|
+
MD
|
73
|
+
|
74
|
+
render Example.new do |e|
|
75
|
+
e.tab "nav.rb", <<~RUBY
|
76
|
+
class Nav < Phlex::View
|
77
|
+
def template
|
78
|
+
nav do
|
79
|
+
ul do
|
80
|
+
li { a "Home", href: "/" }
|
81
|
+
li { a "About", href: "/about" }
|
82
|
+
li { a "Contact", href: "/contact" }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
RUBY
|
88
|
+
|
89
|
+
e.execute "Nav.new.call"
|
90
|
+
end
|
91
|
+
|
92
|
+
render Markdown.new(<<~MD)
|
93
|
+
## Stand-alone text
|
94
|
+
|
95
|
+
You can also output text without wrapping it in an element by using the `text` method. All text content is HTML-escaped, so it’s safe to use with user input.
|
96
|
+
MD
|
97
|
+
|
98
|
+
render Example.new do |e|
|
99
|
+
e.tab "heading.rb", <<~RUBY
|
100
|
+
class Heading < Phlex::View
|
101
|
+
def template
|
102
|
+
h1 { strong "Hello "; text "World!" }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
RUBY
|
106
|
+
|
107
|
+
e.execute "Heading.new.call"
|
108
|
+
end
|
109
|
+
|
110
|
+
render Markdown.new(<<~MD)
|
111
|
+
## Whitespace
|
112
|
+
|
113
|
+
While the examples on this page have been formatted for readability, there is usually no whitespace between HTML tags. If you need to add some whitespace, you can use the `whitespace` method. This is useful for adding space between _inline_ elements to allow them to wrap.
|
114
|
+
MD
|
115
|
+
|
116
|
+
render Example.new do |e|
|
117
|
+
e.tab "links.rb", <<~RUBY
|
118
|
+
class Links < Phlex::View
|
119
|
+
def template
|
120
|
+
a "Home", href: "/"
|
121
|
+
whitespace
|
122
|
+
a "About", href: "/about"
|
123
|
+
whitespace
|
124
|
+
a "Contact", href: "/contact"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
RUBY
|
128
|
+
|
129
|
+
e.execute "Links.new.call"
|
130
|
+
end
|
131
|
+
|
132
|
+
render Markdown.new(<<~MD)
|
133
|
+
## Tokens and classes
|
134
|
+
|
135
|
+
The `tokens` method helps you define conditional HTML attribute tokens (such as CSS classes).
|
136
|
+
|
137
|
+
The `tokens` method accepts a splat of tokens that should always be output, and accepts keyword arguments for conditional tokens.
|
138
|
+
|
139
|
+
The keyword arguments allow you to specify under which conditions certain tokens are applicable. The keyword argument keys are the conditions and the values are the tokens. Conditions can be Procs or Symbols that map to a relevant method. The `:active?` Symbol, for example, maps to the `active?` instance method.
|
140
|
+
|
141
|
+
Here we have a `Link` view that produces an `<a>` tag with the CSS class `nav-item`. And if the link is _active_, we also apply the CSS class `nav-item-active`.
|
142
|
+
MD
|
143
|
+
|
144
|
+
render Example.new do |e|
|
145
|
+
e.tab "link.rb", <<~RUBY
|
146
|
+
class Link < Phlex::View
|
147
|
+
def initialize(text, to:, active:)
|
148
|
+
@text = text
|
149
|
+
@to = to
|
150
|
+
@active = active
|
151
|
+
end
|
152
|
+
|
153
|
+
def template
|
154
|
+
a @text, href: @to, class: tokens("nav-item",
|
155
|
+
active?: "nav-item-active")
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
|
160
|
+
def active? = @active
|
161
|
+
end
|
162
|
+
RUBY
|
163
|
+
|
164
|
+
e.tab "example.rb", <<~RUBY
|
165
|
+
class Example < Phlex::View
|
166
|
+
def template
|
167
|
+
nav do
|
168
|
+
ul do
|
169
|
+
li { render Link.new("Home", to: "/", active: true) }
|
170
|
+
li { render Link.new("About", to: "/about", active: false) }
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
RUBY
|
176
|
+
|
177
|
+
e.execute "Example.new.call"
|
178
|
+
end
|
179
|
+
|
180
|
+
render Markdown.new(<<~MD)
|
181
|
+
You can also use the `classes` helper method to create a token list of classes. Since this method returns a hash (e.g. `{ class: "your CSS classes here" }`), you can destructure it into a `class:` keyword argument using the `**` prefix operator.
|
182
|
+
MD
|
183
|
+
|
184
|
+
render Example.new do |e|
|
185
|
+
e.tab "link.rb", <<~RUBY
|
186
|
+
class Link < Phlex::View
|
187
|
+
def initialize(text, to:, active:)
|
188
|
+
@text = text
|
189
|
+
@to = to
|
190
|
+
@active = active
|
191
|
+
end
|
192
|
+
|
193
|
+
def template
|
194
|
+
a @text, href: @to, **classes("nav-item",
|
195
|
+
active?: "nav-item-active")
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def active? = @active
|
201
|
+
end
|
202
|
+
RUBY
|
203
|
+
|
204
|
+
e.tab "example.rb", <<~RUBY
|
205
|
+
class Example < Phlex::View
|
206
|
+
def template
|
207
|
+
nav do
|
208
|
+
ul do
|
209
|
+
li { render Link.new("Home", to: "/", active: true) }
|
210
|
+
li { render Link.new("About", to: "/about", active: false) }
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
RUBY
|
216
|
+
|
217
|
+
e.execute "Example.new.call"
|
218
|
+
end
|
219
|
+
|
220
|
+
render Markdown.new(<<~MD)
|
221
|
+
## The template element
|
222
|
+
|
223
|
+
Because the `template` method is used to define the view template itself, you need to use the method `template_tag` if you want to to render an HTML `<template>` tag.
|
224
|
+
MD
|
225
|
+
|
226
|
+
render Example.new do |e|
|
227
|
+
e.tab "example.rb", <<~RUBY
|
228
|
+
class Example < Phlex::View
|
229
|
+
def template
|
230
|
+
template_tag do
|
231
|
+
img src: "hidden.jpg", alt: "A hidden image."
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
RUBY
|
236
|
+
|
237
|
+
e.execute "Example.new.call"
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
242
|
end
|