proscenium 0.1.0.alpha3-x86_64-linux → 0.1.1-x86_64-linux
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/README.md +65 -10
- data/app/components/react_component.rb +10 -3
- data/bin/esbuild +0 -0
- data/bin/lightningcss +0 -0
- data/lib/proscenium/compilers/esbuild/compile_error.js +2 -0
- data/lib/proscenium/compilers/esbuild/css/postcss.js +67 -0
- data/lib/proscenium/compilers/esbuild/css_plugin.js +110 -10
- data/lib/proscenium/compilers/esbuild/resolve_plugin.js +42 -43
- data/lib/proscenium/compilers/esbuild/setup_plugin.js +16 -7
- data/lib/proscenium/compilers/esbuild.js +40 -6
- data/lib/proscenium/css_module.rb +48 -6
- data/lib/proscenium/helper.rb +3 -1
- data/lib/proscenium/middleware/esbuild.rb +3 -1
- data/lib/proscenium/middleware/{parcel_css.rb → lightningcss.rb} +3 -3
- data/lib/proscenium/middleware.rb +4 -19
- data/lib/proscenium/phlex.rb +36 -0
- data/lib/proscenium/railtie.rb +2 -10
- data/lib/proscenium/runtime/auto_reload.js +3 -3
- data/lib/proscenium/side_load.rb +14 -40
- data/lib/proscenium/utils.js +8 -0
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/view_component.rb +5 -1
- data/lib/proscenium.rb +1 -0
- metadata +24 -10
- data/bin/parcel_css +0 -0
- data/lib/proscenium/runtime/component_manager/index.js +0 -27
- data/lib/proscenium/runtime/component_manager/render_component.jsx +0 -36
- data/lib/proscenium/runtime/import_css.js +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cb09bd3aacc4ffeec61de7c9aed8795432a096ddec4490f44036c2492d04b7b
|
4
|
+
data.tar.gz: 7a173194f536f01294d18f756da7134e736e892135394325b09ee51765ec29cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fe234a1042f3c813a57c3a1f0edadf11f1d702fd2825cf706a91c06b3e50147864532ddc6e1085cca61f1f4400958104bbb63041cd5a24c400b6800eb9103985
|
7
|
+
data.tar.gz: 42664f98b4e0c74c6ddeab211e73d1c071fad71d335a0664df35cb5b3ef72d25c63a86fbee294725bfb460a21b0c2b2bb733d1afed77bb705699ce5eda9f8bd8
|
data/README.md
CHANGED
@@ -1,12 +1,29 @@
|
|
1
|
-
# Proscenium
|
1
|
+
# Proscenium - Modern Client-Side Tooling for Rails
|
2
2
|
|
3
|
-
-
|
3
|
+
Proscenium treats your client-side code as first class citizens of your Rails app, and assumes a
|
4
|
+
"fast by default" internet. It compiles your JS, JSX and CSS in real time, and on demand, with no
|
5
|
+
configuration at all!
|
6
|
+
|
7
|
+
- Zero configuration.
|
8
|
+
- NO JavaScript rumtime needed - just the browser!
|
9
|
+
- Real-time compilation.
|
10
|
+
- No additional process or server - Just run Rails!
|
11
|
+
- Serve assets from anywhere within your Rails root (/app, /config, /lib).
|
4
12
|
- Automatically side load JS/CSS for your layouts and views.
|
5
|
-
- Import JS and CSS from node_modules, URL, local (relative, absolute)
|
6
|
-
-
|
7
|
-
- Import Map
|
8
|
-
- CSS Modules
|
9
|
-
-
|
13
|
+
- Import JS(X) and CSS from node_modules, URL, local (relative, absolute).
|
14
|
+
- Optional bundling of JS(X) and CSS.
|
15
|
+
- Import Map support for JS and CSS.
|
16
|
+
- CSS Modules.
|
17
|
+
- CSS Custom Media Queries.
|
18
|
+
- CSS mixins.
|
19
|
+
- Minification.
|
20
|
+
- Auto reload after changes (development only).
|
21
|
+
|
22
|
+
## !! EXPERIMENTAL SOFTWARE !!
|
23
|
+
|
24
|
+
While my goal is to use Proscenium in production, I strongly recommended that you **DO NOT** use
|
25
|
+
this in production apps! Right now, this is a play thing, and should only be used for
|
26
|
+
development/testing.
|
10
27
|
|
11
28
|
## Installation
|
12
29
|
|
@@ -34,6 +51,7 @@ Using the examples above...
|
|
34
51
|
- `app/views/users/index.js` => `https://yourapp.com/app/views/users/index.js`
|
35
52
|
- `app/views/layouts/application.css` => `https://yourapp.com/app/views/layouts/application.css`
|
36
53
|
- `lib/utils.js` => `https://yourapp.com/lib/utils.js`
|
54
|
+
- `config/properties.css` => `https://yourapp.com/config/properties.css`
|
37
55
|
|
38
56
|
## Importing
|
39
57
|
|
@@ -166,10 +184,28 @@ to `false`.
|
|
166
184
|
|
167
185
|
## CSS Modules
|
168
186
|
|
169
|
-
|
187
|
+
Give any CSS file a `.module.css` extension, and Proscenium will load it as a CSS Module...
|
188
|
+
|
189
|
+
```css
|
190
|
+
.header {
|
191
|
+
background-color: #00f;
|
192
|
+
}
|
193
|
+
```
|
170
194
|
|
171
|
-
|
172
|
-
|
195
|
+
The above produces:
|
196
|
+
|
197
|
+
```css
|
198
|
+
.header5564cdbb {
|
199
|
+
background-color: #00f;
|
200
|
+
}
|
201
|
+
```
|
202
|
+
|
203
|
+
Importing a CSS file from JS will automatically append the stylesheet to the document's head. The
|
204
|
+
results of the import will be an object of CSS modules.
|
205
|
+
|
206
|
+
```js
|
207
|
+
import styles from './styles.module.css'
|
208
|
+
```
|
173
209
|
|
174
210
|
## Auto Reload
|
175
211
|
|
@@ -191,6 +227,25 @@ You can disable auto reload by setting the `config.proscenium.auto_reload` confi
|
|
191
227
|
|
192
228
|
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.
|
193
229
|
|
230
|
+
## CSS Mixins
|
231
|
+
|
232
|
+
CSS mixins are supported using the `@mixin` at-rule. Simply define your mixins in any number of files ending in `.mixin.css`, and using the `@define-mixin` at-rule...
|
233
|
+
|
234
|
+
```css
|
235
|
+
// /lib/text.mixin.css
|
236
|
+
@define-mixin bigText {
|
237
|
+
font-size: 50px;
|
238
|
+
}
|
239
|
+
```
|
240
|
+
|
241
|
+
```css
|
242
|
+
// /app/views/layouts/application.css
|
243
|
+
p {
|
244
|
+
@mixin bigText;
|
245
|
+
color: red;
|
246
|
+
}
|
247
|
+
```
|
248
|
+
|
194
249
|
## How It Works
|
195
250
|
|
196
251
|
Proscenium provides a Rails middleware that proxies requests for your frontend code. By default, it will simply search for a file of the same name in your Rails root. For example, a request for '/app/views/layouts/application.js' or '/lib/hooks.js' will return that exact file relative to your Rails root.
|
@@ -1,14 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ReactComponent < ApplicationComponent
|
4
|
-
attr_accessor :props
|
4
|
+
attr_accessor :props, :lazy
|
5
5
|
|
6
|
-
|
6
|
+
# @param props: [Hash]
|
7
|
+
# @param lazy: [Boolean] Lazy load the component using IntersectionObserver. Default: true.
|
8
|
+
def initialize(props: {}, lazy: true)
|
7
9
|
@props = props
|
10
|
+
@lazy = lazy
|
11
|
+
|
8
12
|
super
|
9
13
|
end
|
10
14
|
|
11
15
|
def call
|
12
|
-
tag.div
|
16
|
+
tag.div class: ['componentManagedByProscenium', css_module(:component)],
|
17
|
+
data: { component: { path: virtual_path, props: props, lazy: lazy } } do
|
18
|
+
tag.div content
|
19
|
+
end
|
13
20
|
end
|
14
21
|
end
|
data/bin/esbuild
CHANGED
Binary file
|
data/bin/lightningcss
ADDED
Binary file
|
@@ -74,11 +74,13 @@ export default function () {
|
|
74
74
|
padding: 10px 0 0 20px;
|
75
75
|
}
|
76
76
|
.lineText {
|
77
|
+
display: block;
|
77
78
|
white-space: pre-wrap;
|
78
79
|
}
|
79
80
|
.lineCursor {
|
80
81
|
white-space: pre;
|
81
82
|
color: blueviolet;
|
83
|
+
display: block;
|
82
84
|
}
|
83
85
|
</style>
|
84
86
|
<div class="window">
|
@@ -0,0 +1,67 @@
|
|
1
|
+
import { expandGlob } from 'std/fs/mod.ts'
|
2
|
+
import postcss, { CssSyntaxError } from 'postcss'
|
3
|
+
|
4
|
+
export default async (root, path) => {
|
5
|
+
let tmpFile
|
6
|
+
let contents
|
7
|
+
|
8
|
+
const mixinFiles = []
|
9
|
+
for await (const file of expandGlob(`lib/**/*.mixin.css`, { root })) {
|
10
|
+
mixinFiles.push(file.path)
|
11
|
+
}
|
12
|
+
|
13
|
+
// Only process mixins with PostCSS if there are any 'lib/**/*.mixin.css' files.
|
14
|
+
if (mixinFiles.length > 0) {
|
15
|
+
tmpFile = await Deno.makeTempFile()
|
16
|
+
contents = await Deno.readTextFile(path)
|
17
|
+
|
18
|
+
const result = await postcss([mixinsPlugin({ mixinFiles })]).process(contents, { from: path })
|
19
|
+
contents = result.css
|
20
|
+
}
|
21
|
+
|
22
|
+
return [tmpFile, contents]
|
23
|
+
}
|
24
|
+
|
25
|
+
const mixinsPlugin = (opts = {}) => {
|
26
|
+
return {
|
27
|
+
postcssPlugin: 'mixins',
|
28
|
+
|
29
|
+
prepare() {
|
30
|
+
const mixins = {}
|
31
|
+
|
32
|
+
return {
|
33
|
+
async Once(_, helpers) {
|
34
|
+
for (const path of opts.mixinFiles) {
|
35
|
+
const content = await Deno.readTextFile(path)
|
36
|
+
const root = helpers.parse(content, { from: path })
|
37
|
+
|
38
|
+
root.walkAtRules('define-mixin', atrule => {
|
39
|
+
mixins[atrule.params] = atrule
|
40
|
+
})
|
41
|
+
}
|
42
|
+
},
|
43
|
+
|
44
|
+
AtRule: {
|
45
|
+
mixin: (rule, helpers) => {
|
46
|
+
const mixin = mixins[rule.params]
|
47
|
+
|
48
|
+
if (!mixin) {
|
49
|
+
throw rule.error(`Undefined mixin '${rule.params}'`)
|
50
|
+
}
|
51
|
+
|
52
|
+
const proxy = new helpers.Root()
|
53
|
+
for (let i = 0; i < mixin.nodes.length; i++) {
|
54
|
+
const node = mixin.nodes[i].clone()
|
55
|
+
delete node.raws.before
|
56
|
+
proxy.append(node)
|
57
|
+
}
|
58
|
+
|
59
|
+
rule.parent.insertBefore(rule, proxy)
|
60
|
+
|
61
|
+
if (rule.parent) rule.remove()
|
62
|
+
}
|
63
|
+
}
|
64
|
+
}
|
65
|
+
}
|
66
|
+
}
|
67
|
+
}
|
@@ -1,11 +1,16 @@
|
|
1
1
|
import { crypto } from 'std/crypto/mod.ts'
|
2
|
-
import { join, resolve, dirname, fromFileUrl } from 'std/path/mod.ts'
|
2
|
+
import { join, resolve, dirname, basename, fromFileUrl } from 'std/path/mod.ts'
|
3
3
|
|
4
|
+
import { fileExists } from '../../utils.js'
|
5
|
+
import postcss from './css/postcss.js'
|
4
6
|
import setup from './setup_plugin.js'
|
5
7
|
|
6
8
|
export default setup('css', async build => {
|
7
9
|
const cwd = build.initialOptions.absWorkingDir
|
8
|
-
const
|
10
|
+
const lightningcssBin = resolve(
|
11
|
+
dirname(fromFileUrl(import.meta.url)),
|
12
|
+
'../../../../bin/lightningcss'
|
13
|
+
)
|
9
14
|
|
10
15
|
let customMedia
|
11
16
|
try {
|
@@ -20,24 +25,55 @@ export default setup('css', async build => {
|
|
20
25
|
filter: /\.css$/,
|
21
26
|
namespace: 'file',
|
22
27
|
async callback(args) {
|
23
|
-
|
28
|
+
const hash = await digest(args.path.slice(cwd.length))
|
24
29
|
const isCssModule = args.path.endsWith('.module.css')
|
25
|
-
let cmd = [parcelBin, '--nesting', '--targets', '>= 0.25%']
|
26
30
|
|
31
|
+
// If path is a CSS module, imported from JS, and a side-loaded ViewComponent stylesheet,
|
32
|
+
// simply return a JS proxy of the class names. The stylesheet itself will have already been
|
33
|
+
// side loaded. This avoids compiling the CSS all over again.
|
34
|
+
if (isCssModule && args.pluginData?.importedFromJs && (await isViewComponent(args.path))) {
|
35
|
+
return {
|
36
|
+
resolveDir: cwd,
|
37
|
+
loader: 'js',
|
38
|
+
contents: cssModulesProxyTemplate(hash)
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
let cmd = [
|
43
|
+
lightningcssBin,
|
44
|
+
'--nesting',
|
45
|
+
'--error-recovery',
|
46
|
+
args.pluginData?.importedFromJs && '--minify',
|
47
|
+
'--targets',
|
48
|
+
'>= 0.25%'
|
49
|
+
].filter(Boolean)
|
50
|
+
|
51
|
+
// This will process the CSS with Postcss only if it needs to.
|
52
|
+
let [tmpFile, contents] = await postcss(cwd, args.path)
|
53
|
+
|
54
|
+
// As custom media are defined in their own file, we have to append the file contents to our
|
55
|
+
// stylesheet, so that the custom media can be used.
|
27
56
|
if (customMedia) {
|
28
57
|
cmd.push('--custom-media')
|
29
58
|
|
30
|
-
|
31
|
-
|
59
|
+
if (!tmpFile && !contents) {
|
60
|
+
tmpFile = await Deno.makeTempFile()
|
61
|
+
contents = await Deno.readTextFile(args.path)
|
62
|
+
}
|
63
|
+
|
64
|
+
contents += customMedia
|
65
|
+
}
|
66
|
+
|
67
|
+
if (tmpFile && contents) {
|
68
|
+
await Deno.writeTextFile(tmpFile, contents)
|
32
69
|
}
|
33
70
|
|
34
71
|
if (isCssModule) {
|
35
|
-
const hash = await digest(args.path.slice(cwd.length))
|
36
72
|
cmd = cmd.concat(['--css-modules', '--css-modules-pattern', `[local]${hash}`])
|
37
73
|
}
|
38
74
|
|
39
75
|
const p = Deno.run({
|
40
|
-
cmd: [...cmd, path],
|
76
|
+
cmd: [...cmd, tmpFile || args.path],
|
41
77
|
stdout: 'piped',
|
42
78
|
stderr: 'piped'
|
43
79
|
})
|
@@ -51,9 +87,48 @@ export default setup('css', async build => {
|
|
51
87
|
// here.
|
52
88
|
p.close()
|
53
89
|
|
90
|
+
// Success!
|
54
91
|
if (code === 0) {
|
55
|
-
|
56
|
-
|
92
|
+
let contents = new TextDecoder().decode(rawOutput)
|
93
|
+
if (isCssModule) {
|
94
|
+
contents = JSON.parse(contents)
|
95
|
+
}
|
96
|
+
|
97
|
+
// If stylesheet is imported from JS, then we return JS code that appends the stylesheet
|
98
|
+
// in a <style> in the <head> of the page, and if the stylesheet is a CSS module, it
|
99
|
+
// exports a plain object of class names.
|
100
|
+
if (args.pluginData?.importedFromJs) {
|
101
|
+
const code = isCssModule ? contents.code : contents
|
102
|
+
const mod = [
|
103
|
+
`let e = document.querySelector('#_${hash}');`,
|
104
|
+
'if (!e) {',
|
105
|
+
"e = document.createElement('style');",
|
106
|
+
`e.id = '_${hash}';`,
|
107
|
+
'document.head.appendChild(e);',
|
108
|
+
`e.appendChild(document.createTextNode(\`${code}\`));`,
|
109
|
+
'}'
|
110
|
+
]
|
111
|
+
|
112
|
+
if (isCssModule) {
|
113
|
+
const classes = {}
|
114
|
+
for (const key in contents.exports) {
|
115
|
+
if (Object.hasOwnProperty.call(contents.exports, key)) {
|
116
|
+
classes[key] = contents.exports[key].name
|
117
|
+
}
|
118
|
+
}
|
119
|
+
mod.push(`export default ${JSON.stringify(classes)};`)
|
120
|
+
}
|
121
|
+
|
122
|
+
// We are importing from JS, so return the entire result from LightningCSS via the js
|
123
|
+
// loader.
|
124
|
+
return {
|
125
|
+
resolveDir: cwd,
|
126
|
+
loader: 'js',
|
127
|
+
contents: mod.join('')
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
return { loader: 'css', contents: isCssModule ? contents.code : contents }
|
57
132
|
} else {
|
58
133
|
const errorString = new TextDecoder().decode(rawError)
|
59
134
|
throw errorString
|
@@ -74,3 +149,28 @@ async function digest(value) {
|
|
74
149
|
|
75
150
|
return hexCodes.slice(0, 8)
|
76
151
|
}
|
152
|
+
|
153
|
+
async function isViewComponent(path) {
|
154
|
+
const fileName = basename(path)
|
155
|
+
const dirName = dirname(path)
|
156
|
+
|
157
|
+
return (
|
158
|
+
(fileName === 'component.module.css' && (await fileExists(join(dirName, 'component.rb')))) ||
|
159
|
+
(fileName.endsWith('_component.module.css') &&
|
160
|
+
(await fileExists(join(dirName, fileName.replace(/\.module\.css$/, '.rb')))))
|
161
|
+
)
|
162
|
+
}
|
163
|
+
|
164
|
+
function cssModulesProxyTemplate(hash) {
|
165
|
+
return [
|
166
|
+
`export default new Proxy( {}, {`,
|
167
|
+
` get(target, prop, receiver) {`,
|
168
|
+
` if (prop in target || typeof prop === 'symbol') {`,
|
169
|
+
` return Reflect.get(target, prop, receiver)`,
|
170
|
+
` } else {`,
|
171
|
+
` return prop + '${hash}'`,
|
172
|
+
` }`,
|
173
|
+
` }`,
|
174
|
+
`})`
|
175
|
+
].join('')
|
176
|
+
}
|
@@ -3,13 +3,13 @@ import { resolve as resolveFromImportMap } from 'import-maps/resolve'
|
|
3
3
|
|
4
4
|
import setup from './setup_plugin.js'
|
5
5
|
|
6
|
-
const baseURL = new URL('file://')
|
7
6
|
const importKinds = ['import-statement', 'dynamic-import', 'require-call', 'import-rule']
|
8
7
|
|
9
8
|
export default setup('resolve', (build, options) => {
|
10
9
|
const { runtimeDir, importMap } = options
|
11
10
|
const cwd = build.initialOptions.absWorkingDir
|
12
11
|
const runtimeCwdAlias = `${cwd}/proscenium-runtime`
|
12
|
+
let bundled = false
|
13
13
|
|
14
14
|
return [
|
15
15
|
{
|
@@ -29,18 +29,22 @@ export default setup('resolve', (build, options) => {
|
|
29
29
|
}
|
30
30
|
|
31
31
|
// Proscenium runtime
|
32
|
-
if (args.path.startsWith('@proscenium/')) {
|
33
|
-
|
32
|
+
// if (args.path.startsWith('@proscenium/')) {
|
33
|
+
// const result = { suffix: args.suffix }
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
result.path = `${args.path.replace(/^@proscenium/, '/proscenium-runtime')}/index.js`
|
39
|
-
result.external = true
|
40
|
-
}
|
35
|
+
// if (args.queryParams?.has('bundle-all')) {
|
36
|
+
// bundled = true
|
37
|
+
// }
|
41
38
|
|
42
|
-
|
43
|
-
}
|
39
|
+
// if (bundled || args.queryParams?.has('bundle')) {
|
40
|
+
// result.path = join(runtimeDir, `${args.path.replace(/^@proscenium/, '')}/index.js`)
|
41
|
+
// } else {
|
42
|
+
// result.path = `${args.path.replace(/^@proscenium/, '/proscenium-runtime')}/index.js`
|
43
|
+
// result.external = true
|
44
|
+
// }
|
45
|
+
|
46
|
+
// return result
|
47
|
+
// }
|
44
48
|
|
45
49
|
if (args.path.startsWith(runtimeCwdAlias)) {
|
46
50
|
return { path: join(runtimeDir, args.path.slice(runtimeCwdAlias.length)) }
|
@@ -51,32 +55,6 @@ export default setup('resolve', (build, options) => {
|
|
51
55
|
return await unbundleImport(args)
|
52
56
|
}
|
53
57
|
}
|
54
|
-
},
|
55
|
-
|
56
|
-
{
|
57
|
-
type: 'onLoad',
|
58
|
-
filter: /.*/,
|
59
|
-
namespace: 'importStylesheet',
|
60
|
-
callback(args) {
|
61
|
-
const result = {
|
62
|
-
resolveDir: cwd,
|
63
|
-
loader: 'js'
|
64
|
-
}
|
65
|
-
|
66
|
-
if (args.path.endsWith('.module.css')) {
|
67
|
-
result.contents = `
|
68
|
-
import { importCssModule } from '/proscenium-runtime/import_css.js'
|
69
|
-
export default await importCssModule('${args.path}')
|
70
|
-
`
|
71
|
-
} else {
|
72
|
-
result.contents = `
|
73
|
-
import { appendStylesheet } from '/proscenium-runtime/import_css.js'
|
74
|
-
appendStylesheet('${args.path}')
|
75
|
-
`
|
76
|
-
}
|
77
|
-
|
78
|
-
return result
|
79
|
-
}
|
80
58
|
}
|
81
59
|
]
|
82
60
|
|
@@ -90,7 +68,9 @@ export default setup('resolve', (build, options) => {
|
|
90
68
|
const result = { path: params.path, suffix: params.suffix }
|
91
69
|
|
92
70
|
if (importMap) {
|
71
|
+
const baseURL = new URL(params.importer.slice(cwd.length), 'file://')
|
93
72
|
const { matched, resolvedImport } = resolveFromImportMap(params.path, importMap, baseURL)
|
73
|
+
|
94
74
|
if (matched) {
|
95
75
|
if (resolvedImport.protocol === 'file:') {
|
96
76
|
params.path = resolvedImport.pathname
|
@@ -108,24 +88,39 @@ export default setup('resolve', (build, options) => {
|
|
108
88
|
// Resolve the path using esbuild's internal resolution. This allows us to import node packages
|
109
89
|
// and extension-less paths without custom code, as esbuild with resolve them for us.
|
110
90
|
const resolveResult = await build.resolve(result.path, {
|
111
|
-
resolveDir
|
91
|
+
// If path is a bare module (node_modules), and resolveDir is the Proscenium runtime dir, then
|
92
|
+
// use `cwd` as the `resolveDir`, otherwise pass it through as is. This ensures that nested
|
93
|
+
// node_modules are resolved correctly.
|
94
|
+
resolveDir:
|
95
|
+
isBareModule(result.path) && params.resolveDir.startsWith(runtimeDir)
|
96
|
+
? cwd
|
97
|
+
: params.resolveDir,
|
112
98
|
pluginData: {
|
113
99
|
// We use this property later on, as we should ignore this resolution call.
|
114
100
|
isResolvingPath: true
|
115
101
|
}
|
116
102
|
})
|
117
103
|
|
118
|
-
if
|
119
|
-
|
104
|
+
// Simple return the resolved result if we have an error. Usually happens when module is not
|
105
|
+
// found.
|
106
|
+
if (resolveResult.errors.length > 0) return resolveResult
|
107
|
+
|
108
|
+
// If 'bundle-all' queryParam is defined, return the resolveResult.
|
109
|
+
if (bundled || params.queryParams?.has('bundle-all')) {
|
110
|
+
bundled = true
|
111
|
+
return { ...resolveResult, suffix: '?bundle-all' }
|
120
112
|
}
|
121
113
|
|
122
|
-
// If bundle queryParam is defined, return the resolveResult.
|
114
|
+
// If 'bundle' queryParam is defined, return the resolveResult.
|
123
115
|
if (params.queryParams?.has('bundle')) {
|
124
|
-
return { ...resolveResult, suffix:
|
116
|
+
return { ...resolveResult, suffix: '?bundle' }
|
125
117
|
}
|
126
118
|
|
127
119
|
if (resolveResult.path.startsWith(runtimeDir)) {
|
128
120
|
result.path = '/proscenium-runtime' + resolveResult.path.slice(runtimeDir.length)
|
121
|
+
} else if (!resolveResult.path.startsWith(cwd)) {
|
122
|
+
// If resolved path does not start with cwd, then it is most likely linked, so bundle it.
|
123
|
+
return { ...resolveResult, suffix: '?bundle' }
|
129
124
|
} else {
|
130
125
|
result.path = resolveResult.path.slice(cwd.length)
|
131
126
|
}
|
@@ -138,7 +133,7 @@ export default setup('resolve', (build, options) => {
|
|
138
133
|
/\.jsx?$/.test(params.importer)
|
139
134
|
) {
|
140
135
|
// We're importing a CSS file from JS(X).
|
141
|
-
|
136
|
+
return { ...resolveResult, pluginData: { importedFromJs: true } }
|
142
137
|
} else {
|
143
138
|
result.external = true
|
144
139
|
}
|
@@ -146,3 +141,7 @@ export default setup('resolve', (build, options) => {
|
|
146
141
|
return result
|
147
142
|
}
|
148
143
|
})
|
144
|
+
|
145
|
+
function isBareModule(mod) {
|
146
|
+
return !mod.startsWith('/') && !mod.startsWith('.')
|
147
|
+
}
|
@@ -9,13 +9,22 @@ export default (pluginName, pluginFn) => {
|
|
9
9
|
build.onResolve({ filter, namespace }, async params => {
|
10
10
|
if (params.pluginData?.isResolvingPath) return
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
12
|
+
let results
|
13
|
+
|
14
|
+
if (options.debug) {
|
15
|
+
console.debug()
|
16
|
+
console.group(`plugin(${pluginName}):onResolve`, { filter, namespace })
|
17
|
+
console.debug('params:', params)
|
18
|
+
|
19
|
+
try {
|
20
|
+
results = await callback(params)
|
21
|
+
console.debug('results:', results)
|
22
|
+
} finally {
|
23
|
+
console.groupEnd()
|
24
|
+
}
|
25
|
+
} else {
|
26
|
+
results = await callback(params)
|
27
|
+
}
|
19
28
|
|
20
29
|
return results
|
21
30
|
})
|
@@ -11,14 +11,38 @@ import resolvePlugin from './esbuild/resolve_plugin.js'
|
|
11
11
|
import ArgumentError from './esbuild/argument_error.js'
|
12
12
|
import throwCompileError from './esbuild/compile_error.js'
|
13
13
|
|
14
|
+
/**
|
15
|
+
* Compile the given paths, outputting the result to stdout. This is designed to be called as a CLI:
|
16
|
+
*
|
17
|
+
* Example with Deno run (dev and test):
|
18
|
+
* deno run -A lib/proscenium/compilers/esbuild.js --root ./test/internal lib/foo.js
|
19
|
+
* Example with Deno compiled binary:
|
20
|
+
* bin/esbuild lib/proscenium/compilers/esbuild.js --root ./test/internal lib/foo.js
|
21
|
+
*
|
22
|
+
* USAGE:
|
23
|
+
* esbuild [OPTIONS] <PATHS_ARG>...
|
24
|
+
*
|
25
|
+
* ARGS:
|
26
|
+
* <PATHS_ARG>... One or more file paths to compile.
|
27
|
+
*
|
28
|
+
* OPTIONS:
|
29
|
+
* --root
|
30
|
+
* Relative or absolute path to the root or current working directory when compilation will
|
31
|
+
* take place.
|
32
|
+
* --import-map
|
33
|
+
* Path to an import map, relative to the <root>.
|
34
|
+
* --write
|
35
|
+
* Write output to the filesystem according to esbuild logic.
|
36
|
+
* --debug
|
37
|
+
* Debug output,
|
38
|
+
*/
|
14
39
|
if (import.meta.main) {
|
15
40
|
!Deno.env.get('RAILS_ENV') && Deno.env.set('RAILS_ENV', 'development')
|
16
41
|
|
17
42
|
const { _: paths, ...options } = parseArgs(Deno.args, {
|
18
|
-
string: ['root', '
|
43
|
+
string: ['root', 'import-map'],
|
19
44
|
boolean: ['write', 'debug'],
|
20
45
|
alias: {
|
21
|
-
'runtime-dir': 'runtimeDir',
|
22
46
|
'import-map': 'importMap'
|
23
47
|
}
|
24
48
|
})
|
@@ -34,10 +58,12 @@ if (import.meta.main) {
|
|
34
58
|
}
|
35
59
|
|
36
60
|
async function main(paths = [], options = {}) {
|
37
|
-
const {
|
61
|
+
const { write, debug } = { write: false, ...options }
|
38
62
|
|
39
63
|
if (!Array.isArray(paths) || paths.length < 1) throw new ArgumentError('pathsRequired')
|
40
|
-
if (!root) throw new ArgumentError('rootRequired')
|
64
|
+
if (!options.root) throw new ArgumentError('rootRequired')
|
65
|
+
|
66
|
+
const root = resolve(options.root)
|
41
67
|
|
42
68
|
// Make sure that `root` is a valid directory.
|
43
69
|
try {
|
@@ -84,7 +110,6 @@ async function main(paths = [], options = {}) {
|
|
84
110
|
absWorkingDir: root,
|
85
111
|
logLevel: 'silent',
|
86
112
|
logLimit: 1,
|
87
|
-
sourcemap: isTest ? false : isProd ? 'linked' : 'inline',
|
88
113
|
outdir: 'public/assets',
|
89
114
|
outbase: './',
|
90
115
|
format: 'esm',
|
@@ -97,10 +122,18 @@ async function main(paths = [], options = {}) {
|
|
97
122
|
write
|
98
123
|
}
|
99
124
|
|
125
|
+
if (!debug) {
|
126
|
+
params.sourcemap = isProd || isTest ? false : 'inline'
|
127
|
+
}
|
128
|
+
|
100
129
|
let result
|
101
130
|
try {
|
102
131
|
result = await build(params)
|
103
132
|
} catch (error) {
|
133
|
+
if (debug) {
|
134
|
+
throw error
|
135
|
+
}
|
136
|
+
|
104
137
|
return { ...error.errors[0] }
|
105
138
|
} finally {
|
106
139
|
stop()
|
@@ -109,7 +142,8 @@ async function main(paths = [], options = {}) {
|
|
109
142
|
if (write) {
|
110
143
|
return new TextEncoder().encode(JSON.stringify(result))
|
111
144
|
} else {
|
112
|
-
|
145
|
+
const fileIndex = params.sourcemap === 'linked' ? 1 : 0
|
146
|
+
return result.outputFiles[fileIndex].contents
|
113
147
|
}
|
114
148
|
}
|
115
149
|
|
@@ -1,22 +1,64 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Proscenium::CssModule
|
4
|
+
class NotFound < StandardError
|
5
|
+
def initialize(pathname)
|
6
|
+
@pathname = pathname
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
def message
|
11
|
+
"Stylesheet is required, but does not exist: #{@pathname}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
4
15
|
def initialize(path)
|
5
|
-
@path =
|
16
|
+
@path = path
|
17
|
+
@css_module_path = "#{path}.module.css"
|
18
|
+
end
|
6
19
|
|
7
|
-
|
20
|
+
# Parses the given `content` for CSS modules names ('class' attributes beginning with '@'), and
|
21
|
+
# returns the content with said CSS Modules replaced with the compiled class names.
|
22
|
+
#
|
23
|
+
# Example:
|
24
|
+
# <div class="@my_css_module_name"></div>
|
25
|
+
def compile_class_names(content)
|
26
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(content)
|
27
|
+
|
28
|
+
return content if (modules = doc.css('[class*="@"]')).empty?
|
29
|
+
|
30
|
+
modules.each do |ele|
|
31
|
+
classes = ele.classes.map { |cls| cls.starts_with?('@') ? class_names!(cls[1..]) : cls }
|
32
|
+
ele['class'] = classes.join(' ')
|
33
|
+
end
|
8
34
|
|
9
|
-
|
35
|
+
doc.to_html.html_safe
|
10
36
|
end
|
11
37
|
|
12
|
-
#
|
38
|
+
# @returns [Array] of class names generated from the given CSS module `names`.
|
13
39
|
def class_names(*names)
|
14
|
-
|
40
|
+
side_load_css_module
|
41
|
+
names.flatten.compact.map { |name| "#{name.to_s.camelize(:lower)}#{hash}" }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Like #class_names, but requires that the stylesheet exists.
|
45
|
+
#
|
46
|
+
# @raises Proscenium::CssModule::NotFound if stylesheet does not exists.
|
47
|
+
def class_names!(...)
|
48
|
+
raise NotFound, @css_module_path unless Rails.root.join(@css_module_path).exist?
|
49
|
+
|
50
|
+
class_names(...)
|
15
51
|
end
|
16
52
|
|
17
53
|
private
|
18
54
|
|
19
55
|
def hash
|
20
|
-
@hash ||= Digest::SHA1.hexdigest("/#{@
|
56
|
+
@hash ||= Digest::SHA1.hexdigest("/#{@css_module_path}")[..7]
|
57
|
+
end
|
58
|
+
|
59
|
+
def side_load_css_module
|
60
|
+
return unless Rails.application.config.proscenium.side_load
|
61
|
+
|
62
|
+
Proscenium::SideLoad.append "#{@path}.module", :css
|
21
63
|
end
|
22
64
|
end
|
data/lib/proscenium/helper.rb
CHANGED
@@ -11,7 +11,9 @@ module Proscenium
|
|
11
11
|
def side_load_stylesheets
|
12
12
|
return unless Proscenium::Current.loaded
|
13
13
|
|
14
|
-
|
14
|
+
Proscenium::Current.loaded[:css].map do |sheet|
|
15
|
+
stylesheet_link_tag(sheet, id: "_#{Digest::SHA1.hexdigest("/#{sheet}")[..7]}")
|
16
|
+
end.join("\n").html_safe
|
15
17
|
end
|
16
18
|
|
17
19
|
def side_load_javascripts(**options)
|
@@ -31,7 +31,9 @@ module Proscenium
|
|
31
31
|
|
32
32
|
def cli
|
33
33
|
if ENV['PROSCENIUM_TEST']
|
34
|
-
|
34
|
+
[
|
35
|
+
'deno run -q --import-map import_map.json -A', 'lib/proscenium/compilers/esbuild.js'
|
36
|
+
].join(' ')
|
35
37
|
else
|
36
38
|
Gem.bin_path 'proscenium', 'esbuild'
|
37
39
|
end
|
@@ -4,9 +4,9 @@ require 'oj'
|
|
4
4
|
|
5
5
|
module Proscenium
|
6
6
|
class Middleware
|
7
|
-
class
|
7
|
+
class Lightningcss < Base
|
8
8
|
def attempt
|
9
|
-
benchmark :
|
9
|
+
benchmark :lightningcss do
|
10
10
|
with_custom_media { |path| build path }
|
11
11
|
end
|
12
12
|
end
|
@@ -41,7 +41,7 @@ module Proscenium
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def cli
|
44
|
-
Gem.bin_path 'proscenium', '
|
44
|
+
Gem.bin_path 'proscenium', 'lightningcss'
|
45
45
|
end
|
46
46
|
|
47
47
|
def cli_options
|
@@ -9,15 +9,8 @@ module Proscenium
|
|
9
9
|
|
10
10
|
autoload :Base
|
11
11
|
autoload :Esbuild
|
12
|
-
autoload :ParcelCss
|
13
12
|
autoload :Runtime
|
14
13
|
|
15
|
-
MIDDLEWARE_CLASSES = {
|
16
|
-
esbuild: Esbuild,
|
17
|
-
parcelcss: ParcelCss,
|
18
|
-
runtime: Runtime
|
19
|
-
}.freeze
|
20
|
-
|
21
14
|
def initialize(app)
|
22
15
|
@app = app
|
23
16
|
end
|
@@ -38,23 +31,15 @@ module Proscenium
|
|
38
31
|
def attempt(request)
|
39
32
|
return unless (type = find_type(request))
|
40
33
|
|
41
|
-
file_handler.attempt(request.env) ||
|
34
|
+
file_handler.attempt(request.env) || type.attempt(request)
|
42
35
|
end
|
43
36
|
|
44
37
|
# Returns the type of file being requested using Rails.application.config.proscenium.glob_types.
|
45
38
|
def find_type(request)
|
46
|
-
|
47
|
-
|
48
|
-
path = Rails.root.join(request.path[1..])
|
49
|
-
|
50
|
-
type, = glob_types.find do |_, globs|
|
51
|
-
# TODO: Look for the precompiled file in public/assets first
|
52
|
-
# globs.any? { |glob| Rails.public_path.join('assets').glob(glob).any?(path) }
|
53
|
-
|
54
|
-
globs.any? { |glob| Rails.root.glob(glob).any?(path) }
|
55
|
-
end
|
39
|
+
path = Pathname.new(request.path)
|
56
40
|
|
57
|
-
|
41
|
+
return Runtime if path.fnmatch?(glob_types[:runtime], File::FNM_EXTGLOB)
|
42
|
+
return Esbuild if path.fnmatch?(glob_types[:esbuild], File::FNM_EXTGLOB)
|
58
43
|
end
|
59
44
|
|
60
45
|
def file_handler
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Proscenium
|
4
|
+
class Phlex < ::Phlex::View
|
5
|
+
module Sideload
|
6
|
+
def template(...)
|
7
|
+
Proscenium::SideLoad.append self.class.path if Rails.application.config.proscenium.side_load
|
8
|
+
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
attr_accessor :path
|
15
|
+
|
16
|
+
def inherited(child)
|
17
|
+
path = caller_locations(1, 1)[0].path
|
18
|
+
child.path = path.delete_prefix(::Rails.root.to_s).delete_suffix('.rb')[1..]
|
19
|
+
|
20
|
+
child.prepend Sideload
|
21
|
+
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def css_module(name)
|
27
|
+
cssm.class_names!(name).join ' '
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def cssm
|
33
|
+
@cssm ||= Proscenium::CssModule.new(self.class.path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/proscenium/railtie.rb
CHANGED
@@ -12,16 +12,8 @@ module Proscenium
|
|
12
12
|
#
|
13
13
|
# See https://doc.deno.land/https://deno.land/std@0.145.0/path/mod.ts/~/globToRegExp
|
14
14
|
DEFAULT_GLOB_TYPES = {
|
15
|
-
esbuild:
|
16
|
-
|
17
|
-
'lib/**/*.{js,jsx,css}',
|
18
|
-
'app/**/*.{js,jsx,css}'
|
19
|
-
]
|
20
|
-
# parcelcss: [
|
21
|
-
# 'config/**/*.css',
|
22
|
-
# 'lib/**/*.css',
|
23
|
-
# 'app/**/*.css'
|
24
|
-
# ]
|
15
|
+
esbuild: '/{config,app,lib,node_modules}/**.{js,mjs,jsx,css}',
|
16
|
+
runtime: '/proscenium-runtime/**.{js,jsx}'
|
25
17
|
}.freeze
|
26
18
|
|
27
19
|
class << self
|
@@ -7,16 +7,16 @@ export default socketPath => {
|
|
7
7
|
|
8
8
|
consumer.subscriptions.create('Proscenium::ReloadChannel', {
|
9
9
|
received: debounce(() => {
|
10
|
-
console.log('Proscenium
|
10
|
+
console.log('[Proscenium] Files changed; reloading...')
|
11
11
|
location.reload()
|
12
12
|
}, 200),
|
13
13
|
|
14
14
|
connected() {
|
15
|
-
console.log('Proscenium
|
15
|
+
console.log('[Proscenium] Auto-reload websocket connected')
|
16
16
|
},
|
17
17
|
|
18
18
|
disconnected() {
|
19
|
-
console.log('Proscenium
|
19
|
+
console.log('[Proscenium] Auto-reload websocket disconnected')
|
20
20
|
}
|
21
21
|
})
|
22
22
|
}
|
data/lib/proscenium/side_load.rb
CHANGED
@@ -5,17 +5,6 @@ module Proscenium
|
|
5
5
|
DEFAULT_EXTENSIONS = %i[js css].freeze
|
6
6
|
EXTENSIONS = %i[js css].freeze
|
7
7
|
|
8
|
-
class NotFound < StandardError
|
9
|
-
def initialize(pathname)
|
10
|
-
@pathname = pathname
|
11
|
-
super
|
12
|
-
end
|
13
|
-
|
14
|
-
def message
|
15
|
-
"#{@pathname} does not exist"
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
8
|
module_function
|
20
9
|
|
21
10
|
# Side load the given asset `path`, by appending it to `Proscenium::Current.loaded`, which is a
|
@@ -46,40 +35,25 @@ module Proscenium
|
|
46
35
|
end
|
47
36
|
end
|
48
37
|
|
49
|
-
# Like #append, but only accepts a single `path` argument, which must be a Pathname. Raises
|
50
|
-
# `NotFound` if path does not exist,
|
51
|
-
def append!(pathname)
|
52
|
-
Proscenium::Current.loaded ||= EXTENSIONS.to_h { |e| [e, Set[]] }
|
53
|
-
|
54
|
-
unless pathname.is_a?(Pathname)
|
55
|
-
raise ArgumentError, "Argument `pathname` (#{pathname}) must be a Pathname"
|
56
|
-
end
|
57
|
-
|
58
|
-
ext = pathname.extname.sub('.', '').to_sym
|
59
|
-
path = pathname.relative_path_from(Rails.root).to_s
|
60
|
-
|
61
|
-
raise ArgumentError, "unsupported extension: #{ext}" unless EXTENSIONS.include?(ext)
|
62
|
-
|
63
|
-
return if Proscenium::Current.loaded[ext].include?(path)
|
64
|
-
|
65
|
-
raise NotFound, path unless pathname.exist?
|
66
|
-
|
67
|
-
Proscenium::Current.loaded[ext] << path
|
68
|
-
|
69
|
-
Rails.logger.debug "[Proscenium] Side loaded /#{path}"
|
70
|
-
end
|
71
|
-
|
72
38
|
module Monkey
|
73
39
|
module TemplateRenderer
|
74
40
|
private
|
75
41
|
|
76
42
|
def render_template(view, template, layout_name, locals)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
43
|
+
layout = find_layout(layout_name, locals.keys, [formats.first])
|
44
|
+
renderable = template.instance_variable_get(:@renderable)
|
45
|
+
|
46
|
+
if template.is_a?(ActionView::Template::Renderable) &&
|
47
|
+
renderable.class < ::ViewComponent::Base && renderable.class.format == :html
|
48
|
+
# Side load controller rendered ViewComponent
|
49
|
+
Proscenium::SideLoad.append "app/views/#{layout.virtual_path}" if layout
|
50
|
+
Proscenium::SideLoad.append "app/views/#{renderable.virtual_path}"
|
51
|
+
elsif template.respond_to?(:virtual_path) &&
|
52
|
+
template.respond_to?(:type) && template.type == :html
|
53
|
+
# Side load regular view template.
|
54
|
+
Proscenium::SideLoad.append "app/views/#{layout.virtual_path}" if layout
|
55
|
+
|
56
|
+
# Try side loading the variant template
|
83
57
|
if template.respond_to?(:variant) && template.variant
|
84
58
|
Proscenium::SideLoad.append "app/views/#{template.virtual_path}+#{template.variant}"
|
85
59
|
end
|
data/lib/proscenium/version.rb
CHANGED
@@ -5,12 +5,16 @@ module Proscenium::ViewComponent
|
|
5
5
|
|
6
6
|
autoload :TagBuilder
|
7
7
|
|
8
|
+
def render_in(...)
|
9
|
+
cssm.compile_class_names(super)
|
10
|
+
end
|
11
|
+
|
8
12
|
def before_render
|
9
13
|
side_load_assets unless self.class < ReactComponent
|
10
14
|
end
|
11
15
|
|
12
16
|
def css_module(name)
|
13
|
-
cssm.class_names(name
|
17
|
+
cssm.class_names!(name).join ' '
|
14
18
|
end
|
15
19
|
|
16
20
|
private
|
data/lib/proscenium.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: proscenium
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: x86_64-linux
|
6
6
|
authors:
|
7
7
|
- Joel Moss
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actioncable
|
@@ -64,6 +64,20 @@ dependencies:
|
|
64
64
|
- - "~>"
|
65
65
|
- !ruby/object:Gem::Version
|
66
66
|
version: '3.0'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: nokogiri
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '1.13'
|
74
|
+
type: :runtime
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '1.13'
|
67
81
|
- !ruby/object:Gem::Dependency
|
68
82
|
name: oj
|
69
83
|
requirement: !ruby/object:Gem::Requirement
|
@@ -117,7 +131,7 @@ email:
|
|
117
131
|
- joel@developwithstyle.com
|
118
132
|
executables:
|
119
133
|
- esbuild
|
120
|
-
-
|
134
|
+
- lightningcss
|
121
135
|
extensions: []
|
122
136
|
extra_rdoc_files: []
|
123
137
|
files:
|
@@ -129,7 +143,7 @@ files:
|
|
129
143
|
- app/components/application_component.rb
|
130
144
|
- app/components/react_component.rb
|
131
145
|
- bin/esbuild
|
132
|
-
- bin/
|
146
|
+
- bin/lightningcss
|
133
147
|
- config/routes.rb
|
134
148
|
- lib/proscenium.rb
|
135
149
|
- lib/proscenium/compiler.js
|
@@ -137,6 +151,7 @@ files:
|
|
137
151
|
- lib/proscenium/compilers/esbuild.js
|
138
152
|
- lib/proscenium/compilers/esbuild/argument_error.js
|
139
153
|
- lib/proscenium/compilers/esbuild/compile_error.js
|
154
|
+
- lib/proscenium/compilers/esbuild/css/postcss.js
|
140
155
|
- lib/proscenium/compilers/esbuild/css_plugin.js
|
141
156
|
- lib/proscenium/compilers/esbuild/env_plugin.js
|
142
157
|
- lib/proscenium/compilers/esbuild/http_bundle_plugin.js
|
@@ -151,18 +166,17 @@ files:
|
|
151
166
|
- lib/proscenium/middleware.rb
|
152
167
|
- lib/proscenium/middleware/base.rb
|
153
168
|
- lib/proscenium/middleware/esbuild.rb
|
154
|
-
- lib/proscenium/middleware/
|
169
|
+
- lib/proscenium/middleware/lightningcss.rb
|
155
170
|
- lib/proscenium/middleware/runtime.rb
|
156
171
|
- lib/proscenium/middleware/static.rb
|
172
|
+
- lib/proscenium/phlex.rb
|
157
173
|
- lib/proscenium/precompile.rb
|
158
174
|
- lib/proscenium/railtie.rb
|
159
175
|
- lib/proscenium/runtime/auto_reload.js
|
160
|
-
- lib/proscenium/runtime/component_manager/index.js
|
161
|
-
- lib/proscenium/runtime/component_manager/render_component.jsx
|
162
|
-
- lib/proscenium/runtime/import_css.js
|
163
176
|
- lib/proscenium/runtime/react_shim/index.js
|
164
177
|
- lib/proscenium/runtime/react_shim/package.json
|
165
178
|
- lib/proscenium/side_load.rb
|
179
|
+
- lib/proscenium/utils.js
|
166
180
|
- lib/proscenium/version.rb
|
167
181
|
- lib/proscenium/view_component.rb
|
168
182
|
- lib/proscenium/view_component/tag_builder.rb
|
@@ -186,9 +200,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
186
200
|
version: 2.7.0
|
187
201
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
202
|
requirements:
|
189
|
-
- - "
|
203
|
+
- - ">="
|
190
204
|
- !ruby/object:Gem::Version
|
191
|
-
version:
|
205
|
+
version: '0'
|
192
206
|
requirements: []
|
193
207
|
rubygems_version: 3.3.7
|
194
208
|
signing_key:
|
data/bin/parcel_css
DELETED
Binary file
|
@@ -1,27 +0,0 @@
|
|
1
|
-
/* eslint-disable no-console */
|
2
|
-
|
3
|
-
export async function init() {
|
4
|
-
const elements = document.querySelectorAll('[data-component]')
|
5
|
-
|
6
|
-
if (elements.length < 1) return
|
7
|
-
|
8
|
-
const { default: renderComponent } = await import(`./render_component`)
|
9
|
-
|
10
|
-
Array.from(elements, ele => {
|
11
|
-
const data = JSON.parse(ele.getAttribute('data-component'))
|
12
|
-
|
13
|
-
let isVisible = false
|
14
|
-
const observer = new IntersectionObserver(entries => {
|
15
|
-
entries.forEach(entry => {
|
16
|
-
if (!isVisible && entry.isIntersecting) {
|
17
|
-
isVisible = true
|
18
|
-
observer.unobserve(ele)
|
19
|
-
|
20
|
-
renderComponent(ele, data)
|
21
|
-
}
|
22
|
-
})
|
23
|
-
})
|
24
|
-
|
25
|
-
observer.observe(ele)
|
26
|
-
})
|
27
|
-
}
|
@@ -1,36 +0,0 @@
|
|
1
|
-
/* eslint-disable no-console */
|
2
|
-
|
3
|
-
import { RAILS_ENV } from 'env'
|
4
|
-
|
5
|
-
// We don't use JSX, as doing so would auto-inject React. We don't want to do this, as React is lazy
|
6
|
-
// loaded only when needed.
|
7
|
-
export default async function (ele, data) {
|
8
|
-
const { createElement, useEffect, lazy, Suspense } = await import('react')
|
9
|
-
const { createRoot } = await import('react-dom/client')
|
10
|
-
|
11
|
-
const component = lazy(() => import(`/app/components${data.path}.jsx`))
|
12
|
-
const contentLoader = data.contentLoader && ele.firstElementChild
|
13
|
-
|
14
|
-
const Fallback = ({ contentLoader }) => {
|
15
|
-
useEffect(() => {
|
16
|
-
contentLoader && contentLoader.remove()
|
17
|
-
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
18
|
-
|
19
|
-
if (!contentLoader) return null
|
20
|
-
|
21
|
-
return (
|
22
|
-
<div
|
23
|
-
style={{ height: '100%' }}
|
24
|
-
dangerouslySetInnerHTML={{ __html: contentLoader.outerHTML }}
|
25
|
-
></div>
|
26
|
-
)
|
27
|
-
}
|
28
|
-
|
29
|
-
createRoot(ele).render(
|
30
|
-
<Suspense fallback={<Fallback contentLoader={contentLoader} />}>
|
31
|
-
{createElement(component, data.props)}
|
32
|
-
</Suspense>
|
33
|
-
)
|
34
|
-
|
35
|
-
RAILS_ENV === 'development' && console.debug(`[REACT]`, `Rendered ${data.path.slice(1)}`)
|
36
|
-
}
|
@@ -1,46 +0,0 @@
|
|
1
|
-
async function digest(value) {
|
2
|
-
value = new TextEncoder().encode(value)
|
3
|
-
const view = new DataView(await crypto.subtle.digest('SHA-1', value))
|
4
|
-
|
5
|
-
let hexCodes = ''
|
6
|
-
for (let index = 0; index < view.byteLength; index += 4) {
|
7
|
-
hexCodes += view.getUint32(index).toString(16).padStart(8, '0')
|
8
|
-
}
|
9
|
-
|
10
|
-
return hexCodes.slice(0, 8)
|
11
|
-
}
|
12
|
-
|
13
|
-
const proxyCache = {}
|
14
|
-
|
15
|
-
export async function importCssModule(path) {
|
16
|
-
appendStylesheet(path)
|
17
|
-
|
18
|
-
if (Object.keys(proxyCache).includes(path)) {
|
19
|
-
return proxyCache[path]
|
20
|
-
}
|
21
|
-
|
22
|
-
const hashValue = await digest(path)
|
23
|
-
return (proxyCache[path] = new Proxy(
|
24
|
-
{},
|
25
|
-
{
|
26
|
-
get(target, prop, receiver) {
|
27
|
-
if (prop in target || typeof prop === 'symbol') {
|
28
|
-
return Reflect.get(target, prop, receiver)
|
29
|
-
} else {
|
30
|
-
return `${prop}${hashValue}`
|
31
|
-
}
|
32
|
-
}
|
33
|
-
}
|
34
|
-
))
|
35
|
-
}
|
36
|
-
|
37
|
-
export function appendStylesheet(path) {
|
38
|
-
// Make sure we only load the stylesheet once.
|
39
|
-
if (document.head.querySelector(`link[rel=stylesheet][href='${path}']`)) return
|
40
|
-
|
41
|
-
const ele = document.createElement('link')
|
42
|
-
ele.setAttribute('rel', 'stylesheet')
|
43
|
-
ele.setAttribute('media', 'all')
|
44
|
-
ele.setAttribute('href', path)
|
45
|
-
document.head.appendChild(ele)
|
46
|
-
}
|