proscenium 0.6.0-x86_64-darwin → 0.7.0-x86_64-darwin
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 +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
|
-
}
|