proscenium 0.1.0.alpha2-x86_64-darwin → 0.1.0.alpha3-x86_64-darwin

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +158 -29
  3. data/bin/esbuild +0 -0
  4. data/bin/parcel_css +0 -0
  5. data/config/routes.rb +1 -1
  6. data/lib/proscenium/compilers/esbuild/compile_error.js +145 -0
  7. data/lib/proscenium/compilers/esbuild/css_plugin.js +76 -0
  8. data/lib/proscenium/compilers/esbuild/env_plugin.js +6 -4
  9. data/lib/proscenium/compilers/esbuild/http_bundle_plugin.js +53 -0
  10. data/lib/proscenium/compilers/esbuild/import_map.js +59 -0
  11. data/lib/proscenium/compilers/esbuild/resolve_plugin.js +38 -35
  12. data/lib/proscenium/compilers/esbuild/setup_plugin.js +23 -22
  13. data/lib/proscenium/{cli → compilers}/esbuild/solidjs_plugin.js +5 -4
  14. data/lib/proscenium/compilers/esbuild.bench.js +7 -3
  15. data/lib/proscenium/compilers/esbuild.js +60 -19
  16. data/lib/proscenium/helper.rb +2 -2
  17. data/lib/proscenium/middleware/base.rb +13 -3
  18. data/lib/proscenium/middleware/esbuild.rb +14 -0
  19. data/lib/proscenium/middleware/parcel_css.rb +30 -3
  20. data/lib/proscenium/middleware.rb +3 -0
  21. data/lib/proscenium/railtie.rb +16 -18
  22. data/lib/proscenium/runtime/component_manager/index.js +7 -7
  23. data/lib/proscenium/runtime/component_manager/{render_component.js → render_component.jsx} +9 -13
  24. data/lib/proscenium/version.rb +1 -1
  25. metadata +8 -14
  26. data/lib/proscenium/cli/argument_error.js +0 -24
  27. data/lib/proscenium/cli/builders/index.js +0 -1
  28. data/lib/proscenium/cli/builders/javascript.js +0 -45
  29. data/lib/proscenium/cli/builders/react.js +0 -60
  30. data/lib/proscenium/cli/builders/solid.js +0 -46
  31. data/lib/proscenium/cli/esbuild/env_plugin.js +0 -21
  32. data/lib/proscenium/cli/esbuild/resolve_plugin.js +0 -136
  33. data/lib/proscenium/cli/js_builder.js +0 -194
  34. data/lib/proscenium/cli/solid.js +0 -15
  35. data/lib/proscenium/cli/utils.js +0 -93
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d33e162bf11cfad365e816b4dbb6b414af4e27554a9a241a68f0e511d3304120
4
- data.tar.gz: a308f22364895470325ec64ef039ce57d635228c0c38af2b05f26905c23f5b10
3
+ metadata.gz: 1ead562a986b1202c37134ed480888b71eb54704383c611f676c11fef0bbc2b5
4
+ data.tar.gz: '081ebe2a6cbc7bef601d1b698a00e01e371c9fd3fd35f2b2a8dad3da3284b6f3'
5
5
  SHA512:
6
- metadata.gz: c602130d2ceb6a3fb19f3eff6c90c433903f7727aca48d0f474c6ee0b8863a3f7ba355572a4f06fa2005b3f00d708a9c374f061c6314991630558ba177c6ec3d
7
- data.tar.gz: 77205b53c2bbbf504724c82dc44f745f8268469f68b2f955d3a6a85bf9c45402cf1559054a4e23bae419d7b57340a07be5fa6db81b904d7b7bb133b282639b4c
6
+ metadata.gz: 6e38c959ead1d73f71f12f9aee51535e8753941288b82f736e36e378dd44874ee16bb20bb3f02540b8a72a2d36204b6df64e1f026f76bdf109a7003508106360
7
+ data.tar.gz: 2b338f1f1eec4abce62b2d9369502f9c3feb32c4b6adba48214cd146ea2d3737955a0afa6e7973e92a44266a9900e37107f7d2d765fffcba0d2aa15889330db8
data/README.md CHANGED
@@ -1,65 +1,195 @@
1
1
  # Proscenium
