proscenium 0.6.0-arm64-darwin → 0.7.0-arm64-darwin
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/bin/proscenium +0 -0
- data/bin/proscenium.h +109 -0
- data/config/routes.rb +0 -3
- 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/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 -75
- 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/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,172 +0,0 @@
|
|
1
|
-
import { crypto } from 'std/crypto/mod.ts'
|
2
|
-
import { join, dirname, basename } from 'std/path/mod.ts'
|
3
|
-
|
4
|
-
import { fileExists } from '../../utils.js'
|
5
|
-
import postcss from './css/postcss.js'
|
6
|
-
import setup from './setup_plugin.js'
|
7
|
-
|
8
|
-
export default setup('css', async (build, options) => {
|
9
|
-
const cwd = build.initialOptions.absWorkingDir
|
10
|
-
|
11
|
-
let customMedia
|
12
|
-
try {
|
13
|
-
customMedia = await Deno.readTextFile(join(cwd, 'config', 'custom_media_queries.css'))
|
14
|
-
} catch {
|
15
|
-
// do nothing, as we don't require custom media.
|
16
|
-
}
|
17
|
-
|
18
|
-
return [
|
19
|
-
{
|
20
|
-
type: 'onLoad',
|
21
|
-
filter: /\.css$/,
|
22
|
-
namespace: 'file',
|
23
|
-
async callback(args) {
|
24
|
-
const hash = await digest(args.path.slice(cwd.length))
|
25
|
-
const isCssModule = args.path.endsWith('.module.css')
|
26
|
-
|
27
|
-
// If path is a CSS module, imported from JS, and a side-loaded ViewComponent stylesheet,
|
28
|
-
// simply return a JS proxy of the class names. The stylesheet itself will have already been
|
29
|
-
// side loaded. This avoids compiling the CSS all over again.
|
30
|
-
if (isCssModule && args.pluginData?.importedFromJs && (await isViewComponent(args.path))) {
|
31
|
-
return {
|
32
|
-
resolveDir: cwd,
|
33
|
-
loader: 'js',
|
34
|
-
contents: cssModulesProxyTemplate(hash)
|
35
|
-
}
|
36
|
-
}
|
37
|
-
|
38
|
-
let cmd = [
|
39
|
-
options.lightningcssBin,
|
40
|
-
'--nesting',
|
41
|
-
'--error-recovery',
|
42
|
-
args.pluginData?.importedFromJs && '--minify',
|
43
|
-
'--targets',
|
44
|
-
'>= 0.25%'
|
45
|
-
].filter(Boolean)
|
46
|
-
|
47
|
-
// This will process the CSS with Postcss only if it needs to.
|
48
|
-
let [tmpFile, contents] = await postcss(cwd, args.path)
|
49
|
-
|
50
|
-
// As custom media are defined in their own file, we have to append the file contents to our
|
51
|
-
// stylesheet, so that the custom media can be used.
|
52
|
-
if (customMedia) {
|
53
|
-
cmd.push('--custom-media')
|
54
|
-
|
55
|
-
if (!tmpFile && !contents) {
|
56
|
-
tmpFile = await Deno.makeTempFile()
|
57
|
-
contents = await Deno.readTextFile(args.path)
|
58
|
-
}
|
59
|
-
|
60
|
-
contents += customMedia
|
61
|
-
}
|
62
|
-
|
63
|
-
if (tmpFile && contents) {
|
64
|
-
await Deno.writeTextFile(tmpFile, contents)
|
65
|
-
}
|
66
|
-
|
67
|
-
if (isCssModule) {
|
68
|
-
cmd = cmd.concat(['--css-modules', '--css-modules-pattern', `[local]${hash}`])
|
69
|
-
}
|
70
|
-
|
71
|
-
const p = Deno.run({
|
72
|
-
cmd: [...cmd, tmpFile || args.path],
|
73
|
-
stdout: 'piped',
|
74
|
-
stderr: 'piped'
|
75
|
-
})
|
76
|
-
|
77
|
-
const { code } = await p.status()
|
78
|
-
const rawOutput = await p.output()
|
79
|
-
const rawError = await p.stderrOutput()
|
80
|
-
|
81
|
-
// Even though Deno docs say that reading the outputs (above) closes their pipes, warnings
|
82
|
-
// are raised during tests that the child process have not been closed. So we manually close
|
83
|
-
// here.
|
84
|
-
p.close()
|
85
|
-
|
86
|
-
// Success!
|
87
|
-
if (code === 0) {
|
88
|
-
let contents = new TextDecoder().decode(rawOutput)
|
89
|
-
if (isCssModule) {
|
90
|
-
contents = JSON.parse(contents)
|
91
|
-
}
|
92
|
-
|
93
|
-
// If stylesheet is imported from JS, then we return JS code that appends the stylesheet
|
94
|
-
// in a <style> in the <head> of the page, and if the stylesheet is a CSS module, it
|
95
|
-
// exports a plain object of class names.
|
96
|
-
if (args.pluginData?.importedFromJs) {
|
97
|
-
const code = isCssModule ? contents.code : contents
|
98
|
-
const mod = [
|
99
|
-
`let e = document.querySelector('#_${hash}');`,
|
100
|
-
'if (!e) {',
|
101
|
-
"e = document.createElement('style');",
|
102
|
-
`e.id = '_${hash}';`,
|
103
|
-
'document.head.appendChild(e);',
|
104
|
-
`e.appendChild(document.createTextNode(\`${code}\`));`,
|
105
|
-
'}'
|
106
|
-
]
|
107
|
-
|
108
|
-
if (isCssModule) {
|
109
|
-
const classes = {}
|
110
|
-
for (const key in contents.exports) {
|
111
|
-
if (Object.hasOwnProperty.call(contents.exports, key)) {
|
112
|
-
classes[key] = contents.exports[key].name
|
113
|
-
}
|
114
|
-
}
|
115
|
-
mod.push(`export default ${JSON.stringify(classes)};`)
|
116
|
-
}
|
117
|
-
|
118
|
-
// We are importing from JS, so return the entire result from LightningCSS via the js
|
119
|
-
// loader.
|
120
|
-
return {
|
121
|
-
resolveDir: cwd,
|
122
|
-
loader: 'js',
|
123
|
-
contents: mod.join('')
|
124
|
-
}
|
125
|
-
}
|
126
|
-
|
127
|
-
return { loader: 'css', contents: isCssModule ? contents.code : contents }
|
128
|
-
} else {
|
129
|
-
const errorString = new TextDecoder().decode(rawError)
|
130
|
-
throw errorString
|
131
|
-
}
|
132
|
-
}
|
133
|
-
}
|
134
|
-
]
|
135
|
-
})
|
136
|
-
|
137
|
-
async function digest(value) {
|
138
|
-
value = new TextEncoder().encode(value)
|
139
|
-
const view = new DataView(await crypto.subtle.digest('SHA-1', value))
|
140
|
-
|
141
|
-
let hexCodes = ''
|
142
|
-
for (let index = 0; index < view.byteLength; index += 4) {
|
143
|
-
hexCodes += view.getUint32(index).toString(16).padStart(8, '0')
|
144
|
-
}
|
145
|
-
|
146
|
-
return hexCodes.slice(0, 8)
|
147
|
-
}
|
148
|
-
|
149
|
-
async function isViewComponent(path) {
|
150
|
-
const fileName = basename(path)
|
151
|
-
const dirName = dirname(path)
|
152
|
-
|
153
|
-
return (
|
154
|
-
(fileName === 'component.module.css' && (await fileExists(join(dirName, 'component.rb')))) ||
|
155
|
-
(fileName.endsWith('_component.module.css') &&
|
156
|
-
(await fileExists(join(dirName, fileName.replace(/\.module\.css$/, '.rb')))))
|
157
|
-
)
|
158
|
-
}
|
159
|
-
|
160
|
-
function cssModulesProxyTemplate(hash) {
|
161
|
-
return [
|
162
|
-
`export default new Proxy( {}, {`,
|
163
|
-
` get(target, prop, receiver) {`,
|
164
|
-
` if (prop in target || typeof prop === 'symbol') {`,
|
165
|
-
` return Reflect.get(target, prop, receiver)`,
|
166
|
-
` } else {`,
|
167
|
-
` return prop + '${hash}'`,
|
168
|
-
` }`,
|
169
|
-
` }`,
|
170
|
-
`})`
|
171
|
-
].join('')
|
172
|
-
}
|
@@ -1,46 +0,0 @@
|
|
1
|
-
import setup from './setup_plugin.js'
|
2
|
-
|
3
|
-
// Export environment variables as named exports only. You can also import from `env:ENV_VAR_NAME`,
|
4
|
-
// which will return the value of the environment variable as the default export. This allows you to
|
5
|
-
// safely import a variable regardless of its existence.
|
6
|
-
export default setup('env', () => {
|
7
|
-
return [
|
8
|
-
{
|
9
|
-
type: 'onResolve',
|
10
|
-
filter: /^env(:.+)?$/,
|
11
|
-
callback({ path }) {
|
12
|
-
return { path, namespace: 'env' }
|
13
|
-
}
|
14
|
-
},
|
15
|
-
|
16
|
-
{
|
17
|
-
type: 'onLoad',
|
18
|
-
filter: /.*/,
|
19
|
-
namespace: 'env',
|
20
|
-
callback({ path }) {
|
21
|
-
if (path.includes(':')) {
|
22
|
-
const name = Deno.env.get(path.split(':')[1])
|
23
|
-
|
24
|
-
return {
|
25
|
-
loader: 'js',
|
26
|
-
contents: name ? `export default '${name}'` : `export default ${name}`
|
27
|
-
}
|
28
|
-
}
|
29
|
-
|
30
|
-
const env = Deno.env.toObject()
|
31
|
-
const contents = []
|
32
|
-
|
33
|
-
for (const key in env) {
|
34
|
-
if (Object.hasOwnProperty.call(env, key)) {
|
35
|
-
contents.push(`export const ${key} = '${env[key]}'`)
|
36
|
-
}
|
37
|
-
}
|
38
|
-
|
39
|
-
return {
|
40
|
-
loader: 'js',
|
41
|
-
contents: contents.join(';')
|
42
|
-
}
|
43
|
-
}
|
44
|
-
}
|
45
|
-
]
|
46
|
-
})
|
@@ -1,53 +0,0 @@
|
|
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
|
-
})
|
@@ -1,178 +0,0 @@
|
|
1
|
-
import { tryURLParse, tryURLLikeSpecifierParse } from './utils.js'
|
2
|
-
|
3
|
-
/**
|
4
|
-
* @param {ImportMap} input
|
5
|
-
* @param {URL} baseURL
|
6
|
-
* @returns {ParsedImportMap}
|
7
|
-
*/
|
8
|
-
export function parse(input, baseURL) {
|
9
|
-
if (!isJSONObject(input)) {
|
10
|
-
throw new TypeError('Import map JSON must be an object.')
|
11
|
-
}
|
12
|
-
|
13
|
-
if (!(baseURL instanceof URL)) {
|
14
|
-
throw new TypeError('Missing base URL or base URL is not a URL')
|
15
|
-
}
|
16
|
-
|
17
|
-
let sortedAndNormalizedImports = {}
|
18
|
-
if ('imports' in input) {
|
19
|
-
if (!input.imports || !isJSONObject(input.imports)) {
|
20
|
-
throw new TypeError("Import map's imports value must be an object.")
|
21
|
-
}
|
22
|
-
sortedAndNormalizedImports = sortAndNormalizeSpecifierMap(input.imports, baseURL)
|
23
|
-
}
|
24
|
-
|
25
|
-
let sortedAndNormalizedScopes = {}
|
26
|
-
if ('scopes' in input) {
|
27
|
-
if (!input.scopes || !isJSONObject(input.scopes)) {
|
28
|
-
throw new TypeError("Import map's scopes value must be an object.")
|
29
|
-
}
|
30
|
-
sortedAndNormalizedScopes = sortAndNormalizeScopes(input.scopes, baseURL)
|
31
|
-
}
|
32
|
-
|
33
|
-
const badTopLevelKeys = new Set(Object.keys(input))
|
34
|
-
badTopLevelKeys.delete('imports')
|
35
|
-
badTopLevelKeys.delete('scopes')
|
36
|
-
|
37
|
-
for (const badKey of badTopLevelKeys) {
|
38
|
-
throw new TypeError(
|
39
|
-
`Invalid top-level key "${badKey}". Only "imports" and "scopes" can be present.`
|
40
|
-
)
|
41
|
-
}
|
42
|
-
|
43
|
-
// Always have these two keys, and exactly these two keys, in the result.
|
44
|
-
return {
|
45
|
-
imports: sortedAndNormalizedImports,
|
46
|
-
scopes: sortedAndNormalizedScopes
|
47
|
-
}
|
48
|
-
}
|
49
|
-
|
50
|
-
/**
|
51
|
-
* @param {string} input
|
52
|
-
* @param {URL} baseURL
|
53
|
-
* @returns {ParsedImportMap}
|
54
|
-
*/
|
55
|
-
export function parseFromString(input, baseURL) {
|
56
|
-
const importMap = JSON.parse(input)
|
57
|
-
return parse(importMap, baseURL)
|
58
|
-
}
|
59
|
-
|
60
|
-
/**
|
61
|
-
* @param {string} a
|
62
|
-
* @param {string} b
|
63
|
-
*/
|
64
|
-
function codeUnitCompare(a, b) {
|
65
|
-
if (a > b) {
|
66
|
-
return 1
|
67
|
-
}
|
68
|
-
|
69
|
-
if (b > a) {
|
70
|
-
return -1
|
71
|
-
}
|
72
|
-
|
73
|
-
throw new Error('This should never be reached because this is only used on JSON object keys')
|
74
|
-
}
|
75
|
-
|
76
|
-
/**
|
77
|
-
* @param {string} specifierKey
|
78
|
-
* @param {URL} baseURL
|
79
|
-
* @returns {string | undefined}
|
80
|
-
*/
|
81
|
-
function normalizeSpecifierKey(specifierKey, baseURL) {
|
82
|
-
// Ignore attempts to use the empty string as a specifier key
|
83
|
-
if (specifierKey === '') {
|
84
|
-
throw new TypeError(`Invalid empty string specifier key.`)
|
85
|
-
}
|
86
|
-
|
87
|
-
const url = tryURLLikeSpecifierParse(specifierKey, baseURL)
|
88
|
-
if (url) return url.href
|
89
|
-
|
90
|
-
return specifierKey
|
91
|
-
}
|
92
|
-
|
93
|
-
/**
|
94
|
-
* @param {SpecifierMap} obj
|
95
|
-
* @param {URL} baseURL
|
96
|
-
* @returns {ParsedSpecifierMap}
|
97
|
-
*/
|
98
|
-
function sortAndNormalizeSpecifierMap(obj, baseURL) {
|
99
|
-
if (!isJSONObject(obj)) {
|
100
|
-
throw new TypeError('Expect map to be a JSON object.')
|
101
|
-
}
|
102
|
-
|
103
|
-
const normalized = {}
|
104
|
-
|
105
|
-
for (const [specifierKey, value] of Object.entries(obj)) {
|
106
|
-
const normalizedSpecifierKey = normalizeSpecifierKey(specifierKey, baseURL)
|
107
|
-
if (!normalizedSpecifierKey) continue
|
108
|
-
|
109
|
-
if (typeof value !== 'string') {
|
110
|
-
throw new TypeError(
|
111
|
-
`Invalid address ${JSON.stringify(value)} for the specifier key "${specifierKey}". ` +
|
112
|
-
`Addresses must be strings.`
|
113
|
-
)
|
114
|
-
}
|
115
|
-
|
116
|
-
const addressURL = tryURLLikeSpecifierParse(value, baseURL)
|
117
|
-
if (!addressURL) {
|
118
|
-
// Support aliases.
|
119
|
-
// console.warn(`Invalid address "${value}" for the specifier key "${specifierKey}".`)
|
120
|
-
normalized[normalizedSpecifierKey] = value
|
121
|
-
continue
|
122
|
-
}
|
123
|
-
|
124
|
-
if (specifierKey.endsWith('/') && !addressURL.href.endsWith('/')) {
|
125
|
-
throw new TypeError(
|
126
|
-
`Invalid address "${addressURL.href}" for package specifier key "${specifierKey}". ` +
|
127
|
-
`Package addresses must end with "/".`
|
128
|
-
)
|
129
|
-
}
|
130
|
-
|
131
|
-
normalized[normalizedSpecifierKey] = addressURL
|
132
|
-
}
|
133
|
-
|
134
|
-
const sortedAndNormalized = {}
|
135
|
-
const sortedKeys = Object.keys(normalized).sort((a, b) => codeUnitCompare(b, a))
|
136
|
-
for (const key of sortedKeys) {
|
137
|
-
sortedAndNormalized[key] = normalized[key]
|
138
|
-
}
|
139
|
-
|
140
|
-
return sortedAndNormalized
|
141
|
-
}
|
142
|
-
|
143
|
-
/**
|
144
|
-
* @param {ScopesMap} obj
|
145
|
-
* @param {URL} baseURL
|
146
|
-
*/
|
147
|
-
function sortAndNormalizeScopes(obj, baseURL) {
|
148
|
-
const normalized = {}
|
149
|
-
for (const [scopePrefix, potentialSpecifierMap] of Object.entries(obj)) {
|
150
|
-
if (!isJSONObject(potentialSpecifierMap)) {
|
151
|
-
throw new TypeError(`The value for the "${scopePrefix}" scope prefix must be an object.`)
|
152
|
-
}
|
153
|
-
|
154
|
-
const scopePrefixURL = tryURLParse(scopePrefix, baseURL)
|
155
|
-
if (!scopePrefixURL) {
|
156
|
-
throw new TypeError(`Invalid scope "${scopePrefix}" (parsed against base URL "${baseURL}").`)
|
157
|
-
}
|
158
|
-
|
159
|
-
const normalizedScopePrefix = scopePrefixURL.href
|
160
|
-
normalized[normalizedScopePrefix] = sortAndNormalizeSpecifierMap(potentialSpecifierMap, baseURL)
|
161
|
-
}
|
162
|
-
|
163
|
-
const sortedAndNormalized = {}
|
164
|
-
const sortedKeys = Object.keys(normalized).sort((a, b) => codeUnitCompare(b, a))
|
165
|
-
for (const key of sortedKeys) {
|
166
|
-
sortedAndNormalized[key] = normalized[key]
|
167
|
-
}
|
168
|
-
|
169
|
-
return sortedAndNormalized
|
170
|
-
}
|
171
|
-
|
172
|
-
/**
|
173
|
-
* @param {*} value
|
174
|
-
* @returns {value is object}
|
175
|
-
*/
|
176
|
-
function isJSONObject(value) {
|
177
|
-
return typeof value === 'object' && value != null && !Array.isArray(value)
|
178
|
-
}
|
@@ -1,64 +0,0 @@
|
|
1
|
-
//
|
2
|
-
// Taken almost verbatim from https://github.com/open-wc/open-wc/tree/master/packages/import-maps-resolve
|
3
|
-
// Slightly modified to support aliases.
|
4
|
-
//
|
5
|
-
|
6
|
-
import { join } from 'std/path/mod.ts'
|
7
|
-
import { parseFromString } from './parser.js'
|
8
|
-
|
9
|
-
const baseURL = new URL('file://')
|
10
|
-
|
11
|
-
class ImportMapError extends Error {
|
12
|
-
constructor(fileName, ...params) {
|
13
|
-
super(...params)
|
14
|
-
|
15
|
-
if (Error.captureStackTrace) {
|
16
|
-
Error.captureStackTrace(this, ImportMapError)
|
17
|
-
}
|
18
|
-
|
19
|
-
this.name = 'ImportMapError'
|
20
|
-
this.file = fileName
|
21
|
-
}
|
22
|
-
}
|
23
|
-
|
24
|
-
export default function (fileName, rootDir) {
|
25
|
-
let importMap
|
26
|
-
|
27
|
-
if (fileName) {
|
28
|
-
importMap = readFile(fileName, rootDir, true)
|
29
|
-
} else {
|
30
|
-
fileName = ['config/import_map.json', 'config/import_map.js'].find(f => {
|
31
|
-
const result = readFile(f, rootDir)
|
32
|
-
if (result) {
|
33
|
-
importMap = result
|
34
|
-
return true
|
35
|
-
}
|
36
|
-
})
|
37
|
-
}
|
38
|
-
|
39
|
-
return importMap
|
40
|
-
}
|
41
|
-
|
42
|
-
function readFile(file, rootDir, required = false) {
|
43
|
-
let contents = null
|
44
|
-
|
45
|
-
try {
|
46
|
-
contents = Deno.readTextFileSync(join(rootDir, file))
|
47
|
-
} catch (error) {
|
48
|
-
if (required) {
|
49
|
-
throw new ImportMapError(file, error.message, { cause: error })
|
50
|
-
}
|
51
|
-
}
|
52
|
-
|
53
|
-
if (contents === null) return null
|
54
|
-
|
55
|
-
try {
|
56
|
-
if (file.endsWith('.js')) {
|
57
|
-
contents = JSON.stringify(eval(contents)(Deno.env.get('RAILS_ENV')))
|
58
|
-
}
|
59
|
-
|
60
|
-
return parseFromString(contents, baseURL)
|
61
|
-
} catch (error) {
|
62
|
-
throw new ImportMapError(file, error.message, { cause: error })
|
63
|
-
}
|
64
|
-
}
|
@@ -1,95 +0,0 @@
|
|
1
|
-
import { tryURLLikeSpecifierParse, tryURLParse } from './utils.js'
|
2
|
-
|
3
|
-
/**
|
4
|
-
* @param {string} specifier
|
5
|
-
* @param {ParsedImportMap} parsedImportMap
|
6
|
-
* @param {URL} scriptURL
|
7
|
-
* @returns {{ resolvedImport: URL | null, matched: boolean }}
|
8
|
-
*/
|
9
|
-
export default function (specifier, parsedImportMap, scriptURL) {
|
10
|
-
const asURL = tryURLLikeSpecifierParse(specifier, scriptURL)
|
11
|
-
const normalizedSpecifier = asURL ? asURL.href : specifier
|
12
|
-
const scriptURLString = scriptURL.href
|
13
|
-
|
14
|
-
for (const [scopePrefix, scopeImports] of Object.entries(parsedImportMap.scopes || {})) {
|
15
|
-
if (
|
16
|
-
scopePrefix === scriptURLString ||
|
17
|
-
(scopePrefix.endsWith('/') && scriptURLString.startsWith(scopePrefix))
|
18
|
-
) {
|
19
|
-
const scopeImportsMatch = resolveImportsMatch(normalizedSpecifier, scopeImports)
|
20
|
-
|
21
|
-
if (scopeImportsMatch) {
|
22
|
-
return { resolvedImport: scopeImportsMatch, matched: true }
|
23
|
-
}
|
24
|
-
}
|
25
|
-
}
|
26
|
-
|
27
|
-
const topLevelImportsMatch = resolveImportsMatch(
|
28
|
-
normalizedSpecifier,
|
29
|
-
parsedImportMap.imports || {}
|
30
|
-
)
|
31
|
-
|
32
|
-
if (topLevelImportsMatch) {
|
33
|
-
return { resolvedImport: topLevelImportsMatch, matched: true }
|
34
|
-
}
|
35
|
-
|
36
|
-
// The specifier was able to be turned into a URL, but wasn't remapped into anything.
|
37
|
-
if (asURL) {
|
38
|
-
return { resolvedImport: asURL, matched: false }
|
39
|
-
}
|
40
|
-
|
41
|
-
return { resolvedImport: null, matched: false }
|
42
|
-
}
|
43
|
-
|
44
|
-
/**
|
45
|
-
* @param {string} normalizedSpecifier
|
46
|
-
* @param {ParsedSpecifierMap} specifierMap
|
47
|
-
*/
|
48
|
-
function resolveImportsMatch(normalizedSpecifier, specifierMap) {
|
49
|
-
for (const [specifierKey, resolutionResult] of Object.entries(specifierMap)) {
|
50
|
-
// Exact-match case
|
51
|
-
if (specifierKey === normalizedSpecifier) {
|
52
|
-
if (!resolutionResult) {
|
53
|
-
throw new TypeError(`Blocked by a null entry for "${specifierKey}"`)
|
54
|
-
}
|
55
|
-
|
56
|
-
if (!(resolutionResult instanceof URL)) {
|
57
|
-
// Support aliases.
|
58
|
-
// throw new TypeError(`Expected ${resolutionResult} to be a URL.`)
|
59
|
-
}
|
60
|
-
|
61
|
-
return resolutionResult
|
62
|
-
}
|
63
|
-
|
64
|
-
// Package prefix-match case
|
65
|
-
if (specifierKey.endsWith('/') && normalizedSpecifier.startsWith(specifierKey)) {
|
66
|
-
if (!resolutionResult) {
|
67
|
-
throw new TypeError(`Blocked by a null entry for "${specifierKey}"`)
|
68
|
-
}
|
69
|
-
|
70
|
-
if (!(resolutionResult instanceof URL)) {
|
71
|
-
throw new TypeError(`Expected ${resolutionResult} to be a URL.`)
|
72
|
-
}
|
73
|
-
|
74
|
-
const afterPrefix = normalizedSpecifier.substring(specifierKey.length)
|
75
|
-
|
76
|
-
// Enforced by parsing
|
77
|
-
if (!resolutionResult.href.endsWith('/')) {
|
78
|
-
throw new TypeError(`Expected ${resolutionResult.href} to end with a '/'.`)
|
79
|
-
}
|
80
|
-
|
81
|
-
const url = tryURLParse(afterPrefix, resolutionResult)
|
82
|
-
if (!url) {
|
83
|
-
throw new TypeError(`Failed to resolve prefix-match relative URL for "${specifierKey}"`)
|
84
|
-
}
|
85
|
-
|
86
|
-
if (!(url instanceof URL)) {
|
87
|
-
throw new TypeError(`Expected ${url} to be a URL.`)
|
88
|
-
}
|
89
|
-
|
90
|
-
return url
|
91
|
-
}
|
92
|
-
}
|
93
|
-
|
94
|
-
return undefined
|
95
|
-
}
|
@@ -1,25 +0,0 @@
|
|
1
|
-
/**
|
2
|
-
* @param {string} string
|
3
|
-
* @param {URL} [baseURL]
|
4
|
-
* @returns {URL | undefined}
|
5
|
-
*/
|
6
|
-
export function tryURLParse(string, baseURL) {
|
7
|
-
try {
|
8
|
-
return new URL(string, baseURL)
|
9
|
-
} catch (e) {
|
10
|
-
return undefined
|
11
|
-
}
|
12
|
-
}
|
13
|
-
|
14
|
-
/**
|
15
|
-
* @param {string} specifier
|
16
|
-
* @param {URL} baseURL
|
17
|
-
* @returns {URL | undefined}
|
18
|
-
*/
|
19
|
-
export function tryURLLikeSpecifierParse(specifier, baseURL) {
|
20
|
-
if (specifier.startsWith('/') || specifier.startsWith('./') || specifier.startsWith('../')) {
|
21
|
-
return tryURLParse(specifier, baseURL)
|
22
|
-
}
|
23
|
-
|
24
|
-
return tryURLParse(specifier)
|
25
|
-
}
|