proscenium 0.1.0.alpha2-x86_64-linux → 0.1.0.alpha3-x86_64-linux
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +158 -29
- data/bin/esbuild +0 -0
- data/bin/parcel_css +0 -0
- data/config/routes.rb +1 -1
- data/lib/proscenium/compilers/esbuild/compile_error.js +145 -0
- data/lib/proscenium/compilers/esbuild/css_plugin.js +76 -0
- data/lib/proscenium/compilers/esbuild/env_plugin.js +6 -4
- data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +53 -0
- data/lib/proscenium/compilers/esbuild/import_map.js +59 -0
- data/lib/proscenium/compilers/esbuild/resolve_plugin.js +38 -35
- data/lib/proscenium/compilers/esbuild/setup_plugin.js +23 -22
- data/lib/proscenium/{cli → compilers}/esbuild/solidjs_plugin.js +5 -4
- data/lib/proscenium/compilers/esbuild.bench.js +7 -3
- data/lib/proscenium/compilers/esbuild.js +60 -19
- data/lib/proscenium/helper.rb +2 -2
- data/lib/proscenium/middleware/base.rb +13 -3
- data/lib/proscenium/middleware/esbuild.rb +14 -0
- data/lib/proscenium/middleware/parcel_css.rb +30 -3
- data/lib/proscenium/middleware.rb +3 -0
- data/lib/proscenium/railtie.rb +16 -18
- data/lib/proscenium/runtime/component_manager/index.js +7 -7
- data/lib/proscenium/runtime/component_manager/{render_component.js → render_component.jsx} +9 -13
- data/lib/proscenium/version.rb +1 -1
- metadata +8 -14
- data/lib/proscenium/cli/argument_error.js +0 -24
- data/lib/proscenium/cli/builders/index.js +0 -1
- data/lib/proscenium/cli/builders/javascript.js +0 -45
- data/lib/proscenium/cli/builders/react.js +0 -60
- data/lib/proscenium/cli/builders/solid.js +0 -46
- data/lib/proscenium/cli/esbuild/env_plugin.js +0 -21
- data/lib/proscenium/cli/esbuild/resolve_plugin.js +0 -136
- data/lib/proscenium/cli/js_builder.js +0 -194
- data/lib/proscenium/cli/solid.js +0 -15
- data/lib/proscenium/cli/utils.js +0 -93
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16d95e62d81df0d7cdef528f9ef167a457b08d47091fa24baecd0543794cb9df
|
4
|
+
data.tar.gz: 0d3e128c70dd071fd8680bbfe76e70eb810a65a99d7541f9d2f52cb35da0eeff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c81a0c1f16041ce97cb03a880ec1877faaada6ace634edf0840b8548136843cc91e09340c61b00416cf41230d7e641b2d446e51c884487d8ac4fc2dd944d1f0a
|
7
|
+
data.tar.gz: f0e06b2652fe8403b966c896dc81183a4ce48dc3643559ff0b268f9dbb4a36d5ba8008c87a80eefeabafd6e6b690d8b22b0d874482b62493631213b83594acb2
|
data/README.md
CHANGED
@@ -1,65 +1,195 @@
|
|
1
1
|
# Proscenium
|
2
2
|
|
3
|
-
- Serve assets from anywhere within
|
4
|
-
-
|
5
|
-
- JS
|
6
|
-
- Dynamic imports
|
3
|
+
- Serve assets from anywhere within your Rails root.
|
4
|
+
- Automatically side load JS/CSS for your layouts and views.
|
5
|
+
- Import JS and CSS from node_modules, URL, local (relative, absolute)
|
7
6
|
- Real-time bundling of JS, JSX and CSS.
|
8
|
-
- Import
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
- Nested CSS
|
13
|
-
- Import CSS Modules
|
7
|
+
- Import Map
|
8
|
+
- CSS Modules
|
9
|
+
- Minification
|
14
10
|
|
15
11
|
## Installation
|
16
12
|
|
17
|
-
Add this line to your application's Gemfile:
|
13
|
+
Add this line to your application's Gemfile, and you're good to go:
|
18
14
|
|
19
15
|
```ruby
|
20
16
|
gem 'proscenium'
|
21
17
|
```
|
22
18
|
|
23
|
-
##
|
19
|
+
## Frontend Code Anywhere!
|
20
|
+
|
21
|
+
Proscenium believes that your frontend code is just as important as your backend code, and is not an
|
22
|
+
afterthought - they should be first class citizens of your Rails app. So instead of throwing all
|
23
|
+
your JS and CSS into a "app/assets" directory, put them wherever you want!
|
24
|
+
|
25
|
+
For example, if you have JS that is used by your `UsersController#index`, then put it in
|
26
|
+
`app/views/users/index.js`. Or if you have some CSS that is used by your entire application, put it
|
27
|
+
in `app/views/layouts/application.css`. Maybe you have a few JS utility functions, so put them in
|
28
|
+
`lib/utils.js`.
|
29
|
+
|
30
|
+
Simply create your JS(X) and CSS anywhere you want, and they will be served by your Rails app.
|
31
|
+
|
32
|
+
Using the examples above...
|
33
|
+
|
34
|
+
- `app/views/users/index.js` => `https://yourapp.com/app/views/users/index.js`
|
35
|
+
- `app/views/layouts/application.css` => `https://yourapp.com/app/views/layouts/application.css`
|
36
|
+
- `lib/utils.js` => `https://yourapp.com/lib/utils.js`
|
37
|
+
|
38
|
+
## Importing
|
39
|
+
|
40
|
+
Proscenium supports importing JS and CSS from `node_modules`, URL, and local (relative, absolute).
|
41
|
+
|
42
|
+
Imports are assumed to be JS files, so there is no need to specify the file extesnion in such cases.
|
43
|
+
But you can if you like. All other file types must be specified using their full file name and
|
44
|
+
extension.
|
45
|
+
|
46
|
+
### URL Imports
|
47
|
+
|
48
|
+
Any import beginning with `http://` or `https://` will be fetched from the URL provided:
|
49
|
+
|
50
|
+
```js
|
51
|
+
import React from 'https://esm.sh/react`
|
52
|
+
```
|
53
|
+
|
54
|
+
```css
|
55
|
+
@import 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css';
|
56
|
+
```
|
57
|
+
|
58
|
+
### Import from NPM (`node_modules`)
|
59
|
+
|
60
|
+
Bare imports (imports not beginning with `./`, `/`, `https://`, `http://`) are fully supported, and
|
61
|
+
will use your package manager of choice (eg, NPM, Yarn, pnpm):
|
62
|
+
|
63
|
+
```js
|
64
|
+
import React from 'react`
|
65
|
+
```
|
66
|
+
|
67
|
+
### Local Imports
|
68
|
+
|
69
|
+
And of course you can import your own code, using relative or absolute paths (file extension is
|
70
|
+
optional):
|
71
|
+
|
72
|
+
```js /app/views/layouts/application.js
|
73
|
+
import utils from '/lib/utils'
|
74
|
+
```
|
75
|
+
|
76
|
+
```js /lib/utils.js
|
77
|
+
import constants from './constants'
|
78
|
+
```
|
79
|
+
|
80
|
+
## Bundling
|
24
81
|
|
25
|
-
|
82
|
+
Proscenium does not do any bundling, as we believe that **the web is now fast by default**. So we
|
83
|
+
let you decide if and when to bundle your code using query parameters in your JS and CSS imports.
|
26
84
|
|
27
|
-
|
85
|
+
```js
|
86
|
+
import doStuff from 'stuff?bundle'
|
87
|
+
doStuff()
|
88
|
+
```
|
28
89
|
|
29
|
-
|
90
|
+
Bundling a URL import is not supported, as the URL itself may also support query parameters,
|
91
|
+
resulting in conflicts. For example, esm.sh also supports a `?bundle` param, bundling a module's
|
92
|
+
dependencies into a single file. Instead, you should install the module locally using your favourite
|
93
|
+
package manager.
|
94
|
+
|
95
|
+
## Import Map
|
96
|
+
|
97
|
+
Import map for both JS and CSS is supported out of the box, and works with no regard to the browser
|
98
|
+
version being used. Just create `config/import_map.json`:
|
99
|
+
|
100
|
+
```json
|
101
|
+
{
|
102
|
+
"imports": {
|
103
|
+
"react": "https://esm.sh/react@18.2.0",
|
104
|
+
"start": "/lib/start.js",
|
105
|
+
"common": "/lib/common.css",
|
106
|
+
"@radix-ui/colors/": "https://esm.sh/@radix-ui/colors@0.1.8/",
|
107
|
+
}
|
108
|
+
}
|
109
|
+
```
|
110
|
+
|
111
|
+
Using the above import map, we can do...
|
112
|
+
|
113
|
+
```js
|
114
|
+
import { useCallback } from 'react'
|
115
|
+
import startHere from 'start'
|
116
|
+
import styles from 'common'
|
117
|
+
```
|
118
|
+
|
119
|
+
and for CSS...
|
120
|
+
|
121
|
+
```css
|
122
|
+
@import 'common';
|
123
|
+
@import '@radix-ui/colors/blue.css';
|
124
|
+
```
|
125
|
+
|
126
|
+
You can instead write your import map in Javascript instead of JSON. So instead of
|
127
|
+
`config/import_map.json`, create `config/import_map.js`, and define an anonymous function. This
|
128
|
+
function accepts a single `environment` argument.
|
129
|
+
|
130
|
+
```js
|
131
|
+
env => ({
|
132
|
+
imports: {
|
133
|
+
react: env === 'development' ? 'https://esm.sh/react@18.2.0?dev' : 'https://esm.sh/react@18.2.0'
|
134
|
+
}
|
135
|
+
})
|
136
|
+
```
|
137
|
+
|
138
|
+
## Side Loading
|
139
|
+
|
140
|
+
Proscenium has built in support for automatically side loading JS and CSS with your views and
|
141
|
+
layouts.
|
142
|
+
|
143
|
+
Just create a JS and/or CSS file with the same name as any view or layout, and make sure your
|
144
|
+
layouts include `<%= side_load_stylesheets %>` and `<%= side_load_javascripts %>`. Something like
|
145
|
+
this:
|
30
146
|
|
31
147
|
```html
|
32
148
|
<!DOCTYPE html>
|
33
149
|
<html>
|
34
150
|
<head>
|
35
151
|
<title>Hello World</title>
|
36
|
-
<%=
|
152
|
+
<%= side_load_stylesheets %>
|
37
153
|
</head>
|
38
154
|
<body>
|
39
|
-
<%= yield %>
|
155
|
+
<%= yield %>
|
156
|
+
<%= side_load_javascripts defer: true, type: 'module' %>
|
40
157
|
</body>
|
41
158
|
</html>
|
42
159
|
```
|
43
160
|
|
44
|
-
On each page request, Proscenium will check if your layout and view has a JS/CSS file of the same
|
161
|
+
On each page request, Proscenium will check if your layout and view has a JS/CSS file of the same
|
162
|
+
name, and include them into your layout HTML. Partials are not side loaded.
|
45
163
|
|
46
|
-
|
164
|
+
Side loading is enabled by default, but you can disable it by setting `config.proscenium.side_load`
|
165
|
+
to `false`.
|
47
166
|
|
48
|
-
|
167
|
+
## CSS Modules
|
49
168
|
|
50
|
-
|
51
|
-
`import React as * from 'react'`
|
52
|
-
`import { useState } from 'react'`
|
169
|
+
Direct access to CSS files are parsed through @parcel/css.
|
53
170
|
|
54
|
-
|
171
|
+
Importing a CSS file from JS will append the CSS file to the document's head. The results of the
|
172
|
+
import will be an object of CSS modules.
|
55
173
|
|
56
|
-
|
174
|
+
## Auto Reload
|
57
175
|
|
58
|
-
|
176
|
+
To aid fast development, Proscenium comes with an auto reload feature that will automatically reload
|
177
|
+
the page when any files changes. It is enabled by default in development, and requires that you
|
178
|
+
mount the Proscenium Railtie into your `config/routes.rb` file:
|
59
179
|
|
60
|
-
|
180
|
+
```ruby
|
181
|
+
mount Proscenium::Railtie, at: '/proscenium' if Rails.env.development?
|
182
|
+
```
|
183
|
+
|
184
|
+
Changes to CSS/JS(X) files in your `app` and `lib` directories will cause the page to reload.
|
185
|
+
|
186
|
+
NOTE: that this is hot module reloading (HMR) - a full page reload is triggered.
|
61
187
|
|
62
|
-
|
188
|
+
You can disable auto reload by setting the `config.proscenium.auto_reload` config option to false.
|
189
|
+
|
190
|
+
## CSS Custom Media Queries
|
191
|
+
|
192
|
+
Proscenium supports [custom media queries](https://css-tricks.com/can-we-have-custom-media-queries-please/) as per the [spec](https://www.w3.org/TR/mediaqueries-5/#custom-mq). However, because of the way they are parsed, they cannot be imported using `@import`. So if you define your custom media queries in `/config/custom_media_queries.css`, Proscenium will automatically inject them into your CSS, so you can use them anywhere.
|
63
193
|
|
64
194
|
## How It Works
|
65
195
|
|
@@ -77,7 +207,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
77
207
|
|
78
208
|
`deno compile --no-config -o bin/compilers/esbuild --import-map import_map.json -A lib/proscenium/compilers/esbuild.js`
|
79
209
|
|
80
|
-
|
81
210
|
## Contributing
|
82
211
|
|
83
212
|
Bug reports and pull requests are welcome on GitHub at https://github.com/joelmoss/proscenium. 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/proscenium/blob/master/CODE_OF_CONDUCT.md).
|
data/bin/esbuild
CHANGED
Binary file
|
data/bin/parcel_css
CHANGED
Binary file
|
data/config/routes.rb
CHANGED
@@ -0,0 +1,145 @@
|
|
1
|
+
export default function () {
|
2
|
+
if (Deno.env.get('RAILS_ENV') === 'development') {
|
3
|
+
return function (detail) {
|
4
|
+
const template = `
|
5
|
+
<style>
|
6
|
+
:host {
|
7
|
+
position: fixed;
|
8
|
+
z-index: 99999;
|
9
|
+
top: 0;
|
10
|
+
left: 0;
|
11
|
+
width: 100%;
|
12
|
+
height: 100%;
|
13
|
+
overflow-y: scroll;
|
14
|
+
margin: 0;
|
15
|
+
background: rgba(0, 0, 0, 0.66);
|
16
|
+
display: flex;
|
17
|
+
align-items: center;
|
18
|
+
--monospace: 'SFMono-Regular', Consolas,
|
19
|
+
'Liberation Mono', Menlo, Courier, monospace;
|
20
|
+
--red: #ff5555;
|
21
|
+
--yellow: #e2aa53;
|
22
|
+
--purple: #cfa4ff;
|
23
|
+
--cyan: #2dd9da;
|
24
|
+
--dim: #c9c9c9;
|
25
|
+
}
|
26
|
+
|
27
|
+
.window {
|
28
|
+
font-family: var(--monospace);
|
29
|
+
line-height: 1.5;
|
30
|
+
width: 800px;
|
31
|
+
color: #d8d8d8;
|
32
|
+
margin: 30px auto;
|
33
|
+
padding: 25px 40px;
|
34
|
+
position: relative;
|
35
|
+
background: #181818;
|
36
|
+
border-radius: 6px 6px 8px 8px;
|
37
|
+
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
|
38
|
+
overflow: hidden;
|
39
|
+
border-top: 8px solid var(--red);
|
40
|
+
}
|
41
|
+
|
42
|
+
pre {
|
43
|
+
font-family: var(--monospace);
|
44
|
+
font-size: 16px;
|
45
|
+
margin: 0 0 1em 0;
|
46
|
+
overflow-x: scroll;
|
47
|
+
scrollbar-width: none;
|
48
|
+
}
|
49
|
+
|
50
|
+
pre::-webkit-scrollbar {
|
51
|
+
display: none;
|
52
|
+
}
|
53
|
+
|
54
|
+
.title, .message {
|
55
|
+
line-height: 1.3;
|
56
|
+
font-weight: 600;
|
57
|
+
white-space: pre-wrap;
|
58
|
+
}
|
59
|
+
|
60
|
+
.message-body {
|
61
|
+
color: var(--red);
|
62
|
+
}
|
63
|
+
|
64
|
+
.file {
|
65
|
+
color: var(--cyan);
|
66
|
+
margin-bottom: 0;
|
67
|
+
white-space: pre-wrap;
|
68
|
+
word-break: break-all;
|
69
|
+
}
|
70
|
+
|
71
|
+
.code {
|
72
|
+
background: black;
|
73
|
+
border-left: 3px solid gray;
|
74
|
+
padding: 10px 0 0 20px;
|
75
|
+
}
|
76
|
+
.lineText {
|
77
|
+
white-space: pre-wrap;
|
78
|
+
}
|
79
|
+
.lineCursor {
|
80
|
+
white-space: pre;
|
81
|
+
color: blueviolet;
|
82
|
+
}
|
83
|
+
</style>
|
84
|
+
<div class="window">
|
85
|
+
<pre class="title">COMPILE ERROR!</pre>
|
86
|
+
<pre class="message"><span class="message-body"></span> in <span class="file"></span></pre>
|
87
|
+
<pre class="code"><span class="lineText"></span><span class="lineCursor"></span></pre>
|
88
|
+
</div>
|
89
|
+
`
|
90
|
+
|
91
|
+
class ErrorOverlay extends HTMLElement {
|
92
|
+
constructor(err) {
|
93
|
+
super()
|
94
|
+
|
95
|
+
this.root = this.attachShadow({ mode: 'open' })
|
96
|
+
this.root.innerHTML = template
|
97
|
+
this.root.querySelector('.message-body').textContent = err.text.trim()
|
98
|
+
if (err.location) {
|
99
|
+
const location = [err.location.file]
|
100
|
+
err.location.line && location.push(err.location.line)
|
101
|
+
err.location.column && location.push(err.location.column)
|
102
|
+
this.root.querySelector('.file').textContent = `/${location.join(':')}`
|
103
|
+
|
104
|
+
if (err.location.lineText) {
|
105
|
+
this.root.querySelector('.lineText').textContent = err.location.lineText
|
106
|
+
this.root.querySelector('.lineCursor').textContent =
|
107
|
+
''.padStart(err.location.column - 1, ' ') + '^'
|
108
|
+
} else {
|
109
|
+
this.root.querySelector('.code').remove()
|
110
|
+
}
|
111
|
+
|
112
|
+
console.error('%s at %O', err.text, location)
|
113
|
+
} else {
|
114
|
+
console.error(err.text)
|
115
|
+
}
|
116
|
+
|
117
|
+
this.root.querySelector('.window').addEventListener('click', e => {
|
118
|
+
e.stopPropagation()
|
119
|
+
})
|
120
|
+
|
121
|
+
this.addEventListener('click', () => {
|
122
|
+
this.close()
|
123
|
+
})
|
124
|
+
}
|
125
|
+
|
126
|
+
close() {
|
127
|
+
this.parentNode?.removeChild(this)
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
const overlayId = 'proscenium-error-overlay'
|
132
|
+
if (customElements && !customElements.get(overlayId)) {
|
133
|
+
customElements.define(overlayId, ErrorOverlay)
|
134
|
+
}
|
135
|
+
|
136
|
+
document.body.appendChild(new ErrorOverlay(detail))
|
137
|
+
}
|
138
|
+
} else {
|
139
|
+
return function (detail) {
|
140
|
+
const location = `/${detail.location.file}:${detail.location.line}:${detail.location.column}`
|
141
|
+
console.error('%s at %O', detail.text, location)
|
142
|
+
throw `${detail.text} at ${location}`
|
143
|
+
}
|
144
|
+
}
|
145
|
+
}
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { crypto } from 'std/crypto/mod.ts'
|
2
|
+
import { join, resolve, dirname, fromFileUrl } from 'std/path/mod.ts'
|
3
|
+
|
4
|
+
import setup from './setup_plugin.js'
|
5
|
+
|
6
|
+
export default setup('css', async build => {
|
7
|
+
const cwd = build.initialOptions.absWorkingDir
|
8
|
+
const parcelBin = resolve(dirname(fromFileUrl(import.meta.url)), '../../../../bin/parcel_css')
|
9
|
+
|
10
|
+
let customMedia
|
11
|
+
try {
|
12
|
+
customMedia = await Deno.readTextFile(join(cwd, 'config', 'custom_media_queries.css'))
|
13
|
+
} catch {
|
14
|
+
// do nothing, as we don't require custom media.
|
15
|
+
}
|
16
|
+
|
17
|
+
return [
|
18
|
+
{
|
19
|
+
type: 'onLoad',
|
20
|
+
filter: /\.css$/,
|
21
|
+
namespace: 'file',
|
22
|
+
async callback(args) {
|
23
|
+
let path = args.path
|
24
|
+
const isCssModule = args.path.endsWith('.module.css')
|
25
|
+
let cmd = [parcelBin, '--nesting', '--targets', '>= 0.25%']
|
26
|
+
|
27
|
+
if (customMedia) {
|
28
|
+
cmd.push('--custom-media')
|
29
|
+
|
30
|
+
path = await Deno.makeTempFile()
|
31
|
+
await Deno.writeTextFile(path, (await Deno.readTextFile(args.path)) + customMedia)
|
32
|
+
}
|
33
|
+
|
34
|
+
if (isCssModule) {
|
35
|
+
const hash = await digest(args.path.slice(cwd.length))
|
36
|
+
cmd = cmd.concat(['--css-modules', '--css-modules-pattern', `[local]${hash}`])
|
37
|
+
}
|
38
|
+
|
39
|
+
const p = Deno.run({
|
40
|
+
cmd: [...cmd, path],
|
41
|
+
stdout: 'piped',
|
42
|
+
stderr: 'piped'
|
43
|
+
})
|
44
|
+
|
45
|
+
const { code } = await p.status()
|
46
|
+
const rawOutput = await p.output()
|
47
|
+
const rawError = await p.stderrOutput()
|
48
|
+
|
49
|
+
// Even though Deno docs say that reading the outputs (above) closes their pipes, warnings
|
50
|
+
// are raised during tests that the child process have not been closed. So we manually close
|
51
|
+
// here.
|
52
|
+
p.close()
|
53
|
+
|
54
|
+
if (code === 0) {
|
55
|
+
const contents = new TextDecoder().decode(rawOutput)
|
56
|
+
return { loader: 'css', contents: isCssModule ? JSON.parse(contents).code : contents }
|
57
|
+
} else {
|
58
|
+
const errorString = new TextDecoder().decode(rawError)
|
59
|
+
throw errorString
|
60
|
+
}
|
61
|
+
}
|
62
|
+
}
|
63
|
+
]
|
64
|
+
})
|
65
|
+
|
66
|
+
async function digest(value) {
|
67
|
+
value = new TextEncoder().encode(value)
|
68
|
+
const view = new DataView(await crypto.subtle.digest('SHA-1', value))
|
69
|
+
|
70
|
+
let hexCodes = ''
|
71
|
+
for (let index = 0; index < view.byteLength; index += 4) {
|
72
|
+
hexCodes += view.getUint32(index).toString(16).padStart(8, '0')
|
73
|
+
}
|
74
|
+
|
75
|
+
return hexCodes.slice(0, 8)
|
76
|
+
}
|
@@ -1,15 +1,17 @@
|
|
1
1
|
import setup from './setup_plugin.js'
|
2
2
|
|
3
3
|
export default setup('env', () => {
|
4
|
-
return
|
5
|
-
|
4
|
+
return [
|
5
|
+
{
|
6
|
+
type: 'onResolve',
|
6
7
|
filter: /^env$/,
|
7
8
|
callback({ path }) {
|
8
9
|
return { path, namespace: 'env' }
|
9
10
|
}
|
10
11
|
},
|
11
12
|
|
12
|
-
|
13
|
+
{
|
14
|
+
type: 'onLoad',
|
13
15
|
filter: /.*/,
|
14
16
|
namespace: 'env',
|
15
17
|
callback() {
|
@@ -17,5 +19,5 @@ export default setup('env', () => {
|
|
17
19
|
return { loader: 'json', contents: JSON.stringify(env) }
|
18
20
|
}
|
19
21
|
}
|
20
|
-
|
22
|
+
]
|
21
23
|
})
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import setup from './setup_plugin.js'
|
2
|
+
|
3
|
+
export default setup('httpBundle', () => {
|
4
|
+
return [
|
5
|
+
{
|
6
|
+
type: 'onResolve',
|
7
|
+
filter: /^https?:\/\//,
|
8
|
+
callback(args) {
|
9
|
+
let queryParams, suffix
|
10
|
+
if (args.path.includes('?')) {
|
11
|
+
const [path, query] = args.path.split('?')
|
12
|
+
queryParams = new URLSearchParams(query)
|
13
|
+
suffix = `?${query}`
|
14
|
+
args.path = path
|
15
|
+
}
|
16
|
+
|
17
|
+
if (queryParams?.has('bundle')) {
|
18
|
+
return { path: args.path, namespace: 'httpBundle', suffix }
|
19
|
+
} else {
|
20
|
+
return { external: true }
|
21
|
+
}
|
22
|
+
}
|
23
|
+
},
|
24
|
+
|
25
|
+
// Intercept all import paths inside downloaded files and resolve them against the original URL.
|
26
|
+
{
|
27
|
+
type: 'onResolve',
|
28
|
+
filter: /.*/,
|
29
|
+
namespace: 'httpBundle',
|
30
|
+
callback(args) {
|
31
|
+
return {
|
32
|
+
path: new URL(args.path, args.importer).toString(),
|
33
|
+
namespace: 'httpBundle'
|
34
|
+
}
|
35
|
+
}
|
36
|
+
},
|
37
|
+
|
38
|
+
// Download and return the content.
|
39
|
+
//
|
40
|
+
// TODO: cache this!
|
41
|
+
{
|
42
|
+
type: 'onLoad',
|
43
|
+
filter: /.*/,
|
44
|
+
namespace: 'httpBundle',
|
45
|
+
async callback(args) {
|
46
|
+
const textResponse = await fetch(args.path)
|
47
|
+
const contents = await textResponse.text()
|
48
|
+
|
49
|
+
return { contents }
|
50
|
+
}
|
51
|
+
}
|
52
|
+
]
|
53
|
+
})
|
@@ -0,0 +1,59 @@
|
|
1
|
+
import { join } from 'std/path/mod.ts'
|
2
|
+
import { parseFromString } from 'import-maps/resolve'
|
3
|
+
|
4
|
+
const baseURL = new URL('file://')
|
5
|
+
|
6
|
+
class ImportMapError extends Error {
|
7
|
+
constructor(fileName, ...params) {
|
8
|
+
super(...params)
|
9
|
+
|
10
|
+
if (Error.captureStackTrace) {
|
11
|
+
Error.captureStackTrace(this, ImportMapError)
|
12
|
+
}
|
13
|
+
|
14
|
+
this.name = 'ImportMapError'
|
15
|
+
this.file = fileName
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
export function readImportMap(fileName, rootDir) {
|
20
|
+
let importMap
|
21
|
+
|
22
|
+
if (fileName) {
|
23
|
+
importMap = readFile(fileName, rootDir, true)
|
24
|
+
} else {
|
25
|
+
fileName = ['config/import_map.json', 'config/import_map.js'].find(f => {
|
26
|
+
const result = readFile(f, rootDir)
|
27
|
+
if (result) {
|
28
|
+
importMap = result
|
29
|
+
return true
|
30
|
+
}
|
31
|
+
})
|
32
|
+
}
|
33
|
+
|
34
|
+
return importMap
|
35
|
+
}
|
36
|
+
|
37
|
+
function readFile(file, rootDir, required = false) {
|
38
|
+
let contents = null
|
39
|
+
|
40
|
+
try {
|
41
|
+
contents = Deno.readTextFileSync(join(rootDir, file))
|
42
|
+
} catch (error) {
|
43
|
+
if (required) {
|
44
|
+
throw new ImportMapError(file, error.message, { cause: error })
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
if (contents === null) return null
|
49
|
+
|
50
|
+
try {
|
51
|
+
if (file.endsWith('.js')) {
|
52
|
+
contents = JSON.stringify(eval(contents)(Deno.env.get('RAILS_ENV')))
|
53
|
+
}
|
54
|
+
|
55
|
+
return parseFromString(contents, baseURL)
|
56
|
+
} catch (error) {
|
57
|
+
throw new ImportMapError(file, error.message, { cause: error })
|
58
|
+
}
|
59
|
+
}
|