2
2
 
3
- - Serve assets from anywhere within the Rails root. (eg. `/app/views/layouts/application.css`, `/lib/utils/time.js`)
4
- - Side loaded JS/CSS for your layouts and views.
5
- - JS imports
6
- - Dynamic imports
3
+ - Serve assets from anywhere within your Rails root.
4
+ - Automatically side load JS/CSS for your layouts and views.
5
+ - Import JS and CSS from node_modules, URL, local (relative, absolute)
7
6
  - Real-time bundling of JS, JSX and CSS.
8
- - Import CSS and other static assets (images, fonts, etc.)
9
-
10
- ## WANT
11
-
12
- - Nested CSS
13
- - Import CSS Modules
7
+ - Import Map
8
+ - CSS Modules
9
+ - Minification
14
10
 
15
11
  ## Installation
16
12
 
17
- Add this line to your application's Gemfile:
13
+ Add this line to your application's Gemfile, and you're good to go:
18
14
 
19
15
  ```ruby
20
16
  gem 'proscenium'
21
17
  ```
22
18
 
23
- ## Usage
19
+ ## Frontend Code Anywhere!
20
+
21
+ Proscenium believes that your frontend code is just as important as your backend code, and is not an
22
+ afterthought - they should be first class citizens of your Rails app. So instead of throwing all
23
+ your JS and CSS into a "app/assets" directory, put them wherever you want!
24
+
25
+ For example, if you have JS that is used by your `UsersController#index`, then put it in
26
+ `app/views/users/index.js`. Or if you have some CSS that is used by your entire application, put it
27
+ in `app/views/layouts/application.css`. Maybe you have a few JS utility functions, so put them in
28
+ `lib/utils.js`.
29
+
30
+ Simply create your JS(X) and CSS anywhere you want, and they will be served by your Rails app.
31
+
32
+ Using the examples above...
33
+
34
+ - `app/views/users/index.js` => `https://yourapp.com/app/views/users/index.js`
35
+ - `app/views/layouts/application.css` => `https://yourapp.com/app/views/layouts/application.css`
36
+ - `lib/utils.js` => `https://yourapp.com/lib/utils.js`
37
+
38
+ ## Importing
39
+
40
+ Proscenium supports importing JS and CSS from `node_modules`, URL, and local (relative, absolute).
41
+
42
+ Imports are assumed to be JS files, so there is no need to specify the file extesnion in such cases.
43
+ But you can if you like. All other file types must be specified using their full file name and
44
+ extension.
45
+
46
+ ### URL Imports
47
+
48
+ Any import beginning with `http://` or `https://` will be fetched from the URL provided:
49
+
50
+ ```js
51
+ import React from 'https://esm.sh/react`
52
+ ```
53
+
54
+ ```css
55
+ @import 'https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css';
56
+ ```
57
+
58
+ ### Import from NPM (`node_modules`)
59
+
60
+ Bare imports (imports not beginning with `./`, `/`, `https://`, `http://`) are fully supported, and
61
+ will use your package manager of choice (eg, NPM, Yarn, pnpm):
62
+
63
+ ```js
64
+ import React from 'react`
65
+ ```
66
+
67
+ ### Local Imports
68
+
69
+ And of course you can import your own code, using relative or absolute paths (file extension is
70
+ optional):
71
+
72
+ ```js /app/views/layouts/application.js
73
+ import utils from '/lib/utils'
74
+ ```
75
+
76
+ ```js /lib/utils.js
77
+ import constants from './constants'
78
+ ```
79
+
80
+ ## Bundling
24
81
 
25
- ### Side Loading
82
+ Proscenium does not do any bundling, as we believe that **the web is now fast by default**. So we
83
+ let you decide if and when to bundle your code using query parameters in your JS and CSS imports.
26
84
 
27
- Proscenium has built in support for automatically side loading JS and CSS with your views and layouts.
85
+ ```js
86
+ import doStuff from 'stuff?bundle'
87
+ doStuff()
88
+ ```
28
89
 
29
- Just create a JS and/or CSS file with the same name as any view or layout, and make sure your layouts include `<%= yield :side_loaded_js %>` and `<%= yield :side_loaded_css %>`. Something like this:
90
+ Bundling a URL import is not supported, as the URL itself may also support query parameters,
91
+ resulting in conflicts. For example, esm.sh also supports a `?bundle` param, bundling a module's
92
+ dependencies into a single file. Instead, you should install the module locally using your favourite
93
+ package manager.
94
+
95
+ ## Import Map
96
+
97
+ Import map for both JS and CSS is supported out of the box, and works with no regard to the browser
98
+ version being used. Just create `config/import_map.json`:
99
+
100
+ ```json
101
+ {
102
+ "imports": {
103
+ "react": "https://esm.sh/react@18.2.0",
104
+ "start": "/lib/start.js",
105
+ "common": "/lib/common.css",
106
+ "@radix-ui/colors/": "https://esm.sh/@radix-ui/colors@0.1.8/",
107
+ }
108
+ }
109
+ ```
110
+
111
+ Using the above import map, we can do...
112
+
113
+ ```js
114
+ import { useCallback } from 'react'
115
+ import startHere from 'start'
116
+ import styles from 'common'
117
+ ```
118
+
119
+ and for CSS...
120
+
121
+ ```css
122
+ @import 'common';
123
+ @import '@radix-ui/colors/blue.css';
124
+ ```
125
+
126
+ You can instead write your import map in Javascript instead of JSON. So instead of
127
+ `config/import_map.json`, create `config/import_map.js`, and define an anonymous function. This
128
+ function accepts a single `environment` argument.
129
+
130
+ ```js
131
+ env => ({
132
+ imports: {
133
+ react: env === 'development' ? 'https://esm.sh/react@18.2.0?dev' : 'https://esm.sh/react@18.2.0'
134
+ }
135
+ })
136
+ ```
137
+
138
+ ## Side Loading
139
+
140
+ Proscenium has built in support for automatically side loading JS and CSS with your views and
141
+ layouts.
142
+
143
+ Just create a JS and/or CSS file with the same name as any view or layout, and make sure your
144
+ layouts include `<%= side_load_stylesheets %>` and `<%= side_load_javascripts %>`. Something like
145
+ this:
30
146
 
31
147
  ```html
