Search code examples
reactjsaureliacodesandbox

Codesandbox "Preview on edit" does not work for Aurelia but works with other frameworks and vanilla HTML


I'm using Codesandbox to create samples and in a new project we are using the framework Aurelia. I can get Aurelia running on Codesandbox but I can't get Preview on edit to work. I have tried to add a sandbox.config.json with Hard Reload on Change set to true but it does not help. It is not a deal breaker but when you are used to seeing your edits right away it is annoying. I think the problem is that the Template used is Static, however Codesandbox does not provide an Aurelia template. Has anyone solved this?

Aurelia code example, see file app.html:

https://codesandbox.io/s/n3yxrj9lwp

Vanilla HTML, see file index.html:

https://codesandbox.io/s/l73lnlvymq

React, see file index.tsx:

https://codesandbox.io/s/7w5yx8qmz1

Code:

index.html:

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Aurelia App</title>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
</head>

<body style='font-family:arial'>
  <script src='https://unpkg.com/[email protected]'></script>
    </script>
    <script>
        const aurelia = new au.Aurelia();
      aurelia
        .use
          .standardConfiguration()
          .developmentLogging();
      aurelia
        .start()
        .then(() => aurelia.setRoot('app.js', document.body))
        .catch(ex => {
          document.body.textContent = `Bootstrap error: ${ex.toString()}`;
        });
    </script>
</body>
</html>

app.js:

export class App {
  constructor() {
    this.message = "Aurelia Test";
  }
}

app.html:

<template>
  <div class="jumbotron mb-0"><h1>${message}</h1></div>
  <div class="d-flex">Preview does not work here 1</div>
</template>

