importmap-rails 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +34 -17
- data/app/assets/javascripts/es-module-shims.js +1 -1
- data/app/assets/javascripts/{es-module-shims@0.12.2.js → es-module-shims@0.12.8.js} +135 -73
- data/lib/importmap/commands.rb +24 -0
- data/lib/importmap/engine.rb +4 -7
- data/lib/importmap/map.rb +26 -13
- data/lib/importmap/pinner.rb +32 -0
- data/lib/importmap/reloader.rb +1 -1
- data/lib/importmap/version.rb +1 -1
- data/lib/install/bin/importmap +4 -0
- data/lib/install/config/importmap.rb +20 -0
- data/lib/install/install.rb +5 -21
- data/lib/tasks/importmap_tasks.rake +6 -0
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8e3127a8154739158fd8b7feea069b5ae6e8d353d36ba16fa446095f26b70ae0
|
4
|
+
data.tar.gz: c6d7e65df38a98e5c31b5195ee298651c6550cb97e3c438ebf46c8a5290a17b6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e186a8e9785fc7da247ddf1c459d005977a8ce21fb2403aa3e3b63c2b938b9ffa682df1742292633dd9afcae0f7a7f68582af2cafc642afd04195564fd5c2a1
|
7
|
+
data.tar.gz: fc85e86b9d747e9e84e8e009bcca8e4746f6477f25d6355b705958fb917818872005127c5ad9ac8e9f9f7594d1ac06b68013038ab351cd072d45c0d4dc76b7ce
|
data/README.md
CHANGED
@@ -22,30 +22,49 @@ Note: In order to use JavaScript from Rails frameworks like Action Cable, Action
|
|
22
22
|
|
23
23
|
The import map is configured programmatically through the `Rails.application.config.importmap` assignment, which by default is setup in `config/importmap.rb` after running the installer. This file is automatically reloaded in development upon changes, but note that you must restart the server if you remove pins and need them gone from the rendered importmap or list of preloads.
|
24
24
|
|
25
|
-
This programmatically configured import map is inlined in the `<head>` of your application layout using `<%= javascript_importmap_tags %>`, which will setup the JSON configuration inside a `<script type="importmap">` tag. After that, the [es-module-shim](https://github.com/guybedford/es-module-shims) is loaded, and then finally the application entrypoint is imported via `<script type="module">import "application"</script>`. That logical entrypoint, `application`, is mapped in the importmap script tag to the file `app/
|
25
|
+
This programmatically configured import map is inlined in the `<head>` of your application layout using `<%= javascript_importmap_tags %>`, which will setup the JSON configuration inside a `<script type="importmap">` tag. After that, the [es-module-shim](https://github.com/guybedford/es-module-shims) is loaded, and then finally the application entrypoint is imported via `<script type="module">import "application"</script>`. That logical entrypoint, `application`, is mapped in the importmap script tag to the file `app/javascript/application.js`, which is copied and digested by the asset pipeline.
|
26
26
|
|
27
|
-
It's in `app/
|
27
|
+
It's in `app/javascript/application.js` you setup your application by importing any of the modules that have been defined in the import map. You can use the full ESM functionality of importing any particular export of the modules or everything.
|
28
28
|
|
29
29
|
It makes sense to use logical names that match the package names used by NPM, such that if you later want to start transpiling or bundling your code, you'll not have to change any module imports.
|
30
30
|
|
31
31
|
|
32
|
-
##
|
32
|
+
## Using node modules via JavaScript CDNs
|
33
33
|
|
34
|
-
|
34
|
+
Importmap for Rails is designed to be used with JavaScript CDNs for your node package dependencies. The CDNs provide pre-compiled distribution versions ready to use, and offer a fast, efficient way of serving them.
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
You can use the bin/importmap command that's added as part of the install to pin additional packages to your importmap. This command uses an API from JSPM.org to resolve your package dependencies most efficiently, and then add the pins to your config/importmap.rb file. It can resolve these dependencies from JSPM itself, but also from other CDNs, like unpkg.com, jsdelivr.com, skypack.dev, etc.
|
37
|
+
|
38
|
+
It works like so:
|
39
|
+
|
40
|
+
```bash
|
41
|
+
./bin/importmap pin react react-dom
|
42
|
+
|
43
|
+
Pinned 'react' to https://ga.jspm.io/npm:react@17.0.2/index.js
|
44
|
+
Pinned 'react-dom' to https://ga.jspm.io/npm:react-dom@17.0.2/index.js
|
45
|
+
Pinned 'object-assign' to https://ga.jspm.io/npm:object-assign@4.1.1/index.js
|
46
|
+
Pinned 'scheduler' to https://ga.jspm.io/npm:scheduler@0.20.2/index.js
|
47
|
+
|
48
|
+
./bin/importmap json
|
49
|
+
|
50
|
+
{
|
51
|
+
"imports": {
|
52
|
+
"application": "/application.js",
|
53
|
+
"react": "https://ga.jspm.io/npm:react@17.0.2/index.js",
|
54
|
+
"react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/index.js",
|
55
|
+
"object-assign": "https://ga.jspm.io/npm:object-assign@4.1.1/index.js",
|
56
|
+
"scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/index.js"
|
57
|
+
}
|
58
|
+
}
|
41
59
|
```
|
42
60
|
|
61
|
+
As you can see, the two packages react and react-dom resolve to a total of four dependencies, when resolved via the jspm default.
|
62
|
+
|
43
63
|
Now you can use these in your application.js entrypoint like you would any other module:
|
44
64
|
|
45
65
|
```js
|
46
|
-
import "
|
47
|
-
import
|
48
|
-
console.log(md5("Hash it out"))
|
66
|
+
import React from "react"
|
67
|
+
import ReactDOM from "react-dom"
|
49
68
|
```
|
50
69
|
|
51
70
|
|
@@ -57,16 +76,14 @@ Example:
|
|
57
76
|
|
58
77
|
```ruby
|
59
78
|
# config/importmap.rb
|
60
|
-
|
61
|
-
|
62
|
-
pin "md5", to: "https://cdn.skypack.dev/md5", preload: false
|
63
|
-
end
|
79
|
+
pin "@github/hotkey", to: "https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js"
|
80
|
+
pin "md5", to: "https://cdn.jsdelivr.net/npm/md5@2.3.0/md5.js", preload: false
|
64
81
|
|
65
82
|
# app/views/layouts/application.html.erb
|
66
83
|
<%= javascript_importmap_tags %>
|
67
84
|
|
68
85
|
# will include the following link before the importmap is setup:
|
69
|
-
<link rel="modulepreload" href="https://
|
86
|
+
<link rel="modulepreload" href="https://ga.jspm.io/npm:@github/hotkey@1.4.4/dist/index.js">
|
70
87
|
...
|
71
88
|
```
|
72
89
|
|
@@ -1 +1 @@
|
|
1
|
-
//= require ./es-module-shims@0.12.
|
1
|
+
//= require ./es-module-shims@0.12.8.js
|
@@ -1,6 +1,6 @@
|
|
1
|
-
/* ES Module Shims 0.12.
|
1
|
+
/* ES Module Shims 0.12.8 */
|
2
2
|
(function () {
|
3
|
-
//
|
3
|
+
// Bail on all shimming for Chrome until https://github.com/guybedford/es-module-shims/issues/150
|
4
4
|
if (navigator.userAgent.match("Chrome")) return
|
5
5
|
|
6
6
|
const resolvedPromise = Promise.resolve();
|
@@ -16,6 +16,7 @@
|
|
16
16
|
// support browsers without dynamic import support (eg Firefox 6x)
|
17
17
|
let supportsDynamicImport = false;
|
18
18
|
let supportsJsonAssertions = false;
|
19
|
+
let supportsCssAssertions = false;
|
19
20
|
let dynamicImport;
|
20
21
|
try {
|
21
22
|
dynamicImport = (0, eval)('u=>import(u)');
|
@@ -53,6 +54,7 @@
|
|
53
54
|
let supportsImportMaps = false;
|
54
55
|
|
55
56
|
const featureDetectionPromise = Promise.all([
|
57
|
+
dynamicImport(createBlob('import"data:text/css,{}"assert{type:"css"}')).then(() => supportsCssAssertions = true, () => {}),
|
56
58
|
dynamicImport(createBlob('import"data:text/json,{}"assert{type:"json"}')).then(() => supportsJsonAssertions = true, () => {}),
|
57
59
|
dynamicImport(createBlob('import.meta')).then(() => supportsImportMeta = true, () => {}),
|
58
60
|
supportsDynamicImport && hasDocument && new Promise(resolve => {
|
@@ -263,9 +265,13 @@
|
|
263
265
|
load.n = load.d.some(dep => dep.n);
|
264
266
|
}
|
265
267
|
|
268
|
+
let importMap = { imports: {}, scopes: {} };
|
269
|
+
let importMapSrcOrLazy = false;
|
270
|
+
let importMapPromise = resolvedPromise;
|
271
|
+
|
266
272
|
let waitingForImportMapsInterval;
|
267
273
|
let firstTopLevelProcess = true;
|
268
|
-
async function topLevelLoad (url, fetchOpts, source, nativelyLoaded) {
|
274
|
+
async function topLevelLoad (url, fetchOpts, source, nativelyLoaded, lastStaticLoadPromise) {
|
269
275
|
// no need to even fetch if we have feature support
|
270
276
|
await featureDetectionPromise;
|
271
277
|
if (waitingForImportMapsInterval > 0) {
|
@@ -278,8 +284,8 @@
|
|
278
284
|
}
|
279
285
|
await importMapPromise;
|
280
286
|
// early analysis opt-out
|
281
|
-
if (nativelyLoaded && supportsDynamicImport && supportsImportMeta && supportsImportMaps && !importMapSrcOrLazy) {
|
282
|
-
// dont reexec inline for polyfills -> just return null
|
287
|
+
if (nativelyLoaded && supportsDynamicImport && supportsImportMeta && supportsImportMaps && supportsJsonAssertions && supportsCssAssertions && !importMapSrcOrLazy) {
|
288
|
+
// dont reexec inline for polyfills -> just return null (since no module id for executed inline module scripts)
|
283
289
|
return source && nativelyLoaded ? null : dynamicImport(source ? createBlob(source) : url);
|
284
290
|
}
|
285
291
|
await init;
|
@@ -288,17 +294,30 @@
|
|
288
294
|
await loadAll(load, seen);
|
289
295
|
lastLoad = undefined;
|
290
296
|
resolveDeps(load, seen);
|
291
|
-
|
297
|
+
await lastStaticLoadPromise;
|
298
|
+
if (source && !shimMode && !load.n) {
|
299
|
+
if (lastStaticLoadPromise) {
|
300
|
+
didExecForReadyPromise = true;
|
301
|
+
if (domContentLoaded)
|
302
|
+
didExecForDomContentLoaded = true;
|
303
|
+
}
|
292
304
|
const module = dynamicImport(createBlob(source));
|
293
305
|
if (shouldRevokeBlobURLs) revokeObjectURLs(Object.keys(seen));
|
294
306
|
return module;
|
295
307
|
}
|
296
308
|
const module = await dynamicImport(load.b);
|
309
|
+
if (lastStaticLoadPromise && (!nativelyLoaded || load.b !== load.u)) {
|
310
|
+
didExecForReadyPromise = true;
|
311
|
+
if (domContentLoaded)
|
312
|
+
didExecForDomContentLoaded = true;
|
313
|
+
}
|
297
314
|
// if the top-level load is a shell, run its update function
|
298
315
|
if (load.s) {
|
299
316
|
(await dynamicImport(load.s)).u$_(module);
|
300
317
|
}
|
301
318
|
if (shouldRevokeBlobURLs) revokeObjectURLs(Object.keys(seen));
|
319
|
+
// when tla is supported, this should return the tla promise as an actual handle
|
320
|
+
// so readystate can still correspond to the sync subgraph exec completions
|
302
321
|
return module;
|
303
322
|
}
|
304
323
|
|
@@ -323,7 +342,8 @@
|
|
323
342
|
await featureDetectionPromise;
|
324
343
|
// Make sure all the "in-flight" import maps are loaded and applied.
|
325
344
|
await importMapPromise;
|
326
|
-
|
345
|
+
const resolved = await resolve(id, parentUrl);
|
346
|
+
return topLevelLoad(resolved.r || throwUnresolved(id, parentUrl), { credentials: 'same-origin' });
|
327
347
|
}
|
328
348
|
|
329
349
|
self.importShim = importShim;
|
@@ -334,7 +354,8 @@
|
|
334
354
|
|
335
355
|
async function importMetaResolve (id, parentUrl = this.url) {
|
336
356
|
await importMapPromise;
|
337
|
-
|
357
|
+
const resolved = await resolve(id, `${parentUrl}`);
|
358
|
+
return resolved.r || throwUnresolved(id, parentUrl);
|
338
359
|
}
|
339
360
|
|
340
361
|
self._esmsm = meta;
|
@@ -346,6 +367,7 @@
|
|
346
367
|
const skip = esmsInitOptions.skip || /^https?:\/\/(cdn\.skypack\.dev|jspm\.dev)\//;
|
347
368
|
const onerror = esmsInitOptions.onerror || ((e) => { throw e; });
|
348
369
|
const shouldRevokeBlobURLs = esmsInitOptions.revokeBlobURLs;
|
370
|
+
const noLoadEventRetriggers = esmsInitOptions.noLoadEventRetriggers;
|
349
371
|
|
350
372
|
function urlJsString (url) {
|
351
373
|
return `'${url.replace(/'/g, "\\'")}'`;
|
@@ -374,7 +396,7 @@
|
|
374
396
|
const source = load.S;
|
375
397
|
|
376
398
|
// edge doesnt execute sibling in order, so we fix this up by ensuring all previous executions are explicit dependencies
|
377
|
-
let resolvedSource = edge && lastLoad ? `import '${lastLoad}';` : '';
|
399
|
+
let resolvedSource = edge && lastLoad ? `import '${lastLoad}';` : '';
|
378
400
|
|
379
401
|
if (!imports.length) {
|
380
402
|
resolvedSource += source;
|
@@ -449,7 +471,26 @@
|
|
449
471
|
const cssContentType = /^text\/css(;|$)/;
|
450
472
|
const wasmContentType = /^application\/wasm(;|$)/;
|
451
473
|
|
452
|
-
const
|
474
|
+
const cssUrlRegEx = /url\(\s*(?:(["'])((?:\\.|[^\n\\"'])+)\1|((?:\\.|[^\s,"'()\\])+))\s*\)/g;
|
475
|
+
|
476
|
+
async function doFetch (url, fetchOpts) {
|
477
|
+
const res = await fetchHook(url, fetchOpts);
|
478
|
+
if (!res.ok)
|
479
|
+
throw new Error(`${res.status} ${res.statusText} ${res.url}`);
|
480
|
+
const contentType = res.headers.get('content-type');
|
481
|
+
if (jsContentType.test(contentType))
|
482
|
+
return { r: res.url, s: await res.text(), t: 'js' };
|
483
|
+
else if (jsonContentType.test(contentType))
|
484
|
+
return { r: res.url, s: `export default ${await res.text()}`, t: 'json' };
|
485
|
+
else if (cssContentType.test(contentType))
|
486
|
+
return { r: res.url, s: `var s=new CSSStyleSheet();s.replaceSync(${
|
487
|
+
JSON.stringify((await res.text()).replace(cssUrlRegEx, (_match, quotes, relUrl1, relUrl2) => `url(${quotes}${resolveUrl(relUrl1 || relUrl2, url)}${quotes})`))
|
488
|
+
});export default s;`, t: 'css' };
|
489
|
+
else if (wasmContentType.test(contentType))
|
490
|
+
throw new Error('WASM modules not yet supported');
|
491
|
+
else
|
492
|
+
throw new Error(`Unknown Content-Type "${contentType}"`);
|
493
|
+
}
|
453
494
|
|
454
495
|
function getOrCreateLoad (url, fetchOpts, source) {
|
455
496
|
let load = registry[url];
|
@@ -476,27 +517,18 @@
|
|
476
517
|
// shellUrl
|
477
518
|
s: undefined,
|
478
519
|
// needsShim
|
479
|
-
n: false
|
520
|
+
n: false,
|
521
|
+
// type
|
522
|
+
t: null
|
480
523
|
};
|
481
524
|
|
482
525
|
load.f = (async () => {
|
483
526
|
if (!source) {
|
484
527
|
// preload fetch options override fetch options (race)
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
const contentType = res.headers.get('content-type');
|
490
|
-
if (jsContentType.test(contentType))
|
491
|
-
source = await res.text();
|
492
|
-
else if (jsonContentType.test(contentType))
|
493
|
-
source = `export default ${await res.text()}`;
|
494
|
-
else if (cssContentType.test(contentType))
|
495
|
-
throw new Error('CSS modules not yet supported');
|
496
|
-
else if (wasmContentType.test(contentType))
|
497
|
-
throw new Error('WASM modules not yet supported');
|
498
|
-
else
|
499
|
-
throw new Error(`Unknown Content-Type "${contentType}"`);
|
528
|
+
let t;
|
529
|
+
({ r: load.r, s: source, t } = await (fetchCache[url] || doFetch(url, fetchOpts)));
|
530
|
+
if (t === 'css' && !supportsCssAssertions || t === 'json' && !supportsJsonAssertions)
|
531
|
+
load.n = true;
|
500
532
|
}
|
501
533
|
try {
|
502
534
|
load.a = parse(source, load.u);
|
@@ -511,13 +543,12 @@
|
|
511
543
|
|
512
544
|
load.L = load.f.then(async () => {
|
513
545
|
let childFetchOpts = fetchOpts;
|
514
|
-
load.d = await Promise.all(load.a[0].map(({ n, d, a }) => {
|
546
|
+
load.d = (await Promise.all(load.a[0].map(async ({ n, d, a }) => {
|
515
547
|
if (d >= 0 && !supportsDynamicImport ||
|
516
|
-
d === 2 && (!supportsImportMeta || source.slice(end, end + 8) === '.resolve')
|
517
|
-
a && !supportsJsonAssertions)
|
548
|
+
d === 2 && (!supportsImportMeta || source.slice(end, end + 8) === '.resolve'))
|
518
549
|
load.n = true;
|
519
550
|
if (!n) return;
|
520
|
-
const { r, m } = resolve(n, load.r || load.u);
|
551
|
+
const { r, m } = await resolve(n, load.r || load.u);
|
521
552
|
if (m && (!supportsImportMaps || importMapSrcOrLazy))
|
522
553
|
load.n = true;
|
523
554
|
if (d !== -1) return;
|
@@ -527,22 +558,13 @@
|
|
527
558
|
if (childFetchOpts.integrity)
|
528
559
|
childFetchOpts = Object.assign({}, childFetchOpts, { integrity: undefined });
|
529
560
|
return getOrCreateLoad(r, childFetchOpts).f;
|
530
|
-
}).filter(l => l)
|
561
|
+
}))).filter(l => l);
|
531
562
|
});
|
532
563
|
|
533
564
|
return load;
|
534
565
|
}
|
535
566
|
|
536
|
-
|
537
|
-
let importMapSrcOrLazy = false;
|
538
|
-
let importMapPromise = resolvedPromise;
|
539
|
-
|
540
|
-
if (hasDocument) {
|
541
|
-
processScripts();
|
542
|
-
waitingForImportMapsInterval = setInterval(processScripts, 20);
|
543
|
-
}
|
544
|
-
|
545
|
-
async function processScripts () {
|
567
|
+
function processScripts () {
|
546
568
|
if (waitingForImportMapsInterval > 0 && document.readyState !== 'loading') {
|
547
569
|
clearTimeout(waitingForImportMapsInterval);
|
548
570
|
waitingForImportMapsInterval = 0;
|
@@ -550,29 +572,9 @@
|
|
550
572
|
for (const link of document.querySelectorAll('link[rel="modulepreload"]'))
|
551
573
|
processPreload(link);
|
552
574
|
for (const script of document.querySelectorAll('script[type="module-shim"],script[type="importmap-shim"],script[type="module"],script[type="importmap"]'))
|
553
|
-
|
575
|
+
processScript(script);
|
554
576
|
}
|
555
577
|
|
556
|
-
new MutationObserver(mutations => {
|
557
|
-
for (const mutation of mutations) {
|
558
|
-
if (mutation.type !== 'childList') continue;
|
559
|
-
for (const node of mutation.addedNodes) {
|
560
|
-
if (node.tagName === 'SCRIPT' && node.type)
|
561
|
-
processScript(node, !firstTopLevelProcess);
|
562
|
-
else if (node.tagName === 'LINK' && node.rel === 'modulepreload')
|
563
|
-
processPreload(node);
|
564
|
-
else if (node.querySelectorAll) {
|
565
|
-
for (const script of node.querySelectorAll('script[type="module-shim"],script[type="importmap-shim"],script[type="module"],script[type="importmap"]')) {
|
566
|
-
processScript(script, !firstTopLevelProcess);
|
567
|
-
}
|
568
|
-
for (const link of node.querySelectorAll('link[rel=modulepreload]')) {
|
569
|
-
processPreload(link);
|
570
|
-
}
|
571
|
-
}
|
572
|
-
}
|
573
|
-
}
|
574
|
-
}).observe(document, { childList: true, subtree: true });
|
575
|
-
|
576
578
|
function getFetchOpts (script) {
|
577
579
|
const fetchOpts = {};
|
578
580
|
if (script.integrity)
|
@@ -588,12 +590,29 @@
|
|
588
590
|
return fetchOpts;
|
589
591
|
}
|
590
592
|
|
591
|
-
|
593
|
+
let staticLoadCnt = 0;
|
594
|
+
let didExecForReadyPromise = false;
|
595
|
+
let didExecForDomContentLoaded = false;
|
596
|
+
let lastStaticLoadPromise = Promise.resolve();
|
597
|
+
let domContentLoaded = false;
|
598
|
+
document.addEventListener('DOMContentLoaded', () => domContentLoaded = true);
|
599
|
+
function staticLoadCheck () {
|
600
|
+
staticLoadCnt--;
|
601
|
+
if (staticLoadCnt === 0 && !noLoadEventRetriggers) {
|
602
|
+
if (didExecForDomContentLoaded)
|
603
|
+
document.dispatchEvent(new Event('DOMContentLoaded'));
|
604
|
+
if (didExecForReadyPromise && document.readyState === 'complete')
|
605
|
+
document.dispatchEvent(new Event('readystatechange'));
|
606
|
+
}
|
607
|
+
}
|
608
|
+
|
609
|
+
function processScript (script, dynamic) {
|
592
610
|
if (script.ep) // ep marker = script processed
|
593
611
|
return;
|
594
612
|
const shim = script.type.endsWith('-shim');
|
595
613
|
if (shim) shimMode = true;
|
596
|
-
const type =
|
614
|
+
const type = shimMode ? script.type.slice(0, -5) : script.type;
|
615
|
+
// dont process module scripts in shim mode or noshim module scripts in polyfill mode
|
597
616
|
if (!shim && shimMode || script.getAttribute('noshim') !== null)
|
598
617
|
return;
|
599
618
|
// empty inline scripts sometimes show before domready
|
@@ -601,7 +620,14 @@
|
|
601
620
|
return;
|
602
621
|
script.ep = true;
|
603
622
|
if (type === 'module') {
|
604
|
-
|
623
|
+
const isReadyScript = document.readyState !== 'complete';
|
624
|
+
if (isReadyScript) staticLoadCnt++;
|
625
|
+
const p = topLevelLoad(script.src || `${baseUrl}?${id++}`, getFetchOpts(script), !script.src && script.innerHTML, !shimMode, isReadyScript && lastStaticLoadPromise);
|
626
|
+
p.catch(onerror);
|
627
|
+
if (isReadyScript) {
|
628
|
+
lastStaticLoadPromise = p.catch(staticLoadCheck);
|
629
|
+
p.then(staticLoadCheck);
|
630
|
+
}
|
605
631
|
}
|
606
632
|
else if (type === 'importmap') {
|
607
633
|
importMapPromise = importMapPromise.then(async () => {
|
@@ -612,20 +638,51 @@
|
|
612
638
|
}
|
613
639
|
}
|
614
640
|
|
641
|
+
const fetchCache = {};
|
615
642
|
function processPreload (link) {
|
616
643
|
if (link.ep) // ep marker = processed
|
617
644
|
return;
|
618
645
|
link.ep = true;
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
fetchOptsMap.set(link.href, fetchOpts);
|
623
|
-
fetch(link.href, fetchOpts);
|
646
|
+
if (fetchCache[link.href])
|
647
|
+
return;
|
648
|
+
fetchCache[link.href] = doFetch(link.href, getFetchOpts(link));
|
624
649
|
}
|
625
650
|
|
626
|
-
|
627
|
-
const
|
628
|
-
|
651
|
+
new MutationObserver(mutations => {
|
652
|
+
for (const mutation of mutations) {
|
653
|
+
if (mutation.type !== 'childList') continue;
|
654
|
+
for (const node of mutation.addedNodes) {
|
655
|
+
if (node.tagName === 'SCRIPT' && node.type)
|
656
|
+
processScript(node, !firstTopLevelProcess);
|
657
|
+
else if (node.tagName === 'LINK' && node.rel === 'modulepreload')
|
658
|
+
processPreload(node);
|
659
|
+
else if (node.querySelectorAll) {
|
660
|
+
for (const script of node.querySelectorAll('script[type="module-shim"],script[type="importmap-shim"],script[type="module"],script[type="importmap"]')) {
|
661
|
+
processScript(script, !firstTopLevelProcess);
|
662
|
+
}
|
663
|
+
for (const link of node.querySelectorAll('link[rel=modulepreload]')) {
|
664
|
+
processPreload(link);
|
665
|
+
}
|
666
|
+
}
|
667
|
+
}
|
668
|
+
}
|
669
|
+
}).observe(document, { childList: true, subtree: true });
|
670
|
+
|
671
|
+
async function defaultResolve (id, parentUrl) {
|
672
|
+
return resolveImportMap(importMap, resolveIfNotPlainOrUrl(id, parentUrl) || id, parentUrl);
|
673
|
+
}
|
674
|
+
|
675
|
+
async function resolve (id, parentUrl) {
|
676
|
+
let urlResolved = resolveIfNotPlainOrUrl(id, parentUrl);
|
677
|
+
|
678
|
+
let resolved;
|
679
|
+
if (esmsInitOptions.resolve) {
|
680
|
+
resolved = await esmsInitOptions.resolve(id, parentUrl, defaultResolve);
|
681
|
+
}
|
682
|
+
else {
|
683
|
+
resolved = resolveImportMap(importMap, urlResolved || id, parentUrl);
|
684
|
+
}
|
685
|
+
|
629
686
|
return { r: resolved, m: urlResolved !== resolved };
|
630
687
|
}
|
631
688
|
|
@@ -633,4 +690,9 @@
|
|
633
690
|
throw Error("Unable to resolve specifier '" + id + (parentUrl ? "' from " + parentUrl : "'"));
|
634
691
|
}
|
635
692
|
|
693
|
+
if (hasDocument) {
|
694
|
+
processScripts();
|
695
|
+
waitingForImportMapsInterval = setInterval(processScripts, 20);
|
696
|
+
}
|
697
|
+
|
636
698
|
}());
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "importmap/pinner"
|
3
|
+
|
4
|
+
class Importmap::Commands < Thor
|
5
|
+
desc "pin [*PACKAGES]", "Pin new packages"
|
6
|
+
option :env, type: :string, aliases: :e, default: "production"
|
7
|
+
option :from, type: :string, aliases: :f, default: "jspm"
|
8
|
+
def pin(*packages)
|
9
|
+
if imports = Importmap::Pinner.new.pin(*packages, env: options[:env], from: options[:from])
|
10
|
+
imports.each do |package, url|
|
11
|
+
puts "Pinned '#{package}' to #{url}"
|
12
|
+
end
|
13
|
+
else
|
14
|
+
puts "Couldn't find any packages in #{packages.inspect} on #{options[:provider]}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "json", "Show the full importmap in json"
|
19
|
+
def json
|
20
|
+
puts Rails.application.config.importmap.to_json(resolver: ActionController::Base.helpers)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
Importmap::Commands.start(ARGV)
|
data/lib/importmap/engine.rb
CHANGED
@@ -2,19 +2,16 @@ require "importmap/map"
|
|
2
2
|
|
3
3
|
module Importmap
|
4
4
|
class Engine < ::Rails::Engine
|
5
|
-
config.importmap = Importmap::Map.new
|
6
|
-
|
5
|
+
config.importmap = Importmap::Map.new.draw("config/importmap.rb")
|
7
6
|
config.autoload_once_paths = %W( #{root}/app/helpers )
|
8
7
|
|
9
8
|
initializer "importmap.reloader" do |app|
|
10
9
|
app.config.paths.add "config/importmap.rb"
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
reloader.execute
|
15
|
-
app.reloaders << reloader
|
16
|
-
app.reloader.to_run do
|
11
|
+
Importmap::Reloader.new.tap do |reloader|
|
17
12
|
reloader.execute
|
13
|
+
app.reloaders << reloader
|
14
|
+
app.reloader.to_run { reloader.execute }
|
18
15
|
end
|
19
16
|
end
|
20
17
|
|
data/lib/importmap/map.rb
CHANGED
@@ -1,17 +1,30 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
1
3
|
class Importmap::Map
|
2
|
-
attr_reader :
|
4
|
+
attr_reader :packages, :directories
|
3
5
|
attr_accessor :cached
|
4
6
|
|
5
7
|
def initialize
|
6
|
-
@
|
8
|
+
@packages, @directories = {}, {}
|
7
9
|
end
|
8
10
|
|
9
|
-
def draw(&block)
|
10
|
-
|
11
|
+
def draw(path = nil, &block)
|
12
|
+
if path
|
13
|
+
begin
|
14
|
+
instance_eval(File.read(path))
|
15
|
+
rescue Exception => e
|
16
|
+
Rails.logger.error "Unable to parse import map from #{path}: #{e.message}"
|
17
|
+
raise "Unable to parse import map from #{path}: #{e.message}"
|
18
|
+
end
|
19
|
+
else
|
20
|
+
instance_eval(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
self
|
11
24
|
end
|
12
25
|
|
13
26
|
def pin(name, to: nil, preload: true)
|
14
|
-
@
|
27
|
+
@packages[name] = MappedFile.new(name: name, path: to || "#{name}.js", preload: preload)
|
15
28
|
end
|
16
29
|
|
17
30
|
def pin_all_from(dir, under: nil, to: nil, preload: true)
|
@@ -20,19 +33,19 @@ class Importmap::Map
|
|
20
33
|
|
21
34
|
def preloaded_module_paths(resolver:)
|
22
35
|
cache_as(:preloaded_module_paths) do
|
23
|
-
resolve_asset_paths(
|
36
|
+
resolve_asset_paths(expanded_preloading_packages_and_directories, resolver: resolver).values
|
24
37
|
end
|
25
38
|
end
|
26
39
|
|
27
40
|
def to_json(resolver:)
|
28
41
|
cache_as(:json) do
|
29
|
-
{ "imports" => resolve_asset_paths(
|
42
|
+
JSON.pretty_generate({ "imports" => resolve_asset_paths(expanded_packages_and_directories, resolver: resolver) })
|
30
43
|
end
|
31
44
|
end
|
32
45
|
|
33
46
|
private
|
34
|
-
MappedFile = Struct.new(:name, :path, :preload, keyword_init: true)
|
35
47
|
MappedDir = Struct.new(:dir, :path, :under, :preload, keyword_init: true)
|
48
|
+
MappedFile = Struct.new(:name, :path, :preload, keyword_init: true)
|
36
49
|
|
37
50
|
def cache_as(name)
|
38
51
|
if (cached && result = instance_variable_get("@cached_#{name}"))
|
@@ -53,12 +66,12 @@ class Importmap::Map
|
|
53
66
|
end.compact
|
54
67
|
end
|
55
68
|
|
56
|
-
def
|
57
|
-
|
69
|
+
def expanded_preloading_packages_and_directories
|
70
|
+
expanded_packages_and_directories.select { |name, mapping| mapping.preload }
|
58
71
|
end
|
59
72
|
|
60
|
-
def
|
61
|
-
@
|
73
|
+
def expanded_packages_and_directories
|
74
|
+
@packages.dup.tap { |expanded| expand_directories_into expanded }
|
62
75
|
end
|
63
76
|
|
64
77
|
def expand_directories_into(paths)
|
@@ -80,7 +93,7 @@ class Importmap::Map
|
|
80
93
|
end
|
81
94
|
|
82
95
|
def module_path_from(filename, mapping)
|
83
|
-
[ mapping.path || mapping.under, filename.to_s ].join("/")
|
96
|
+
[ mapping.path || mapping.under, filename.to_s ].compact.join("/")
|
84
97
|
end
|
85
98
|
|
86
99
|
def find_javascript_files_in_tree(path)
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "httparty"
|
2
|
+
|
3
|
+
class Importmap::Pinner
|
4
|
+
include HTTParty
|
5
|
+
base_uri "https://api.jspm.io"
|
6
|
+
|
7
|
+
def pin(*packages, env: "production", from: "jspm")
|
8
|
+
fetch_imports(*packages, env: env, provider: from)&.tap do |imports|
|
9
|
+
imports.each do |package, url|
|
10
|
+
append_to_importmap package, url
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def append_to_importmap(package, url)
|
17
|
+
Rails.root.join("config/importmap.rb").open("a") do |config|
|
18
|
+
config.puts %(pin "#{package}", to: "#{url}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def fetch_imports(*packages, env:, provider:)
|
23
|
+
response = self.class.post("/generate", body: {
|
24
|
+
"install" => Array(packages),
|
25
|
+
"flattenScope" => true,
|
26
|
+
"env" => [ "browser", env ],
|
27
|
+
"provider" => provider.to_s
|
28
|
+
}.to_json)
|
29
|
+
|
30
|
+
response.dig("map", "imports")
|
31
|
+
end
|
32
|
+
end
|
data/lib/importmap/reloader.rb
CHANGED
data/lib/importmap/version.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Use Action Cable channels (remember to import "@rails/actionable" in your application.js)
|
2
|
+
# pin "@rails/actioncable", to: "actioncable.esm.js"
|
3
|
+
# pin_all_from "app/javascript/channels", under: "channels"
|
4
|
+
|
5
|
+
# Use direct uploads for Active Storage (remember to import "@rails/activestorage" in your application.js)
|
6
|
+
# pin "@rails/activestorage", to: "activestorage.esm.js"
|
7
|
+
|
8
|
+
# Pin vendored modules by first adding the following to app/assets/config/manifest.js:
|
9
|
+
# //= link_tree ../../../vendor/assets/javascripts .js
|
10
|
+
# pin_all_from "vendor/assets/javascripts"
|
11
|
+
|
12
|
+
# JavaScript CDN provider. Also available: :jsdelivr, :esmsh, :unpkg, :skypack
|
13
|
+
provider :jspm
|
14
|
+
|
15
|
+
# Use NPM libraries from CDN
|
16
|
+
# pin "local-time", version: "2.1.0", file: "app/assets/javascripts/local-time.js"
|
17
|
+
# pin "vue", version: "2.6.14", file: "dist/vue.esm.browser.js", from: :jsdelivr
|
18
|
+
# pin "d3", version: "7.0.1", file: "?bundle", from: :esmsh
|
19
|
+
|
20
|
+
pin "application"
|
data/lib/install/install.rb
CHANGED
@@ -10,10 +10,7 @@ end
|
|
10
10
|
|
11
11
|
say "Create application.js module as entrypoint"
|
12
12
|
create_file Rails.root.join("app/javascript/application.js") do <<-JS
|
13
|
-
// Configure your import map in config/importmap.rb
|
14
|
-
|
15
|
-
// import "@rails/actioncable"
|
16
|
-
// import "@rails/activestorage"
|
13
|
+
// Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
|
17
14
|
JS
|
18
15
|
end
|
19
16
|
|
@@ -21,21 +18,8 @@ say "Ensure JavaScript files are in the asset pipeline manifest"
|
|
21
18
|
append_to_file Rails.root.join("app/assets/config/manifest.js"), %(//= link_tree ../../javascript .js\n)
|
22
19
|
|
23
20
|
say "Configure importmap paths in config/importmap.rb"
|
24
|
-
|
25
|
-
Rails.application.config.importmap.draw do
|
26
|
-
pin "application"
|
27
|
-
|
28
|
-
# Use libraries available via the asset pipeline (locally or via gems).
|
29
|
-
# pin "@rails/actioncable", to: "actioncable.esm.js"
|
30
|
-
# pin "@rails/activestorage", to: "activestorage.esm.js"
|
31
|
-
|
32
|
-
# Use libraries directly from JavaScript CDNs (see https://www.skypack.dev, https://esm.sh, https://www.jsdelivr.com/esm)
|
33
|
-
# pin "vue", to: "https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.esm.browser.js"
|
34
|
-
# pin "d3", to: "https://esm.sh/d3?bundle"
|
21
|
+
copy_file "#{__dir__}/config/importmap.rb", "config/importmap.rb"
|
35
22
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
end
|
40
|
-
RUBY
|
41
|
-
end
|
23
|
+
say "Copying binstub"
|
24
|
+
copy_file "#{__dir__}/bin/importmap", "bin/importmap"
|
25
|
+
chmod "bin", 0755 & ~File.umask, verbose: false
|
@@ -3,4 +3,10 @@ namespace :importmap do
|
|
3
3
|
task :install do
|
4
4
|
system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/install.rb", __dir__)}"
|
5
5
|
end
|
6
|
+
|
7
|
+
desc "Show the importmap"
|
8
|
+
task :pins do
|
9
|
+
require Rails.root.join("config/environment")
|
10
|
+
puts Rails.application.config.importmap.to_json(resolver: ActionController::Base.helpers)
|
11
|
+
end
|
6
12
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: importmap-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-08-
|
11
|
+
date: 2021-08-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 6.0.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httparty
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.16'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.16'
|
27
41
|
description:
|
28
42
|
email: david@loudthinking.com
|
29
43
|
executables: []
|
@@ -34,13 +48,17 @@ files:
|
|
34
48
|
- README.md
|
35
49
|
- Rakefile
|
36
50
|
- app/assets/javascripts/es-module-shims.js
|
37
|
-
- app/assets/javascripts/es-module-shims@0.12.
|
51
|
+
- app/assets/javascripts/es-module-shims@0.12.8.js
|
38
52
|
- app/helpers/importmap/importmap_tags_helper.rb
|
39
53
|
- lib/importmap-rails.rb
|
54
|
+
- lib/importmap/commands.rb
|
40
55
|
- lib/importmap/engine.rb
|
41
56
|
- lib/importmap/map.rb
|
57
|
+
- lib/importmap/pinner.rb
|
42
58
|
- lib/importmap/reloader.rb
|
43
59
|
- lib/importmap/version.rb
|
60
|
+
- lib/install/bin/importmap
|
61
|
+
- lib/install/config/importmap.rb
|
44
62
|
- lib/install/install.rb
|
45
63
|
- lib/shim.js
|
46
64
|
- lib/tasks/importmap_tasks.rake
|