aurelia-hot-module-reload
Version:
Tools designed to enable HMR for Aurelia's loaders.
124 lines (102 loc) • 4.62 kB
text/typescript
import { TargetInstruction, Controller, View, BehaviorInstruction } from 'aurelia-templating';
import { AUController, ViewFactoryWithTemplate, ViewCorrect, ViewSlotCorrect } from './_typings';
import { Container } from 'aurelia-dependency-injection';
export function recreateView(viewFactory: ViewFactoryWithTemplate, oldViewContainer: Container) {
let parentContainer = oldViewContainer.parent || oldViewContainer;
const targetInstruction = oldViewContainer.get(TargetInstruction) as TargetInstruction;
// const targetInstruction = container.get(TargetInstruction) as TargetInstruction;
let factoryCreateInstruction = targetInstruction.elementInstruction || ({partReplacements: null} as BehaviorInstruction);
// let factoryCreateInstruction = ({partReplacements: null} as BehaviorInstruction);
// console.log(`new element instruction`, targetInstruction, factoryCreateInstruction);
const newContainer = parentContainer.createChild();
newContainer._resolvers = oldViewContainer._resolvers;
// const newContainer = oldViewContainer;
const newView = viewFactory.create(newContainer, factoryCreateInstruction) as ViewCorrect;
newView._isUserControlled = true;
return newView as ViewCorrect;
}
export function cleanupView(view: ViewCorrect) {
const firstChild = view.firstChild;
const lastChild = view.lastChild;
const nextSibling = lastChild.nextSibling;
const parent = firstChild.parentElement;
const { bindingContext, overrideContext, container } = view;
view.removeNodes();
let wasAttached = view.isAttached;
if (wasAttached) {
view.detached();
}
let wasBound = view.isBound;
if (wasBound) {
view.unbind();
}
view._invalidView = true;
return {nextSibling, parent, wasBound, wasAttached, bindingContext, overrideContext, container}
}
export function rerenderController(e: AUController, type: 'scope' | 'view', newViewFactory: ViewFactoryWithTemplate) {
let oldView = e[type] as ViewCorrect;
if (!oldView) {
// view was removed from the controller in a previous run, ignore
return
}
if (oldView._invalidView) {
// previously re-rendered, ensure controller is set and skip
if (oldView._replacementView) {
e[type] = oldView._replacementView;
}
return;
}
const { nextSibling, parent, wasBound, wasAttached, bindingContext, overrideContext, container: oldViewContainer } = cleanupView(oldView)
// create & add view:
const newView = oldView._replacementView = e[type] = recreateView(newViewFactory || oldView.viewFactory, oldViewContainer);
if (!newView.isBound && wasBound) {
newView.bind(bindingContext, overrideContext);
}
if (nextSibling) {
newView.insertNodesBefore(nextSibling);
} else {
newView.appendNodesTo(parent as HTMLElement);
}
if (!newView.isAttached && wasAttached) {
newView.attached();
}
}
export function rerenderMatchingSlotChildren(slot: ViewSlotCorrect, newViewFactory?: ViewFactoryWithTemplate, originalFactoryTemplate?: any, onlyViews?: Array<ViewCorrect>) {
const previousChildren = slot.children.slice()
const viewsToReplace = previousChildren.filter(view => (onlyViews && onlyViews.indexOf(view) >= 0) || (view.viewFactory && view.viewFactory.template === originalFactoryTemplate))
const bindingContexts = new Map<View, any>()
const overrideContexts = new Map<View, any>()
const controllers = new Map<View, Controller>()
viewsToReplace.forEach(oldView => {
// store contexts because they'll be removed when unbound:
bindingContexts.set(oldView, oldView.bindingContext);
overrideContexts.set(oldView, oldView.overrideContext);
controllers.set(oldView, oldView.controller);
if (oldView.isBound) {
oldView.unbind();
}
oldView._invalidView = true
})
slot.removeMany(viewsToReplace, false, true);
// recreate removed Views in the same place:
previousChildren.forEach((oldView, index) => {
if (!oldView._invalidView) {
// don't do anything to non-matching Views
return
}
const bindingContext = bindingContexts.get(oldView);
const overrideContext = overrideContexts.get(oldView);
const controller = controllers.get(oldView);
const view = recreateView(newViewFactory || oldView.viewFactory, oldView.container);
// setup _replacementView in case the same view is looped over again
oldView._replacementView = view;
if (controller) {
controller.view = view
}
if (!view.isBound) {
view.bind(bindingContext, overrideContext);
}
// indicies should match up and grow as we iterate up
slot.insert(index, view);
});
}