diff --git a/packages/repl-sdk/example/samples.js b/packages/repl-sdk/example/samples.js
index c305c43c56..0028905f2d 100644
--- a/packages/repl-sdk/example/samples.js
+++ b/packages/repl-sdk/example/samples.js
@@ -46,6 +46,18 @@ export const svelte = `
Hello {name}!
`.trim();
+export const gjs = `
+const greeting = 'hello there';
+
+
+ {{greeting}}
+
+
+
+`.trim();
+
export const md = `
# Markdown
diff --git a/packages/repl-sdk/src/compilers.js b/packages/repl-sdk/src/compilers.js
index 106aede10b..15d2e17a94 100644
--- a/packages/repl-sdk/src/compilers.js
+++ b/packages/repl-sdk/src/compilers.js
@@ -154,10 +154,17 @@ export const compilers = {
},
},
md: {
- compiler: async (config = {}, api) => {
+ compiler: async (...args) => {
const markdown = await import('./compilers/markdown.js');
- return markdown.compiler(config, api);
+ return markdown.compiler(...args);
+ },
+ },
+ gjs: {
+ compiler: async (...args) => {
+ const gjs = await import('./compilers/glimmer-js.js');
+
+ return gjs.compiler(...args);
},
},
};
diff --git a/packages/repl-sdk/src/compilers/glimmer-js.js b/packages/repl-sdk/src/compilers/glimmer-js.js
new file mode 100644
index 0000000000..1bab3f4996
--- /dev/null
+++ b/packages/repl-sdk/src/compilers/glimmer-js.js
@@ -0,0 +1,64 @@
+import { esmsh, jsdelivr } from './cdn.js';
+
+/**
+ * @param {import('../types.ts').ResolvedCompilerOptions} config
+ * @param {import('../types.ts').PublicMethods} api
+ */
+export async function compiler(config = {}, api) {
+ const versions = config.versions || {};
+
+ const [babel, templatePlugin, { default: templateCompiler }, { Preprocessor }] =
+ await esmsh.importAll(versions, [
+ '@babel/standalone',
+ 'babel-plugin-ember-template-compilation',
+ 'ember-source/dist/ember-template-compiler.js',
+ 'content-tag',
+ // Failed to load (will need to PR for browser support),
+ // so we have to use babel's decorator transforms,
+ // which ... aren't great.
+ // They force over-transforming of classes.
+ // 'decorator-transforms',
+ ]);
+
+ const preprocessor = new Preprocessor();
+
+ return {
+ compile: async (text) => {
+ let preprocessed = preprocessor.process(text, 'dynamic-repl.js');
+ let transformed = await transform({ babel, templatePlugin, templateCompiler }, preprocessed);
+
+ return transformed.code;
+ },
+ render: async (element, compiled, extra, compiler) => {
+ element.innerHTML = compiled;
+ },
+ };
+}
+
+async function transform({ babel, templatePlugin, templateCompiler }, text) {
+ console.log(templateCompiler, templatePlugin);
+ return babel.transform(text, {
+ filename: `dynamic-repl.js`,
+ plugins: [
+ [
+ templatePlugin,
+ {
+ compiler: templateCompiler,
+ },
+ ],
+ // [babel.availablePlugins['proposal-decorators'], { legacy: true }],
+ // [babel.availablePlugins['proposal-class-properties']],
+ ],
+ presets: [
+ [
+ babel.availablePresets['env'],
+ {
+ // false -- keeps ES Modules
+ modules: false,
+ targets: { esmodules: true },
+ forceAllTransforms: false,
+ },
+ ],
+ ],
+ });
+}
diff --git a/packages/repl-sdk/src/types.ts b/packages/repl-sdk/src/types.ts
index 9b5cbfe2be..2b39f76651 100644
--- a/packages/repl-sdk/src/types.ts
+++ b/packages/repl-sdk/src/types.ts
@@ -1,20 +1,16 @@
interface PublicMethods {
- compile: (
- format: string,
- text: string,
- options?: {
- flavor?: string;
- fileName?: string;
- }
- ) => Promise;
+ compile: (format: string, text: string, options?: {
+ flavor?: string;
+ fileName?: string;
+ }) => Promise
- optionsFor: (format: string, flavor?: string) => Omit;
+ optionsFor: (format: string, flavor?: string) => Omit
}
export interface ResolvedCompilerOptions {
- importMap?: { [importPath: string]: string };
- resolve?: { [importPath: string]: unknown };
+ importMap: { [importPath: string]: string; };
+ resolve: { [importPath: string]: unknown; };
needsLiveMeta?: boolean;
- versions?: { [packageName: string]: string };
+ versions: { [packageName: string]: string };
}
export interface CompilerConfig {
@@ -86,7 +82,7 @@ export interface CompilerConfig {
element: HTMLElement,
defaultExport: any,
extras: { compiled: string } & Record,
- compiler: PublicMethods
+ compiler: PublicMethods,
) => void;
}>;
}
@@ -97,26 +93,26 @@ export interface Options {
*
* Thehse will take precedence over the default CDN fallback.
*/
- importMap?: { [importPath: string]: string };
+ importMap?: { [importPath: string]: string; };
/**
* Map of pre-resolved JS values to use as the import map
* These could assume the role of runtime virtual modules.
*
* These will take precedence over the importMap, and implicit CDN fallback.
*/
- resolve?: { [importPath: string]: unknown };
+ resolve?: { [importPath: string]: unknown; }
/**
- * Specifies which vesions of dependencies to when pulling from a CDN.
- * Defaults to latest.
- */
+ * Specifies which vesions of dependencies to when pulling from a CDN.
+ * Defaults to latest.
+ */
versions?: { [packageName: string]: string };
formats: {
[fileExtension: string]:
- | CompilerConfig
- | {
- [flavor: string]: CompilerConfig;
- };
+ | CompilerConfig
+ | {
+ [flavor: string]: CompilerConfig;
+ };
};
}
diff --git a/packages/repl-sdk/tests-self/tests/ember.test.ts b/packages/repl-sdk/tests-self/tests/ember.test.ts
new file mode 100644
index 0000000000..3d52e0dafd
--- /dev/null
+++ b/packages/repl-sdk/tests-self/tests/ember.test.ts
@@ -0,0 +1,64 @@
+import { Compiler } from 'repl-sdk';
+import { describe, expect, test } from 'vitest';
+
+describe('ember', () => {
+ describe('gjs', () => {
+ test('template-only', async () => {
+ let compiler = new Compiler();
+ let element = await compiler.compile(
+ 'gjs',
+ `
+ const name = 'world';
+
+
+ Hello {{name}}!
+
+
+
+ `
+ );
+
+ // getComputedStyle doesn't work without the element existing in the document
+ document.body.appendChild(element);
+
+ let h1 = element.querySelector('h1');
+
+ expect(h1).toBeTruthy();
+ expect(h1?.textContent).toContain('Hello world!');
+ expect(window.getComputedStyle(h1!).color).toBe('rgb(255, 0, 0)');
+ });
+
+ test('class component', async () => {
+ let compiler = new Compiler();
+ let element = await compiler.compile(
+ 'gjs',
+ `
+ import Component from '@glimmer/component';
+
+ export default class Demo extends Component {
+ name = 'world';
+
+
+ Hello {{this.name}}!
+
+
+
+ }
+ `
+ );
+
+ // getComputedStyle doesn't work without the element existing in the document
+ document.body.appendChild(element);
+
+ let h1 = element.querySelector('h1');
+
+ expect(h1).toBeTruthy();
+ expect(h1?.textContent).toContain('Hello world!');
+ expect(window.getComputedStyle(h1!).color).toBe('rgb(255, 0, 0)');
+ });
+ });
+});