Skip to content

Commit e0a9811

Browse files
authored
One-page: Pure ESM (#1277)
Hey! What do you know? `importmap`s now mostly work! Which means we can update our browser one-page version to use just ESM. Fixes: - Allow more tolerant `react-syntax-highligher` import behavior to better accommodate both CJS and ESM. Notes: - No more UMD needed! 🎉 - **BUT**, first load if not cached is 2010-era `requirejs`-in-dev-mode slow! 🐢 - `react*` libs of course don't have ESM versions. And lots of other libraries don't play nice. So instead we use esm.sh which adjusts for ESM things - We have to manually override react to 18 (since some transitive deps bring in 17)
1 parent ebbf5a8 commit e0a9811

File tree

7 files changed

+144
-18
lines changed

7 files changed

+144
-18
lines changed

.changeset/twelve-trainers-laugh.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'spectacle': patch
3+
---
4+
5+
Allow react-syntax-highlighter prism theme imports to have more tolerant `.default` behavior with CJS vs. ESM.

examples/js/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import ReactDOM from 'react-dom';
23
import {
34
FlexBox,
45
Heading,
@@ -21,7 +22,6 @@ import {
2122
DefaultTemplate,
2223
SlideLayout
2324
} from 'spectacle';
24-
import ReactDOM from 'react-dom';
2525

2626
const formidableLogo =
2727
'https://avatars2.githubusercontent.com/u/5078602?s=280&v=4';

examples/one-page/index.html

+40-10
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,45 @@
99
<body>
1010
<div id="root"></div>
1111

12-
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
13-
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
14-
<script src="https://unpkg.com/[email protected]/umd/react-is.production.min.js"></script>
15-
<script src="https://unpkg.com/[email protected]/prop-types.min.js"></script>
16-
<script src="https://unpkg.com/spectacle@^10/lib/index.global.js"></script>
17-
<!-- <script src="../../packages/spectacle/lib/index.global.js"></script>-->
18-
12+
<script type="importmap">
13+
{
14+
"imports": {
15+
"htm": "https://esm.sh/v121/htm@^[email protected]",
16+
"spectacle": "https://esm.sh/v121/spectacle@^[email protected]",
17+
"broadcast-channel": "https://esm.sh/v121/broadcast-channel@^[email protected]",
18+
"history": "https://esm.sh/v121/history@^[email protected]",
19+
"kbar": "https://esm.sh/v121/[email protected][email protected]",
20+
"mdast-builder": "https://esm.sh/v121/mdast-builder@^[email protected]",
21+
"mdast-zone": "https://esm.sh/v121/mdast-zone@^[email protected]",
22+
"merge-anything": "https://esm.sh/v121/merge-anything@^[email protected]",
23+
"mousetrap": "https://esm.sh/v121/mousetrap@^[email protected]",
24+
"query-string": "https://esm.sh/v121/query-string@^[email protected]",
25+
"react": "https://esm.sh/v121/[email protected][email protected]",
26+
"react/jsx-runtime": "https://esm.sh/v121/[email protected]/[email protected]",
27+
"react-dom": "https://esm.sh/v121/react-dom@>[email protected]",
28+
"react-fast-compare": "https://esm.sh/v121/react-fast-compare@^[email protected]",
29+
"react-is": "https://esm.sh/v121/react-is@^[email protected]",
30+
"react-spring": "https://esm.sh/v121/react-spring@^[email protected]",
31+
"react-swipeable": "https://esm.sh/v121/react-swipeable@^[email protected]",
32+
"react-syntax-highlighter": "https://esm.sh/v121/react-syntax-highlighter@^[email protected]",
33+
"react-syntax-highlighter/dist/cjs/styles/prism/vs-dark.js": "https://esm.sh/v121/react-syntax-highlighter@^15.5.0/dist/esm/styles/prism/[email protected]",
34+
"react-syntax-highlighter/dist/cjs/styles/prism/index.js": "https://esm.sh/v121/react-syntax-highlighter@^15.5.0/dist/esm/styles/prism/[email protected]",
35+
"rehype-raw": "https://esm.sh/v121/rehype-raw@^[email protected]",
36+
"rehype-react": "https://esm.sh/v121/rehype-react@^[email protected]",
37+
"remark-parse": "https://esm.sh/v121/remark-parse@^[email protected]",
38+
"remark-rehype": "https://esm.sh/v121/remark-rehype@^[email protected]",
39+
"styled-components": "https://esm.sh/v121/styled-components@^[email protected]",
40+
"styled-system": "https://esm.sh/v121/[email protected][email protected]",
41+
"unified": "https://esm.sh/v121/unified@^[email protected]",
42+
"unist-util-visit": "https://esm.sh/v121/unist-util-visit@^[email protected]",
43+
"use-resize-observer": "https://esm.sh/v121/use-resize-observer@^[email protected]"
44+
}
45+
}
46+
</script>
1947
<script type="module">
20-
const {
48+
import React from 'react';
49+
import ReactDOM from 'react-dom';
50+
import {
2151
FlexBox,
2252
Heading,
2353
SpectacleLogo,
@@ -38,9 +68,9 @@
3868
Notes,
3969
DefaultTemplate,
4070
SlideLayout
41-
} = Spectacle;
71+
} from 'spectacle';
4272

43-
import htm from 'https://unpkg.com/htm@^3?module';
73+
import htm from 'htm';
4474
const html = htm.bind(React.createElement);
4575
const formidableLogo = 'https://avatars2.githubusercontent.com/u/5078602?s=280&v=4';
4676

examples/one-page/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"pretty": "^2.0.0"
66
},
77
"scripts": {
8+
"start": "pnpm exec serve --listen 5000 ../..",
89
"build": "wireit",
910
"lint": "wireit",
1011
"lint:fix": "wireit",

examples/one-page/scripts/one-page.js

+88-5
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,86 @@ const path = require('path');
99
const { transformFileAsync } = require('@babel/core');
1010
const pretty = require('pretty');
1111

12+
// Paths
1213
const EXAMPLES = path.resolve(__dirname, '../..');
14+
const SPECTACLE_PATH = path.resolve(__dirname, '../../../packages/spectacle');
1315
const SRC_FILE = path.join(EXAMPLES, 'js/index.js');
1416
const DEST_FILE = path.join(EXAMPLES, 'one-page/index.html');
1517

18+
// Dependencies.
19+
const ESM_SH_VERSION = 'v121'; // v121, stable, etc.
20+
const {
21+
dependencies,
22+
peerDependencies
23+
} = require(`${SPECTACLE_PATH}/package.json`);
24+
const reactPkgPath = require.resolve('react/package.json', {
25+
paths: [SPECTACLE_PATH]
26+
});
27+
const { version: reactVersion } = require(reactPkgPath);
28+
const DEPS = `deps=react@${reactVersion}`;
29+
30+
// Toggle dev resources. (Use if debugging load / dependency errors).
31+
const IS_DEV = false;
32+
const DEV = IS_DEV ? '&dev' : '';
33+
34+
// Use local built spectacle? Toggle to `true` for dev-only.
35+
// Note: Due to CORS, you'll need to run `pnpm run --filter ./examples/one-page start` and
36+
// open http://localhost:5000/examples/one-page to work.
37+
const USE_LOCAL = false;
38+
39+
// ================================================================================================
40+
// Import Map
41+
// ================================================================================================
42+
const importUrl = (k, v, extra = '') => {
43+
// Pin react.
44+
if (k === 'react') {
45+
v = reactVersion;
46+
}
47+
48+
return `https://esm.sh/${ESM_SH_VERSION}/${k}@${v}${extra}?${DEPS}${DEV}`;
49+
};
50+
51+
const getImportMap = () => {
52+
// Start with extra imports for one-page alone.
53+
const importMap = {
54+
htm: importUrl('htm', '^3'),
55+
spectacle: USE_LOCAL
56+
? '../../packages/spectacle/lib/index.mjs'
57+
: importUrl('spectacle', '^10')
58+
};
59+
60+
Object.entries(Object.assign({}, dependencies, peerDependencies))
61+
.sort((a, b) => a[0].localeCompare(b[0]))
62+
.forEach(([k, v]) => {
63+
// General
64+
importMap[k] = importUrl(k, v);
65+
66+
// Special case internal deps
67+
if (k === 'react') {
68+
importMap[`${k}/jsx-runtime`] = importUrl(k, v, '/jsx-runtime');
69+
}
70+
if (k === 'react-syntax-highlighter') {
71+
importMap[`${k}/dist/cjs/styles/prism/vs-dark.js`] = importUrl(
72+
k,
73+
v,
74+
'/dist/esm/styles/prism/vs-dark.js'
75+
);
76+
importMap[`${k}/dist/cjs/styles/prism/index.js`] = importUrl(
77+
k,
78+
v,
79+
'/dist/esm/styles/prism/index.js'
80+
);
81+
}
82+
});
83+
84+
return importMap;
85+
};
86+
87+
// ================================================================================================
88+
// Rewriting
89+
// ================================================================================================
1690
const htmImport = `
17-
import htm from 'https://unpkg.com/htm@^3?module';
91+
import htm from 'htm';
1892
const html = htm.bind(React.createElement);
1993
`
2094
.replace(/ /gm, '')
@@ -27,7 +101,7 @@ const spectacleImportReplacer = (match, imports) => {
27101
.map((i) => ` ${i.trim()}`)
28102
.join(`,\n`);
29103

30-
return `const {\n${imports}\n} = Spectacle;\n\n${htmImport}`;
104+
return `import {\n${imports}\n} from 'spectacle';\n\n${htmImport}`;
31105
};
32106

33107
const getSrcContent = async (src) => {
@@ -40,7 +114,6 @@ const getSrcContent = async (src) => {
40114
// Mutate exports and comments.
41115
code = code
42116
// Mutate exports to our global imports.
43-
.replace(/import React(|DOM) from 'react(|-dom)';[\n]*/gm, '')
44117
.replace(/import {[ ]*(.*)} from 'spectacle';/, spectacleImportReplacer)
45118
// Hackily fix / undo babel's poor control comment placment.
46119
.replace(/\/\/ SPECTACLE_CLI/gm, '\n// SPECTACLE_CLI')
@@ -84,17 +157,27 @@ const getSrcContent = async (src) => {
84157
return code;
85158
};
86159

160+
// ================================================================================================
161+
// Output
162+
// ================================================================================================
87163
const writeDestContent = async (destFile, code) => {
88164
// Format for indentation in index.html.
89165
const indent = ' ';
90-
code = `${indent}${code}`;
91-
code = code.split('\n').join(`\n${indent}`);
166+
code = `${indent}${code.split('\n').join(`\n${indent}`)}`;
167+
168+
// Import map
169+
let importMap = JSON.stringify({ imports: getImportMap() }, null, 2);
170+
importMap = `${indent}${importMap.split('\n').join(`\n${indent}`)}`;
92171

93172
// Get destination content.
94173
let destContent = (await fs.readFile(destFile)).toString();
95174

96175
// Mutate in our updated code.
97176
destContent = destContent
177+
.replace(
178+
/(<script type="importmap">\n)[\s\S]*?(\n[ ]*<\/script>)/m,
179+
(match, open, close) => `${open}${importMap}${close}`
180+
)
98181
.replace(
99182
/(<script type="module">\n)[\s\S]*?(\n[ ]*<\/script>\n[ ]*<\/body>\n[ ]*<\/html>)/m,
100183
(match, open, close) => `${open}${code}${close}`

examples/typescript/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import React from 'react';
2+
import { createRoot } from 'react-dom/client';
23
import {
34
FlexBox,
45
Heading,
@@ -22,7 +23,6 @@ import {
2223
SlideLayout,
2324
codePaneThemes
2425
} from 'spectacle';
25-
import { createRoot } from 'react-dom/client';
2626

2727
const formidableLogo =
2828
'https://avatars2.githubusercontent.com/u/5078602?s=280&v=4';

packages/spectacle/src/components/code-pane.tsx

+8-1
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@ import { compose, layout, position } from 'styled-system';
1616
* We export all the themes from the index file and the VSCode Dark Theme.
1717
* The default VSCode dark theme is not part of the index file, so we need
1818
* to import it separately and re-export with the rest of the themes.
19+
*
20+
* **Note**: For ESM + one-page we swap out for `dist/esm/**` files instead.
1921
*/
2022
// @ts-ignore
2123
import vsDark from 'react-syntax-highlighter/dist/cjs/styles/prism/vs-dark.js';
2224
// @ts-ignore
2325
import * as allThemes from 'react-syntax-highlighter/dist/cjs/styles/prism/index.js';
24-
export const codePaneThemes = { vsDark: vsDark.default, ...allThemes };
26+
27+
// Allow for rewriting of RSH `/cjs/` to `/esm/` by flexibly falling back to object
28+
// if `.default` isn't available.
29+
const vsDarkTheme = vsDark.default || vsDark;
30+
31+
export const codePaneThemes = { vsDark: vsDarkTheme, ...allThemes };
2532

2633
type Ranges = Array<number | number[]>;
2734

0 commit comments

Comments
 (0)