NullVoxPopuli / ember-repl

Tools for building playgrounds and repls with and for ember. Useful for interactive styleguides, too
MIT License
13 stars 4 forks source link
ember ember-addon emberjs hacktoberfest interactive javascript playground repl

ember-repl

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.


npm version CI

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.

Compatibility

Installation

ember install ember-repl

Usage

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 />

Using existing components

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
      }
    }
  );
}

Modifiers and Helpers

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

Expecting Errors

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:

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)

API

Methods

Properties

interface 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;
}

Using in an app that uses Embroider

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 imported and used via:

let { COMPONENT_MAP } = await import('/ember-repl/component-map.js');

let { component, error, name } = await compileJS(code, COMPONENT_MAP);

Security

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

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.