GithubHelp home page GithubHelp logo

aurelia / aurelia Goto Github PK

View Code? Open in Web Editor NEW
1.3K 57.0 146.0 188.42 MB

Aurelia 2, a standards-based, front-end framework designed for high-performing, ambitious applications.

License: MIT License

TypeScript 98.37% HTML 0.37% JavaScript 1.23% CSS 0.01% EJS 0.01% Shell 0.01%
aurelia typescript javascript web pwa web-framework web-performance html mobile spa

aurelia's Introduction

Aurelia

License: MIT npm version CircleCI TypeScript Twitter

Backers on Open Collective Sponsors on Open Collective Discord Chat

Aurelia 2

This is the Aurelia 2 monorepo, containing core and plugin packages, examples, benchmarks, and documentation for the upcoming major version of everybody's favorite modern JavaScript framework, Aurelia.

Introduction

Aurelia is a modern, front-end JavaScript framework for building browser, mobile, and desktop applications. It focuses on aligning closely with web platform specifications, using convention over configuration, and having minimal framework intrusion. Basically, we want you to just write your code without the framework getting in your way. ๐Ÿ˜‰

Aurelia applications are built by composing a series of simple components. By convention, components are made up of a vanilla JavaScript or Typescript class, with a corresponding HTML template.

//app.js
export class App {
  welcome = "Welcome to Aurelia";

  quests = [
    "To seek the holy grail",
    "To take the ring to Mordor",
    "To rescue princess Leia"
  ];
}
<!-- app.html -->
<form>
  <label>
    <span>What is your name?</span>
    <input value.bind="name & debounce:500">
  </label>

  <label>
    <span>What is your quest?</span>
    <select value.bind="quest">
      <option></option>
      <option repeat.for="q of quests">${q}</option>
    </select>
  </label>
</form>

<p if.bind="name">${welcome}, ${name}!</p>
<p if.bind="quest">Now set forth ${quest.toLowerCase()}!</p>

This example shows you some of the powerful features of the aurelia binding syntax. To learn further, please see our documentation.

Feeling excited? Check out how to use makes to get started in the next section.

Note: Please keep in mind that Aurelia 2 is still in beta. A number of features and use cases around the public API are still untested and there will be a few more breaking changes.

Getting Started

First, ensure that you have Node.js v8.9.0 or above installed on your system. Next, using npx, a tool distributed as part of Node.js, we'll create a new Aurelia 2 app. At a command prompt, run the following command:

npx makes aurelia

This will cause npx to download the makes scaffolding tool, along with the aurelia generator, which it will use to guide you through the setup process. Once complete, you'll have a new Aurelia 2 project ready to run. For more information on Aurelia's use of makes, see here. If you aren't interested in taking our preferred approach to generating a project, you can also see the examples folder in this repo for pure JIT setups (no conventions) with various loaders and bundlers.

Documentation

You can read the documentation on Aurelia 2 here. Our new docs are currently a work-in-progress, so the most complete documentation is available in our getting started section. If you've never used Aurelia before, you'll want to begin with our Quick Start Guide.

Contributing

If you are interested in contributing to Aurelia, please see our contributor documentation for more information. You'll learn how to build the code and run tests, how best to engage in our social channels, how to submit PRs, and even how to contribute to our documentation. We welcome you and thank you in advance for joining with us in this endeavor.

Staying Up-to-Date

To keep up to date on Aurelia, please visit and subscribe to the official blog and our email list. We also invite you to follow us on twitter. If you have questions, have a look around our Discourse forum. For chat on Aurelia 2, join our new Aurelia 2 community on Discord. If you'd like to join the growing list of Aurelia sponsors, please back us on Open Collective.

License

Aurelia is MIT licensed. You can find out more and read the license document here.

aurelia's People

Contributors

3cp avatar aegenet avatar bbosman avatar ben-girardet avatar bigopon avatar brandonseydel avatar dannybies avatar davismj avatar dependabot[bot] avatar eisenbergeffect avatar ekzobrain avatar elitastic avatar fkleuver avatar ghiscoding avatar gkaganas-mparticle avatar green3g avatar ivanbacher avatar jwx avatar lakerfield avatar marekpetak avatar praveengandhi avatar redfeet avatar renovate-bot avatar rluba avatar romkevdmeulen avatar sayan751 avatar tomtomau avatar vheissu avatar xenoterracide avatar zewa666 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

aurelia's Issues

Cleanup / separate the tests

Some sets of unit tests have gotten a bit too messy and take a long time to run, in particular the observer and repeater tests.
I would propose getting rid of the unit subfolder and appending the "suite" name to the file:

  • .unit.spec.ts
    • Very fast (< 5 seconds total). Stick to the definition of a unit test to the extent that it's practical.
    • Every module that has code, should have a corresponding .unit.spec.ts
  • .integration.spec.ts
    • Relatively slow (up to a minute or more) and thorough
    • Mostly non-kernel/runtime packages, and high-level api's like aurelia.ts, renderer.ts
  • .smoke.spec.ts
    • Similar to integration tests, but higher level, fewer in numbers and run fast, making them a good supplement for TDD / watch mode. Typically use jit/aot to cover lots of ground per test. Not sensitive to low-level api changes.
    • Don't need to map to specific files - one file per package (e.g. runtime.smoke.spec.ts) could be just fine
  • .regression.spec.ts
    • Can fall under any of the 3 above categories and more. Target parts of the code base that probably won't change much (if ever) and thus don't need to be verified with every run. Examples: extra thorough variants of the parser and observer tests, and compiler tests that try many combinations of elements/attributes/events/data types.
    • May map to specific files or packages. May also map to issue numbers or pieces of the ES / HTML specs, etc.