Solution

  • Working example with Aurelia Framework 1.3.0:

    app.html

    <template>
      <h1>${message}</h1>
    </template>
    

    app.js

    export class App {
      message = 'Hello World!';
    }
    

    index.html

    <!doctype html>
    <html lang="en">
    
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <title>Aurelia App</title>
    </head>
    
    <body>
    </body>
    
    </html>
    

    main.js

    import { Aurelia } from "aurelia-framework";
    
    export async function configure(aurelia) {
      aurelia.use.standardConfiguration().developmentLogging();
    
      await aurelia
        .start()
        .then(a => a.setRoot("app", document.body))
        .catch(ex => {
          document.body.textContent = `Bootstrap error: ${ex}`;
        });
    }
    

    codesandbox.js

    import "aurelia-polyfills";
    import { initialize } from "aurelia-pal-browser";
    import { Aurelia } from "aurelia-framework";
    import { Origin } from "aurelia-metadata";
    import { Loader, TemplateRegistryEntry, LoaderPlugin } from "aurelia-loader";
    import { DOM, PLATFORM } from "aurelia-pal";
    import { join } from "aurelia-path";
    
    // IMPORTANT
    // The code in this file is not needed in a real Aurelia app.
    // It is only needed to configure CodeSandbox.io for Aurelia.
    // Aurelia application code begins in the src/main.ts file.
    // ORIGINAL AUTHOR: Bazyli Brzóska https://github.com/niieani
    
    class TextTemplateLoader {
      async loadTemplate(loader, entry) {
        const text = await loader.loadText(entry.address);
        entry.template = DOM.createTemplateFromMarkup(text);
      }
    }
    
    function ensureOriginOnExports(moduleExports, moduleId) {
      let target = moduleExports;
      let key;
      let exportedValue;
    
      if (!moduleExports) {
        return moduleExports;
      }
    
      if (target.__useDefault) {
        target = target.default;
      }
    
      Origin.set(target, new Origin(moduleId, "default"));
    
      if (typeof target === "object") {
        for (key in target) {
          exportedValue = target[key];
    
          if (typeof exportedValue === "function") {
            Origin.set(exportedValue, new Origin(moduleId, key));
          }
        }
      }
    
      return moduleExports;
    }
    
    async function getModule(moduleName) {
      // if (moduleName.includes("/")) {
      //   moduleName = join("src", moduleName);
      // }
    
      const hasJS = moduleName.endsWith(".js");
    
      try {
        return await import(`${moduleName}` /* webpackMode: 'eager' */);
      } catch (e) {}
    
      try {
        return await import(`./${moduleName}` /* webpackMode: 'eager' */);
      } catch (e) {}
    
      try {
        return await import(`${moduleName}/dist/amd/${moduleName}` /* webpackMode: 'eager' */);
      } catch (e) {}
    
      try {
        return await import(`./${moduleName}` /* webpackMode: 'eager' */);
      } catch (e) {}
    
      if (moduleName.includes("/")) {
        try {
          const [dep, ...path] = moduleName.split("/");
          return import(`${dep}/dist/amd/${path.join(
            "/"
          )}` /* webpackMode: 'eager' */);
        } catch (e) {}
      }
    
      if (!hasJS) {
        return await getModule(`${moduleName}.js`);
      }
    }
    
    class SandboxLoader extends Loader {
      moduleRegistry = Object.create(null);
      loaderPlugins = Object.create(null);
      modulesBeingLoaded = new Map();
    
      templateLoader: TextTemplateLoader;
    
      constructor() {
        super();
    
        this.useTemplateLoader(new TextTemplateLoader());
        this.addPlugin("template-registry-entry", {
          fetch: async moduleId => {
            const entry = this.getOrCreateTemplateRegistryEntry(moduleId);
            if (!entry.templateIsLoaded) {
              await this.templateLoader.loadTemplate(this, entry);
            }
            return entry;
          }
        });
      }
    
      async _import(address, defaultHMR = true) {
        const addressParts = address.split("!");
        const moduleId = addressParts.splice(addressParts.length - 1, 1)[0];
        const loaderPlugin = addressParts.length === 1 ? addressParts[0] : null;
    
        if (loaderPlugin) {
          const plugin = this.loaderPlugins[loaderPlugin];
          if (!plugin) {
            throw new Error(
              `Plugin ${loaderPlugin} is not registered in the loader.`
            );
          }
          return await plugin.fetch(moduleId);
        }
    
        const m = await getModule(moduleId);
        return m;
      }
    
      map(id, source) {}
    
      normalizeSync(moduleId, relativeTo) {
        return moduleId;
      }
    
      normalize(moduleId, relativeTo) {
        return Promise.resolve(moduleId);
      }
    
      useTemplateLoader(templateLoader) {
        this.templateLoader = templateLoader;
      }
    
      loadAllModules(ids) {
        return Promise.all(ids.map(id => this.loadModule(id)));
      }
    
      async loadModule(moduleId, defaultHMR = true) {
        let existing = this.moduleRegistry[moduleId];
        if (existing) {
          return existing;
        }
        let beingLoaded = this.modulesBeingLoaded.get(moduleId);
        if (beingLoaded) {
          return beingLoaded;
        }
        beingLoaded = this._import(moduleId, defaultHMR);
        this.modulesBeingLoaded.set(moduleId, beingLoaded);
        const moduleExports = await beingLoaded;
        this.moduleRegistry[moduleId] = ensureOriginOnExports(
          moduleExports,
          moduleId
        );
        this.modulesBeingLoaded.delete(moduleId);
        return moduleExports;
      }
    
      loadTemplate(url) {
        return this.loadModule(
          this.applyPluginToUrl(url, "template-registry-entry"),
          false
        );
      }
    
      async loadText(url) {
        const result = await this.loadModule(url, false);
        if (result.default && "string" == typeof result.default) {
          // we're dealing with a file loaded using the css-loader:
          return result.default;
        }
        return result;
      }
    
      applyPluginToUrl(url, pluginName) {
        return `${pluginName}!${url}`;
      }
    
      addPlugin(pluginName, implementation) {
        this.loaderPlugins[pluginName] = implementation;
      }
    }
    
    (async () => {
      try {
        initialize();
        const aurelia = new Aurelia(new SandboxLoader());
        await getModule("./main").then(m => m.configure(aurelia));
      } catch (ex) {
        console.error(ex);
        document.body.textContent = ex;
      }
    })();
    

    package.json

    {
      "name": "a-simple-component",
      "version": "1.0.0",
      "private": true,
      "keywords": [],
      "description": "An Aurelia application that shows how to build a simple component.",
      "main": "codesandbox.js",
      "dependencies": {
        "aurelia-event-aggregator": "1.0.1",
        "aurelia-framework": "1.3.0",
        "aurelia-history-browser": "1.2.0",
        "aurelia-loader": "1.0.0",
        "aurelia-logging": "1.5.0",
        "aurelia-logging-console": "1.0.0",
        "aurelia-metadata": "1.0.4",
        "aurelia-pal": "1.8.0",
        "aurelia-pal-browser": "1.8.0",
        "aurelia-polyfills": "1.3.0",
        "aurelia-router": "1.6.3",
        "aurelia-templating": "1.10.1",
        "aurelia-templating-binding": "1.5.2",
        "aurelia-templating-resources": "1.7.1",
        "aurelia-templating-router": "1.3.3"
      }
    }
    

    https://codesandbox.io/s/849oxmjm82

    Example with Aurelia Framework 1.0.7:

    https://codesandbox.io/s/4ql5qvml49