froxy 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/.gitignore +10 -0
- data/.rubocop.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +113 -0
- data/LICENSE.txt +21 -0
- data/README.md +200 -0
- data/Rakefile +12 -0
- data/bin/console +15 -0
- data/bin/froxy +69 -0
- data/bin/setup +8 -0
- data/config.ru +10 -0
- data/froxy.gemspec +30 -0
- data/lib/froxy.rb +12 -0
- data/lib/froxy/esbuild/plugins/alias.js +47 -0
- data/lib/froxy/esbuild/plugins/css.js +50 -0
- data/lib/froxy/esbuild/plugins/env.js +19 -0
- data/lib/froxy/esbuild/plugins/images.js +32 -0
- data/lib/froxy/esbuild/plugins/load_style.js +22 -0
- data/lib/froxy/esbuild/plugins/root.js +14 -0
- data/lib/froxy/esbuild/utils.js +83 -0
- data/lib/froxy/helper.rb +11 -0
- data/lib/froxy/log_subscriber.rb +35 -0
- data/lib/froxy/monkey/side_load_assets.rb +61 -0
- data/lib/froxy/proxy.rb +83 -0
- data/lib/froxy/railtie.rb +35 -0
- data/lib/froxy/version.rb +5 -0
- data/package.json +15 -0
- data/yarn.lock +13 -0
- metadata +95 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ba923cd0a854a238a03b4fb501c027282477ccc90ca2005379001492f1f531f1
|
|
4
|
+
data.tar.gz: 43e8f7e24dc5ace4efc8c1ff7cba5af6786db2dd7a94b271bc83916ac09c6a84
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4fee298a6f956749521698d204ea6422d2a7fbfdc43d5e1d1443554cff8dba5bf59c57c92836c6759f79c1287614d20f6a7ee4ebd44e61f563021464666387b5
|
|
7
|
+
data.tar.gz: 5d5c56b0a0dff9581f339ee563dc83a5c00cc9896355996a887b95a70e461f07a4777b9cac202a0bed9cdda49df80f4cc32aac630b6be100a5b50e376140255f
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
|
2
|
+
|
|
3
|
+
## Our Pledge
|
|
4
|
+
|
|
5
|
+
In the interest of fostering an open and welcoming environment, we as
|
|
6
|
+
contributors and maintainers pledge to making participation in our project and
|
|
7
|
+
our community a harassment-free experience for everyone, regardless of age, body
|
|
8
|
+
size, disability, ethnicity, gender identity and expression, level of experience,
|
|
9
|
+
nationality, personal appearance, race, religion, or sexual identity and
|
|
10
|
+
orientation.
|
|
11
|
+
|
|
12
|
+
## Our Standards
|
|
13
|
+
|
|
14
|
+
Examples of behavior that contributes to creating a positive environment
|
|
15
|
+
include:
|
|
16
|
+
|
|
17
|
+
* Using welcoming and inclusive language
|
|
18
|
+
* Being respectful of differing viewpoints and experiences
|
|
19
|
+
* Gracefully accepting constructive criticism
|
|
20
|
+
* Focusing on what is best for the community
|
|
21
|
+
* Showing empathy towards other community members
|
|
22
|
+
|
|
23
|
+
Examples of unacceptable behavior by participants include:
|
|
24
|
+
|
|
25
|
+
* The use of sexualized language or imagery and unwelcome sexual attention or
|
|
26
|
+
advances
|
|
27
|
+
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
28
|
+
* Public or private harassment
|
|
29
|
+
* Publishing others' private information, such as a physical or electronic
|
|
30
|
+
address, without explicit permission
|
|
31
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
|
32
|
+
professional setting
|
|
33
|
+
|
|
34
|
+
## Our Responsibilities
|
|
35
|
+
|
|
36
|
+
Project maintainers are responsible for clarifying the standards of acceptable
|
|
37
|
+
behavior and are expected to take appropriate and fair corrective action in
|
|
38
|
+
response to any instances of unacceptable behavior.
|
|
39
|
+
|
|
40
|
+
Project maintainers have the right and responsibility to remove, edit, or
|
|
41
|
+
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
42
|
+
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
43
|
+
permanently any contributor for other behaviors that they deem inappropriate,
|
|
44
|
+
threatening, offensive, or harmful.
|
|
45
|
+
|
|
46
|
+
## Scope
|
|
47
|
+
|
|
48
|
+
This Code of Conduct applies both within project spaces and in public spaces
|
|
49
|
+
when an individual is representing the project or its community. Examples of
|
|
50
|
+
representing a project or community include using an official project e-mail
|
|
51
|
+
address, posting via an official social media account, or acting as an appointed
|
|
52
|
+
representative at an online or offline event. Representation of a project may be
|
|
53
|
+
further defined and clarified by project maintainers.
|
|
54
|
+
|
|
55
|
+
## Enforcement
|
|
56
|
+
|
|
57
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
58
|
+
reported by contacting the project team at joel@developwithstyle.com. All
|
|
59
|
+
complaints will be reviewed and investigated and will result in a response that
|
|
60
|
+
is deemed necessary and appropriate to the circumstances. The project team is
|
|
61
|
+
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
62
|
+
Further details of specific enforcement policies may be posted separately.
|
|
63
|
+
|
|
64
|
+
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
65
|
+
faith may face temporary or permanent repercussions as determined by other
|
|
66
|
+
members of the project's leadership.
|
|
67
|
+
|
|
68
|
+
## Attribution
|
|
69
|
+
|
|
70
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
|
71
|
+
available at [https://contributor-covenant.org/version/1/4][version]
|
|
72
|
+
|
|
73
|
+
[homepage]: https://contributor-covenant.org
|
|
74
|
+
[version]: https://contributor-covenant.org/version/1/4/
|
data/Gemfile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
source 'https://rubygems.org'
|
|
4
|
+
|
|
5
|
+
# Specify your gem's dependencies in froxy.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
gem 'combustion', '~> 1.3'
|
|
9
|
+
gem 'minitest', '~> 5.0'
|
|
10
|
+
gem 'minitest-focus'
|
|
11
|
+
gem 'puma'
|
|
12
|
+
gem 'rake', '~> 13.0'
|
|
13
|
+
gem 'rubocop', require: false
|
|
14
|
+
gem 'rubocop-minitest', require: false
|
|
15
|
+
gem 'rubocop-rake', require: false
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
froxy (0.1.0)
|
|
5
|
+
activesupport (>= 5.0.0, < 7.0)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
actionpack (6.1.3)
|
|
11
|
+
actionview (= 6.1.3)
|
|
12
|
+
activesupport (= 6.1.3)
|
|
13
|
+
rack (~> 2.0, >= 2.0.9)
|
|
14
|
+
rack-test (>= 0.6.3)
|
|
15
|
+
rails-dom-testing (~> 2.0)
|
|
16
|
+
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
|
17
|
+
actionview (6.1.3)
|
|
18
|
+
activesupport (= 6.1.3)
|
|
19
|
+
builder (~> 3.1)
|
|
20
|
+
erubi (~> 1.4)
|
|
21
|
+
rails-dom-testing (~> 2.0)
|
|
22
|
+
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
|
23
|
+
activesupport (6.1.3)
|
|
24
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
25
|
+
i18n (>= 1.6, < 2)
|
|
26
|
+
minitest (>= 5.1)
|
|
27
|
+
tzinfo (~> 2.0)
|
|
28
|
+
zeitwerk (~> 2.3)
|
|
29
|
+
ast (2.4.2)
|
|
30
|
+
builder (3.2.4)
|
|
31
|
+
combustion (1.3.1)
|
|
32
|
+
activesupport (>= 3.0.0)
|
|
33
|
+
railties (>= 3.0.0)
|
|
34
|
+
thor (>= 0.14.6)
|
|
35
|
+
concurrent-ruby (1.1.8)
|
|
36
|
+
crass (1.0.6)
|
|
37
|
+
erubi (1.10.0)
|
|
38
|
+
i18n (1.8.9)
|
|
39
|
+
concurrent-ruby (~> 1.0)
|
|
40
|
+
loofah (2.9.0)
|
|
41
|
+
crass (~> 1.0.2)
|
|
42
|
+
nokogiri (>= 1.5.9)
|
|
43
|
+
method_source (1.0.0)
|
|
44
|
+
mini_portile2 (2.5.0)
|
|
45
|
+
minitest (5.14.3)
|
|
46
|
+
minitest-focus (1.2.1)
|
|
47
|
+
minitest (>= 4, < 6)
|
|
48
|
+
nio4r (2.5.5)
|
|
49
|
+
nokogiri (1.11.1)
|
|
50
|
+
mini_portile2 (~> 2.5.0)
|
|
51
|
+
racc (~> 1.4)
|
|
52
|
+
parallel (1.20.1)
|
|
53
|
+
parser (3.0.0.0)
|
|
54
|
+
ast (~> 2.4.1)
|
|
55
|
+
puma (5.2.1)
|
|
56
|
+
nio4r (~> 2.0)
|
|
57
|
+
racc (1.5.2)
|
|
58
|
+
rack (2.2.3)
|
|
59
|
+
rack-test (1.1.0)
|
|
60
|
+
rack (>= 1.0, < 3)
|
|
61
|
+
rails-dom-testing (2.0.3)
|
|
62
|
+
activesupport (>= 4.2.0)
|
|
63
|
+
nokogiri (>= 1.6)
|
|
64
|
+
rails-html-sanitizer (1.3.0)
|
|
65
|
+
loofah (~> 2.3)
|
|
66
|
+
railties (6.1.3)
|
|
67
|
+
actionpack (= 6.1.3)
|
|
68
|
+
activesupport (= 6.1.3)
|
|
69
|
+
method_source
|
|
70
|
+
rake (>= 0.8.7)
|
|
71
|
+
thor (~> 1.0)
|
|
72
|
+
rainbow (3.0.0)
|
|
73
|
+
rake (13.0.3)
|
|
74
|
+
regexp_parser (2.0.3)
|
|
75
|
+
rexml (3.2.4)
|
|
76
|
+
rubocop (1.10.0)
|
|
77
|
+
parallel (~> 1.10)
|
|
78
|
+
parser (>= 3.0.0.0)
|
|
79
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
80
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
81
|
+
rexml
|
|
82
|
+
rubocop-ast (>= 1.2.0, < 2.0)
|
|
83
|
+
ruby-progressbar (~> 1.7)
|
|
84
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
|
85
|
+
rubocop-ast (1.4.1)
|
|
86
|
+
parser (>= 2.7.1.5)
|
|
87
|
+
rubocop-minitest (0.10.3)
|
|
88
|
+
rubocop (>= 0.87, < 2.0)
|
|
89
|
+
rubocop-rake (0.5.1)
|
|
90
|
+
rubocop
|
|
91
|
+
ruby-progressbar (1.11.0)
|
|
92
|
+
thor (1.1.0)
|
|
93
|
+
tzinfo (2.0.4)
|
|
94
|
+
concurrent-ruby (~> 1.0)
|
|
95
|
+
unicode-display_width (2.0.0)
|
|
96
|
+
zeitwerk (2.4.2)
|
|
97
|
+
|
|
98
|
+
PLATFORMS
|
|
99
|
+
ruby
|
|
100
|
+
|
|
101
|
+
DEPENDENCIES
|
|
102
|
+
combustion (~> 1.3)
|
|
103
|
+
froxy!
|
|
104
|
+
minitest (~> 5.0)
|
|
105
|
+
minitest-focus
|
|
106
|
+
puma
|
|
107
|
+
rake (~> 13.0)
|
|
108
|
+
rubocop
|
|
109
|
+
rubocop-minitest
|
|
110
|
+
rubocop-rake
|
|
111
|
+
|
|
112
|
+
BUNDLED WITH
|
|
113
|
+
2.2.9
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 Joel Moss
|
|
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,200 @@
|
|
|
1
|
+
# Froxy - Fast ESModule based Frontend Bundling for Rails
|
|
2
|
+
|
|
3
|
+
Froxy serves as a delivery machanism for all your frontend assets in Rails. It is
|
|
4
|
+
designed specifically for Rails applications, and can completely replace Webpacker and the Rails
|
|
5
|
+
asset pipeline (Sprockets) by bundling your ESModule based frontend code in real time and on demand.
|
|
6
|
+
It does this by proxying all frontend requests to the amazing [esbuild](https://esbuild.github.io).
|
|
7
|
+
|
|
8
|
+
## ! NOT PRODUCTION READY !
|
|
9
|
+
|
|
10
|
+
Froxy is currently an experimental library and does not yet have a production or deployment mode.
|
|
11
|
+
While you could deploy it and run it in production, it is highly recommended that you do not do
|
|
12
|
+
this. This is because almost every asset is bundled and built in real time by request. A future
|
|
13
|
+
production mode will likely include pre-built cachable assets.
|
|
14
|
+
|
|
15
|
+
-- _YOU HAVE BEEN WARNED!_
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- Real-time bundling of JS, JSX and CSS.
|
|
20
|
+
- Import CSS and other static assets (images, fonts, etc.)
|
|
21
|
+
- Serve assets from anywhere within the Rails root. (eg. `/app/views/layouts/application.css`, or `/lib/utils/time.js`)
|
|
22
|
+
- Side loaded JS/CSS for your layouts and views.
|
|
23
|
+
- [Tree shaking](https://esbuild.github.io/api/#tree-shaking).
|
|
24
|
+
- Code Splitting.
|
|
25
|
+
- Source Maps.
|
|
26
|
+
- Minification.
|
|
27
|
+
|
|
28
|
+
## Roadmap
|
|
29
|
+
|
|
30
|
+
In no particular order:
|
|
31
|
+
|
|
32
|
+
- Pre-bundling / cached assets.
|
|
33
|
+
- Typescript.
|
|
34
|
+
- CSS Modules.
|
|
35
|
+
- PostCSS support.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Froxy requires Rails 6+ and Node.
|
|
40
|
+
|
|
41
|
+
Add this line to your application's Gemfile:
|
|
42
|
+
|
|
43
|
+
```ruby
|
|
44
|
+
gem 'froxy'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
And then execute:
|
|
48
|
+
|
|
49
|
+
$ bundle install
|
|
50
|
+
|
|
51
|
+
Or install it yourself as:
|
|
52
|
+
|
|
53
|
+
$ gem install froxy
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
### Javascript
|
|
58
|
+
|
|
59
|
+
Import any JS:
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
import start from '/my/start' // Local absolute path
|
|
63
|
+
import start from './start' // Local relative path
|
|
64
|
+
import start from 'start' // From node modules
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
All JS is [bundled](https://esbuild.github.io/api/#bundle) by esbuild, which will inline any
|
|
68
|
+
imported dependencies into the file itself.
|
|
69
|
+
|
|
70
|
+
The JS file extension is not required and is assumed.
|
|
71
|
+
|
|
72
|
+
### CSS
|
|
73
|
+
|
|
74
|
+
CSS requested directly will return a plain stylesheet - as you would expect. But CSS that is
|
|
75
|
+
imported from JS will result in the requested CSS injected into an HTML `link` tag.
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
import '/my/styles.css'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Images/Fonts, etc.
|
|
82
|
+
|
|
83
|
+
When called directly, images are served directly - avoiding a call to esbuild. But when an image is
|
|
84
|
+
imported from JS or used in a `url()` in CSS, the URL path is returned.
|
|
85
|
+
|
|
86
|
+
Examples (where 'avatar.png' is located in '/app/images', but could be anywhere):
|
|
87
|
+
|
|
88
|
+
```javascript
|
|
89
|
+
// /app/views/home.js
|
|
90
|
+
import imgUrl from '../images/avatar.png' // imgUrl == "/app/images/avatar.png"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
body {
|
|
95
|
+
background-image: url('/app/images/avatar.png');
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Side Loaded JS/CSS
|
|
100
|
+
|
|
101
|
+
Froxy also has built in support for automatically side loading JS and CSS with your views and
|
|
102
|
+
layouts.
|
|
103
|
+
|
|
104
|
+
Just create a JS and/or CSS file with the same name as any view or layout, and make sure your
|
|
105
|
+
layouts include the `<%= yield :side_loaded_js %>` and `<%= yield :side_loaded_css %>`. Something
|
|
106
|
+
like this:
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<!DOCTYPE html>
|
|
110
|
+
<html>
|
|
111
|
+
<head>
|
|
112
|
+
<title>Hello World</title>
|
|
113
|
+
<%= yield :side_loaded_css %>
|
|
114
|
+
</head>
|
|
115
|
+
<body>
|
|
116
|
+
<%= yield %> <%= yield :side_loaded_js %>
|
|
117
|
+
</body>
|
|
118
|
+
</html>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
On each page request, Froxy will check if your layout and view has a JS/CSS file of the same name,
|
|
122
|
+
and include them into your layout HTML.
|
|
123
|
+
|
|
124
|
+
### Import aliases
|
|
125
|
+
|
|
126
|
+
Module aliases can be defined in your package.json, supporting local and node modules.
|
|
127
|
+
|
|
128
|
+
In your package.json:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
"froxy": {
|
|
132
|
+
"aliases": {
|
|
133
|
+
"_": "lodash", // a node module
|
|
134
|
+
"myalias": "/absolute/path/to/alias.js", // local path
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Then import:
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
import { map } from '_'
|
|
143
|
+
import axios from 'myaxios'
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Configuration
|
|
147
|
+
|
|
148
|
+
There are a few options that you can customise, and they are all defined in your `package.json`. For
|
|
149
|
+
example:
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
"froxy": {
|
|
153
|
+
"target": [],
|
|
154
|
+
"aliases": {
|
|
155
|
+
"_": "lodash"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `target`
|
|
161
|
+
|
|
162
|
+
See esbuild's documentation on [defining targets](https://esbuild.github.io/api/#target).
|
|
163
|
+
|
|
164
|
+
#### `inject`
|
|
165
|
+
|
|
166
|
+
See esbuild's documentation on [inject](https://esbuild.github.io/api/#inject).
|
|
167
|
+
|
|
168
|
+
#### `aliases`
|
|
169
|
+
|
|
170
|
+
See [aliases](#import-aliases)
|
|
171
|
+
|
|
172
|
+
#### `minify`
|
|
173
|
+
|
|
174
|
+
(default: `false`)
|
|
175
|
+
|
|
176
|
+
See esbuild's documentation on [minification](https://esbuild.github.io/api/#minify).
|
|
177
|
+
|
|
178
|
+
#### `sourcemap`
|
|
179
|
+
|
|
180
|
+
(default: `true`)
|
|
181
|
+
|
|
182
|
+
See esbuild's documentation on [sourcemap](https://esbuild.github.io/api/#sourcemap).
|
|
183
|
+
|
|
184
|
+
## Development
|
|
185
|
+
|
|
186
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
187
|
+
|
|
188
|
+
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).
|
|
189
|
+
|
|
190
|
+
## Contributing
|
|
191
|
+
|
|
192
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/joelmoss/froxy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/joelmoss/froxy/blob/master/CODE_OF_CONDUCT.md).
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
197
|
+
|
|
198
|
+
## Code of Conduct
|
|
199
|
+
|
|
200
|
+
Everyone interacting in the Froxy project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/joelmoss/froxy/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'froxy'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/froxy
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const cli = require('cac')()
|
|
6
|
+
const esbuild = require('esbuild')
|
|
7
|
+
const crypto = require('crypto')
|
|
8
|
+
|
|
9
|
+
const parsed = cli.parse()
|
|
10
|
+
const [absWorkingDir, entryPoint] = parsed.args
|
|
11
|
+
const entryPointKey = crypto.createHash('sha1').update(entryPoint).digest('base64')
|
|
12
|
+
|
|
13
|
+
const { resolve, config } = require('../lib/froxy/esbuild/utils')
|
|
14
|
+
const loadStylePlugin = require('../lib/froxy/esbuild/plugins/load_style')
|
|
15
|
+
const envPlugin = require('../lib/froxy/esbuild/plugins/env')
|
|
16
|
+
const aliasPlugin = require('../lib/froxy/esbuild/plugins/alias')(absWorkingDir)
|
|
17
|
+
const cssPlugin = require('../lib/froxy/esbuild/plugins/css')(absWorkingDir)
|
|
18
|
+
const imagesPlugin = require('../lib/froxy/esbuild/plugins/images')(absWorkingDir)
|
|
19
|
+
const rootPlugin = require('../lib/froxy/esbuild/plugins/root')(absWorkingDir)
|
|
20
|
+
|
|
21
|
+
const testPlugin = {
|
|
22
|
+
name: 'froxy.test',
|
|
23
|
+
setup(build) {
|
|
24
|
+
build.onResolve({ filter: /.*/ }, args => {
|
|
25
|
+
console.log('onResolve', args)
|
|
26
|
+
})
|
|
27
|
+
build.onLoad({ filter: /.*/ }, args => {
|
|
28
|
+
console.log('onLoad', args)
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const conf = config(absWorkingDir)
|
|
34
|
+
|
|
35
|
+
const buildOptions = {
|
|
36
|
+
absWorkingDir,
|
|
37
|
+
entryPoints: [entryPoint],
|
|
38
|
+
bundle: true,
|
|
39
|
+
target: conf.target,
|
|
40
|
+
minify: conf.minify,
|
|
41
|
+
inject: conf.inject,
|
|
42
|
+
sourcemap: conf.sourcemap,
|
|
43
|
+
format: 'esm',
|
|
44
|
+
splitting: true,
|
|
45
|
+
outdir: 'public/froxy/build',
|
|
46
|
+
outbase: '.',
|
|
47
|
+
logLevel: 'error',
|
|
48
|
+
define: {
|
|
49
|
+
global: 'globalThis',
|
|
50
|
+
'process.env.NODE_ENV': `"${process.env.NODE_ENV || 'development'}"`,
|
|
51
|
+
'process.env.RAILS_ENV': `"${process.env.RAILS_ENV || 'development'}"`
|
|
52
|
+
},
|
|
53
|
+
// metafile: `public/froxy/meta/${entryPointKey}.json`,
|
|
54
|
+
plugins: [aliasPlugin, envPlugin, loadStylePlugin, cssPlugin, imagesPlugin, rootPlugin]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
esbuild.build(buildOptions).catch(() => {
|
|
58
|
+
process.exit(1)
|
|
59
|
+
})
|
|
60
|
+
// .then(results => {
|
|
61
|
+
// // console.log(results)
|
|
62
|
+
// const meta = JSON.parse(fs.readFileSync(`${absWorkingDir}/${buildOptions.metafile}`, 'utf8'))
|
|
63
|
+
// console.log(meta)
|
|
64
|
+
// // console.log(Object.keys(meta.inputs))
|
|
65
|
+
// // console.log(Object.keys(meta.outputs))
|
|
66
|
+
|
|
67
|
+
// process.stdout.write(buildOptions.metafile)
|
|
68
|
+
// // process.stdout.write(fs.readFileSync(`${absWorkingDir}/${buildOptions.metafile}`, 'utf8'))
|
|
69
|
+
// })
|
data/bin/setup
ADDED
data/config.ru
ADDED
data/froxy.gemspec
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/froxy/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'froxy'
|
|
7
|
+
spec.version = Froxy::VERSION
|
|
8
|
+
spec.authors = ['Joel Moss']
|
|
9
|
+
spec.email = ['joel@developwithstyle.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'Fast ESModule based Frontend Bundling for Rails'
|
|
12
|
+
spec.homepage = 'https://github.com/joelmoss/froxy'
|
|
13
|
+
spec.license = 'MIT'
|
|
14
|
+
spec.required_ruby_version = Gem::Requirement.new('>= 2.7.0')
|
|
15
|
+
|
|
16
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
17
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
|
18
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/releases"
|
|
19
|
+
|
|
20
|
+
# Specify which files should be added to the gem when it is released.
|
|
21
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
22
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
23
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
24
|
+
end
|
|
25
|
+
spec.bindir = 'exe'
|
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
|
|
29
|
+
spec.add_runtime_dependency 'activesupport', ['>= 5.0.0', '< 7.0']
|
|
30
|
+
end
|
data/lib/froxy.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const { resolve, config } = require('../utils')
|
|
2
|
+
|
|
3
|
+
// esbuild plugin to support module aliases as defined in package.json. Supports local and node
|
|
4
|
+
// modules.
|
|
5
|
+
//
|
|
6
|
+
// In your package.json:
|
|
7
|
+
//
|
|
8
|
+
// "froxy": {
|
|
9
|
+
// "aliases": {
|
|
10
|
+
// "_": "lodash", // a node module
|
|
11
|
+
// "myalias": "/absolutle/path/to/alias.js", // local path
|
|
12
|
+
// }
|
|
13
|
+
// }
|
|
14
|
+
//
|
|
15
|
+
// Then import:
|
|
16
|
+
//
|
|
17
|
+
// import { map } from '_'
|
|
18
|
+
// import axios from 'myaxios'
|
|
19
|
+
//
|
|
20
|
+
module.exports = absWorkingDir => ({
|
|
21
|
+
name: 'froxy.alias',
|
|
22
|
+
setup(build) {
|
|
23
|
+
let map = []
|
|
24
|
+
let aliases = {}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
map = config(absWorkingDir).aliases
|
|
28
|
+
aliases = Object.keys(map)
|
|
29
|
+
} catch {
|
|
30
|
+
// Fail silently, as there is no package.json.
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (aliases.length > 0) {
|
|
35
|
+
const re = new RegExp(`^${aliases.map(x => escapeRegExp(x)).join('|')}$`)
|
|
36
|
+
|
|
37
|
+
build.onResolve({ filter: re }, async ({ resolveDir, path }) => ({
|
|
38
|
+
path: await resolve(absWorkingDir, resolveDir, map[path], { fallbackToEsbuild: true })
|
|
39
|
+
}))
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
function escapeRegExp(string) {
|
|
45
|
+
// $& means the whole matched string
|
|
46
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
47
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
|
|
4
|
+
const { resolve } = require('../utils')
|
|
5
|
+
|
|
6
|
+
module.exports = absWorkingDir => ({
|
|
7
|
+
name: 'froxy.css',
|
|
8
|
+
setup(build) {
|
|
9
|
+
// Handle CSS imported from JS.
|
|
10
|
+
build.onResolve({ filter: /\.css$/ }, args => {
|
|
11
|
+
let resolvedPath = resolve(absWorkingDir, args.resolveDir, args.path)
|
|
12
|
+
|
|
13
|
+
if (resolvedPath === args.path) {
|
|
14
|
+
const nodeModulesDir = path.join(absWorkingDir, 'node_modules')
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
fs.accessSync(nodeModulesDir, fs.constants.R_OK)
|
|
18
|
+
resolvedPath = path.join(nodeModulesDir, args.path)
|
|
19
|
+
} catch {
|
|
20
|
+
// Do nothing
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
path: resolvedPath,
|
|
26
|
+
namespace: args.importer.endsWith('.js') ? 'cssFromJs' : 'file'
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Handles CSS imports from JS (eg `import from 'some.css'`) by simply marking it as external.
|
|
31
|
+
// This then allows the browser to handle the import. However, browsers do not yet support
|
|
32
|
+
// importing non-JS assets, and will not include the CSS. So the Froxy proxy will return the
|
|
33
|
+
// imported CSS as a JS file that inserts the CSS directly into the DOM. This unfortunately may
|
|
34
|
+
// result in a flash of unstyled content (FOUC).
|
|
35
|
+
//
|
|
36
|
+
// --- OR
|
|
37
|
+
//
|
|
38
|
+
// esbuild returns the content of both the JS and CSS. Then Froxy returns the JS as normal,
|
|
39
|
+
// and additionally includes the CSS directly into the rendered HTML. This way, there will be no
|
|
40
|
+
// FOUC. But this method is a little more complex, as Froxy will need to somehow pass the CSS
|
|
41
|
+
// content to Rails for insertion into the rendered view.
|
|
42
|
+
build.onLoad({ filter: /\.css$/, namespace: 'cssFromJs' }, args => ({
|
|
43
|
+
contents: `
|
|
44
|
+
import loadStyle from 'loadStyle'
|
|
45
|
+
loadStyle("${args.path.slice(absWorkingDir.length)}")
|
|
46
|
+
`,
|
|
47
|
+
loader: 'js'
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: 'froxy.env',
|
|
3
|
+
setup(build) {
|
|
4
|
+
// Intercept import paths called "env" so esbuild doesn't attempt
|
|
5
|
+
// to map them to a file system location. Tag them with the "env-ns"
|
|
6
|
+
// namespace to reserve them for this plugin.
|
|
7
|
+
build.onResolve({ filter: /^env$/ }, args => ({
|
|
8
|
+
path: args.path,
|
|
9
|
+
namespace: 'env-ns'
|
|
10
|
+
}))
|
|
11
|
+
|
|
12
|
+
// Load paths tagged with the "env-ns" namespace and behave as if
|
|
13
|
+
// they point to a JSON file containing the environment variables.
|
|
14
|
+
build.onLoad({ filter: /.*/, namespace: 'env-ns' }, () => ({
|
|
15
|
+
contents: JSON.stringify(process.env),
|
|
16
|
+
loader: 'json'
|
|
17
|
+
}))
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const { resolve } = require('../utils')
|
|
2
|
+
|
|
3
|
+
module.exports = absWorkingDir => ({
|
|
4
|
+
name: 'froxy.images',
|
|
5
|
+
setup(build) {
|
|
6
|
+
const IMAGE_TYPES = /\.(png|gif|jpe?g|svg|ico|webp|avif)$/
|
|
7
|
+
|
|
8
|
+
// Froxy proxy will render images directly. esbuild will just rewrite the path when an image is
|
|
9
|
+
// imported from JS, and embed the file name into the bundle as a string. This string is
|
|
10
|
+
// exported using the default export. Including an image in CSS using `url()`, will simply
|
|
11
|
+
// return the relative URL of the image.
|
|
12
|
+
build.onResolve({ filter: IMAGE_TYPES }, args => {
|
|
13
|
+
const resolvedPath = resolve(absWorkingDir, args.resolveDir, args.path)
|
|
14
|
+
|
|
15
|
+
if (args.importer.endsWith('.css')) {
|
|
16
|
+
return {
|
|
17
|
+
path: resolvedPath.slice(absWorkingDir.length),
|
|
18
|
+
external: true
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
return { path: resolvedPath }
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
build.onLoad({ filter: IMAGE_TYPES }, args => {
|
|
26
|
+
return {
|
|
27
|
+
contents: `export default '${args.path.slice(absWorkingDir.length)}';`,
|
|
28
|
+
loader: 'js'
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
})
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
name: "froxy.loadStyle",
|
|
3
|
+
|
|
4
|
+
setup(build) {
|
|
5
|
+
build.onResolve({ filter: /^loadStyle$/ }, () => ({
|
|
6
|
+
path: "loadStyle",
|
|
7
|
+
namespace: "loadStyleShim",
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
build.onLoad({ filter: /^loadStyle$/, namespace: "loadStyleShim" }, () => ({
|
|
11
|
+
contents: `
|
|
12
|
+
export default function (path) {
|
|
13
|
+
const ele = document.createElement('link')
|
|
14
|
+
ele.setAttribute('rel', 'stylesheet')
|
|
15
|
+
ele.setAttribute('media', 'all')
|
|
16
|
+
ele.setAttribute('href', path)
|
|
17
|
+
document.head.appendChild(ele)
|
|
18
|
+
}
|
|
19
|
+
`,
|
|
20
|
+
}));
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
|
|
3
|
+
module.exports = absWorkingDir => ({
|
|
4
|
+
name: 'froxy.root',
|
|
5
|
+
setup(build) {
|
|
6
|
+
// Resolves paths starting with a `/` to the Rails root.
|
|
7
|
+
//
|
|
8
|
+
// Example:
|
|
9
|
+
// import '/my/lib.js' //-> import '{Rails.root}/my/lib.js'
|
|
10
|
+
build.onResolve({ filter: /^\// }, args => ({
|
|
11
|
+
path: path.join(absWorkingDir, args.path)
|
|
12
|
+
}))
|
|
13
|
+
}
|
|
14
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const esbuild = require('esbuild')
|
|
4
|
+
|
|
5
|
+
// Resolve the given path (`p`) to an absolute path.
|
|
6
|
+
const resolve = (absWorkingDir, b, p, options = {}) => {
|
|
7
|
+
options = {
|
|
8
|
+
fallbackToEsbuild: false,
|
|
9
|
+
...options
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (p.startsWith('/')) return path.resolve(absWorkingDir, p.slice(1))
|
|
13
|
+
if (p.startsWith('.')) return path.resolve(b, p)
|
|
14
|
+
|
|
15
|
+
if (options.fallbackToEsbuild) {
|
|
16
|
+
return (async () => await esbuildResolve(p, b))()
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return p
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const config = absWorkingDir => {
|
|
23
|
+
let packageConfig = {}
|
|
24
|
+
const defaultConfig = {
|
|
25
|
+
minify: false,
|
|
26
|
+
sourcemap: true
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const pkg = fs.readFileSync(path.join(absWorkingDir, 'package.json'))
|
|
31
|
+
packageConfig = JSON.parse(pkg).froxy
|
|
32
|
+
} catch {
|
|
33
|
+
// Fail silently, as there is no package.json.
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { ...defaultConfig, ...packageConfig }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Resolves a module using esbuild module resolution.
|
|
40
|
+
//
|
|
41
|
+
// @param {string} id Module to resolve
|
|
42
|
+
// @param {string} [resolveDir] The directory to resolve from
|
|
43
|
+
// @returns {string} The resolved module
|
|
44
|
+
async function esbuildResolve(id, resolveDir = process.cwd()) {
|
|
45
|
+
let _resolve
|
|
46
|
+
const resolvedPromise = new Promise(resolve => (_resolve = resolve))
|
|
47
|
+
return Promise.race([
|
|
48
|
+
resolvedPromise,
|
|
49
|
+
esbuild
|
|
50
|
+
.build({
|
|
51
|
+
sourcemap: false,
|
|
52
|
+
write: false,
|
|
53
|
+
bundle: true,
|
|
54
|
+
format: 'esm',
|
|
55
|
+
logLevel: 'silent',
|
|
56
|
+
platform: 'node',
|
|
57
|
+
stdin: {
|
|
58
|
+
contents: `import ${JSON.stringify(id)}`,
|
|
59
|
+
loader: 'js',
|
|
60
|
+
resolveDir,
|
|
61
|
+
sourcefile: __filename
|
|
62
|
+
},
|
|
63
|
+
plugins: [
|
|
64
|
+
{
|
|
65
|
+
name: 'esbuildResolve',
|
|
66
|
+
setup(build) {
|
|
67
|
+
build.onLoad({ filter: /.*/ }, ({ path }) => {
|
|
68
|
+
id = path
|
|
69
|
+
_resolve(id)
|
|
70
|
+
return { contents: '' }
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
})
|
|
76
|
+
.then(() => id)
|
|
77
|
+
])
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
resolve,
|
|
82
|
+
config
|
|
83
|
+
}
|
data/lib/froxy/helper.rb
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/log_subscriber'
|
|
4
|
+
|
|
5
|
+
module Froxy
|
|
6
|
+
class LogSubscriber < ActiveSupport::LogSubscriber
|
|
7
|
+
VIEWS_PATTERN = %r{^app/views/}.freeze
|
|
8
|
+
|
|
9
|
+
def side_loaded_assets(event)
|
|
10
|
+
return if (asset_types = event.payload[:asset_types]).empty?
|
|
11
|
+
|
|
12
|
+
identifier_from_root = from_rails_root(event.payload[:identifier])
|
|
13
|
+
|
|
14
|
+
info do
|
|
15
|
+
message = +" Side loaded #{asset_types.join(',')} for #{identifier_from_root}"
|
|
16
|
+
message << " (Duration: #{event.duration.round(1)}ms | Allocations: #{event.allocations})"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
EMPTY = ''
|
|
23
|
+
def from_rails_root(string)
|
|
24
|
+
string = string.sub(rails_root, EMPTY)
|
|
25
|
+
string.sub!(VIEWS_PATTERN, EMPTY)
|
|
26
|
+
string
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def rails_root
|
|
30
|
+
@rails_root ||= "#{Rails.root}/"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Froxy::LogSubscriber.attach_to :action_view
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'froxy/log_subscriber'
|
|
4
|
+
|
|
5
|
+
module Froxy
|
|
6
|
+
module Monkey
|
|
7
|
+
module SideLoadAssets
|
|
8
|
+
def render_template(view, template, layout_name, locals)
|
|
9
|
+
return super if template.type != :html
|
|
10
|
+
|
|
11
|
+
# Side load layout assets - if any.
|
|
12
|
+
if layout_name
|
|
13
|
+
layout = find_layout(layout_name, locals.keys, [formats.first])
|
|
14
|
+
layout && side_load_assets(view, layout)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Side load view assets - if any.
|
|
18
|
+
side_load_assets view, template
|
|
19
|
+
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def side_load_assets(view, tpl)
|
|
26
|
+
path = tpl.short_identifier.delete_suffix('.html.erb')
|
|
27
|
+
|
|
28
|
+
instrument :side_loaded_assets, identifier: tpl.identifier, asset_types: [] do |payload|
|
|
29
|
+
side_load_js path, view, payload
|
|
30
|
+
side_load_css path, view, payload
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def side_load_js(path, view, log_payload)
|
|
35
|
+
# Check that the file actually exists.
|
|
36
|
+
return unless Rails.root.join(path).sub_ext('.js').exist?
|
|
37
|
+
|
|
38
|
+
view.content_for :side_loaded_js do
|
|
39
|
+
view.javascript_include_tag(path, type: :module).tap do |tag|
|
|
40
|
+
!tag.nil? && (log_payload[:asset_types] << :js)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def side_load_css(path, view, log_payload)
|
|
46
|
+
# Check that the file actually exists.
|
|
47
|
+
return unless Rails.root.join(path).sub_ext('.css').exist?
|
|
48
|
+
|
|
49
|
+
view.content_for :side_loaded_css do
|
|
50
|
+
view.stylesheet_link_tag(path).tap do |tag|
|
|
51
|
+
!tag.nil? && (log_payload[:asset_types] << :css)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def instrument(action, payload, &block)
|
|
57
|
+
ActiveSupport::Notifications.instrument("#{action}.action_view", payload, &block)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/froxy/proxy.rb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'rack/utils'
|
|
5
|
+
|
|
6
|
+
# Proxies files to esbuild.
|
|
7
|
+
module Froxy
|
|
8
|
+
class Proxy
|
|
9
|
+
BUILD_PATH = 'public/froxy/build'
|
|
10
|
+
CLI = File.expand_path('../../bin/froxy', __dir__)
|
|
11
|
+
IMAGE_TYPES = /\.(png|gif|jpeg|jpg|svg|ico|webp|avif)$/i.freeze
|
|
12
|
+
FILE_EXT_MAP = {
|
|
13
|
+
'.jsx' => '.js'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def initialize(app)
|
|
17
|
+
@app = app
|
|
18
|
+
@file_server = Rack::Files.new(Rails.root)
|
|
19
|
+
@build_file_server = Rack::Files.new(Rails.root.join(BUILD_PATH))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(env)
|
|
23
|
+
req = Rack::Request.new(env)
|
|
24
|
+
path_info = req.path_info
|
|
25
|
+
|
|
26
|
+
if req.get? || req.head?
|
|
27
|
+
# Let images through.
|
|
28
|
+
return @file_server.call(env) if IMAGE_TYPES.match?(path_info)
|
|
29
|
+
|
|
30
|
+
# Let JS sourcemaps through.
|
|
31
|
+
return @build_file_server.call(env) if /\.js\.map$/i.match?(path_info)
|
|
32
|
+
|
|
33
|
+
# Let esbuild handle JS and CSS.
|
|
34
|
+
if /\.(js|jsx|css)$/i.match?(path_info)
|
|
35
|
+
return unless (path = clean_path(path_info))
|
|
36
|
+
return [404, {}, []] unless file_readable?(path)
|
|
37
|
+
return @file_server.call(env) unless Rails.application.config.froxy.use_esbuild
|
|
38
|
+
|
|
39
|
+
return build env, req, path
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@app.call req.env
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def path_to_file(env, request, path)
|
|
49
|
+
ext = Pathname.new(path).extname
|
|
50
|
+
request.path_info = path.sub(/#{ext}$/, FILE_EXT_MAP[ext]) if FILE_EXT_MAP.key?(ext)
|
|
51
|
+
|
|
52
|
+
@build_file_server.call env
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Build the file from the given `path` using ESbuild. Returns a Rack::Response.
|
|
56
|
+
def build(env, request, path)
|
|
57
|
+
stdout, stderr, status = Open3.capture3(CLI, Rails.root.to_s, path)
|
|
58
|
+
|
|
59
|
+
if status.success?
|
|
60
|
+
Rails.logger.info "[froxy] built #{path}"
|
|
61
|
+
raise "[froxy] build failed: #{stderr}" unless stderr.empty?
|
|
62
|
+
else
|
|
63
|
+
non_empty_streams = [stdout, stderr].delete_if(&:empty?)
|
|
64
|
+
raise "[froxy] build failed:\n#{non_empty_streams.join("\n\n")}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
path_to_file env, request, path
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def file_readable?(path)
|
|
71
|
+
file_stat = File.stat(Rails.root.join(path.delete_prefix('/').b).to_s)
|
|
72
|
+
rescue SystemCallError
|
|
73
|
+
false
|
|
74
|
+
else
|
|
75
|
+
file_stat.file? && file_stat.readable?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def clean_path(path_info)
|
|
79
|
+
path = Rack::Utils.unescape_path path_info.chomp('/').delete_prefix('/')
|
|
80
|
+
Rack::Utils.clean_path_info path if Rack::Utils.valid_path? path
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/railtie'
|
|
4
|
+
|
|
5
|
+
module Froxy
|
|
6
|
+
class Railtie < ::Rails::Railtie
|
|
7
|
+
config.froxy = ActiveSupport::OrderedOptions.new
|
|
8
|
+
|
|
9
|
+
initializer 'froxy.configuration' do |app|
|
|
10
|
+
options = app.config.froxy
|
|
11
|
+
|
|
12
|
+
options.use_proxy = true if options.use_proxy.nil?
|
|
13
|
+
options.use_esbuild = true if options.use_esbuild.nil?
|
|
14
|
+
options.side_load_assets = true if options.side_load_assets.nil?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
initializer 'froxy.proxy' do |app|
|
|
18
|
+
next unless app.config.froxy.use_proxy
|
|
19
|
+
|
|
20
|
+
app.middleware.insert_after ActionDispatch::Static, Froxy::Proxy
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
initializer 'froxy.side_load_assets' do |app|
|
|
24
|
+
next unless app.config.froxy.side_load_assets
|
|
25
|
+
|
|
26
|
+
ActiveSupport.on_load :action_view do
|
|
27
|
+
require 'froxy/monkey/side_load_assets'
|
|
28
|
+
ActionView::TemplateRenderer.prepend Froxy::Monkey::SideLoadAssets
|
|
29
|
+
|
|
30
|
+
require 'froxy/helper'
|
|
31
|
+
include Froxy::Helper
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
data/package.json
ADDED
data/yarn.lock
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
2
|
+
# yarn lockfile v1
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
cac@^6.7.1:
|
|
6
|
+
version "6.7.2"
|
|
7
|
+
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.2.tgz#e7f0d21f4776c46c7d0de7976e56fa5562e17597"
|
|
8
|
+
integrity sha512-w0bH1IF9rEjdi0a6lTtlXYT+vBZEJL9oytaXXRdsD68MH6+SrZGOGsu7s2saHQvYXqwo/wBdkW75tt8wFpj+mw==
|
|
9
|
+
|
|
10
|
+
esbuild@^0.8.1:
|
|
11
|
+
version "0.8.48"
|
|
12
|
+
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.48.tgz#a57e4dde84ec56da1c6ecaefee97e9da6c5b00b5"
|
|
13
|
+
integrity sha512-lrH8lA8wWQ6Lpe1z6C7ZZaFSmRsUlcQAqe16nf7ITySQ7MV4+vI7qAqQlT/u+c3+9AL3VXmT4MXTxV2e63pO4A==
|
metadata
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: froxy
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Joel Moss
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2021-02-22 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activesupport
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 5.0.0
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '7.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: 5.0.0
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.0'
|
|
33
|
+
description:
|
|
34
|
+
email:
|
|
35
|
+
- joel@developwithstyle.com
|
|
36
|
+
executables: []
|
|
37
|
+
extensions: []
|
|
38
|
+
extra_rdoc_files: []
|
|
39
|
+
files:
|
|
40
|
+
- ".gitignore"
|
|
41
|
+
- ".rubocop.yml"
|
|
42
|
+
- CODE_OF_CONDUCT.md
|
|
43
|
+
- Gemfile
|
|
44
|
+
- Gemfile.lock
|
|
45
|
+
- LICENSE.txt
|
|
46
|
+
- README.md
|
|
47
|
+
- Rakefile
|
|
48
|
+
- bin/console
|
|
49
|
+
- bin/froxy
|
|
50
|
+
- bin/setup
|
|
51
|
+
- config.ru
|
|
52
|
+
- froxy.gemspec
|
|
53
|
+
- lib/froxy.rb
|
|
54
|
+
- lib/froxy/esbuild/plugins/alias.js
|
|
55
|
+
- lib/froxy/esbuild/plugins/css.js
|
|
56
|
+
- lib/froxy/esbuild/plugins/env.js
|
|
57
|
+
- lib/froxy/esbuild/plugins/images.js
|
|
58
|
+
- lib/froxy/esbuild/plugins/load_style.js
|
|
59
|
+
- lib/froxy/esbuild/plugins/root.js
|
|
60
|
+
- lib/froxy/esbuild/utils.js
|
|
61
|
+
- lib/froxy/helper.rb
|
|
62
|
+
- lib/froxy/log_subscriber.rb
|
|
63
|
+
- lib/froxy/monkey/side_load_assets.rb
|
|
64
|
+
- lib/froxy/proxy.rb
|
|
65
|
+
- lib/froxy/railtie.rb
|
|
66
|
+
- lib/froxy/version.rb
|
|
67
|
+
- package.json
|
|
68
|
+
- yarn.lock
|
|
69
|
+
homepage: https://github.com/joelmoss/froxy
|
|
70
|
+
licenses:
|
|
71
|
+
- MIT
|
|
72
|
+
metadata:
|
|
73
|
+
homepage_uri: https://github.com/joelmoss/froxy
|
|
74
|
+
source_code_uri: https://github.com/joelmoss/froxy
|
|
75
|
+
changelog_uri: https://github.com/joelmoss/froxy/releases
|
|
76
|
+
post_install_message:
|
|
77
|
+
rdoc_options: []
|
|
78
|
+
require_paths:
|
|
79
|
+
- lib
|
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: 2.7.0
|
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
requirements: []
|
|
91
|
+
rubygems_version: 3.2.3
|
|
92
|
+
signing_key:
|
|
93
|
+
specification_version: 4
|
|
94
|
+
summary: Fast ESModule based Frontend Bundling for Rails
|
|
95
|
+
test_files: []
|