ember-repl has moved to a new home
This is to help more quickly iterate on implementation as the limber/glimdown is a primary consumer.
Tools for easily creating your own Ember Playground / REPL and/or Interactive StyleGuide for your design system.
This package will include all available dev-time dependencies provided by
ember + glimmer as well as @babel/standalone
.
Your payload will be affected and Embroider is recommended
with maximum strictness enabled so automatic bundle splitting occurs to help
your app's initial time-to-interactive/etc stats.
ember install ember-repl
compileJS
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { compileJS } from 'ember-repl';
export class Renderer extends Component {
@tracked compileResult;
constructor(...args) {
super(...args);
compileJS('...').then((compileResult) => this.compileResult = compileResult);
}
}
{{#if this.compileResult.component}}
<this.compileResult.component />
{{/if}}
compileHBS
import Component from '@glimmer/component';
import { compileHBS } from 'ember-repl';
export class Renderer extends Component {
compileResult = compileHBS(this.args.input);
}
<this.compileResult.component />
ember-repl
is strict-mode only, so any component that you want to invoke
needs to be passed to the scope option of compileHBS
or compileJS
.
Following code is assuming that right next to our Renderer
component
there is a component named Bar
.
import Component from '@glimmer/component';
import { compileHBS } from 'ember-repl';
import BarComponent from './bar'
export class Renderer extends Component {
compileResult = compileHBS(
'<Bar />',
{
scope: {
Bar: BarComponent
}
}
);
}
When writing components / demos / examples using this library, you must use template-strict mode. Strict mode isn't available by default in proper ember apps yet. The main difference in strict mode is that all globals must be imported.
Example of a template-only component that only provides a button:
import { on } from '@ember/modifier';
import { fn, hash } from '@ember/helper';
<template>
<button {{on 'click' (fn @callback (hash a=1 b=2))}}>...</button>
</template>
For a list of all the imports for things that are global in loose mode, view the Strict Mode RFC
compileJS
and compileHBS
may result an an error.
To handle this, you'll want to make sure that rendering the component
output is
guarded by either:
component
(which is undefined if error
is present)error
(which is undefined if compilation was successful)Depending on your desired UI/UX, how the async build of updates to input is conveyed
may vary and is not provided by this library.
Here is an example of a way that someone could handle rendering with compileJS
:
export default class AwaitBuild extends Component {
@tracked component;
@tracked error;
constructor(...args) {
super(...args);
compileJS(args.inputText)
.then(({ component, error }) => {
this.component = component;
this.error = error;
})
.catch(error => this.error = error);
}
get isPending() {
return !this.component';
}
}
{{#if this.error}}
Error: {{this.error}}
{{else if this.isPending}}
Building...
{{else}}
<this.component />
{{/if}}
A Note on Capabilities
This library currently uses a CommonJS technique for modules, but as browser-support
permits, this library will eventually switch to using a web-worker with an import-map
for lightning fast, eval
-free REPLing. (But the same security caution below would
still apply)
compileJS
: async, returns compileResult
- compiles a single JS file
uses the syntax from ember-template-importscompileHBS
: returns compileResult
- compiles a template-only component with no dependenciesinvocationOf
: string
- converts hyphenated text to an <AngleBracketInvocation />
nameFor
: string
- generates a component-safe GUID-like derivation from codeinterface CompileResult {
// invokable from templates
component?: unknown;
// if there is a compilation error, this will be non-falsey
error?: Error;
// the name assigned to the input text via UUIDv5
name: string;
}
If you are using the Webpack
packager, you will need these settings:
packagerOptions: {
webpackConfig: {
node: {
global: false,
__filename: true,
__dirname: true,
},
resolve: {
fallback: {
path: 'path-browserify',
},
},
},
},
If you are using ember-repl to showcase a styleguide and have maximum strictness enabled in embroider,
you'll need to manually (or programatically) list out each of the components you want to force to be
included in the build output using the buildComponentMap
function in your ember-cli-build.js
.
For example:
const { Webpack } = require('@embroider/webpack');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
return require('@embroider/compat').compatBuild(app, Webpack, {
extraPublicTrees: [
require('ember-repl').buildComponentMap([
'limber/components/limber/menu',
'limber/components/limber/header',
'limber/components/external-link',
'limber/components/popper-j-s',
'ember-repl',
]),
],
// ...
});
this emits an /ember-repl/component-map.js
file in your public tree,
which can then be await import
ed and used via:
let { COMPONENT_MAP } = await import('/ember-repl/component-map.js');
let { component, error, name } = await compileJS(code, COMPONENT_MAP);
Many developers know that evaluating runnable user input is a huge security risk. To mitigate risk, this library should not be used in an environment that has access to sensitive data. Additionally, end-users of this library (users of the consuming app) should be made aware of the risk so that they themselves do not paste foreign / unrecognized / untrusted code into the REPL.
This library itself will stay as up to date as possible, and if there are any security concerns, please email security [at] nullvoxpopuli.com
See the Contributing guide for details.
This project is licensed under the MIT License.