Start Aurelia2 from other host

I have some problems getting Aurelia2 to start in another host (SPFx). The context is that there is already a webpack configuration and it loads on to the page as is. To include Aurelia I updated the webpack configuration with below:

    var rule1 = { test:  /[/\\]src[/\\].+\.html$/i, use: '@aurelia/webpack-loader', exclude: /node_modules/} ;
    generatedConfiguration.module.rules.push(rule1)

    var rule2 = { test: /\.ts$/i, use: ['ts-loader', '@aurelia/webpack-loader'], exclude: /node_modules/  };
    generatedConfiguration.module.rules.push(rule2);

I also removed any previous rules for .html files that were in the webpack config file.

I have installed alpha.26 of aurelia.

In the current host there is a render function that sets the innerHTML like below:

public async render() {
    this.domElement.innerHTML = `<my-component></mycomponent>`;
}

I have added the startup code just below that to:

Aurelia.register(<any>MyComponent)
  .app(MyComponent)
  .start();

If I inline my view as below it works fine:

import { PartialCustomElementDefinition } from '@aurelia/runtime-html';
import {customElement} from 'aurelia';

@customElement(<PartialCustomElementDefinition>{
    template: '<template>Hello from the inline view<div class="message">${message}</div></template>',
    name: 'my-component'
  })
export class MyComponent
{
    public message = 'Hello World!';
}

But if I remove the @customElement attribute and try to get it to load my-component.html file instead. It does not get into the yellow branch because Aurelia does not think my component is a component.
image

And the end result is below error:
image

So I guess I have messed up the webpack config somehow for the html files. Or what could be wrong?

Hi @magnusdanielson! You cannot go wrong with the @customElement decorator. That’s how it actually works. The webpack plugin uses a convention to define the custom element behind the scene. So when in doubt, or your setup is “unconventional” you can always fall back on the decorator to explicitly define your custom elements. However, I can externalize the html to external .html file and import the content into the ts/js file (webpack will take care of it; you might need to fall back on the html-loader to bypass the Aurelia convention).

Having said that if you like to leverage the convention, can you please share a minimal reproduction in a GH repo? That will help immensely to find out the issue/bug (if any) as well as suggest a solution/workaround fto you.

1 Like

Ok, after some tests at least I got it working with

import html from "./mycomponent.html";
@customElement(<PartialCustomElementDefinition>{
    template: html,
    name: 'mycomponent'
  })
export class mycomponent
{
    public message = 'Hello World!';
}

But I would really like to use the normal Aurelia convention with just placing the corresponding html file next to the ts file.

I have a repo here: GitHub - magnusdanielson/spfxau2

You do need a SharePoint online site to test it. If you don’t have any I will provide it for you. There is a link in the read me on how to create your own developer tenant if you would like.

Thanks for all your help :slight_smile:

1 Like

I have updated the repo. The “only” files that I think are interesting are:
image

I could live with use the customElement on the top component, but it would be really nice to just have Aurelia do the rest on the inner components.

I think the problem is that aurelia/webpack-loader never gets used to load the html file.
Another thing that bugs me is the in the current setup the ts files gets compiled from the src folder to the lib folder.

I have spent some more time on this and the setup I have use webpack 4. Is that a problem?

I also looked at the setup that works for Aurelia1. Below works fine for Aurelia1, everything else is the same. The only changes I have made is in the gulp file that configures webpack. Another difference is also the start of application, but that goes without saying.

// Works for Aurelia1
generatedConfiguration.module.rules[0].issuer = {
      // only when the issuer is a .js/.ts file, so the loaders are not applied inside templates
      test: /\.[tj]s$/i,
    };

    var rule1 = { test: /\.css$/i,issuer: [{ test: /\.html$/i }], use: "css-loader"} ;
    generatedConfiguration.module.rules.push(rule1)

    var rule2 = { test: /\.ts$/i, use: "ts-loader" };
    generatedConfiguration.module.rules.push(rule2);
      
    generatedConfiguration.plugins.push(new AureliaPlugin(
    {
      aureliaApp: undefined
    }));

Conventions are working through an aurelia loader for TS files. Check that you’re using it in your webpack config