32
148
  <!DOCTYPE html>
33
149
  <html>
34
150
  <head>
35
151
  <title>Hello World</title>
36
- <%= yield :side_loaded_css %>
152
+ <%= side_load_stylesheets %>
37
153
  </head>
38
154
  <body>
39
- <%= yield %> <%= yield :side_loaded_js %>
155
+ <%= yield %>
156
+ <%= side_load_javascripts defer: true, type: 'module' %>
40
157
  </body>
41
158
  </html>
42
159
  ```
43
160
 
44
- On each page request, Proscenium will check if your layout and view has a JS/CSS file of the same name, and include them into your layout HTML.
161
+ On each page request, Proscenium will check if your layout and view has a JS/CSS file of the same
162
+ name, and include them into your layout HTML. Partials are not side loaded.
45
163
 
46
- ### Importing in JS with `import`
164
+ Side loading is enabled by default, but you can disable it by setting `config.proscenium.side_load`
165
+ to `false`.
47
166
 
48
- Imports that do not begin with a `./` or `/` are bare imports, and will import a package using your local Node resolution algorithm.
167
+ ## CSS Modules
49
168
 
50
- `import 'react'`
51
- `import React as * from 'react'`
52
- `import { useState } from 'react'`
169
+ Direct access to CSS files are parsed through @parcel/css.
53
170
 
54
- Absolute and relative import paths are supported (`/*`, `./*`), and will behave as you would expect.
171
+ Importing a CSS file from JS will append the CSS file to the document's head. The results of the
172
+ import will be an object of CSS modules.
55
173
 
56
- Imports are assumed to be JS files, so there is no need to specify the file extesnion in such cases. But you can if you like. All other file types must be specified using their fill file name and extension.
174
+ ## Auto Reload
57
175
 
58
- ### CSS
176
+ To aid fast development, Proscenium comes with an auto reload feature that will automatically reload
177
+ the page when any files changes. It is enabled by default in development, and requires that you
178
+ mount the Proscenium Railtie into your `config/routes.rb` file:
59
179
 
60
- Direct access to CSS files are parsed through @parcel/css.
180
+ ```ruby
181
+ mount Proscenium::Railtie, at: '/proscenium' if Rails.env.development?
182
+ ```
183
+
184
+ Changes to CSS/JS(X) files in your `app` and `lib` directories will cause the page to reload.
185
+
186
+ NOTE: that this is hot module reloading (HMR) - a full page reload is triggered.
61
187
 
62
- Importing a CSS file from JS will append the CSS file to the document's head. The results of the import will be an object of CSS modules.
188
+ You can disable auto reload by setting the `config.proscenium.auto_reload` config option to false.
189
+
190
+ ## CSS Custom Media Queries
191
+
192
+ Proscenium supports [custom media queries](https://css-tricks.com/can-we-have-custom-media-queries-please/) as per the [spec](https://www.w3.org/TR/mediaqueries-5/#custom-mq). However, because of the way they are parsed, they cannot be imported using `@import`. So if you define your custom media queries in `/config/custom_media_queries.css`, Proscenium will automatically inject them into your CSS, so you can use them anywhere.
63
193
 
64
194
  ## How It Works
65
195
 
@@ -77,7 +207,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
77
207
 
78
208
  `deno compile --no-config -o bin/compilers/esbuild --import-map import_map.json -A lib/proscenium/compilers/esbuild.js`
79
209
 
80
-
81
210
  ## Contributing
82
211
 
83
212
  Bug reports and pull requests are welcome on GitHub at https://github.com/joelmoss/proscenium. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/joelmoss/proscenium/blob/master/CODE_OF_CONDUCT.md).
data/bin/esbuild CHANGED
Binary file
data/bin/parcel_css CHANGED
Binary file
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  Proscenium::Railtie.routes.draw do
2
- if Proscenium.config.auto_refresh
2
+ if Proscenium.config.auto_reload
3
3
  mount Proscenium::Railtie.websocket => Proscenium.config.cable_mount_path
4
4
  end
5
5
  end
@@ -0,0 +1,145 @@
1
+ export default function () {
2
+ if (Deno.env.get('RAILS_ENV') === 'development') {
3
+ return function (detail) {
4
+ const template = `
5
+ <style>
6
+ :host {
7
+ position: fixed;
8
+ z-index: 99999;
9
+ top: 0;
10
+ left: 0;
11
+ width: 100%;
12
+ height: 100%;
13
+ overflow-y: scroll;
14
+ margin: 0;
15
+ background: rgba(0, 0, 0, 0.66);
16
+ display: flex;
17
+ align-items: center;
18
+ --monospace: 'SFMono-Regular', Consolas,
19
+ 'Liberation Mono', Menlo, Courier, monospace;
20
+ --red: #ff5555;
21
+ --yellow: #e2aa53;
22
+ --purple: #cfa4ff;
23
+ --cyan: #2dd9da;
24
+ --dim: #c9c9c9;
25
+ }
26
+
27
+ .window {
28
+ font-family: var(--monospace);
29
+ line-height: 1.5;
30
+ width: 800px;
31
+ color: #d8d8d8;
32
+ margin: 30px auto;
33
+ padding: 25px 40px;
34
+ position: relative;
35
+ background: #181818;
36
+ border-radius: 6px 6px 8px 8px;
37
+ box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22);
38
+ overflow: hidden;
39
+ border-top: 8px solid var(--red);
40
+ }
41
+
42
+ pre {
43
+ font-family: var(--monospace);
44
+ font-size: 16px;
45
+ margin: 0 0 1em 0;
46
+ overflow-x: scroll;
47
+ scrollbar-width: none;
48
+ }
49
+
50
+ pre::-webkit-scrollbar {
51
+ display: none;
52
+ }
53
+
54
+ .title, .message {
55
+ line-height: 1.3;
56
+ font-weight: 600;
57
+ white-space: pre-wrap;
58
+ }
59
+
60
+ .message-body {
61
+ color: var(--red);
62
+ }
63
+
64
+ .file {
65
+ color: var(--cyan);
66
+ margin-bottom: 0;
67
+ white-space: pre-wrap;
68
+ word-break: break-all;
69
+ }
70
+
71
+ .code {
72
+ background: black;
73
+ border-left: 3px solid gray;
74
+ padding: 10px 0 0 20px;
75
+ }
76
+ .lineText {
77
+ white-space: pre-wrap;
78
+ }
79
+ .lineCursor {
80
+ white-space: pre;
81
+ color: blueviolet;
82
+ }
83
+ </style>
84
+ <div class="window">
85
+ <pre class="title">COMPILE ERROR!</pre>
86
+ <pre class="message"><span class="message-body"></span> in <span class="file"></span></pre>
87
+ <pre class="code"><span class="lineText"></span><span class="lineCursor"></span></pre>
88
+ </div>
89
+ `
90
+
91
+ class ErrorOverlay extends HTMLElement {
92
+ constructor(err) {
93
+ super()
94
+
95
+ this.root = this.attachShadow({ mode: 'open' })
96
+ this.root.innerHTML = template
97
+ this.root.querySelector('.message-body').textContent = err.text.trim()
98
+ if (err.location) {
99
+ const location = [err.location.file]
100
+ err.location.line && location.push(err.location.line)
101
+ err.location.column && location.push(err.location.column)
102
+ this.root.querySelector('.file').textContent = `/${location.join(':')}`
103
+
104
+ if (err.location.lineText) {
105
+ this.root.querySelector('.lineText').textContent = err.location.lineText
106
+ this.root.querySelector('.lineCursor').textContent =
107
+ ''.padStart(err.location.column - 1, ' ') + '^'
108
+ } else {
109
+ this.root.querySelector('.code').remove()
110
+ }
111
+
112
+ console.error('%s at %O', err.text, location)
113
+ } else {
114
+ console.error(err.text)
115
+ }
116
+
117
+ this.root.querySelector('.window').addEventListener('click', e => {
118
+ e.stopPropagation()
119
+ })
120
+
121
+ this.addEventListener('click', () => {
122
+ this.close()
123
+ })
124
+ }
125
+
126
+ close() {
127
+ this.parentNode?.removeChild(this)
128
+ }
129
+ }
130
+
131
+ const overlayId = 'proscenium-error-overlay'
132
+ if (customElements && !customElements.get(overlayId)) {
133
+ customElements.define(overlayId, ErrorOverlay)
134
+ }
135
+
136
+ document.body.appendChild(new ErrorOverlay(detail))
137
+ }
138
+ } else {
139
+ return function (detail) {
140
+ const location = `/${detail.location.file}:${detail.location.line}:${detail.location.column}`
141
+ console.error('%s at %O', detail.text, location)
142
+ throw `${detail.text} at ${location}`
143
+ }
144
+ }
145
+ }
@@ -0,0 +1,76 @@
1
+ import { crypto } from 'std/crypto/mod.ts'
2
+ import { join, resolve, dirname, fromFileUrl } from 'std/path/mod.ts'
3
+
4
+ import setup from './setup_plugin.js'
5
+
6
+ export default setup('css', async build => {
7
+ const cwd = build.initialOptions.absWorkingDir
8
+ const parcelBin = resolve(dirname(fromFileUrl(import.meta.url)), '../../../../bin/parcel_css')
9
+
10
+ let customMedia
11
+ try {
12
+ customMedia = await Deno.readTextFile(join(cwd, 'config', 'custom_media_queries.css'))
13
+ } catch {
14
+ // do nothing, as we don't require custom media.
15
+ }
16
+
17
+ return [
18
+ {
19
+ type: 'onLoad',
20
+ filter: /\.css$/,
21
+ namespace: 'file',
22
+ async callback(args) {
23
+ let path = args.path
24
+ const isCssModule = args.path.endsWith('.module.css')
25
+ let cmd = [parcelBin, '--nesting', '--targets', '>= 0.25%']
26
+
27
+ if (customMedia) {
28
+ cmd.push('--custom-media')
29
+
30
+ path = await Deno.makeTempFile()
31
+ await Deno.writeTextFile(path, (await Deno.readTextFile(args.path)) + customMedia)
32
+ }
33
+
34
+ if (isCssModule) {
35
+ const hash = await digest(args.path.slice(cwd.length))
36
+ cmd = cmd.concat(['--css-modules', '--css-modules-pattern', `[local]${hash}`])
37
+ }
38
+
39
+ const p = Deno.run({
40
+ cmd: [...cmd, path],
41
+ stdout: 'piped',
42
+ stderr: 'piped'
43
+ })
44
+
45
+ const { code } = await p.status()
46
+ const rawOutput = await p.output()
47
+ const rawError = await p.stderrOutput()
48
+
49
+ // Even though Deno docs say that reading the outputs (above) closes their pipes, warnings
50
+ // are raised during tests that the child process have not been closed. So we manually close
51
+ // here.
52
+ p.close()
53
+
54
+ if (code === 0) {
55
+ const contents = new TextDecoder().decode(rawOutput)
56
+ return { loader: 'css', contents: isCssModule ? JSON.parse(contents).code : contents }
57
+ } else {
58
+ const errorString = new TextDecoder().decode(rawError)
59
+ throw errorString
60
+ }
61
+ }
62
+ }
63
+ ]
64
+ })
65
+
66
+ async function digest(value) {
67
+ value = new TextEncoder().encode(value)
68
+ const view = new DataView(await crypto.subtle.digest('SHA-1', value))
69
+
70
+ let hexCodes = ''
71
+ for (let index = 0; index < view.byteLength; index += 4) {
72
+ hexCodes += view.getUint32(index).toString(16).padStart(8, '0')
73
+ }
74
+
75
+ return hexCodes.slice(0, 8)
76
+ }
@@ -1,15 +1,17 @@
1
1
  import setup from './setup_plugin.js'
2
2
 
3
3
  export default setup('env', () => {
4
- return {
5
- onResolve: {
4
+ return [
5
+ {
6
+ type: 'onResolve',
6
7
  filter: /^env$/,
7
8
  callback({ path }) {
8
9
  return { path, namespace: 'env' }
9
10
  }
10
11
  },
11
12
 
12
- onLoad: {
13
+ {
14
+ type: 'onLoad',
13
15
  filter: /.*/,
