proscenium 0.6.0-x86_64-linux → 0.8.2-x86_64-linux

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +126 -109
  3. data/lib/proscenium/css_module/class_names_resolver.rb +66 -0
  4. data/lib/proscenium/css_module/resolver.rb +76 -0
  5. data/lib/proscenium/css_module.rb +18 -39
  6. data/lib/proscenium/esbuild/golib.rb +97 -0
  7. data/lib/proscenium/esbuild.rb +32 -0
  8. data/lib/proscenium/ext/proscenium +0 -0
  9. data/lib/proscenium/ext/proscenium.h +108 -0
  10. data/lib/proscenium/helper.rb +0 -23
  11. data/lib/proscenium/log_subscriber.rb +26 -0
  12. data/lib/proscenium/middleware/base.rb +28 -36
  13. data/lib/proscenium/middleware/esbuild.rb +18 -44
  14. data/lib/proscenium/middleware/url.rb +1 -6
  15. data/lib/proscenium/middleware.rb +12 -16
  16. data/lib/proscenium/phlex/component_concerns.rb +27 -0
  17. data/lib/proscenium/phlex/page.rb +62 -0
  18. data/lib/proscenium/phlex/react_component.rb +52 -8
  19. data/lib/proscenium/phlex/resolve_css_modules.rb +67 -0
  20. data/lib/proscenium/phlex.rb +34 -33
  21. data/lib/proscenium/railtie.rb +41 -67
  22. data/lib/proscenium/side_load/ensure_loaded.rb +25 -0
  23. data/lib/proscenium/side_load/helper.rb +25 -0
  24. data/lib/proscenium/side_load/monkey.rb +48 -0
  25. data/lib/proscenium/side_load.rb +58 -52
  26. data/lib/proscenium/version.rb +1 -1
  27. data/lib/proscenium/view_component/react_component.rb +14 -0
  28. data/lib/proscenium/view_component.rb +28 -18
  29. data/lib/proscenium.rb +79 -2
  30. metadata +35 -77
  31. data/app/channels/proscenium/connection.rb +0 -13
  32. data/app/channels/proscenium/reload_channel.rb +0 -9
  33. data/bin/esbuild +0 -0
  34. data/bin/lightningcss +0 -0
  35. data/config/routes.rb +0 -7
  36. data/lib/proscenium/compiler.js +0 -84
  37. data/lib/proscenium/compilers/esbuild/argument_error.js +0 -24
  38. data/lib/proscenium/compilers/esbuild/compile_error.js +0 -148
  39. data/lib/proscenium/compilers/esbuild/css/postcss.js +0 -67
  40. data/lib/proscenium/compilers/esbuild/css_plugin.js +0 -172
  41. data/lib/proscenium/compilers/esbuild/env_plugin.js +0 -46
  42. data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +0 -53
  43. data/lib/proscenium/compilers/esbuild/import_map/parser.js +0 -178
  44. data/lib/proscenium/compilers/esbuild/import_map/read.js +0 -64
  45. data/lib/proscenium/compilers/esbuild/import_map/resolver.js +0 -95
  46. data/lib/proscenium/compilers/esbuild/import_map/utils.js +0 -25
  47. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +0 -207
  48. data/lib/proscenium/compilers/esbuild/setup_plugin.js +0 -45
  49. data/lib/proscenium/compilers/esbuild/solidjs_plugin.js +0 -24
  50. data/lib/proscenium/compilers/esbuild.bench.js +0 -14
  51. data/lib/proscenium/compilers/esbuild.js +0 -179
  52. data/lib/proscenium/link_to_helper.rb +0 -40
  53. data/lib/proscenium/middleware/lightningcss.rb +0 -64
  54. data/lib/proscenium/middleware/outside_root.rb +0 -26
  55. data/lib/proscenium/middleware/runtime.rb +0 -22
  56. data/lib/proscenium/middleware/static.rb +0 -14
  57. data/lib/proscenium/phlex/component.rb +0 -9
  58. data/lib/proscenium/precompile.rb +0 -31
  59. data/lib/proscenium/runtime/auto_reload.js +0 -40
  60. data/lib/proscenium/runtime/react_shim/index.js +0 -1
  61. data/lib/proscenium/runtime/react_shim/package.json +0 -5
  62. data/lib/proscenium/utils.js +0 -12
  63. data/lib/tasks/assets.rake +0 -19
@@ -1,67 +0,0 @@
1
- import { expandGlob } from 'std/fs/mod.ts'
2
- import postcss 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, globstar: true })) {
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,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
- }