repl-sdk 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +15 -0
- package/declarations/index.d.ts +73 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
- package/src/index.d.ts +10 -0
- package/src/index.js +163 -0
- package/src/types.ts +62 -0
- package/src/utils.js +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 NullVoxPopuli
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# REPL SDK
|
|
2
|
+
|
|
3
|
+
A Runtime compiler for anything that you could want to build a lighting fast REPL with.
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Uses [es-module-shims](https://github.com/guybedford/es-module-shims)
|
|
8
|
+
- Built in support for JavaScript, Mermaid, React, Vue, Svelte, Ember, Markdown
|
|
9
|
+
- On-Demand Runtime: only pay for what you compile for - the async APIs mean that your users only load what they need.
|
|
10
|
+
- Supports nested languages (for markdown)
|
|
11
|
+
- Add any additional compiler/renderer at any time -- the flexible API allows for new libraries/frameworks to be added easily
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
export declare class Compiler {
|
|
2
|
+
constructor(options?: Options);
|
|
3
|
+
|
|
4
|
+
compile(format: string, text: string): Promise<HTMLElement>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export declare const defaultFormats: Options['formats'];
|
|
8
|
+
|
|
9
|
+
export declare const defaults: Options;
|
|
10
|
+
|
|
11
|
+
declare interface Options {
|
|
12
|
+
formats: {
|
|
13
|
+
[fileExtension: string]: {
|
|
14
|
+
/**
|
|
15
|
+
* When using this file extension in markdown documents,
|
|
16
|
+
* should we only evaluate the code block if the "live"
|
|
17
|
+
* meta is attached to the codefence?
|
|
18
|
+
*
|
|
19
|
+
* If you don't use markdown-embedded rendering,
|
|
20
|
+
* you can ignore this option. Default behavior is "false",
|
|
21
|
+
* but it doesn't matter if you don't render markdown anyway.
|
|
22
|
+
*
|
|
23
|
+
* For example, with `needsLiveMeta: false`:
|
|
24
|
+
* \`\`\`js
|
|
25
|
+
* console.log('hello');
|
|
26
|
+
* \`\`\`
|
|
27
|
+
* will be evaluated and log to the console.
|
|
28
|
+
* However, with `needsLiveMeta: true`, the above snippet would not
|
|
29
|
+
* be evaluated. To evaluate a snippet with `needsLiveMeta: true`:
|
|
30
|
+
* \`\`\`js live
|
|
31
|
+
* console.log('hello');
|
|
32
|
+
* \`\`\`
|
|
33
|
+
*/
|
|
34
|
+
needsLiveMeta?: boolean;
|
|
35
|
+
compiler: () => Promise<{
|
|
36
|
+
/**
|
|
37
|
+
* Convert a string from "fileExtension" to standard JavaScript.
|
|
38
|
+
* This will be loaded as a module and then passed to the render method.
|
|
39
|
+
*/
|
|
40
|
+
compile: (text: string) => Promise<string>;
|
|
41
|
+
/**
|
|
42
|
+
* For the root of a node rendered for this compiler,
|
|
43
|
+
* how will this particular library / framework
|
|
44
|
+
* render in to the given element?
|
|
45
|
+
*
|
|
46
|
+
* Example:
|
|
47
|
+
* ```js
|
|
48
|
+
* {
|
|
49
|
+
* async compiler() {
|
|
50
|
+
* const { createRoot } = await import('react-dom/client');
|
|
51
|
+
*
|
|
52
|
+
* return {
|
|
53
|
+
* render(element, defaultExport) {
|
|
54
|
+
* const root = createRoot(element);
|
|
55
|
+
*
|
|
56
|
+
* root.render(defaultExport);
|
|
57
|
+
* },
|
|
58
|
+
* // ...
|
|
59
|
+
* }
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @param {HTMLElement} the element to render in to, this is provided by repl-sdk.
|
|
65
|
+
* @param {any} the default export from the compiled module.
|
|
66
|
+
*/
|
|
67
|
+
render: (element: HTMLElement, defaultExport: any) => void;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
function assert(message, test) {
|
|
2
|
+
if (!test) {
|
|
3
|
+
throw new Error(message);
|
|
4
|
+
}
|
|
5
|
+
}
|
|
6
|
+
assert(`There is no document. repl-sdk is meant to be ran in a browser`, globalThis.document);
|
|
7
|
+
const defaultFormats = {
|
|
8
|
+
mermaid: {
|
|
9
|
+
compiler: async () => {
|
|
10
|
+
return {
|
|
11
|
+
compile: async (text) => {
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const defaults = {
|
|
18
|
+
formats: defaultFormats
|
|
19
|
+
};
|
|
20
|
+
const secret = Symbol.for("__repl-sdk__compiler__");
|
|
21
|
+
assert(
|
|
22
|
+
`There is already an instance of repl-sdk, and there can only be one. Make sure that your dependency graph is correct.`,
|
|
23
|
+
!globalThis[secret]
|
|
24
|
+
);
|
|
25
|
+
class Compiler {
|
|
26
|
+
/** @type {Options} */
|
|
27
|
+
#options;
|
|
28
|
+
/**
|
|
29
|
+
* Options may be passed to the compiler to add to its behavior.
|
|
30
|
+
*/
|
|
31
|
+
constructor(options = defaults) {
|
|
32
|
+
this.#options = options;
|
|
33
|
+
globalThis[secret] = this;
|
|
34
|
+
globalThis.window.esmsInitOptions = {
|
|
35
|
+
shimMode: true,
|
|
36
|
+
skip: `https://esm.sh/`,
|
|
37
|
+
revokeBlobURLs: true,
|
|
38
|
+
// default false
|
|
39
|
+
// Permit overrides to import maps
|
|
40
|
+
mapOverrides: true,
|
|
41
|
+
// default false
|
|
42
|
+
// Hook all module resolutions
|
|
43
|
+
resolve: (id, parentUrl, resolve) => {
|
|
44
|
+
if (id.startsWith("blob:")) return id;
|
|
45
|
+
if (id.startsWith("https://")) return id;
|
|
46
|
+
console.log("Resolving", id);
|
|
47
|
+
return `https://esm.sh/*${id}`;
|
|
48
|
+
},
|
|
49
|
+
// Hook source fetch function
|
|
50
|
+
fetch: async (url, options2) => {
|
|
51
|
+
console.log(`Fetching`, url);
|
|
52
|
+
if (url.endsWith("example.js")) {
|
|
53
|
+
const transformed = `export const js = 'transformed'`;
|
|
54
|
+
return new Response(new Blob([transformed], { type: "application/javascript" }));
|
|
55
|
+
}
|
|
56
|
+
const response = await fetch(url, options2);
|
|
57
|
+
return response;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* @param {string} format
|
|
63
|
+
* @param {string} text
|
|
64
|
+
*/
|
|
65
|
+
async compile(format, text) {
|
|
66
|
+
await import("es-module-shims");
|
|
67
|
+
const compiler = await this.#getCompiler(format);
|
|
68
|
+
const compiledText = await compiler.compile(text);
|
|
69
|
+
const asBlobUrl = textToBlobUrl(compiledText);
|
|
70
|
+
const { default: defaultExport } = await importShim(
|
|
71
|
+
/* @vite-ignore */
|
|
72
|
+
asBlobUrl
|
|
73
|
+
);
|
|
74
|
+
return this.#render(compiler, defaultExport);
|
|
75
|
+
}
|
|
76
|
+
async #getCompiler(format) {
|
|
77
|
+
const config = this.#options.formats[format];
|
|
78
|
+
assert(
|
|
79
|
+
`${format} is not a configured format. The currently configured formats are ${Object.keys(this.#options.formats).join(", ")}`,
|
|
80
|
+
config
|
|
81
|
+
);
|
|
82
|
+
const compiler = await config.compiler();
|
|
83
|
+
return compiler;
|
|
84
|
+
}
|
|
85
|
+
async #render(compiler, whatToRender) {
|
|
86
|
+
const div = this.#createDiv();
|
|
87
|
+
await compiler.render(div, whatToRender);
|
|
88
|
+
return div;
|
|
89
|
+
}
|
|
90
|
+
#createDiv() {
|
|
91
|
+
let div = document.createElement("div");
|
|
92
|
+
div.setAttribute("data-repl-output", "");
|
|
93
|
+
div.id = "some-random-string";
|
|
94
|
+
return div;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function textToBlobUrl(text) {
|
|
98
|
+
const blob = new Blob([text], { type: "text/javascript" });
|
|
99
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
100
|
+
return blobUrl;
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
Compiler,
|
|
104
|
+
defaultFormats,
|
|
105
|
+
defaults
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils.js","../src/index.js"],"sourcesContent":["/**\n * @param {string} message\n * @param {unknown} test\n * @asserts test\n */\nexport function assert(message, test) {\n if (!test) {\n throw new Error(message);\n }\n}\n","/**\n * @typedef {import(\"./types.ts\").Options} Options\n */\nimport { assert } from './utils.js';\n\nassert(`There is no document. repl-sdk is meant to be ran in a browser`, globalThis.document);\n\nexport const defaultFormats = {\n mermaid: {\n compiler: async () => {\n return {\n compile: async (text) => {},\n };\n },\n },\n};\n\nexport const defaults = {\n formats: defaultFormats,\n};\n\nconst secret = Symbol.for('__repl-sdk__compiler__');\n\nassert(\n `There is already an instance of repl-sdk, and there can only be one. Make sure that your dependency graph is correct.`,\n !globalThis[secret]\n);\n\nexport class Compiler {\n /** @type {Options} */\n #options;\n\n /**\n * Options may be passed to the compiler to add to its behavior.\n */\n constructor(options = defaults) {\n this.#options = options;\n\n globalThis[secret] = this;\n globalThis.window.esmsInitOptions = {\n shimMode: true,\n skip: `https://esm.sh/`,\n revokeBlobURLs: true, // default false\n // Permit overrides to import maps\n mapOverrides: true, // default false\n // Hook all module resolutions\n resolve: (id, parentUrl, resolve) => {\n if (id.startsWith('blob:')) return id;\n if (id.startsWith('https://')) return id;\n\n console.log('Resolving', id);\n /**\n * TODO: locally defined scope\n * vs\n * proxy to esm.sh\n */\n\n /**\n * For esm.sh, we want all imports to declare they don't want\n * their dependencies bundled. We want to have every import go\n * through this resolve/fetch combo of things so we have a chance\n * to compile if we need to.\n */\n return `https://esm.sh/*${id}`;\n },\n // Hook source fetch function\n fetch: async (url, options) => {\n console.log(`Fetching`, url);\n /**\n * Do transformations here based on file extension\n */\n if (url.endsWith('example.js')) {\n const transformed = `export const js = 'transformed'`;\n return new Response(new Blob([transformed], { type: 'application/javascript' }));\n }\n\n const response = await fetch(url, options);\n\n // if (response.url.endsWith('.ts')) {\n // const source = await response.body();\n // const transformed = tsCompile(source);\n // return new Response(new Blob([transformed], { type: 'application/javascript' }));\n // }\n return response;\n },\n };\n // addShim();\n }\n\n /**\n * @param {string} format\n * @param {string} text\n */\n async compile(format, text) {\n await import('es-module-shims');\n\n const compiler = await this.#getCompiler(format);\n\n // TODO: pass this through es-module-shims\n // for getting the actual module back\n const compiledText = await compiler.compile(text);\n\n const asBlobUrl = textToBlobUrl(compiledText);\n\n const { default: defaultExport } = await importShim(/* @vite-ignore */ asBlobUrl);\n\n return this.#render(compiler, defaultExport);\n }\n\n async #getCompiler(format) {\n const config = this.#options.formats[format];\n\n assert(\n `${format} is not a configured format. ` +\n `The currently configured formats are ${Object.keys(this.#options.formats).join(', ')}`,\n config\n );\n\n const compiler = await config.compiler();\n\n return compiler;\n }\n\n async #render(compiler, whatToRender) {\n const div = this.#createDiv();\n\n await compiler.render(div, whatToRender);\n\n return div;\n }\n\n #createDiv() {\n let div = document.createElement('div');\n div.setAttribute('data-repl-output', '');\n div.id = 'some-random-string';\n return div;\n }\n}\n\nfunction addShim() {\n let url = 'https://ga.jspm.io/npm:es-module-shims@1.10.0/dist/es-module-shims.js';\n\n if (document.querySelector('script[src=\"${url}\"]')) {\n // Shim is already present\n return;\n }\n\n let script = document.createElement('script');\n // script.setAttribute('async', '');\n script.setAttribute('src', `<script async src=\"${url}\"></script>`);\n\n document.head.appendChild(script);\n}\n\n/**\n * @param {string} text\n */\nfunction textToBlobUrl(text) {\n const blob = new Blob([text], { type: 'text/javascript' });\n\n const blobUrl = URL.createObjectURL(blob);\n return blobUrl;\n}\n"],"names":["options"],"mappings":"AAKO,SAAS,OAAO,SAAS,MAAM;AACpC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,MAAM,OAAO;AAAA,EACxB;AACH;ACJA,OAAO,kEAAkE,WAAW,QAAQ;AAEhF,MAAC,iBAAiB;AAAA,EAC5B,SAAS;AAAA,IACP,UAAU,YAAY;AACpB,aAAO;AAAA,QACL,SAAS,OAAO,SAAS;AAAA,QAAE;AAAA,MACnC;AAAA,IACK;AAAA,EACF;AACH;AAEY,MAAC,WAAW;AAAA,EACtB,SAAS;AACX;AAEA,MAAM,SAAS,OAAO,IAAI,wBAAwB;AAElD;AAAA,EACE;AAAA,EACA,CAAC,WAAW,MAAM;AACpB;AAEO,MAAM,SAAS;AAAA;AAAA,EAEpB;AAAA;AAAA;AAAA;AAAA,EAKA,YAAY,UAAU,UAAU;AAC9B,SAAK,WAAW;AAEhB,eAAW,MAAM,IAAI;AACrB,eAAW,OAAO,kBAAkB;AAAA,MAClC,UAAU;AAAA,MACV,MAAM;AAAA,MACN,gBAAgB;AAAA;AAAA;AAAA,MAEhB,cAAc;AAAA;AAAA;AAAA,MAEd,SAAS,CAAC,IAAI,WAAW,YAAY;AACnC,YAAI,GAAG,WAAW,OAAO,EAAG,QAAO;AACnC,YAAI,GAAG,WAAW,UAAU,EAAG,QAAO;AAEtC,gBAAQ,IAAI,aAAa,EAAE;AAa3B,eAAO,mBAAmB,EAAE;AAAA,MAC7B;AAAA;AAAA,MAED,OAAO,OAAO,KAAKA,aAAY;AAC7B,gBAAQ,IAAI,YAAY,GAAG;AAI3B,YAAI,IAAI,SAAS,YAAY,GAAG;AAC9B,gBAAM,cAAc;AACpB,iBAAO,IAAI,SAAS,IAAI,KAAK,CAAC,WAAW,GAAG,EAAE,MAAM,yBAA0B,CAAA,CAAC;AAAA,QAChF;AAED,cAAM,WAAW,MAAM,MAAM,KAAKA,QAAO;AAOzC,eAAO;AAAA,MACR;AAAA,IACP;AAAA,EAEG;AAAA;AAAA;AAAA;AAAA;AAAA,EAMD,MAAM,QAAQ,QAAQ,MAAM;AAC1B,UAAM,OAAO,iBAAiB;AAE9B,UAAM,WAAW,MAAM,KAAK,aAAa,MAAM;AAI/C,UAAM,eAAe,MAAM,SAAS,QAAQ,IAAI;AAEhD,UAAM,YAAY,cAAc,YAAY;AAE5C,UAAM,EAAE,SAAS,cAAa,IAAK,MAAM;AAAA;AAAA,MAA8B;AAAA,IAAS;AAEhF,WAAO,KAAK,QAAQ,UAAU,aAAa;AAAA,EAC5C;AAAA,EAED,MAAM,aAAa,QAAQ;AACzB,UAAM,SAAS,KAAK,SAAS,QAAQ,MAAM;AAE3C;AAAA,MACE,GAAG,MAAM,qEACiC,OAAO,KAAK,KAAK,SAAS,OAAO,EAAE,KAAK,IAAI,CAAC;AAAA,MACvF;AAAA,IACN;AAEI,UAAM,WAAW,MAAM,OAAO;AAE9B,WAAO;AAAA,EACR;AAAA,EAED,MAAM,QAAQ,UAAU,cAAc;AACpC,UAAM,MAAM,KAAK;AAEjB,UAAM,SAAS,OAAO,KAAK,YAAY;AAEvC,WAAO;AAAA,EACR;AAAA,EAED,aAAa;AACX,QAAI,MAAM,SAAS,cAAc,KAAK;AACtC,QAAI,aAAa,oBAAoB,EAAE;AACvC,QAAI,KAAK;AACT,WAAO;AAAA,EACR;AACH;AAoBA,SAAS,cAAc,MAAM;AAC3B,QAAM,OAAO,IAAI,KAAK,CAAC,IAAI,GAAG,EAAE,MAAM,kBAAiB,CAAE;AAEzD,QAAM,UAAU,IAAI,gBAAgB,IAAI;AACxC,SAAO;AACT;"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "repl-sdk",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./declarations/index.d.ts",
|
|
8
|
+
"default": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"repl",
|
|
13
|
+
"playground",
|
|
14
|
+
"SDK",
|
|
15
|
+
"render",
|
|
16
|
+
"play",
|
|
17
|
+
"javascript",
|
|
18
|
+
"live",
|
|
19
|
+
"interactive"
|
|
20
|
+
],
|
|
21
|
+
"author": "NullVoxPopuli",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"declarations",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@tsconfig/ember": "^3.0.7",
|
|
30
|
+
"prettier": "^3.2.5",
|
|
31
|
+
"typescript": "^5.4.5",
|
|
32
|
+
"vite": "^5.2.11",
|
|
33
|
+
"vite-plugin-dts": "4.0.0-beta.1"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"es-module-shims": "^1.10.0"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"start": "vite build --watch"
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Options } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export const defaultFormats: Options['formats'];
|
|
4
|
+
export const defaults: Options;
|
|
5
|
+
|
|
6
|
+
export class Compiler {
|
|
7
|
+
constructor(options?: Options);
|
|
8
|
+
|
|
9
|
+
compile(format: string, text: string): Promise<HTMLElement>;
|
|
10
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("./types.ts").Options} Options
|
|
3
|
+
*/
|
|
4
|
+
import { assert } from './utils.js';
|
|
5
|
+
|
|
6
|
+
assert(`There is no document. repl-sdk is meant to be ran in a browser`, globalThis.document);
|
|
7
|
+
|
|
8
|
+
export const defaultFormats = {
|
|
9
|
+
mermaid: {
|
|
10
|
+
compiler: async () => {
|
|
11
|
+
return {
|
|
12
|
+
compile: async (text) => {},
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const defaults = {
|
|
19
|
+
formats: defaultFormats,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const secret = Symbol.for('__repl-sdk__compiler__');
|
|
23
|
+
|
|
24
|
+
assert(
|
|
25
|
+
`There is already an instance of repl-sdk, and there can only be one. Make sure that your dependency graph is correct.`,
|
|
26
|
+
!globalThis[secret]
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
export class Compiler {
|
|
30
|
+
/** @type {Options} */
|
|
31
|
+
#options;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Options may be passed to the compiler to add to its behavior.
|
|
35
|
+
*/
|
|
36
|
+
constructor(options = defaults) {
|
|
37
|
+
this.#options = options;
|
|
38
|
+
|
|
39
|
+
globalThis[secret] = this;
|
|
40
|
+
globalThis.window.esmsInitOptions = {
|
|
41
|
+
shimMode: true,
|
|
42
|
+
skip: `https://esm.sh/`,
|
|
43
|
+
revokeBlobURLs: true, // default false
|
|
44
|
+
// Permit overrides to import maps
|
|
45
|
+
mapOverrides: true, // default false
|
|
46
|
+
// Hook all module resolutions
|
|
47
|
+
resolve: (id, parentUrl, resolve) => {
|
|
48
|
+
if (id.startsWith('blob:')) return id;
|
|
49
|
+
if (id.startsWith('https://')) return id;
|
|
50
|
+
|
|
51
|
+
console.log('Resolving', id);
|
|
52
|
+
/**
|
|
53
|
+
* TODO: locally defined scope
|
|
54
|
+
* vs
|
|
55
|
+
* proxy to esm.sh
|
|
56
|
+
*/
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* For esm.sh, we want all imports to declare they don't want
|
|
60
|
+
* their dependencies bundled. We want to have every import go
|
|
61
|
+
* through this resolve/fetch combo of things so we have a chance
|
|
62
|
+
* to compile if we need to.
|
|
63
|
+
*/
|
|
64
|
+
return `https://esm.sh/*${id}`;
|
|
65
|
+
},
|
|
66
|
+
// Hook source fetch function
|
|
67
|
+
fetch: async (url, options) => {
|
|
68
|
+
console.log(`Fetching`, url);
|
|
69
|
+
/**
|
|
70
|
+
* Do transformations here based on file extension
|
|
71
|
+
*/
|
|
72
|
+
if (url.endsWith('example.js')) {
|
|
73
|
+
const transformed = `export const js = 'transformed'`;
|
|
74
|
+
return new Response(new Blob([transformed], { type: 'application/javascript' }));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const response = await fetch(url, options);
|
|
78
|
+
|
|
79
|
+
// if (response.url.endsWith('.ts')) {
|
|
80
|
+
// const source = await response.body();
|
|
81
|
+
// const transformed = tsCompile(source);
|
|
82
|
+
// return new Response(new Blob([transformed], { type: 'application/javascript' }));
|
|
83
|
+
// }
|
|
84
|
+
return response;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
// addShim();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} format
|
|
92
|
+
* @param {string} text
|
|
93
|
+
*/
|
|
94
|
+
async compile(format, text) {
|
|
95
|
+
await import('es-module-shims');
|
|
96
|
+
|
|
97
|
+
const compiler = await this.#getCompiler(format);
|
|
98
|
+
|
|
99
|
+
// TODO: pass this through es-module-shims
|
|
100
|
+
// for getting the actual module back
|
|
101
|
+
const compiledText = await compiler.compile(text);
|
|
102
|
+
|
|
103
|
+
const asBlobUrl = textToBlobUrl(compiledText);
|
|
104
|
+
|
|
105
|
+
const { default: defaultExport } = await importShim(/* @vite-ignore */ asBlobUrl);
|
|
106
|
+
|
|
107
|
+
return this.#render(compiler, defaultExport);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async #getCompiler(format) {
|
|
111
|
+
const config = this.#options.formats[format];
|
|
112
|
+
|
|
113
|
+
assert(
|
|
114
|
+
`${format} is not a configured format. ` +
|
|
115
|
+
`The currently configured formats are ${Object.keys(this.#options.formats).join(', ')}`,
|
|
116
|
+
config
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const compiler = await config.compiler();
|
|
120
|
+
|
|
121
|
+
return compiler;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async #render(compiler, whatToRender) {
|
|
125
|
+
const div = this.#createDiv();
|
|
126
|
+
|
|
127
|
+
await compiler.render(div, whatToRender);
|
|
128
|
+
|
|
129
|
+
return div;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
#createDiv() {
|
|
133
|
+
let div = document.createElement('div');
|
|
134
|
+
div.setAttribute('data-repl-output', '');
|
|
135
|
+
div.id = 'some-random-string';
|
|
136
|
+
return div;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function addShim() {
|
|
141
|
+
let url = 'https://ga.jspm.io/npm:es-module-shims@1.10.0/dist/es-module-shims.js';
|
|
142
|
+
|
|
143
|
+
if (document.querySelector('script[src="${url}"]')) {
|
|
144
|
+
// Shim is already present
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let script = document.createElement('script');
|
|
149
|
+
// script.setAttribute('async', '');
|
|
150
|
+
script.setAttribute('src', `<script async src="${url}"></script>`);
|
|
151
|
+
|
|
152
|
+
document.head.appendChild(script);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @param {string} text
|
|
157
|
+
*/
|
|
158
|
+
function textToBlobUrl(text) {
|
|
159
|
+
const blob = new Blob([text], { type: 'text/javascript' });
|
|
160
|
+
|
|
161
|
+
const blobUrl = URL.createObjectURL(blob);
|
|
162
|
+
return blobUrl;
|
|
163
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface Options {
|
|
2
|
+
formats: {
|
|
3
|
+
[fileExtension: string]: {
|
|
4
|
+
/**
|
|
5
|
+
* When using this file extension in markdown documents,
|
|
6
|
+
* should we only evaluate the code block if the "live"
|
|
7
|
+
* meta is attached to the codefence?
|
|
8
|
+
*
|
|
9
|
+
* If you don't use markdown-embedded rendering,
|
|
10
|
+
* you can ignore this option. Default behavior is "false",
|
|
11
|
+
* but it doesn't matter if you don't render markdown anyway.
|
|
12
|
+
*
|
|
13
|
+
* For example, with `needsLiveMeta: false`:
|
|
14
|
+
* \`\`\`js
|
|
15
|
+
* console.log('hello');
|
|
16
|
+
* \`\`\`
|
|
17
|
+
* will be evaluated and log to the console.
|
|
18
|
+
* However, with `needsLiveMeta: true`, the above snippet would not
|
|
19
|
+
* be evaluated. To evaluate a snippet with `needsLiveMeta: true`:
|
|
20
|
+
* \`\`\`js live
|
|
21
|
+
* console.log('hello');
|
|
22
|
+
* \`\`\`
|
|
23
|
+
*/
|
|
24
|
+
needsLiveMeta?: boolean;
|
|
25
|
+
compiler: () => Promise<{
|
|
26
|
+
/**
|
|
27
|
+
* Convert a string from "fileExtension" to standard JavaScript.
|
|
28
|
+
* This will be loaded as a module and then passed to the render method.
|
|
29
|
+
*/
|
|
30
|
+
compile: (text: string) => Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* For the root of a node rendered for this compiler,
|
|
33
|
+
* how will this particular library / framework
|
|
34
|
+
* render in to the given element?
|
|
35
|
+
*
|
|
36
|
+
* Example:
|
|
37
|
+
* ```js
|
|
38
|
+
* {
|
|
39
|
+
* async compiler() {
|
|
40
|
+
* const { createRoot } = await import('react-dom/client');
|
|
41
|
+
*
|
|
42
|
+
* return {
|
|
43
|
+
* render(element, defaultExport) {
|
|
44
|
+
* const root = createRoot(element);
|
|
45
|
+
*
|
|
46
|
+
* root.render(defaultExport);
|
|
47
|
+
* },
|
|
48
|
+
* // ...
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
* }
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @param {HTMLElement} the element to render in to, this is provided by repl-sdk.
|
|
55
|
+
* @param {any} the default export from the compiled module.
|
|
56
|
+
*/
|
|
57
|
+
render: (element: HTMLElement, defaultExport: any) => void;
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|