14
16
  namespace: 'env',
15
17
  callback() {
@@ -17,5 +19,5 @@ export default setup('env', () => {
17
19
  return { loader: 'json', contents: JSON.stringify(env) }
18
20
  }
19
21
  }
20
- }
22
+ ]
21
23
  })
@@ -0,0 +1,53 @@
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
+ })
@@ -0,0 +1,59 @@
1
+ import { join } from 'std/path/mod.ts'
2
+ import { parseFromString } from 'import-maps/resolve'
3
+
4
+ const baseURL = new URL('file://')
5
+
6
+ class ImportMapError extends Error {
7
+ constructor(fileName, ...params) {
8
+ super(...params)
9
+
10
+ if (Error.captureStackTrace) {
11
+ Error.captureStackTrace(this, ImportMapError)
12
+ }
13
+
14
+ this.name = 'ImportMapError'
15
+ this.file = fileName
16
+ }
17
+ }
18
+
19
+ export function readImportMap(fileName, rootDir) {
20
+ let importMap
21
+
22
+ if (fileName) {
23
+ importMap = readFile(fileName, rootDir, true)
24
+ } else {
25
+ fileName = ['config/import_map.json', 'config/import_map.js'].find(f => {
26
+ const result = readFile(f, rootDir)
27
+ if (result) {
28
+ importMap = result
29
+ return true
30
+ }
31
+ })
32
+ }
33
+
34
+ return importMap
35
+ }
36
+
37
+ function readFile(file, rootDir, required = false) {
38
+ let contents = null
39
+
40
+ try {
41
+ contents = Deno.readTextFileSync(join(rootDir, file))
42
+ } catch (error) {
43
+ if (required) {
44
+ throw new ImportMapError(file, error.message, { cause: error })
45
+ }
46
+ }
47
+
48
+ if (contents === null) return null
49
+
50
+ try {
51
+ if (file.endsWith('.js')) {
52
+ contents = JSON.stringify(eval(contents)(Deno.env.get('RAILS_ENV')))
53
+ }
54
+
55
+ return parseFromString(contents, baseURL)
56
+ } catch (error) {
57
+ throw new ImportMapError(file, error.message, { cause: error })
58
+ }
59
+ }