Test commands:

  • npm run tdd - runs the unit and smoke tests in watch mode, for during development. Only a few seconds to finish
  • npm run test - runs the unit, integration and smoke tests in non-watch mode (single run), for before pushing. CI will also run this on each commit. Up to 2-3 minutes to finish for all packages
  • npm run test:full - runs all tests. CI will run this on a nightly basis and will only publish a new dev version when the tests passed. Up to 30 minutes to finish
  • npm run e2e - runs the e2e tests (can only be run from within examples that have them). CI will run these on each commit. Might later be split up as well, but no plans for that yet.

@EisenbergEffect @bigopon This is just an initial idea, I don't feel particularly strongly about this. Thoughts?

Context traversal not working inside custom elements

๐Ÿ› Bug Report

A custom element does not have access to $parent via the parentOverrideContext, at least not when there's a repeater / template controller on it.

๐Ÿค” Expected Behavior

@customElement's $hydrate should create its scope based on the parentScope so that the bindingContext inside it can traverse upwards

๐Ÿ˜ฏ Current Behavior

It does this: this.$scope = BindingContext.createScope(this); (line 148, custom-element.ts)

Consequently, @customElement has no access to parent context

๐Ÿ’ Possible Solution

The $scope somehow needs to be passed down from parent to child during hydration because currently there seems to be no direct way to access it.

We could either directly register/store it via the renderingEngine or renderContext, or somehow let child resources access their parents and get the parent scope that way. I'm hesitant on either approach.

We could also make the scope a first-class citizen during the rendering process, as an extra argument in addition to renderable / target / instruction. It doesn't necessarily seem wrong to be because the scope is the same level of granularity of the target, which the renderer also explicitly passes around.

I have not looked at or considered how vCurrent solves this. These are just my initial thoughts.

I can also be completely and utterly wrong and this could be an issue with how the template compiler processes bindable attributes on custom elements after wrapping them. I haven't debugged it yet.

๐Ÿ”ฆ Context

Template controllers will wrap custom elements in a new context and thereby they lose access to the properties they're bound to, meaning nothing will work with a repeater+custom element unless all the bindable properties come first.

๐Ÿ’ป Code Sample

See this integration test

๐ŸŒ Your Environment

N/A

Consider adding `$bind` and `$unbind` to all AST nodes (and remove them from observers)

We can simplify the observers and make the AST have a more consistent interface by doing this.

Benchmarks will be needed to check the performance implications, but overall I think it would be a good idea to do this. We'll be able to get rid of various if (sourceExpression.bind) type of checks and have room for more easily add intelligent behavior to pieces of the AST where it may be needed.

I'll do some experiments with this in the near future. Just creating an issue for this to put the thought out.

[WIP] Route Configuration API Proposal [RFC]

  • IRouteConfig interface
  • Basic routes
  • Canonical urls
  • Aliased routes
  • Custom viewports
  • Child routes
  • Absolute paths
  • Case sensitive routes
  • Parameterized routes
  • Custom parameterizers
  • Navigation model
  • Route metadata

Please let me know if there are any use cases that are missing from this specification.

The Route Configuration API

Route configuration can be done one of three ways: Up-front (no definition yet), using the @route decorator, or by defining a static routes property. For clarity, I've included the most terse definition with the decorator approach and the most verbose definition with the static routes approach for each example. This will demonstrate what they will look like in a typical development case as well as an explicit definition what they imply. I've also commented out implicit or conventional values that will be set by Aurelia under the hood.

IRouteConfig

The names of properties are up for grabs. I have chosen names that I believe are most illuminating and most standard.

interface IRouteConfig {

  // A string or array of strings of matching route paths.
  // (Renamed from route in vCurrent)
  path: string | string[],

  // A constructor function for the view-model to attach for this route. 
  // For route decorator and static routes approaches, Aurelia will set this
  // value under the hood. 
  // (Repurposed from moduleId in vCurrent)
  component?: IComponent,

  // A uniquely identifiable name for the route, for canonical navigation.
  // For route decorator and static routes approaches, Aurelia will try to
  // set this value by convention if not specified explicitly.
  name?: string,

  // Optional, the name of the viewport to attach the controller to. If not
  // specified, the default viewport will be used.
  viewport?: string,

  // Optional, the name of the parent route or routes, matched by the `name` property.
  parent?: string | string[],

  // Optional, flag to opt the route out of the navigation model. Defaults 
  // to true.
  nav?: boolean,

  // Optional, an object with additional information available to the 
  // view-model throughout the activation lifecycle. 
  // (Renamed from settings in vCurrent)
  meta?: any
}

A basic route

Defining a basic route is dead simple.

@route('home')
export class MyViewModel {
  static routes = [
    { 
      path: 'home',
      //, name: 'my'
      //, component: this
    }
  ];
}

Canonical urls

Multiple routes can redirect to the same route, creating a canonical url for that page.

@route('home')
@route({ path: '', redirect: 'home' })
export class MyViewModel {
  static routes = [
    {
      path: 'home'.
      //, name: 'my'
      //, component: this
    },
    {
      path: '',
      redirect: 'home'
    }
  ]  
}

Aliased routes

Alternatively, multiple routes can load the same page without redirecting the route.

@route(['', 'home'])
export class MyViewModel {
  static routes = [
    {
      path: ['', 'home'],
      //, name: 'my'
      //, component: this
    }
  ]
}

Custom viewports

If a page has multiple viewports defined, each route should specify which viewport the route targets. Multiple route configurations can target the same paths.

<template>
  <viewport name="left"></viewport>
  <viewport name="right"></viewport>