Conventions works by reading your file name, it can NOT break “my” from “component” in your “mycomponent.ts”.

You should use Aurelia convention naming “my-component.ts” (the kebab case). We also tolerate other file naming conventions such as “MyComponent.ts”, “myComponent.ts”, “my_component.ts”.

The bottom line is, your file name needs to be easy to break words, so the tooling can match it up with the resource name class MyComponent.

BTW, your can pair mycomponent.ts with class Mycomponent. Just use one word.

Thank you for clarifying that. I have switched to kebab case for files and Pascal case for classes, but it does not work without @customElement

This is the render method in HelloWorldWebPart.ts:

//
// Other imports removed for clarity
//
import { MyComponent } from './my-component';
import { OtherStuff } from './other-stuff';

//
// Other methods removed for clarity
//
public async render() {

    // This line renders the html on the page
    this.domElement.innerHTML = `<my-component></my-component>`;

    try {

      var au = new Aurelia();
      au.register(<any>MyComponent)
        .register(<any>OtherStuff)
        .app({
          component: MyComponent,
          host: document.querySelector('my-component')
        })
        .start();
    }
    catch (error) {
      console.log(error);
    }
  }

And this is the file layout:
image

I have tried my best to use the aurelia loader. If I console.log all the module.rules from the webpack configuration this is what I have:

