Skip to content

Commit 17d793d

Browse files
committed
Fix issues with copydecks not being loadable in browsers / React
1 parent b47223f commit 17d793d

File tree

6 files changed

+88
-8
lines changed

6 files changed

+88
-8
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- [ ] Ensure all strings in `src/` are localised
44
- [ ] Consider renaming `explain()` to something like `friendlyExplain()`?
55
- [ ] Figure out where the built packages should live / be served from
6+
- [ ] Set up automated publishing through GitHub Actions
67
- [ ] BEM-style formatting for CSS classes?
78
- [ ] Open source?
89

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@raspberrypifoundation/python-friendly-error-messages",
3-
"version": "0.1.0",
3+
"version": "0.1.3",
44
"type": "module",
55
"sideEffects": false,
66
"exports": {
@@ -22,7 +22,7 @@
2222
"clean": "rimraf dist",
2323
"prebuild": "npm run clean",
2424
"build": "tsc -p tsconfig.json",
25-
"build:browser": "esbuild src/index.ts --bundle --format=esm --outfile=dist/index.browser.js",
25+
"build:browser": "esbuild src/index.browser.ts --bundle --format=esm --outfile=dist/index.browser.js --loader:.json=json",
2626
"build:assets": "mkdir -p dist/copydecks && cp -R copydecks/* dist/copydecks/",
2727
"build:all": "npm run build && npm run build:browser && npm run build:assets",
2828
"test": "vitest run",

src/index.browser.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type { Trace, ExplainOptions, ExplainResult, CopyDeck, AdapterFn } from "./types.js";
2+
export { loadCopydeck, registerAdapter, explain } from "./engine.js";
3+
export { skulptAdapter } from "./adapters/skulpt.js";
4+
export { pyodideAdapter } from "./adapters/pyodide.js";
5+
export { loadCopydeckFor } from "./loaders.browser.js";
6+

src/loaders.browser.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { loadCopydeck } from "./engine.js";
2+
import type { CopyDeck } from "./types.js";
3+
import enCopydeck from "../copydecks/en/copydeck.json";
4+
5+
// is this the best way to do this when we come to adding more languages / copydecks?
6+
const bundledCopydecks: Record<string, CopyDeck> = {
7+
en: enCopydeck as CopyDeck,
8+
};
9+
10+
type Options = { base?: string };
11+
12+
export async function loadCopydeckFor(locale = "en", opts: Options = {}): Promise<CopyDeck> {
13+
const tryLocales = [locale];
14+
const baseLang = locale.split("-")[0];
15+
if (baseLang !== locale) tryLocales.push(baseLang);
16+
if (!tryLocales.includes("en")) tryLocales.push("en");
17+
18+
// first try bundled copydecks (ie. available in browser bundle)
19+
for (const lang of tryLocales) {
20+
if (bundledCopydecks[lang]) {
21+
const deck = bundledCopydecks[lang];
22+
loadCopydeck(deck);
23+
return deck;
24+
}
25+
}
26+
27+
// fallback to fetch if base URL is provided (eg. additional copydecks for locales not bundled)
28+
if (opts.base) {
29+
const baseUrl = new URL(opts.base, window.location.origin);
30+
for (const lang of tryLocales) {
31+
try {
32+
const url = new URL(`${lang}/copydeck.json`, baseUrl).toString();
33+
const res = await fetch(url);
34+
if (res.ok) {
35+
const deck = (await res.json()) as CopyDeck;
36+
loadCopydeck(deck);
37+
return deck;
38+
}
39+
} catch {}
40+
}
41+
}
42+
43+
throw new Error(`No copydeck found for ${tryLocales.join(", ")}`);
44+
}
45+

src/loaders.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,34 @@ export async function loadCopydeckFor(locale = "en", opts: Options = {}): Promis
99
if (baseLang !== locale) tryLocales.push(baseLang);
1010
if (!tryLocales.includes("en")) tryLocales.push("en");
1111

12-
const baseUrl = opts.base
13-
? new URL(opts.base, window.location.origin)
14-
: new URL("./copydecks/", import.meta.url);
12+
let baseUrl: URL;
13+
if (opts.base) {
14+
baseUrl = new URL(opts.base, window.location.origin);
15+
} else {
16+
// try to resolve copydecks relative to package location
17+
// in bundled environments (webpack/vite), import.meta.url points to the bundle
18+
try {
19+
const metaUrl = import.meta.url;
20+
// construct path dynamically to avoid webpack static analysis
21+
const copydecksDir = 'copydecks';
22+
const separator = '/';
23+
const path = '.' + separator + copydecksDir + separator;
24+
25+
// check if this looks like a bundled file
26+
if (metaUrl.includes('index.browser.js') || metaUrl.includes('dist/')) {
27+
// in a bundled environment, copydecks are in dist/copydecks
28+
const bundleDir = metaUrl.substring(0, metaUrl.lastIndexOf('/'));
29+
baseUrl = new URL(path, bundleDir);
30+
} else {
31+
baseUrl = new URL(path, metaUrl);
32+
}
33+
} catch {
34+
throw new Error(
35+
'Unable to resolve copydecks path. Please provide the base URL: ' +
36+
'loadCopydeckFor(locale, { base: "/path/to/copydecks/" })'
37+
);
38+
}
39+
}
1540

1641
for (const lang of tryLocales) {
1742
try {
@@ -22,7 +47,7 @@ export async function loadCopydeckFor(locale = "en", opts: Options = {}): Promis
2247
loadCopydeck(deck);
2348
return deck;
2449
}
25-
} catch { /* try next */ }
50+
} catch {}
2651
}
2752
throw new Error(`No copydeck found for ${tryLocales.join(", ")}`);
2853
}

tsconfig.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
"moduleResolution": "Bundler",
77
"declaration": true,
88
"outDir": "dist",
9+
"rootDir": "src",
910
"strict": true,
1011
"skipLibCheck": true,
11-
"forceConsistentCasingInFileNames": true
12+
"forceConsistentCasingInFileNames": true,
13+
"resolveJsonModule": true
1214
},
13-
"include": ["src", "tests"]
15+
"include": ["src"],
16+
"exclude": ["tests", "node_modules", "dist", "src/**/*.browser.ts"]
1417
}

0 commit comments

Comments
 (0)