</template>
@route({ path: 'forwards', viewport: 'left' })
@route({ path: 'backwards', viewport: 'right' })
export class FirstViewModel {
  static routes = [
    {
      path: 'forwards',
      viewport: 'left',
      //, name: 'first'
      //, component: this
    }, 
    {
      path: 'backwards',
      viewport: 'right'
      //, name: 'first'
      //, component: this
    }
  ]
}

@route({ path: 'forwards', viewport: 'right' })
@route({ path: 'backwards', viewport: 'left' })
export class LastViewModel {
  static routes = [
    {
      path: 'forwards',
      viewport: 'right',
      //, name: 'last'
      //, component: this
    }, 
    {
      path: 'backwards',
      viewport: 'left'
      //, name: 'last'
      //, component: this
    }
  ]
}

If two configurations target the same paths and viewports, a warning should be raised.

@route('home')
export class HomeViewModel { }

@route('home') // Warning! Duplicate route definition for path "home" in viewport "default".
@route({ path: 'home', viewport: 'side' }) // OK.
export class NotHomeViewModel { }

Child routes

NOTE I've done my best to get various points of views concerning the importance of child routes. I'm still looking for use cases that may not be covered here.

A route can be defined as a child route by specifying its parent by name. This allows (a) creating a child viewport that is refreshed without touching any other viewports and (b) defining a more flexible navigation model (below) without having to maintain a separate route configuration table.

// 'parent' loads parent only into the default viewport
@route({ path: 'parent' }) 
export class ParentViewModel { }
<template id="parent">
  <viewport></viewport>
</template>
// 'child' loads child only into the default viewport
@route({ path: 'child' })

// 'parent/child' loads child into the default viewport of parent
@route({ path: 'child', parent: 'parent' })
export class ChildViewModel {
  static routes = [
    {
      path: 'child',
      //, name: 'child'
      //, component: this
    },
    {
      path: 'child',
      parent: 'parent',
      //, component: this
    }
  ]
}

Absolute paths (aka alias via Vue)

Paths defined with a leading forward slash match the app's base url. This is particularly useful when you want a child route to match a path independent of its parent's path.

// 'special-child' loads child into the default viewport of parent
@route({ path: '/special-child', parent: 'parent' })
export class ChildViewModel {
  static routes = [
    {
      path: '/special-child',
      parent: 'parent',
      //, component: this
    }
  ]
}

Parameterized routes

Parameterized routes define dynamic sections of the path (parameters) that are made available throughout the activation lifecycle. If a type is specified, the route will only match values that can be coerced to the specified type, and the value will be available as the specified type throughout the activation lifecycle.

// matches 'foo/bar1!@#$%'
@route('foo/{name}')

// matches 'foo/123' and 'foo' but not 'foo/bar'
@route('foo/{id?:number}')
export class MyViewModel {
  static routes = [
    {
      path: 'foo/{name: string}',
      //, component: this
    },
    {
      path: 'foo/{id?: number}',
      //, component: this
    }
  ];
}

Custom parameterizers

In addition to native JavaScript types, custom types can be registered with the router.

router.registerParameterizer('Guid', (param: string) => {
  if (/^[0-9a-f]{8}-?(?:[0-9a-f]{4}-?){3}[0-9a-f]{12}$/i.test(param)) {
    return param;
  }
  // If no value is returned, the route does not match.
});

// matches 'foo/6e380650-8b50-4cb4-8a09-a5449abf597b' but not 'foo/123'
@route('foo/{id: Guid}')
export class FooViewModel { }

Navigation model

Route metadata

Special metadata can be specified on each route that is made available throughout the activation lifecycle. This is particularly useful for defining implicit or hidden parameters when a user navigates through a particular route.

