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'; + + +`.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'; + + + ` + ); + + // 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'; + + + } + ` + ); + + // 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)'); + }); + }); +});