{
  use: [
    {
      loader: 'C:\\GitProjects\\spfxau2\\node_modules\\@microsoft\\loader-load-themed-styles',
      options: [Object]
    },
    { loader: 'C:\\GitProjects\\spfxau2\\node_modules\\css-loader' },
    {
      loader: 'C:\\GitProjects\\spfxau2\\node_modules\\postcss-loader',
      options: [Object]
    }
  ],
  test: /\.css$/
}
{
  use: [
    {
      loader: 'C:\\GitProjects\\spfxau2\\node_modules\\@microsoft\\sp-css-loader',
      options: [Object]
    },
    {
      loader: 'C:\\GitProjects\\spfxau2\\node_modules\\sass-loader\\dist\\cjs.js',
      options: [Object]
    }
  ],
  test: /\.module\.scss$/
}
{
  use: [
    {
      loader: 'C:\\GitProjects\\spfxau2\\node_modules\\@microsoft\\sp-css-loader',
      options: [Object]
    },
    {
      loader: 'C:\\GitProjects\\spfxau2\\node_modules\\sass-loader\\dist\\cjs.js',
      options: [Object]
    }
  ],
  test: /(?<!\.module)\.scss$/
}
{
  use: [
    {
      loader: 'C:\\GitProjects\\spfxau2\\node_modules\\file-loader',
      options: [Object]
    }
  ],
  test: {
    or: [
      /\.jpg((\?|\#).+)?$/,
      /\.png((\?|\#).+)?$/,
      /\.woff((\?|\#).+)?$/,
      /\.eot((\?|\#).+)?$/,
      /\.ttf((\?|\#).+)?$/,
      /\.svg((\?|\#).+)?$/,
      /\.gif((\?|\#).+)?$/,
      /\.dds((\?|\#).+)?$/
    ]
  }
}
{
  test: /\.js$/,
  enforce: 'pre',
  use: 'C:\\GitProjects\\spfxau2\\node_modules\\source-map-loader',
  exclude: [ /node_modules/ ]
}
{
  test: /\.ts$/i,
  use: [ 'ts-loader', '@aurelia/webpack-loader' ],
  exclude: /node_modules/
}
{
  test: /\.html$/i,
  use: [ '@aurelia/webpack-loader' ],
  exclude: /node_modules/
}

Try adding “CustomElement” suffix on your class name.
As far as I can see, this is the list of conventional names
CustomElement|CustomAttribute|ValueConverter|BindingBehavior|BindingCommand|TemplateController

Thanks for all your help!

I tried this but no success.

export class OtherStuffCustomElement
{
    public text:string = "other text";
}

I also tried to update the file names to other-stuff-custom-element.ts and other-stuff-custom-element.html but that did not help.

When I step through the code it looks like the Aurelia framework think it gets a pure class with no aurelia metadata.

How can I verify that the auerlia/webpack-loader is invoked and does its job? I have never really had to understand the inner workings of webpack before.

Have a look at the source in the browser. If the plugin worked you will see additional code near you class

I had a play of your repo. I have to say what ever @microsoft/sp-build-web is doing, it’s a mess.

resolve: { alias: {}, modules: [ 'node_modules', 'lib' ] },
  entry: {
    'hello-world-web-part': '/Users/huocp/playground/spfxau2/lib/webparts/helloWorld/HelloWorldWebPart.js'
  },

Look at the resolve and entry, it’s not using your code in src/ folder, it’s using compiled code in lib/ folder. The compiled code are all js files, not ts files.

The compiled code is something in ES5 syntax, removed class syntax which our webpack-loader cannot deal with even if you fix your rule to target *.js files.

> cat lib/webparts/helloWorld/my-component.js
// I would like to not need line 5-9
// import html from "./my-component.html";
// @customElement(<PartialCustomElementDefinition>{
//     template: html,
//     name: 'my-component'
//   })
var MyComponent = /** @class */ (function () {
    function MyComponent() {
        this.message = 'Hello World!';
    }
    return MyComponent;
}());
export { MyComponent };
//# sourceMappingURL=my-component.js.map

The ms sp-build-web only applies webpack after those lib/ files.
I think you need to remove sp-build-web, and use webpack straight.

1 Like

Ok, there is something you can do to get closer.

  1. update tsconfig to "target": "esnext",. This fixed the syntax in those lib files.
  2. update gulp file var rule2 = { test: /\.js$/i to target js files in lib folder.
  3. npm i -D ts-loader@8.3.0 to have a ts-loader working with old webpack 4.

There are still lots of issues, but it will get you moving.

BTW, you have conflict html rules from sp-build-web (one from it, another from you) that you need to fix too.

2 Likes

OK, I think I have got it to work with a small change to the plugin-convention file I think @huochunpeng is making changes to.

So first I really want to thank you for your great help and a great framework. It is amazing. :grinning:

In the spfx build pipeline it is possible to add gulp tasks before the ts compile step. So instead of trying to wrangle in the webpack loader I just created my own gulp task that uses code from @aurelia/plugin-convention. The same code the webpack loader uses.

One small problem was left and it seams like this setup does not really like the custom element that is generated by the plugin-convention so I made a small change as below. In preprocess-resource.ts line 145. It just adds the name property and then everything works fine.

conventionalDecorators.push([implicitElement.pos, 
`@${dec}(${viewDef})\n`]);

was changed to

// Dunite
conventionalDecorators.push([implicitElement.pos, 
`@${dec}({name: '${expectedResourceName}', template: ${viewDef}.toString()})\n`]);

I have checked in my setup in the repo GitHub - magnusdanielson/spfxau2 at gulp and added above change to the read-me file. I do have three questions though.

  1. It now works with the convention example below. Is there any obvious change I can do so that the name attribute is inserted in the customAttribute without my code change above?
  2. Is there any down side to above change? I don’t really think is changes the convention declared by Aurelia.
  3. Can I make PR for the change?

Convention example that works now

//from more-stuff.ts
export class MoreStuff{
    message:string = "sfsdfs";
}

//from more-stuff.html
<template>
    <div>more ${message}</div>
</template>

FYI we have a gulp plugin if you are not aware.

You need to pipe both ts and html files to it.

Let me know if the official one fixes your issue.

1 Like

From your code, I can tell you didn’t run html files through our conventions.

We compile html file into js code, there is much more logic than just the template string. You can check official doc on all the meta tags you can use inside the html file.

1 Like

OK, I now get the html files “compiled” to js files with the official gulp plugin. But webpack does not bundle that file. It looks like it bundles the pure html file instead. I will first try to get webpack to work from a folder with only the output of the gulp task. Again thanks for your help.

You need to check the conflicted html rules.

1 Like

Woot! :tada:

Now it works great! I have tested and got this working:

  • <require from="" instead of .register()
  • register() component
  • html + ts convention
  • <require css
  • sass modules from spfx framework (with small change)

That is about all dependencies I have in my projects, so I think I am all set for Aurelia2.

Is there anything you would like me to test?

2 Likes