proscenium 0.6.0-x86_64-linux → 0.7.0-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 +128 -107
- data/lib/proscenium/css_module/class_names_resolver.rb +66 -0
- data/lib/proscenium/css_module/resolver.rb +76 -0
- data/lib/proscenium/css_module.rb +18 -39
- data/lib/proscenium/esbuild/golib.rb +97 -0
- data/lib/proscenium/esbuild.rb +32 -0
- data/lib/proscenium/ext/proscenium +0 -0
- data/lib/proscenium/ext/proscenium.h +109 -0
- data/lib/proscenium/helper.rb +0 -23
- data/lib/proscenium/log_subscriber.rb +26 -0
- data/lib/proscenium/middleware/base.rb +28 -36
- data/lib/proscenium/middleware/esbuild.rb +18 -44
- data/lib/proscenium/middleware/url.rb +1 -6
- data/lib/proscenium/middleware.rb +12 -16
- data/lib/proscenium/phlex/component_concerns.rb +27 -0
- data/lib/proscenium/phlex/page.rb +62 -0
- data/lib/proscenium/phlex/react_component.rb +52 -8
- data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
- data/lib/proscenium/phlex.rb +34 -33
- data/lib/proscenium/railtie.rb +41 -67
- data/lib/proscenium/side_load/ensure_loaded.rb +25 -0
- data/lib/proscenium/side_load/helper.rb +25 -0
- data/lib/proscenium/side_load/monkey.rb +48 -0
- data/lib/proscenium/side_load.rb +58 -52
- data/lib/proscenium/version.rb +1 -1
- data/lib/proscenium/view_component/react_component.rb +14 -0
- data/lib/proscenium/view_component.rb +28 -18
- data/lib/proscenium.rb +79 -2
- metadata +35 -77
- data/app/channels/proscenium/connection.rb +0 -13
- data/app/channels/proscenium/reload_channel.rb +0 -9
- data/bin/esbuild +0 -0
- data/bin/lightningcss +0 -0
- data/config/routes.rb +0 -7
- data/lib/proscenium/compiler.js +0 -84
- data/lib/proscenium/compilers/esbuild/argument_error.js +0 -24
- data/lib/proscenium/compilers/esbuild/compile_error.js +0 -148
- data/lib/proscenium/compilers/esbuild/css/postcss.js +0 -67
- data/lib/proscenium/compilers/esbuild/css_plugin.js +0 -172
- data/lib/proscenium/compilers/esbuild/env_plugin.js +0 -46
- data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +0 -53
- data/lib/proscenium/compilers/esbuild/import_map/parser.js +0 -178
- data/lib/proscenium/compilers/esbuild/import_map/read.js +0 -64
- data/lib/proscenium/compilers/esbuild/import_map/resolver.js +0 -95
- data/lib/proscenium/compilers/esbuild/import_map/utils.js +0 -25
- data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -207
- data/lib/proscenium/compilers/esbuild/setup_plugin.js +0 -45
- data/lib/proscenium/compilers/esbuild/solidjs_plugin.js +0 -24
- data/lib/proscenium/compilers/esbuild.bench.js +0 -14
- data/lib/proscenium/compilers/esbuild.js +0 -179
- data/lib/proscenium/link_to_helper.rb +0 -40
- data/lib/proscenium/middleware/lightningcss.rb +0 -64
- data/lib/proscenium/middleware/outside_root.rb +0 -26
- data/lib/proscenium/middleware/runtime.rb +0 -22
- data/lib/proscenium/middleware/static.rb +0 -14
- data/lib/proscenium/phlex/component.rb +0 -9
- data/lib/proscenium/precompile.rb +0 -31
- data/lib/proscenium/runtime/auto_reload.js +0 -40
- data/lib/proscenium/runtime/react_shim/index.js +0 -1
- data/lib/proscenium/runtime/react_shim/package.json +0 -5
- data/lib/proscenium/utils.js +0 -12
- data/lib/tasks/assets.rake +0 -19
@@ -1,207 +0,0 @@
|
|
1
|
-
import { join, resolve } from 'std/path/mod.ts'
|
2
|
-
import resolveFromImportMap from './import_map/resolver.js'
|
3
|
-
import { cache } from 'cache'
|
4
|
-
|
5
|
-
import setup from './setup_plugin.js'
|
6
|
-
import { isBareModule } from '../../utils.js'
|
7
|
-
|
8
|
-
const importKinds = ['import-statement', 'dynamic-import', 'require-call', 'import-rule']
|
9
|
-
|
10
|
-
export default setup('resolve', (build, options) => {
|
11
|
-
const { runtimeDir, importMap } = options
|
12
|
-
const cwd = build.initialOptions.absWorkingDir
|
13
|
-
const runtimeCwdAlias = `${cwd}/proscenium-runtime`
|
14
|
-
let bundled = false
|
15
|
-
|
16
|
-
const env = Deno.env.get('RAILS_ENV')
|
17
|
-
const isProd = env === 'production'
|
18
|
-
|
19
|
-
return [
|
20
|
-
{
|
21
|
-
// Filters for imports starting with `url:http://` or `url:https://`; returning the path
|
22
|
-
// without the `url:` prefix, and a namespace of 'url`
|
23
|
-
type: 'onResolve',
|
24
|
-
filter: /^url:https?:\/\//,
|
25
|
-
callback(args) {
|
26
|
-
return {
|
27
|
-
path: args.path.slice(4),
|
28
|
-
namespace: 'url'
|
29
|
-
}
|
30
|
-
}
|
31
|
-
},
|
32
|
-
|
33
|
-
{
|
34
|
-
type: 'onResolve',
|
35
|
-
filter: /.*/,
|
36
|
-
namespace: 'url',
|
37
|
-
callback(args) {
|
38
|
-
if (!isBareModule(args.path)) {
|
39
|
-
return {
|
40
|
-
path: new URL(args.path, args.importer).toString(),
|
41
|
-
namespace: 'url'
|
42
|
-
}
|
43
|
-
}
|
44
|
-
}
|
45
|
-
},
|
46
|
-
|
47
|
-
{
|
48
|
-
type: 'onLoad',
|
49
|
-
filter: /.*/,
|
50
|
-
namespace: 'url',
|
51
|
-
async callback(args) {
|
52
|
-
const file = await cache(args.path)
|
53
|
-
const contents = await Deno.readTextFile(file.path)
|
54
|
-
|
55
|
-
return { contents }
|
56
|
-
}
|
57
|
-
},
|
58
|
-
|
59
|
-
{
|
60
|
-
type: 'onResolve',
|
61
|
-
filter: /.*/,
|
62
|
-
async callback(args) {
|
63
|
-
if (args.path.includes('?')) {
|
64
|
-
const [path, query] = args.path.split('?')
|
65
|
-
args.path = path
|
66
|
-
args.suffix = `?${query}`
|
67
|
-
args.queryParams = new URLSearchParams(query)
|
68
|
-
} else if (options.cacheQueryString && options.cacheQueryString !== '') {
|
69
|
-
args.suffix = `?${options.cacheQueryString}`
|
70
|
-
}
|
71
|
-
|
72
|
-
// Mark remote modules as external. If not css, then the path is prefixed with "url:", which
|
73
|
-
// is then handled by the Url Middleware.
|
74
|
-
if (
|
75
|
-
!args.importer.endsWith('.css') &&
|
76
|
-
(args.path.startsWith('http://') || args.path.startsWith('https://'))
|
77
|
-
) {
|
78
|
-
return { path: `/url:${encodeURIComponent(args.path)}`, external: true }
|
79
|
-
}
|
80
|
-
|
81
|
-
// Rewrite the path to the actual runtime directory.
|
82
|
-
if (args.path.startsWith(runtimeCwdAlias)) {
|
83
|
-
return { path: join(runtimeDir, args.path.slice(runtimeCwdAlias.length)) }
|
84
|
-
}
|
85
|
-
|
86
|
-
// Everything else is unbundled.
|
87
|
-
if (importKinds.includes(args.kind)) {
|
88
|
-
return await unbundleImport(args)
|
89
|
-
}
|
90
|
-
}
|
91
|
-
}
|
92
|
-
]
|
93
|
-
|
94
|
-
// Resolve the given `params.path` to a path relative to the Rails root.
|
95
|
-
//
|
96
|
-
// Examples:
|
97
|
-
// 'react' -> '/.../node_modules/react/index.js'
|
98
|
-
// './my_module' -> '/.../app/my_module.js'
|
99
|
-
// '/app/my_module' -> '/.../app/my_module.js'
|
100
|
-
async function unbundleImport(params) {
|
101
|
-
const result = { path: params.path, suffix: params.suffix }
|
102
|
-
|
103
|
-
if (importMap) {
|
104
|
-
let baseURL
|
105
|
-
if (params.importer.startsWith('https://') || params.importer.startsWith('http://')) {
|
106
|
-
baseURL = new URL(params.importer)
|
107
|
-
} else {
|
108
|
-
baseURL = new URL(params.importer.slice(cwd.length), 'file://')
|
109
|
-
}
|
110
|
-
|
111
|
-
const { matched, resolvedImport } = resolveFromImportMap(params.path, importMap, baseURL)
|
112
|
-
// console.log({ importMap, matched, resolvedImport })
|
113
|
-
|
114
|
-
if (matched) {
|
115
|
-
if (resolvedImport instanceof URL) {
|
116
|
-
if (resolvedImport.protocol === 'file:') {
|
117
|
-
params.path = resolvedImport.pathname
|
118
|
-
} else {
|
119
|
-
if (params.importer.endsWith('.css')) {
|
120
|
-
return { path: resolvedImport.href, external: true }
|
121
|
-
}
|
122
|
-
|
123
|
-
return { path: `/url:${encodeURIComponent(resolvedImport.href)}`, external: true }
|
124
|
-
}
|
125
|
-
} else {
|
126
|
-
result.path = resolvedImport
|
127
|
-
}
|
128
|
-
}
|
129
|
-
}
|
130
|
-
|
131
|
-
// Absolute path - append to current working dir.
|
132
|
-
if (params.path.startsWith('/')) {
|
133
|
-
result.path = resolve(cwd, params.path.slice(1))
|
134
|
-
}
|
135
|
-
|
136
|
-
const resOptions = {
|
137
|
-
// If path is a bare module (node_modules), and resolveDir is the Proscenium runtime dir, or
|
138
|
-
// is the current working dir, then use `cwd` as the `resolveDir`, otherwise pass it through
|
139
|
-
// as is. This ensures that nested node_modules are resolved correctly.
|
140
|
-
resolveDir:
|
141
|
-
isBareModule(result.path) &&
|
142
|
-
(!params.resolveDir.startsWith(cwd) || params.resolveDir.startsWith(runtimeDir))
|
143
|
-
? cwd
|
144
|
-
: params.resolveDir,
|
145
|
-
kind: params.kind,
|
146
|
-
pluginData: {
|
147
|
-
// We use this property later on, as we should ignore this resolution call.
|
148
|
-
isResolvingPath: true
|
149
|
-
}
|
150
|
-
}
|
151
|
-
|
152
|
-
// Resolve the path using esbuild's internal resolution. This allows us to import node packages
|
153
|
-
// and extension-less paths without custom code, as esbuild with resolve them for us.
|
154
|
-
const resolveResult = await build.resolve(result.path, resOptions)
|
155
|
-
|
156
|
-
// Simple return the resolved result if we have an error. Usually happens when module is not
|
157
|
-
// found.
|
158
|
-
if (resolveResult.errors.length > 0) return resolveResult
|
159
|
-
|
160
|
-
// If 'bundle-all' queryParam is defined, return the resolveResult.
|
161
|
-
if (bundled || params.queryParams?.has('bundle-all')) {
|
162
|
-
bundled = true
|
163
|
-
return { ...resolveResult, suffix: '?bundle-all' }
|
164
|
-
}
|
165
|
-
|
166
|
-
// If 'bundle' queryParam is defined, return the resolveResult.
|
167
|
-
if (params.queryParams?.has('bundle')) {
|
168
|
-
return { ...resolveResult, suffix: '?bundle' }
|
169
|
-
}
|
170
|
-
|
171
|
-
if (resolveResult.path.startsWith(runtimeDir)) {
|
172
|
-
result.path = '/proscenium-runtime' + resolveResult.path.slice(runtimeDir.length)
|
173
|
-
} else if (!resolveResult.path.startsWith(cwd) && !isProd) {
|
174
|
-
// Resolved path is not in the current working directory. It could be linked to a file outside
|
175
|
-
// the CWD, or it's just invalid. If not in production, return as an outsideRoot namespaced,
|
176
|
-
// and externally suffixed path. This lets the Rails Proscenium::Middleware::OutsideRoot
|
177
|
-
// handle the import.
|
178
|
-
return {
|
179
|
-
...resolveResult,
|
180
|
-
namespace: 'outsideRoot',
|
181
|
-
path: `${resolveResult.path}?outsideRoot`,
|
182
|
-
external: true
|
183
|
-
}
|
184
|
-
} else {
|
185
|
-
result.path = resolveResult.path.slice(cwd.length)
|
186
|
-
}
|
187
|
-
|
188
|
-
result.sideEffects = resolveResult.sideEffects
|
189
|
-
|
190
|
-
if (
|
191
|
-
params.path.endsWith('.css') &&
|
192
|
-
params.kind === 'import-statement' &&
|
193
|
-
/\.jsx?$/.test(params.importer)
|
194
|
-
) {
|
195
|
-
// We're importing a CSS file from JS(X).
|
196
|
-
return { ...resolveResult, pluginData: { importedFromJs: true } }
|
197
|
-
} else {
|
198
|
-
result.external = true
|
199
|
-
}
|
200
|
-
|
201
|
-
if (result.suffix && result.suffix !== '') {
|
202
|
-
result.path = `${result.path}${result.suffix}`
|
203
|
-
}
|
204
|
-
|
205
|
-
return result
|
206
|
-
}
|
207
|
-
})
|
@@ -1,45 +0,0 @@
|
|
1
|
-
export default (pluginName, pluginFn) => {
|
2
|
-
return (options = {}) => ({
|
3
|
-
name: pluginName,
|
4
|
-
async setup(build) {
|
5
|
-
const callbacks = await pluginFn(build, options)
|
6
|
-
|
7
|
-
callbacks.forEach(({ type, callback, filter, namespace }) => {
|
8
|
-
if (type === 'onResolve') {
|
9
|
-
build.onResolve({ filter, namespace }, async params => {
|
10
|
-
if (params.pluginData?.isResolvingPath) return
|
11
|
-
|
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
|
-
}
|
28
|
-
|
29
|
-
return results
|
30
|
-
})
|
31
|
-
} else if (type === 'onLoad') {
|
32
|
-
build.onLoad({ filter, namespace }, async params => {
|
33
|
-
options.debug && console.debug(`plugin(${pluginName}):onLoad`, { params })
|
34
|
-
|
35
|
-
const results = await callback(params)
|
36
|
-
|
37
|
-
options.debug && console.debug(`plugin(${pluginName}):onLoad`, { results })
|
38
|
-
|
39
|
-
return results
|
40
|
-
})
|
41
|
-
}
|
42
|
-
})
|
43
|
-
}
|
44
|
-
})
|
45
|
-
}
|
@@ -1,24 +0,0 @@
|
|
1
|
-
import { basename } from 'std/path/mod.ts'
|
2
|
-
import { transformAsync } from '@babel/core'
|
3
|
-
import solid from 'babel-preset-solid'
|
4
|
-
|
5
|
-
import { setup } from './setup_plugin.js'
|
6
|
-
|
7
|
-
export default setup('solidjs', () => {
|
8
|
-
return [
|
9
|
-
{
|
10
|
-
type: 'onLoad',
|
11
|
-
filter: /\.jsx$/,
|
12
|
-
async callback(args) {
|
13
|
-
const source = await Deno.readTextFile(args.path)
|
14
|
-
|
15
|
-
const { code } = await transformAsync(source, {
|
16
|
-
presets: [solid],
|
17
|
-
filename: basename(args.path)
|
18
|
-
})
|
19
|
-
|
20
|
-
return { contents: code, loader: 'js' }
|
21
|
-
}
|
22
|
-
}
|
23
|
-
]
|
24
|
-
})
|
@@ -1,14 +0,0 @@
|
|
1
|
-
import { join } from 'std/path/mod.ts'
|
2
|
-
|
3
|
-
import compile from './esbuild.js'
|
4
|
-
|
5
|
-
const root = join(Deno.cwd(), 'test', 'internal')
|
6
|
-
const lightningcssBin = join(Deno.cwd(), 'bin', 'lightningcss')
|
7
|
-
|
8
|
-
Deno.bench('esbuild js', async () => {
|
9
|
-
await compile(['lib/foo.js'], { root, lightningcssBin })
|
10
|
-
})
|
11
|
-
|
12
|
-
Deno.bench('esbuild css', async () => {
|
13
|
-
await compile(['lib/foo.css'], { root, lightningcssBin })
|
14
|
-
})
|
@@ -1,179 +0,0 @@
|
|
1
|
-
import { writeAll } from 'std/streams/mod.ts'
|
2
|
-
import { parse as parseArgs } from 'std/flags/mod.ts'
|
3
|
-
import { expandGlob } from 'std/fs/mod.ts'
|
4
|
-
import { join, isGlob, resolve, dirname, fromFileUrl } from 'std/path/mod.ts'
|
5
|
-
import { build, stop } from 'esbuild'
|
6
|
-
|
7
|
-
import readImportMap from './esbuild/import_map/read.js'
|
8
|
-
import envPlugin from './esbuild/env_plugin.js'
|
9
|
-
import cssPlugin from './esbuild/css_plugin.js'
|
10
|
-
import resolvePlugin from './esbuild/resolve_plugin.js'
|
11
|
-
import ArgumentError from './esbuild/argument_error.js'
|
12
|
-
import throwCompileError from './esbuild/compile_error.js'
|
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 or globs to compile.
|
27
|
-
*
|
28
|
-
* OPTIONS:
|
29
|
-
* --root <PATH>
|
30
|
-
* Relative or absolute path to the root or current working directory when compilation will
|
31
|
-
* take place.
|
32
|
-
* --import-map <PATH>
|
33
|
-
* Path to an import map, relative to the <root>.
|
34
|
-
* --lightningcss-bin <PATH>
|
35
|
-
* Path to the lightningcss CLI binary.
|
36
|
-
* --write
|
37
|
-
* Write output to the filesystem according to esbuild logic.
|
38
|
-
* --cache-query-string <STRING>
|
39
|
-
* Query string to append to all imports as a cache buster. Example: `v1`.
|
40
|
-
* --debug
|
41
|
-
* Debug output,
|
42
|
-
*/
|
43
|
-
if (import.meta.main) {
|
44
|
-
!Deno.env.get('RAILS_ENV') && Deno.env.set('RAILS_ENV', 'development')
|
45
|
-
|
46
|
-
const { _: paths, ...options } = parseArgs(Deno.args, {
|
47
|
-
string: ['root', 'import-map', 'lightningcss-bin', 'cache-query-string'],
|
48
|
-
boolean: ['write', 'debug'],
|
49
|
-
alias: {
|
50
|
-
'import-map': 'importMap',
|
51
|
-
'cache-query-string': 'cacheQueryString',
|
52
|
-
'lightningcss-bin': 'lightningcssBin'
|
53
|
-
}
|
54
|
-
})
|
55
|
-
|
56
|
-
let result = await main(paths, options)
|
57
|
-
|
58
|
-
// `result` is an error object, so return to stderr as JSON, and an exit code of 1.
|
59
|
-
if (isPlainObject(result)) {
|
60
|
-
result = new TextEncoder().encode(`(${throwCompileError()})(${JSON.stringify(result)})`)
|
61
|
-
}
|
62
|
-
|
63
|
-
await writeAll(Deno.stdout, result)
|
64
|
-
}
|
65
|
-
|
66
|
-
async function main(paths = [], options = {}) {
|
67
|
-
const { write, debug } = { write: false, ...options }
|
68
|
-
|
69
|
-
if (!Array.isArray(paths) || paths.length < 1) throw new ArgumentError('pathsRequired')
|
70
|
-
if (!options.root) throw new ArgumentError('rootRequired')
|
71
|
-
if (!options.lightningcssBin) throw new ArgumentError('lightningcssBinRequired')
|
72
|
-
|
73
|
-
const root = resolve(options.root)
|
74
|
-
|
75
|
-
// Make sure that `root` is a valid directory.
|
76
|
-
try {
|
77
|
-
const stat = Deno.lstatSync(root)
|
78
|
-
if (!stat.isDirectory) throw new ArgumentError('rootUnknown', { root })
|
79
|
-
} catch {
|
80
|
-
throw new ArgumentError('rootUnknown', { root })
|
81
|
-
}
|
82
|
-
|
83
|
-
const env = Deno.env.get('RAILS_ENV')
|
84
|
-
const isProd = env === 'production'
|
85
|
-
const isTest = env === 'test'
|
86
|
-
const entryPoints = new Set()
|
87
|
-
|
88
|
-
for (let i = 0; i < paths.length; i++) {
|
89
|
-
const path = paths[i]
|
90
|
-
|
91
|
-
if (isGlob(path)) {
|
92
|
-
for await (const file of expandGlob(path, { root })) {
|
93
|
-
file.isFile && entryPoints.add(file.path)
|
94
|
-
}
|
95
|
-
} else if (path.startsWith('/') || /^url:https?:\/\//.test(path)) {
|
96
|
-
// Path is absolute, or is prefixed with 'url:', so it must be outsideRoot, or Url. Don't
|
97
|
-
// prefix the root.
|
98
|
-
// See Proscenium::Middleware::[OutsideRoot|Url].
|
99
|
-
entryPoints.add(path)
|
100
|
-
} else {
|
101
|
-
entryPoints.add(join(root, path))
|
102
|
-
}
|
103
|
-
}
|
104
|
-
|
105
|
-
let importMap
|
106
|
-
try {
|
107
|
-
importMap = readImportMap(options.importMap, root)
|
108
|
-
} catch (error) {
|
109
|
-
return {
|
110
|
-
detail: error.stack,
|
111
|
-
text: `Cannot read/parse import map: ${error.message}`,
|
112
|
-
location: {
|
113
|
-
file: error.file
|
114
|
-
}
|
115
|
-
}
|
116
|
-
}
|
117
|
-
|
118
|
-
const runtimeDir = resolve(dirname(fromFileUrl(import.meta.url)), '../runtime')
|
119
|
-
|
120
|
-
const params = {
|
121
|
-
entryPoints: Array.from(entryPoints),
|
122
|
-
absWorkingDir: root,
|
123
|
-
logLevel: 'silent',
|
124
|
-
logLimit: 1,
|
125
|
-
outdir: 'public/assets',
|
126
|
-
outbase: './',
|
127
|
-
format: 'esm',
|
128
|
-
jsx: 'automatic',
|
129
|
-
jsxDev: !isTest && !isProd,
|
130
|
-
minify: isProd,
|
131
|
-
bundle: true,
|
132
|
-
|
133
|
-
// The Esbuild default places browser before module, but we're building for modern browsers
|
134
|
-
// which support esm. So we prioritise that. Some libraries export a "browser" build that still
|
135
|
-
// uses CJS.
|
136
|
-
mainFields: ['module', 'browser', 'main'],
|
137
|
-
|
138
|
-
plugins: [
|
139
|
-
envPlugin(),
|
140
|
-
resolvePlugin({ runtimeDir, importMap, debug, cacheQueryString: options.cacheQueryString }),
|
141
|
-
cssPlugin({ lightningcssBin: options.lightningcssBin, debug })
|
142
|
-
],
|
143
|
-
metafile: write,
|
144
|
-
write
|
145
|
-
}
|
146
|
-
|
147
|
-
if (!debug) {
|
148
|
-
params.sourcemap = isProd || isTest ? false : 'inline'
|
149
|
-
}
|
150
|
-
|
151
|
-
let result
|
152
|
-
try {
|
153
|
-
result = await build(params)
|
154
|
-
} catch (error) {
|
155
|
-
if (debug) {
|
156
|
-
throw error
|
157
|
-
}
|
158
|
-
|
159
|
-
return { ...error.errors[0] }
|
160
|
-
} finally {
|
161
|
-
stop()
|
162
|
-
}
|
163
|
-
|
164
|
-
if (write) {
|
165
|
-
return new TextEncoder().encode(JSON.stringify(result))
|
166
|
-
} else {
|
167
|
-
const fileIndex = params.sourcemap === 'linked' ? 1 : 0
|
168
|
-
return result.outputFiles[fileIndex].contents
|
169
|
-
}
|
170
|
-
}
|
171
|
-
|
172
|
-
export function isPlainObject(value) {
|
173
|
-
if (Object.prototype.toString.call(value) !== '[object Object]') return false
|
174
|
-
|
175
|
-
const prototype = Object.getPrototypeOf(value)
|
176
|
-
return prototype === null || prototype === Object.prototype
|
177
|
-
}
|
178
|
-
|
179
|
-
export default main
|
@@ -1,40 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Proscenium
|
4
|
-
module LinkToHelper
|
5
|
-
# Overrides ActionView::Helpers::UrlHelper#link_to to allow passing a component instance as the
|
6
|
-
# URL, which will build the URL from the component path, eg. `/components/my_component`. The
|
7
|
-
# resulting link tag will also populate the `data` attribute with the component props.
|
8
|
-
#
|
9
|
-
# Example:
|
10
|
-
# link_to 'Go to', MyComponent
|
11
|
-
#
|
12
|
-
# TODO: ummm, todo it! ;)
|
13
|
-
end
|
14
|
-
|
15
|
-
# Component handling for the `link_to` helper.
|
16
|
-
class LinkToComponentArguments
|
17
|
-
def initialize(options, name_argument_index, context)
|
18
|
-
@options = options
|
19
|
-
@name_argument_index = name_argument_index
|
20
|
-
@component = @options[@name_argument_index]
|
21
|
-
|
22
|
-
# We have to render the component, and then extract the props from the component. Rendering
|
23
|
-
# first ensures that we have all the correct props.
|
24
|
-
context.render @component
|
25
|
-
end
|
26
|
-
|
27
|
-
def helper_options
|
28
|
-
@options[@name_argument_index] = "/components#{@component.virtual_path}"
|
29
|
-
@options[@name_argument_index += 1] ||= {}
|
30
|
-
@options[@name_argument_index][:rel] = 'nofollow'
|
31
|
-
@options[@name_argument_index][:data] ||= {}
|
32
|
-
@options[@name_argument_index][:data][:component] = {
|
33
|
-
path: @component.virtual_path,
|
34
|
-
props: @component.props
|
35
|
-
}
|
36
|
-
|
37
|
-
@options
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'oj'
|
4
|
-
|
5
|
-
module Proscenium
|
6
|
-
class Middleware
|
7
|
-
class Lightningcss < Base
|
8
|
-
def attempt
|
9
|
-
benchmark :lightningcss do
|
10
|
-
with_custom_media { |path| build path }
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
private
|
15
|
-
|
16
|
-
def with_custom_media
|
17
|
-
if custom_media?
|
18
|
-
Tempfile.create do |f|
|
19
|
-
contents = Pathname.new("#{root}#{@request.path}").read
|
20
|
-
f.write contents, "\n", custom_media_path.read
|
21
|
-
f.rewind
|
22
|
-
|
23
|
-
yield f.path
|
24
|
-
end
|
25
|
-
else
|
26
|
-
yield "#{root}#{@request.path}"
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def build(path)
|
31
|
-
results = super("#{cli} #{cli_options.join ' '} #{path}")
|
32
|
-
render_response css_module? ? Oj.load(results, mode: :strict)['code'] : results
|
33
|
-
end
|
34
|
-
|
35
|
-
def custom_media?
|
36
|
-
@custom_media ||= custom_media_path.exist?
|
37
|
-
end
|
38
|
-
|
39
|
-
def custom_media_path
|
40
|
-
@custom_media_path ||= Rails.root.join('lib', 'custom_media_queries.css')
|
41
|
-
end
|
42
|
-
|
43
|
-
def cli
|
44
|
-
Gem.bin_path 'proscenium', 'lightningcss'
|
45
|
-
end
|
46
|
-
|
47
|
-
def cli_options
|
48
|
-
options = ['--nesting', '--targets', "'>= 0.25%'"]
|
49
|
-
options << '--custom-media' if custom_media?
|
50
|
-
|
51
|
-
if css_module?
|
52
|
-
hash = Digest::SHA1.hexdigest(@request.path)[..7]
|
53
|
-
options += ['--css-modules', '--css-modules-pattern', "'[local]#{hash}'"]
|
54
|
-
end
|
55
|
-
|
56
|
-
Rails.env.production? ? options << '-m' : options
|
57
|
-
end
|
58
|
-
|
59
|
-
def css_module?
|
60
|
-
@css_module ||= /\.module\.css$/i.match?(@request.path_info)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Proscenium
|
4
|
-
class Middleware
|
5
|
-
# Provides a way to render files outside of the Rails root during non-production. This is
|
6
|
-
# primarily to support linked NPM modules, for example when using `pnpm link ...`.
|
7
|
-
class OutsideRoot < Esbuild
|
8
|
-
private
|
9
|
-
|
10
|
-
# @override [Esbuild] reassigns root to '/'.
|
11
|
-
def renderable?
|
12
|
-
old_root = root
|
13
|
-
@root = Pathname.new('/')
|
14
|
-
|
15
|
-
super
|
16
|
-
ensure
|
17
|
-
@root = old_root
|
18
|
-
end
|
19
|
-
|
20
|
-
# @override [Esbuild] does not remove leading slash, ensuring it is an absolute path.
|
21
|
-
def path
|
22
|
-
@request.path
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Proscenium
|
4
|
-
class Middleware
|
5
|
-
class Runtime < Esbuild
|
6
|
-
private
|
7
|
-
|
8
|
-
def renderable?
|
9
|
-
old_root = root
|
10
|
-
old_path_info = @request.path_info
|
11
|
-
|
12
|
-
@root = Pathname.new(__dir__).join('../')
|
13
|
-
@request.path_info = @request.path_info.sub(%r{^/proscenium-runtime/}, 'runtime/')
|
14
|
-
|
15
|
-
super
|
16
|
-
ensure
|
17
|
-
@request.path_info = old_path_info
|
18
|
-
@root = old_root
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Proscenium
|
4
|
-
module Middleware
|
5
|
-
# Serves static files from disk that end with .js or .css.
|
6
|
-
class Static < Base
|
7
|
-
def attempt
|
8
|
-
benchmark :static do
|
9
|
-
Rack::File.new(root, { 'X-Proscenium-Middleware' => 'static' }).call(@request.env)
|
10
|
-
end
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'open3'
|
4
|
-
|
5
|
-
module Proscenium
|
6
|
-
class Precompile
|
7
|
-
def self.call
|
8
|
-
new.call
|
9
|
-
end
|
10
|
-
|
11
|
-
def call
|
12
|
-
Rails.application.config.proscenium.glob_types.find do |type, globs|
|
13
|
-
cmd = "#{cli type} --root #{Rails.root} '#{globs.join "' '"}' --write"
|
14
|
-
_, stderr, status = Open3.capture3(cmd)
|
15
|
-
|
16
|
-
raise stderr unless status.success?
|
17
|
-
raise "#{type} compiliation failed -- #{stderr}" unless stderr.empty?
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def cli(type)
|
24
|
-
if ENV['PROSCENIUM_TEST']
|
25
|
-
"deno run -q --import-map import_map.json -A lib/proscenium/compilers/#{type}.js"
|
26
|
-
else
|
27
|
-
Gem.bin_path 'proscenium', type.to_s
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|