@route('')
@route({ path: 'other', meta: { referral: true })
export class MyViewModel {
  static routes = [
    {
      path: '',
      //, component: this
    },
    {
      path: 'other',
      meta: { referral: true },
      //, component: this
    }
  ]

  activate(params, config) {
    config.meta.referral === true;
  }
}

Decouple renderer

At the moment, it seems to me Renderer has very weak connection with the rest of runtime, and only strongly coupled with instructions list. It seems to make sense to me that we make those instructions at externally configurable, together with a plug-able renderer for those instructions to depend on. This way, we can have default combo of compiler and renderer for web platform. To target different platform, we can provide different combo instead of our current structure. This requires some changes in the way how runtime understands the basic behavior, or we can even make all behavior externally configurable, like .. there is no such thing as custom element. It's a renderer thing.
@EisenbergEffect @fkleuver

Proxies for array.length and array[index] observation

I have been experimenting with this but it will be more involved than simply adding it on top of the existing observation infrastructure (in which case it breaks). Creating an issue for this because I've been procrastinating on this and I would welcome any suggestions.

Only create `$children` observer if the property is defined?

๐Ÿ’ฌ RFC

Currently every single component is initialized with this:

image

Seems rather wasteful to have an extra array and observer object for each and every element when they're only used in very specific cases.

Do these need to be declared and instantiated for any particular reason or can this change?

๐Ÿ”ฆ Context

๐Ÿ’ป Examples

[proposal] [rough idea] api for plugin to enhance (inject logic) lifecycle

I am thinking about this idea after I did https://github.com/aurelia-contrib/aurelia-combo/blob/master/src/resources/index.js

I learnt that trick from https://github.com/aurelia-ui-toolkits/aurelia-after-attached-plugin/blob/master/src/aurelia-after-attached-plugin.js

That monkey patching on Controller.prototype.attached is very hacky. What about if vNext provided something like:

export function configure(aurelia) {
  aurelia.enhanceLifecycle({
    // optionally enhance created, bind, attaching, attached,
    // detaching, detached, unbind.
    // for instance:
    attached(viewModel: any, element: Element): any | Promise<any> {
      // ...
    }
  });
}

Why?

  1. enable composition of convention.

Like the aurelia-combo I did, a plugin can use this new api to introduce new convention to Aurelia. Like if viewModel defined this method, or decorated something, the plugin can hook a 3rd party js lib to magically bring up new feature.

  1. (rough idea) clean up core modules implementation.

In vCurrent, some convention like <compose>'s activate, and router's activate/canActivate/configureRouter were implemented very obfuscated to me. If possible, It might be cleaner to bring in those convention by enhancing lifecycle logic. But I definitely don't know Aurelia enough to make the assertion.

If core modules can demonstrate an easy approachable way on introducing magical features, 3rd party plugins can follow the example.

Note

On the pair of some lifecycle enhancements, probably need to execute them in opposite order.

enhancing attaching: plugin_A, plugin_B, plugin_C
enhancing detaching: plugin_C, plugin_B, plugin_A

Rework SubscriberCollection

The disconnect/reconnect cycle is a very hot path that takes up to 50% of the CPU utilization in dbmonster-like benchmarks. There's no question that $unbind is where the biggest gains can be made in terms of performance.

With BindingFlags we don't need to store context / origin together with the subscribers anymore. We can simply pass the origin along with the flags from where the change originates, and the subscriber will have the information it needs to decide whether or not to act on a mutation.
This means SubscriberCollection will likely not need the relatively high-overhead array pool mechanism anymore and can become much simpler and have better performance.

Also look into making SubscriberCollection an injectable dependency (composition over inheritance) with the added benefit of making subscribers shareable / swappable. We might not need to fully disconnect and reconnect bindings anymore when the binding context changes.

CheckedObserver truthy values and matcher coercion

In fixing the CheckedObserver tests / logic for the SubscriberCollection PR I noticed a few things that might be up for improvement, but in order to keep that PR clean I'm adding some notes here instead.

If you set the value property of a checkbox to a non-string primitive and have the checked property bound to an array with that same non-string primitive, the corresponding input element would not be checked because the value property of a checkbox is always coerced to a string. Users might consider this unintuitive.

In the default matcher function:

(a, b) => a === b

We could coerce the value (a, which comes from the VM) to strings:

(a, b) => (a+'') === b

This would make the behavior consistent with how HTML behaves.
The performance impact here would be negligible since adding an empty string to a string is usually optimized by browser jit.

Furthermore, for similar reasons we could consider being more loose with the checked property comparison. Booleans have a tendency to get coerced to strings somewhere along the flow of binding.
So we might want to change this:

...
if (value === true) {
      element.checked = true;
...

To this:

...
if (value === true || value === 'true') {
      element.checked = true;
...

@EisenbergEffect @bigopon

Type is not a constructor, for unregistered compiler resources

For one reason or another the template compiler may try to get resources like binding-command:null, causing DI to JitRegister this as a string instance which results to a non constructable type.
See also: aurelia-contrib/aurelia-vnext-starter#1
We should probably guard against this sort of thing a bit deeper into DI so that it just returns null (or throws a more descriptive error) if it gets a non-function, rather than trying to construct whatever it gets.

Review and add full test coverage for expression parsing

I need to add some notes about which precise pieces of the spec we are implementing and where we deviate. We need to ensure full test coverage for every possible edge case. This can be done by simply cross-checking with the spec: https://tc39.github.io/ecma262.
We're almost there already, but I know some things are missing. A good example is the various restrictions on member expressions (currently object and array literals are not allowed on the left-hand side, which is already fixed in vCurrent).
Also need to think about (and document) the various rules that would apply to semicolon-separated custom attribute expressions, interpolations, restrictions on repeat.for, and so forth.

Implement rest/spread operators

We have Assign, so I think this also belongs in our expression syntax. It's easy to implement, just needs a DotDotDotToken in the parser, a small tweak to the scanning logic and possibly a new AST node.

Describe gitflow

Add some info to the readme's on how branching and merging works in the context of this repo

Difficulty using UMD

I have had some difficulty using UMD with webpack, both via my local setup and stackblitz / codesandbox, though it theoretically shouldn't cause any issue. I think we should switch it to either commonjs or es2015 and let consumer decide what they want to do with it. Note that it happens when i'm using JS, not TS

@EisenbergEffect @fkleuver

Make the reporter report on flags

binding.ts line 92: throw Reporter.error(15, context);, this is something we probably need to fix in other places as well. The reporter (in debug) would also be the perfect place for some verbose mapping codes that translates flags into something more human readable for debugging purposes

Rework Binding/Ref/Call/Listener and/or deprecate BindingMode

We could consider deprecating BindingMode and merging it into BindingFlags, or simply renaming it to BindingCommand. There is overlap and they serve the same purpose: add contextual information about a binding command for the IBinding implementor.
We might be able to have a single Binding class that handles all commands, or at least have less duplicated code across them and reuse logic via decorators. I thought about applying @binding or @bindingCommand to a class that handles a binding / binding command.

We could also go in the opposite direction: completely remove the contextual information from the flags, remove BindingMode, not have a BindingCommand, but simply have a separate IBindingCommand implementor for each command. So there will be a specialized ToViewBinding, TwoWayBinding, FromViewBinding, CallBinding, etc for each command. This would likely be a fair performance improvement because less checks need to happen, and we might be able to do this without adding a lot of code, by reusing logic in decorators.

I don't think backwards compatibility would suffer too much from this because we can have a fairly thin compatibility adaption layer to make some translations if need be.

I need to experiment with both of these, but in the meantime any thoughts are welcome.

Rework ObserverLocator

ObserverLocator has some room for improvement in clarity and correctness as well as in performance.

My idea is to use Object.prototype.toString.call(obj) on the passed-in object and have an object lookup with things like this:

{
    ['[object Array]']: createArrayObserver,
    ['[object HTMLInputElement]']: createElementObserver,
}

We'd need to make a practical tradeoff between the size of this lookup (including all possible element constructors is probably too much) and the performance overhead of having a more generalized solution (for example stripping off everything between "HTML" and "Element" in a charAt loop)

In any case we can probably get rid of the regexes without adding too much code.

View Lifecycle Improvements

Background

At present, when a renderable (custom element or view) is told to detach, it always removes its DOM nodes, even if its parent has already been removed from the DOM. This is highly inefficient and serves no purpose.

Plan

We should evolve the lifecycle code to ensure that only the owner of the lifecycle removes its nodes. This will result in the minimal amount of DOM modification on a detach. Following up from that, if the same renderable is attached again later, only its top-level nodes should be placed back in the DOM, since all the children are still connected into their parents. One final scenario needs to be explored: custom element view projection. A custom element should only project its view once, regardless of how many times the element is added or removed from the DOM. An exception to this (read complication) is containerless, which would need to remove their nodes when the element is the lifecycle owner, since they don't have a single container (host or shadowDOM root) to remain attached to. This should be handled by the IElementProjector abstraction, so it's hopefully not too complicated.

bundle the allure report :)

Uploading the allure report files takes twice as long as running the e2e tests. This is because every individual test result is stored in a json file. I think it adds up to a couple hundred files already of 50 bytes - 1kb bytes each.
The allure report is pretty sweet though, so maybe a good job for parcel or webpack to try and bundle it up to cut down on the upload time. Will save at least 4 minutes per workflow run

View compiler extension point

At the moment, we have very nice view compilation model, thanks to binding command resource from @fkleuver . However, there is something still lacking: a way to handle an arbitrary unknown binding command - attribute combination.

<div
  background-color.style='bgColor'
  style.color='color'
  selected.class='selected'
  class.selected='selected'
  ref='div'></div>

With the above usage, declaring new binding command does not always give the ability to handle the scenario. I think we may want to introduce inspectAttribute, that will be called before anything else to give a chance to have those highly dynamic usage a solution. Our default implementation will be a noop function, or a function that handle ref only

@fkleuver @EisenbergEffect

[RFC] Require/Import in View Templates

vNext dropped <require> tag for special syntax @import

@import './hello.html';
<template>
  <!-- ... -->
</template>

I like the decision to move dependency declaration out of <template> tag. This will solve the annoying restriction that <tr> cannot have a child <require> element.

But the new syntax destroys my editor's syntax validation. Should we change the syntax to this? To keep code editor calm.

Option1

<require from="./hello.html"></require>

<template>
  <!-- ... -->
</template>

Option2

Maybe even like this since we do not feed those header lines to browser native html parser.

<require from="./hello.html">

<template>
  <!-- ... -->
</template>

Option3

Or use attribute.

<template require="./hello.html, ./hello.css">
  <!-- ... -->
</template>

Cleaner variant of option3

<template require="./hello.html" require="./hello.css">
  <!-- ... -->
</template>

Change TargetedInstructionType to string (or use array instead of object lookup)

As discussed with @EisenbergEffect having numeric properties on an object is a performance killer. Numeric properties / property indexers on a non-array object gives a member accessor that is between 20-50 times slower than either array indexers or normal string property accessors.
The consensus of our chat is to turn TargetedInstructionType into an object and prepend the numbers with a dollar $1, $2. It just needs to happen now. I'll look into this soon-ish but I'm not currently working on it, so anyone else looking for something to do feel free to pick it up

Runtime ExpressionParser caching issues

@bigopon discovered a problem today where trying to parse an interpolation expression will actually return the cached value of a previously parsed non-interpolation expression.

I didn't think the caching through very well at the time I changed it, nor tested it very properly, so after looking at it a bit more closely I think this mechanism is due for a rework. I'm just putting my thoughts out here to give you a heads up on what I'll be working on soonish (I may or may not do some more templating tests first)

I'm considering tackling this together with improving the coverage on the renderer and making targeted instruction types numeric again (using an array as the backing store) so they can more easily be inserted (addressing #182), swapped, and have some cleverness with bitwise ops applied to them when appropriate*.

There would be 3 or so lookups in the expression parser, one for each set of parsing rules (interpolation, iterator statement and normal expressions)

The instruction types would be split up again per binding type / command, each having a unique number. The numbers of the instruction types can then, via bitwise operators, be translated to one out of these cache indices.

With all this in mind there are some const enums that need to be cleaned up. The BindingType and ExpressionKind enums have become an incoherent mess and I'd like to try to just get rid of them. Same goes for some of those direct prototype assignments on instruction classes and AST nodes.

Naturally this would also touch the AST and expression parser and probably break a few thousand tests, so those need to be tidied up as well. So that's a fairly big chunk of work upcoming, but I think things will be much cleaner and easier to understand when it's done.

Use plugin-requirejs in the jit-aurelia-cli example

Use plugin-requirejs in the jit-aurelia-cli example so we can do import * as view from 'view!./app.html' and @customElement(view) instead of the current (verbose) code.
I'm not sure how to accomplish this. Since @EisenbergEffect knows the plugin and @huochunpeng knows the current cli better, could you two chime in on how this should work?

Dynamic Behavior APIs

I'm using this issue to put down some design thoughts on our dynamic behavior APIs. There are APIs that would be used by the compose element internally, but that also would be used directly by developers who need to create and apply attributes or elements on the fly.

Note: No interface or method names are final here. I'm just trying to get some ideas down...

Idea 1

For convenience, I'm thinking we'll hang these APIs off of the Aurelia object. We'll have APIs for elements, attributes and template controllers. Something like this:

const au = new Aurelia();
const a = au.createElement(MyCustomElement);
const b = au.createAttribute(MyCustomAttribute);
const c = au.createTemplateController(MyTemplateController, myTemplate);

Rather than having these APIs return the element, attribute and template controller directly, I'm thinking they will return something like an IBehaviorManipulator. The reason for this is that once you've got an instance of one of these things, what do you do with it? Well, there are a couple of different things I think developers might want to do. The most common is that I think they'd want to graft it into an existing view hierarchy within their app. With vCurrent, developers had some clunky APIs to create things on the fly and then once they created it, they had to manage the entire lifecycle directly. For vNext, I think you should be able to create one of these behaviors and then graft it into the existing view hierarchy so that it can manage the lifecycle for you from that point forward. Here's what that might look like:

const ele = au.createElement(MyCustomElement);
const handle = ele.insertBeforeViewNode(document.getElementById('foo'));

At some later time, you could remove the behavior, if you want as well:

handle.removeFromView();

The interfaces should be defined so that you can't call the wrong methods at the wrong time. So, you might have something like this:

class Aurelia {
  createElement(elementType: Constructable): IElementManipulator;
}

interface IElementManipulator {
  insertBeforeViewNode(node: INode): IConnectedElement;
  appendToViewNode(node: INode): IConnectedElement;
}

interface IConnectedElement {
  removeFromView(): IElementManipulator;
}

I think the manipulator will look a bit different for attributes and template controllers:

interface IAttributeManipulator {
  applyToViewNode(node: INode): IConnectedAttribute;
}

interface IConnectedAttribute {
  removeFromView(): IAttributeManipulator;
}

Beyond this, I think people will want to add behaviors to the dom and maintain manual control over the lifecycle. So, I think there would be additional methods on the behavior manipulator, perhaps something like this:

interface IElementManipulator {
  takeManualControl(): ICustomElement;
}

interface IAttributeManipulator {
  takeManualControl(): ICustomAttribute;
}

Should we allow bindings to be set as well?

interface IElementManipulator<TElement> {
  setBindings(props: Record<keyof TElement, IBinding>);
}

Elements probably also need the ability to set their content:

interface IElementManipulator {
  setContent(nodes: INode[]);
}

Alternatively, this could be done as part of the create API:

au.createElement(MyCustomElement, {
  bindings: { ... },
  content: [ ... ]
}).insertBeforeViewNode(someNode);

Does this cover the main scenarios?

Idea 2

I'm not sure why this didn't occur to me above, but we could mirror the React.createElement API and open up Aurelia to JSX views.

au.createElement(MyElement, props, children);

I'm not sure what this would return exactly just yet. I'll need to play around with it a bit.

Error Codes

For vNext, we'd like to have an amazing story around error messages. This involves:

  • Verbose messages with guidance to help developers resolve the problem.
  • Error codes that are easy to search and lookup in documentation.
  • Error code ranges to classify and organize codes (inspired by HTTP codes).

vNext Router notes

Best parts of vCurrent router

Worst parts of vCurrent router

Notes from WebAPI

As @fkleuver noted, Aurelia is the framework of choice for .NET developers.

route decorators

This will be a huge win as it feels very natural, especially coming from a .NET background.

@route('my/{id:number?}') // id is optional and parsed as a JavaScript number
export class MyIdViewModel { }

This should be opt-in with more standards-compliant implementations available.

import { Route } from 'aurelia-framework'; // Symbol('route')

export class MyIdViewModel {
  static [Route] = 'my/{id:number?}';
}

The route definition should be shorthand for a full route configuration object. The static [Route] parameter definition should follow the decorator argument as well.

@route('my/{id:number?}`)
@route({
  route: 'my/{id:number?}',
  name: 'myId', // by convention, {name}ViewModel
  title: MyId, // by convention, {Title}ViewModel
  nav: true, // true by default
})

route table

This should essentially compile to a single route table, which the developer could hand write if desired. Additionally, code splitting, lazy loading, and child routing together will be impossible without an up front static definition of routes.

src/routes.js

export const routes = [{
  { route: 'my/{id}', name: 'myId', title: 'MyId', nav: true }
}];

The developer is free to mix strategies, and routes configured on the view-model will be appended to the route table.

We may also provide a tool that auto generates this routes.js file.

Notes from Vue

In general, the Vue router seems to have taken a lot of notes from the Aurelia router. They added some improvements that I'd like to keep in mind.

router navigation api

https://router.vuejs.org/guide/essentials/navigation.html

vCurrent router has a couple very non-standard api methods, router.navigate(url, options) and router.navigateToRoute(name, params, options). The Vue implementation mirrors the native history API. It is simple, clean, predictable.

router.push(routeConfig)
router.replace(routeConfig)
router.go(direction)

The routeConfig definition does a much better job of signaling to the developer what parameters mean.

interface RouteConfig {
  name: string; // the name of the route
  params: object; // params to pass to the route
  path: string; // the relative (or absolute?) path of the route, mutually exclusive to the above two properties
}

route lifecycles

https://router.vuejs.org/guide/advanced/navigation-guards.html#in-component-guards

vCurrent router had some pipeline extensions tacked on that enabled some pretty important development features, but their API was pretty clunky. The most common case is AuthorizeStep.

class AuthorizeStep {
  run(navigationInstruction, next) {
    if (navigationInstruction.getAllInstructions().some(i => i.config.settings.roles.indexOf('admin') !== -1)) {
      var isAdmin = /* insert magic here */false;
      if (!isAdmin) {
        return next.cancel(new Redirect('welcome'));
      }
    }

    return next();
  }
}

configureRouter(config) {
  config.addPipelineStep('authorize', AuthorizeStep);
}

In Vue, these lifecycle callbacks are first class citizens and dead simple. The above example requires (1) configuring metadata on the route, (2) creating a new pipeline step that runs on every route, (3) checking for the metadata in the pipeline step, and (4) running the required logic when the metadata matches. Compare a the Vue implementation, which supports both global and route-specific callbacks.

const authCheck = (to, from, next) => {
  const isAdmin = /* insert magic here */ false;
  if (!isAdmin) {
    return next({ name: 'welcome' });
  }
  return next();
}
const timeoutCheck = (to, from, next) => {
  const timedOut = true;
  if (timedOut) {
    return next({ name: 'login' }
  }
  return next();
}
const router = new VueRouter({
  routes: [
    {
      path: '/foo',
      component: Foo,
      beforeEnter: authCheck
    },{
      path: '/bar',
      component: Bar,
      beforeEnter: authCheck
    },{
      path: '/welcome',
      component: Welcome
    }
  ]
});
router.beforeEach(timeoutCheck);

[WIP] Router Use Cases [RFC]

Dynamic Redirect

const redirect = (from, to, next) => next({ redirect: 'new' });
router.mapRoutes([ { route: 'old', before: redirect } ]);

Prevent Exit

const preventExit = (from, to, next) => next(??? ? null : 'home')
router.mapRoutes([ { route: '', name: 'home' ]);
router.before = [preventExit];

Custom Titles

router.buildTitle = (instructions) => instructions.reverse().map(i => i.title).join(' | ');

Differently named attributes/properties on custom elements

Custom element unit tests are still on my todo-list for the template compiler, but @vegarringdal reported a bug that's specifically worth mentioning as it relates to the intricate combinations of possible edge-case configurations that we may get.

@bigopon This may also be related to what you mentioned before. Due to the lack of tests in this particular use case I'm not sure yet if this is a template compiler issue or a renderer issue (it might be both), but at this point I agree with you it's probably better to keep this sort of complexity out of the renderer.

Automatically generate performance report

Now that we have some e2e benchmark scripts, I'd like to see us have something a bit similar to the krausest benchmark with a generated HTML report showing:

  • The perf difference between vNext and vCurrent
  • The perf difference between the master branch and the PR being tested

The reason I didn't simply copy-paste their code is because I prefer fully understanding how it works, and then custom-tailoring it to include more specific information that is useful to us. Then perhaps in the future we can contribute some of our findings back to the krausest repo and hopefully improve the overall quality and accuracy of benchmark land

Create test placeholders / instructions for testing

So I thought about our tests, and decided to create this. Things are working and looking good, but we need way more tests. One way to solve it is to ask @fkleuver to do everything, but it will probably be too much and take long time for him to finish everything alone, so I think I'll walk through every missing tests and create placeholder for them, together with some specs, such as what should be tested, what scenarios should be validated. Then we can easily split them up into individual issues and label test + help-wanted. That way, we can invite community members to share fun building Aurelia. Thoughts ? @EisenbergEffect @fkleuver

Split up packages?

I see several potential benefits in extracting task-queue, resources (perhaps a package per resource / logically similar resources), and the dom into their own packages.

Same for two-way element observers (the ones in element-observation.ts).
I personally think having one observer class for each input type= would make the whole observation system more robust, performant and easier to understand. We can use decorators to register events instead of using the EventManager, etc.
But why bloat up the runtime with that if people might not always need two-way observation.

The main argument against splitting stuff up was the difficulty in testing and versioning. We've overcome that problem by moving to a monorepo. We can easily cross-reference packages (even internal api) and there are no hacky workarounds needed to do cross-package integration tests.

I'm not advocating a split-up similar to vCurrent though. templating and binding make sense to have in the same package (runtime) since the separation is only in terms of lifecycle hooks - it's not like one would be usable without the other. But I don't think we should cram all runtime into the runtime package.

Good / bad idea?

If good idea, the next question is: how will we prefix these package (if we'll add them at all)? @aurelia/runtime-element-observation or simply @aurelia/element-observation?

Error: npm build / yarn build

I'm submitting a bug report

  • Library Version:
    0.1.0

Please tell us about your environment:

  • Operating System:
    Linux (Arch)

  • Node Version:
    v9.10.1

  • NPM Version:
    5.6.0

Current behavior:
npm build throws an error:

compiler/index.ts โ†’ compiler/dist/aurelia-compiler.js...

[!] (rpt2 plugin) Error: /home/ai114/experiment/compiler/binding-language.ts(236,5): semantic error TS2454 Variable 'parts' is used before being assigned.
compiler/binding-language.ts

Error: /home/ai114/experiment/compiler/binding-language.ts(236,5): semantic error TS2454 Variable 'parts' is used before being assigned.

    at error (/home/ai114/experiment/node_modules/rollup/dist/rollup.js:168:15)
    at /home/ai114/experiment/node_modules/rollup/dist/rollup.js:19392:17
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:118:7)
    at Function.Module.runMain (module.js:692:11)
    at startup (bootstrap_node.js:194:16)
    at bootstrap_node.js:666:3
  • What is the motivation?
    I want to understand / debug the DI code.

[WIP] Router API Proposal [RFC]

  • Navigation
  • Events
  • Configuration
    • View Caching
  • Navigation Model
  • Link generation

Navigation

interface IHistory {
  async back(): Promise<>
  async forward(): Promise<>
  async go(direction: number): Promise<>
  async push(???): Promise<>
  async replace(???): Promise<>
}

Events

interface INavigationStatus {
  index: number,
  instruction: INavigationInstruction,
  isFirst: boolean,
  isNew: boolean,
  isForward: boolean,
  isBack: boolean,
  isRefresh: boolean,
  isError: boolean
}

router.addEventListener('navigating', (status: INavigationStatus) => {});
router.addEventListener('navigated', (status: INavigationStatus) => {});

Configuration

interface IRouter {
  navigation: INavigationModel[];
  handleUnknownRoutes: (navigation: INavigationInstruction) => INavigationInstruction;
  buildTitle: (instructions: INavigationInstruction[]) => string;

  mapRoutes(routes: IRouteConfig[]): void;
}

type RouteGuard = (to: INavigationInstruction[], from: INavigationInstruction[], next: Next);
interface INavigable {
  before: RouteGuard[];
}

class Router implements IRouter, INavigable { }
class RouteConfig implements INavigable { }

const router = new Router();
router.handleUnknownRoutes = (instructions: INavigationInstruction[]) => { return { redirect: 'home' } };
router.buildTitle = (instructions: INavigationInstruction[]) => { 
  return instructions
    .filter((instruction) => instruction.title)
    .join(' | ');
};

const authCheck = (to, from, next): RouteGuard => {
  const isAdmin = false;
  if (!isAdmin) {
    return next({ redirect: 'home' });
  }
  return next();
};
const timeoutCheck = (to, from, next) => {
  const timedOut = true;
  if (timedOut) {
    return next({ name: 'login' }
  }
  return next();
};

router.before = [
  timeoutCheck,
  (to, from, next) => {
    const index = Math.random() * 100 % router.navigation.length;
    const route: IRouteConfig = router.navigation[index].config;
    return next(route);
  }
];
router.mapRoutes([
  {
    path: 'foo',
    component: Foo,
    before: [
      authCheck,
      (to, from, next) => next({ redirect: 'home' })
    ]
  },{
    path: 'bar',
    component: Bar,
    before: [authCheck]
  },{
    path: 'welcome',
    component: Welcome
  }
]);

Navigation Model

Make binding and templating more intelligent with information from to the w3/whatwg specs

Just some notes:

Move all JIT attribute value parsing to the ExpressionParser

  • Interpolation parsing currently happens in the TemplateCompiler. Moving this to the ExpressionParser allows interpolation expressions to be cached just like other expressions
  • Semicolon-separated custom attribute values are not being parsed yet. This should be done by the ExpressionParser.
  • Pass more context information (attribute type + binding command) to the ExpressionParser so it can make the right decisions about valid/invalid syntax.
  • Implement loose vs strict parsing mode, where strict mode will detect and throw errors as early as possible (e.g. if a two-way binding command has a non-assignable expression, warn in loose mode and throw in strict mode?) (not appropriate for this scope)

Element with only surrogate behavior gets compiled incorrectly

<template
  css="width: {size}px; height: ${size}px;">
</template>

with @bindable size

// Expected
{
  surrogates: [[StyleBindingInstruction]],
  instructions: []
}
// Actual
{
  surrogates: [[StyleBindingInstruction]],
  instructions: [[StyleBindingInstruction]]
}

This will throw 31

Wrong nodes referenced with multiple sibling/child template controllers on surrogate elements

๐Ÿ› Bug Report

Having a repeat and an if/else (this applies to various combinations and placements) on template elements results in the wrong nodes (namely the <au-loc> nodes) being removed.

my initial impression is that this is a problem in the rendering process, not in the template compiler, because the compiled instructions appear to be correct as per the unit tests (and rendering has very low coverage)

๐Ÿค” Expected Behavior

For example: <template repeat.for="item of items" if.bind="show">${item}</template>
Let's say we assign [1, 2, 3] as the items. and show=true It renders 123, terrific.
Then we assign a smaller array [1, 2]. it stays 123

๐Ÿ˜ฏ Current Behavior

Continuing on the above, after assigning [1, 2] it should render 12

๐Ÿ’ Possible Solution

I'm pretty sure it's going wrong somewhere in the rendering process. The test coverage here is low, and in my various earlier debugging sessions some of it felt somewhat happypath-coded.

Unless @EisenbergEffect has some immediate ideas, I suggest we systematically work up the test coverage in the rendering process especially around render locations and node adding/removal.

๐Ÿ”ฆ Context

I'm trying to get integration tests up and running for just about every thinkable combination of template controllers in various sibling and/or parent/child placements, on normal elements, template elements, and so forth. All of it needs to work 100%. This is step 1.

๐Ÿ’ป Code Sample

See this commented out integration test

๐ŸŒ Your Environment

N/A

Implement destructuring in the AST

The classes are already there (see ArrayBindingPattern and ObjectBindingPattern), but the parsing might need a few tweaks and the logic still needs to be implemented.

The idea is this, consider repeat.for="{ prop1, prop2: { prop2Child1, prop2Child2 } }". This can be done in a fully spec-compliant manner with the existing array and object literal parsing logic that we already have in place.
All that the AST nodes need to do is recursively loop through the keys in the BindingPattern and flatten+assign them to the BindingContext. The question is: what should evaluate do? Actually do the assignment, or just return a nested key + flat key mapping? Or should assign take care of it? Maybe add a new method instead, such as destructure? Needs experimenting

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.