with the almost done pr by @bigopon it should be possible to define a component as global by passing the class directly instead of a string. this is a step in the right direction in terms of getting aurelia to work correctly with the already standard module syntax. after all aurelia prides itself in following standards.
I understand that when aurelia came to be, the module syntax was not part of the spec (similar to how angularjs came up with it’s own module system). so it had to bolt itself on to already existing loaders to reduce friction.
but the times have changed. now modules are part of the spec, and bundlers (like webpack, parcel, fusebox) use the standard syntax to make crazy optimizations to our code (tree shaking, dead code elimination, and all the buss words).
this post is not to dump on aurelia, as I said, at the time that was probably the best course of action. my purpose is to start a discussion on whether it’s time for aurelia’s module loading to align itself with the spec and let developers make better use of tools in the ecosystem without some required plugin (webpack plugin, fusebox plugin) to accomodate the framework.
some routing examples
adapted from the documentation
this is the current way a router is defined
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'home/index' },
{ route: 'users', name: 'users', moduleId: 'users/index', nav: true, title: 'Users' },
{ route: 'users/:id/detail', name: 'userDetail', moduleId: 'users/detail' },
{ route: 'files/*path', name: 'files', moduleId: 'files/index', nav: 0, title: 'Files', href:'#files' }
]);
}
}
my proposal is to allow us to pass the components directly instead of a string.
import {RouterConfiguration, Router} from 'aurelia-router';
import {HomePage} from './home/index';
import {UsersPage} from './users/index';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', module: HomePage },
{ route: 'users', name: 'users', module: UsersPage, nav: true, title: 'Users' },
]);
}
}
“but code splitting!!!”, I hear you say…
import {RouterConfiguration, Router} from 'aurelia-router';
export class App {
router: Router;
configureRouter(config: RouterConfiguration, router: Router): void {
this.router = router;
config.title = 'Aurelia';
config.map([
{ route: ['', 'home'], name: 'home', module: () => import('./home/index') },
{ route: 'users', name: 'users', module: () => import('./users/index'), nav: true, title: 'Users' },
]);
}
}
that way the code is understood by bundlers and they’ll know to split at that point, and aurelia doesn’t have to concern itself with loading modules.
view templates using @inlineView
not a commonly used feature, inlineView
might just be the key to make all of this possible. according to the documentation, inlineView has the following ts signature
inlineView(markup: string, dependencies?: Array, dependencyBaseUrl?: string): any
if you know about inlineView then you probably know about the first argument: the view of the component.
const view = `
<template>
sooper cool content
</template>
`;
@inlineView(view)
class App {}
but I bet few people know about the second and third arguments. the second one is an Array
of objects with from
and as
properties (similar to the <require>
element’s from
and as
attributes.
const view = `
<template>
sooper cool content
<custom-element></custom-element>
</template>
`;
@inlineView(view, [{ from: '../custom-element', as: 'custom-element' }])
class App {}
but this still suffers from the same problems as the router, it’s not using standard syntax and so you need the magical PLATFORM.moduleName
to tell the bundler to form the relationship. in reality it should be possible to simply pass in the class.
// custom-element.ts
@customElement('custom-element')
export class CustomElement {}
// app.ts
import {CustomElement} from './custom-element';
const view = `
<template>
sooper cool content
<custom-element></custom-element>
</template>
`;
@inlineView(view, [CustomElement])
class App {}
I asked about this on #585. You can read @bigopon’s response there.
“but separation of concerns!!! I want my html on a different file”
although this is not part of the spec (and likely never will), all bundlers have the ability to import different file types (not sure about fusebox on this one), including html.
import view from './app.html';
@inlineView(view)
class App {}
“but other file types and async import are not part of the spec!!”
but neither are decorators… that is to say that something not being yet part of the spec hasn’t stopped aurelia from using it as part of the core api. there is precedent for these types of risks, and it’s very likely that dynamic imports will become standard. In fact it might get there before decorators do.
boostrapping
right now, manually bootstrapping an app is a mystery to me. but I believe this is a place that would also benefit from using standard module syntax.
if you’re using webpack, the way to boostrap right now is to set your app’s entry to aurelia-bootstrapper
and it will magically know to use a main
file and use the configure
exported function. and it just happens. I would say this is not the way it should be. my dream api for this would be
<!-- src/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>the title</title>
</head>
<body>
<main id="root"></main>
<script src="./main"></script>
</body>
</html>
// src/main.ts
import {bootstrap, Aurelia} from 'aurelia-framework';
import {App} from './app';
async function start(){
const host = document.getElementById('root');
const aurelia: Aurelia = await bootstrap(App, host);
aurelia.use
// add plugins here
.standardConfiguration()
.developmentLogging();
await aurelia.start();
}
start();
and if you want to use parcel just point it to src/index.html
and it knows what to do. if using webpack just point it to src/main.ts
and it will know what to do without the need for special plugins. speaking of which…
"but conventions!!! "
this is where I believe plugins have their place. instead of being an absolute necessity, they would add extra features like:
- no need to use
@customElement
to give the names yourself, the plugin would add the decorator based on the file name - same with
@inlineView
as the plugin could find the html view file, add the decorator, and actually inline the html in there - etc
for the noobs
let’s be honest starting with aurelia is not as easy as we would want it to be. in my case, this was due to it not being as popular (lack of examples) and the “conventions” to which aurelia aligned itself where not always entirely clear. hopefully, this shift would lessen the initial mental burden of learning something new. because it would be just javascript.
fin
hopefully my thoughts came through the text, and this can start a discussion to revamp/rethink aurelia’s module system
@Alexander-Taran the result of the gitter chat and some more thought