Skip to content

Commit

Permalink
One-page: Pure ESM (#1277)
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
ryan-roemer authored May 17, 2023
1 parent ebbf5a8 commit e0a9811
Show file tree
Hide file tree
Showing 7 changed files with 144 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/twelve-trainers-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'spectacle': patch
---

Allow react-syntax-highlighter prism theme imports to have more tolerant `.default` behavior with CJS vs. ESM.
2 changes: 1 addition & 1 deletion examples/js/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import {
FlexBox,
Heading,
Expand All @@ -21,7 +22,6 @@ import {
DefaultTemplate,
SlideLayout
} from 'spectacle';
import ReactDOM from 'react-dom';

const formidableLogo =
'https://avatars2.githubusercontent.com/u/5078602?s=280&v=4';
Expand Down
50 changes: 40 additions & 10 deletions examples/one-page/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,45 @@
<body>
<div id="root"></div>

<script src="https://unpkg.com/react@18.2.0/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/react-is@18.2.0/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/prop-types@15.7.2/prop-types.min.js"></script>
<script src="https://unpkg.com/spectacle@^10/lib/index.global.js"></script>
<!-- <script src="../../packages/spectacle/lib/index.global.js"></script>-->

<script type="importmap">
{
"imports": {
"htm": "https://esm.sh/v121/htm@^3?deps=react@18.2.0",
"spectacle": "https://esm.sh/v121/spectacle@^10?deps=react@18.2.0",
"broadcast-channel": "https://esm.sh/v121/broadcast-channel@^4.17.0?deps=react@18.2.0",
"history": "https://esm.sh/v121/history@^5.3.0?deps=react@18.2.0",
"kbar": "https://esm.sh/v121/kbar@0.1.0-beta.40?deps=react@18.2.0",
"mdast-builder": "https://esm.sh/v121/mdast-builder@^1.1.1?deps=react@18.2.0",
"mdast-zone": "https://esm.sh/v121/mdast-zone@^4.0.0?deps=react@18.2.0",
"merge-anything": "https://esm.sh/v121/merge-anything@^3.0.3?deps=react@18.2.0",
"mousetrap": "https://esm.sh/v121/mousetrap@^1.6.5?deps=react@18.2.0",
"query-string": "https://esm.sh/v121/query-string@^7.1.1?deps=react@18.2.0",
"react": "https://esm.sh/v121/react@18.2.0?deps=react@18.2.0",
"react/jsx-runtime": "https://esm.sh/v121/react@18.2.0/jsx-runtime?deps=react@18.2.0",
"react-dom": "https://esm.sh/v121/react-dom@>=18.0.0?deps=react@18.2.0",
"react-fast-compare": "https://esm.sh/v121/react-fast-compare@^3.2.0?deps=react@18.2.0",
"react-is": "https://esm.sh/v121/react-is@^18.1.0?deps=react@18.2.0",
"react-spring": "https://esm.sh/v121/react-spring@^9.5.5?deps=react@18.2.0",
"react-swipeable": "https://esm.sh/v121/react-swipeable@^7.0.0?deps=react@18.2.0",
"react-syntax-highlighter": "https://esm.sh/v121/react-syntax-highlighter@^15.5.0?deps=react@18.2.0",
"react-syntax-highlighter/dist/cjs/styles/prism/vs-dark.js": "https://esm.sh/v121/react-syntax-highlighter@^15.5.0/dist/esm/styles/prism/vs-dark.js?deps=react@18.2.0",
"react-syntax-highlighter/dist/cjs/styles/prism/index.js": "https://esm.sh/v121/react-syntax-highlighter@^15.5.0/dist/esm/styles/prism/index.js?deps=react@18.2.0",
"rehype-raw": "https://esm.sh/v121/rehype-raw@^5.1.0?deps=react@18.2.0",
"rehype-react": "https://esm.sh/v121/rehype-react@^6.0.0?deps=react@18.2.0",
"remark-parse": "https://esm.sh/v121/remark-parse@^8.0.3?deps=react@18.2.0",
"remark-rehype": "https://esm.sh/v121/remark-rehype@^7.0.0?deps=react@18.2.0",
"styled-components": "https://esm.sh/v121/styled-components@^5.3.6?deps=react@18.2.0",
"styled-system": "https://esm.sh/v121/styled-system@5.1.5?deps=react@18.2.0",
"unified": "https://esm.sh/v121/unified@^9.0.0?deps=react@18.2.0",
"unist-util-visit": "https://esm.sh/v121/unist-util-visit@^2.0.3?deps=react@18.2.0",
"use-resize-observer": "https://esm.sh/v121/use-resize-observer@^9.0.2?deps=react@18.2.0"
}
}
</script>
<script type="module">
const {
import React from 'react';
import ReactDOM from 'react-dom';
import {
FlexBox,
Heading,
SpectacleLogo,
Expand All @@ -38,9 +68,9 @@
Notes,
DefaultTemplate,
SlideLayout
} = Spectacle;
} from 'spectacle';

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

Expand Down
1 change: 1 addition & 0 deletions examples/one-page/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"pretty": "^2.0.0"
},
"scripts": {
"start": "pnpm exec serve --listen 5000 ../..",
"build": "wireit",
"lint": "wireit",
"lint:fix": "wireit",
Expand Down
93 changes: 88 additions & 5 deletions examples/one-page/scripts/one-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,86 @@ const path = require('path');
const { transformFileAsync } = require('@babel/core');
const pretty = require('pretty');

// Paths
const EXAMPLES = path.resolve(__dirname, '../..');
const SPECTACLE_PATH = path.resolve(__dirname, '../../../packages/spectacle');
const SRC_FILE = path.join(EXAMPLES, 'js/index.js');
const DEST_FILE = path.join(EXAMPLES, 'one-page/index.html');

// Dependencies.
const ESM_SH_VERSION = 'v121'; // v121, stable, etc.
const {
dependencies,
peerDependencies
} = require(`${SPECTACLE_PATH}/package.json`);
const reactPkgPath = require.resolve('react/package.json', {
paths: [SPECTACLE_PATH]
});
const { version: reactVersion } = require(reactPkgPath);
const DEPS = `deps=react@${reactVersion}`;

// Toggle dev resources. (Use if debugging load / dependency errors).
const IS_DEV = false;
const DEV = IS_DEV ? '&dev' : '';

// Use local built spectacle? Toggle to `true` for dev-only.
// Note: Due to CORS, you'll need to run `pnpm run --filter ./examples/one-page start` and
// open http://localhost:5000/examples/one-page to work.
const USE_LOCAL = false;

// ================================================================================================
// Import Map
// ================================================================================================
const importUrl = (k, v, extra = '') => {
// Pin react.
if (k === 'react') {
v = reactVersion;
}

return `https://esm.sh/${ESM_SH_VERSION}/${k}@${v}${extra}?${DEPS}${DEV}`;
};

const getImportMap = () => {
// Start with extra imports for one-page alone.
const importMap = {
htm: importUrl('htm', '^3'),
spectacle: USE_LOCAL
? '../../packages/spectacle/lib/index.mjs'
: importUrl('spectacle', '^10')
};

Object.entries(Object.assign({}, dependencies, peerDependencies))
.sort((a, b) => a[0].localeCompare(b[0]))
.forEach(([k, v]) => {
// General
importMap[k] = importUrl(k, v);

// Special case internal deps
if (k === 'react') {
importMap[`${k}/jsx-runtime`] = importUrl(k, v, '/jsx-runtime');
}
if (k === 'react-syntax-highlighter') {
importMap[`${k}/dist/cjs/styles/prism/vs-dark.js`] = importUrl(
k,
v,
'/dist/esm/styles/prism/vs-dark.js'
);
importMap[`${k}/dist/cjs/styles/prism/index.js`] = importUrl(
k,
v,
'/dist/esm/styles/prism/index.js'
);
}
});

return importMap;
};

// ================================================================================================
// Rewriting
// ================================================================================================
const htmImport = `
import htm from 'https://unpkg.com/htm@^3?module';
import htm from 'htm';
const html = htm.bind(React.createElement);
`
.replace(/ /gm, '')
Expand All @@ -27,7 +101,7 @@ const spectacleImportReplacer = (match, imports) => {
.map((i) => ` ${i.trim()}`)
.join(`,\n`);

return `const {\n${imports}\n} = Spectacle;\n\n${htmImport}`;
return `import {\n${imports}\n} from 'spectacle';\n\n${htmImport}`;
};

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

// ================================================================================================
// Output
// ================================================================================================
const writeDestContent = async (destFile, code) => {
// Format for indentation in index.html.
const indent = ' ';
code = `${indent}${code}`;
code = code.split('\n').join(`\n${indent}`);
code = `${indent}${code.split('\n').join(`\n${indent}`)}`;

// Import map
let importMap = JSON.stringify({ imports: getImportMap() }, null, 2);
importMap = `${indent}${importMap.split('\n').join(`\n${indent}`)}`;

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

// Mutate in our updated code.
destContent = destContent
.replace(
/(<script type="importmap">\n)[\s\S]*?(\n[ ]*<\/script>)/m,
(match, open, close) => `${open}${importMap}${close}`
)
.replace(
/(<script type="module">\n)[\s\S]*?(\n[ ]*<\/script>\n[ ]*<\/body>\n[ ]*<\/html>)/m,
(match, open, close) => `${open}${code}${close}`
Expand Down
2 changes: 1 addition & 1 deletion examples/typescript/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import {
FlexBox,
Heading,
Expand All @@ -22,7 +23,6 @@ import {
SlideLayout,
codePaneThemes
} from 'spectacle';
import { createRoot } from 'react-dom/client';

const formidableLogo =
'https://avatars2.githubusercontent.com/u/5078602?s=280&v=4';
Expand Down
9 changes: 8 additions & 1 deletion packages/spectacle/src/components/code-pane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@ import { compose, layout, position } from 'styled-system';
* We export all the themes from the index file and the VSCode Dark Theme.
* The default VSCode dark theme is not part of the index file, so we need
* to import it separately and re-export with the rest of the themes.
*
* **Note**: For ESM + one-page we swap out for `dist/esm/**` files instead.
*/
// @ts-ignore
import vsDark from 'react-syntax-highlighter/dist/cjs/styles/prism/vs-dark.js';
// @ts-ignore
import * as allThemes from 'react-syntax-highlighter/dist/cjs/styles/prism/index.js';
export const codePaneThemes = { vsDark: vsDark.default, ...allThemes };

// Allow for rewriting of RSH `/cjs/` to `/esm/` by flexibly falling back to object
// if `.default` isn't available.
const vsDarkTheme = vsDark.default || vsDark;

export const codePaneThemes = { vsDark: vsDarkTheme, ...allThemes };

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

Expand Down

1 comment on commit e0a9811

@vercel
Copy link

@vercel vercel bot commented on e0a9811 May 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.