Skip to content

Commit

Permalink
Allow appOptions to resolve asynchronously (#69)
Browse files Browse the repository at this point in the history
Co-authored-by: Sergej Popov <sergej.popov@just-eat.com>
  • Loading branch information
Sergej-Popov and Sergej Popov committed Feb 3, 2021
1 parent bf7a4d5 commit 1101b22
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 60 deletions.
126 changes: 67 additions & 59 deletions src/single-spa-vue.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,83 +62,91 @@ function bootstrap(opts) {
}
}

function resolveAppOptions(opts) {
if (typeof opts.appOptions === "function") {
return opts.appOptions();
}
return Promise.resolve({ ...opts.appOptions });
}

function mount(opts, mountedInstances, props) {
const instance = {};
return Promise.resolve().then(() => {
const appOptions = { ...opts.appOptions };
if (props.domElement && !appOptions.el) {
appOptions.el = props.domElement;
}
return resolveAppOptions(opts).then((appOptions) => {
if (props.domElement && !appOptions.el) {
appOptions.el = props.domElement;
}

let domEl;
if (appOptions.el) {
if (typeof appOptions.el === "string") {
domEl = document.querySelector(appOptions.el);
if (!domEl) {
throw Error(
`If appOptions.el is provided to single-spa-vue, the dom element must exist in the dom. Was provided as ${appOptions.el}`
);
let domEl;
if (appOptions.el) {
if (typeof appOptions.el === "string") {
domEl = document.querySelector(appOptions.el);
if (!domEl) {
throw Error(
`If appOptions.el is provided to single-spa-vue, the dom element must exist in the dom. Was provided as ${appOptions.el}`
);
}
} else {
domEl = appOptions.el;
if (!domEl.id) {
domEl.id = `single-spa-application:${props.name}`;
}
appOptions.el = `#${CSS.escape(domEl.id)}`;
}
} else {
domEl = appOptions.el;
if (!domEl.id) {
domEl.id = `single-spa-application:${props.name}`;
const htmlId = `single-spa-application:${props.name}`;
appOptions.el = `#${CSS.escape(htmlId)}`;
domEl = document.getElementById(htmlId);
if (!domEl) {
domEl = document.createElement("div");
domEl.id = htmlId;
document.body.appendChild(domEl);
}
appOptions.el = `#${CSS.escape(domEl.id)}`;
}
} else {
const htmlId = `single-spa-application:${props.name}`;
appOptions.el = `#${CSS.escape(htmlId)}`;
domEl = document.getElementById(htmlId);
if (!domEl) {
domEl = document.createElement("div");
domEl.id = htmlId;
document.body.appendChild(domEl);
}
}

appOptions.el = appOptions.el + " .single-spa-container";
appOptions.el = appOptions.el + " .single-spa-container";

// single-spa-vue@>=2 always REPLACES the `el` instead of appending to it.
// We want domEl to stick around and not be replaced. So we tell Vue to mount
// into a container div inside of the main domEl
if (!domEl.querySelector(".single-spa-container")) {
const singleSpaContainer = document.createElement("div");
singleSpaContainer.className = "single-spa-container";
domEl.appendChild(singleSpaContainer);
}
// single-spa-vue@>=2 always REPLACES the `el` instead of appending to it.
// We want domEl to stick around and not be replaced. So we tell Vue to mount
// into a container div inside of the main domEl
if (!domEl.querySelector(".single-spa-container")) {
const singleSpaContainer = document.createElement("div");
singleSpaContainer.className = "single-spa-container";
domEl.appendChild(singleSpaContainer);
}

instance.domEl = domEl;
instance.domEl = domEl;

if (!appOptions.render && !appOptions.template && opts.rootComponent) {
appOptions.render = (h) => h(opts.rootComponent);
}
if (!appOptions.render && !appOptions.template && opts.rootComponent) {
appOptions.render = (h) => h(opts.rootComponent);
}

if (!appOptions.data) {
appOptions.data = {};
}
if (!appOptions.data) {
appOptions.data = {};
}

appOptions.data = () => ({ ...appOptions.data, ...props });
appOptions.data = () => ({ ...appOptions.data, ...props });

if (opts.createApp) {
instance.vueInstance = opts.createApp(appOptions);
if (opts.handleInstance) {
opts.handleInstance(instance.vueInstance);
}
instance.vueInstance.mount(appOptions.el);
} else {
instance.vueInstance = new opts.Vue(appOptions);
if (instance.vueInstance.bind) {
instance.vueInstance = instance.vueInstance.bind(instance.vueInstance);
}
if (opts.handleInstance) {
opts.handleInstance(instance.vueInstance);
if (opts.createApp) {
instance.vueInstance = opts.createApp(appOptions);
if (opts.handleInstance) {
opts.handleInstance(instance.vueInstance);
}
instance.vueInstance.mount(appOptions.el);
} else {
instance.vueInstance = new opts.Vue(appOptions);
if (instance.vueInstance.bind) {
instance.vueInstance = instance.vueInstance.bind(instance.vueInstance);
}
if (opts.handleInstance) {
opts.handleInstance(instance.vueInstance);
}
}
}

mountedInstances[props.name] = instance;
mountedInstances[props.name] = instance;

return instance.vueInstance;
return instance.vueInstance;
});
});
}

Expand Down
21 changes: 21 additions & 0 deletions src/single-spa-vue.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,27 @@ describe("single-spa-vue", () => {
});
});

it(`resolves appOptions from Promise and passes straight through to Vue`, () => {
const appOptions = () =>
Promise.resolve({
something: "random",
});

const lifecycles = new singleSpaVue({
Vue,
appOptions,
});

return lifecycles
.bootstrap(props)
.then(() => lifecycles.mount(props))
.then(() => {
expect(Vue).toHaveBeenCalled();
expect(Vue.mock.calls[0][0].something).toBeTruthy();
return lifecycles.unmount(props);
});
});

it(`implements a render function for you if you provide loadRootComponent`, () => {
const opts = {
Vue,
Expand Down
2 changes: 1 addition & 1 deletion types/single-spa-vue.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ declare module "single-spa-vue" {
};

interface BaseSingleSpaVueOptions {
appOptions: AppOptions;
appOptions: AppOptions | (() => Promise<AppOptions>);
template?: string;
loadRootComponent?(): Promise<any>;
}
Expand Down

0 comments on commit 1101b22

Please sign in to comment.