Skip to content

Commit

Permalink
Merge pull request #1 from CanopyTax/initial-impl
Browse files Browse the repository at this point in the history
Initial implementation
  • Loading branch information
joeldenning authored Jul 29, 2019
2 parents 6c5fad3 + 401ae4d commit 6849095
Show file tree
Hide file tree
Showing 9 changed files with 4,878 additions and 1 deletion.
4 changes: 4 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"presets": ["@babel/preset-env"],
"plugins": ["@babel/plugin-transform-modules-umd"]
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,5 @@ typings/

# next.js build output
.next

lib
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.gitignore
lib
LICENSE
yarn.lock
.prettierignore
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: node_js
node_js:
- "node"
script: yarn test && yarn check-format && yarn build
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# single-spa-html
A helper library for single-spa and vanilla html / web components

A helper library for single-spa and vanilla html / web components.

[Full documentation](https://single-spa.js.org/docs/ecosystem-html-web-components.html)
42 changes: 42 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "single-spa-html",
"description": "A helper library for mounting and unmount html / web components as single-spa applications and parcels",
"scripts": {
"build": "rimraf lib && babel src --out-dir lib --ignore '**/*.test.js' --source-maps",
"prepublisOnly": "yarn build",
"check-format": "prettier '**/*' --check",
"test": "jest"
},
"files": [
"lib",
"typings"
],
"keywords": [
"single-spa",
"web components",
"polymer"
],
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
},
"version": "1.0.0",
"main": "lib/single-spa-html.js",
"repository": "https://github.com/CanopyTax/single-spa-html.git",
"author": "Joel Denning <joeldenning@gmail.com>",
"license": "MIT",
"devDependencies": {
"@babel/cli": "^7.5.5",
"@babel/core": "^7.5.5",
"@babel/plugin-transform-modules-umd": "^7.2.0",
"@babel/preset-env": "^7.5.5",
"@types/jest": "^24.0.15",
"babel-jest": "^24.8.0",
"husky": "^3.0.1",
"jest": "^24.8.0",
"prettier": "^1.18.2",
"pretty-quick": "^1.11.1",
"rimraf": "^2.6.3"
}
}
92 changes: 92 additions & 0 deletions src/single-spa-html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const defaultOpts = {
template: "",
domElementGetter: null
};

export default function singleSpaHtml(opts) {
if (!opts) {
throw Error(`single-spa-html must be called with an opts object`);
}

opts = { ...defaultOpts, ...opts };

if (typeof opts.template !== "string" || opts.template.trim().length === 0) {
throw Error(
`single-spa-html must be passed a 'template' opt that is a non empty string`
);
}

if (opts.domElementGetter && typeof opts.domElementGetter !== "function") {
throw Error(
`single-spa-html was given 'opts.domElementGetter', but it isn't a function`
);
}

return {
bootstrap: bootstrap.bind(null, opts),
mount: mount.bind(null, opts),
unmount: unmount.bind(null, opts)
};
}

function bootstrap(opts, props) {
return Promise.resolve();
}

function mount(opts, props) {
return Promise.resolve().then(() => {
const domElementGetter = chooseDomElementGetter(opts, props);
const domEl = domElementGetter();
if (!domEl) {
throw Error(
`single-spa-html: domElementGetter did not return a valid dom element`
);
}
domEl.innerHTML = opts.template;
});
}

function unmount(opts, props) {
return Promise.resolve().then(() => {
const domElementGetter = chooseDomElementGetter(opts, props);
const domEl = domElementGetter();
if (!domEl) {
throw Error(
`single-spa-html: domElementGetter did not return a valid dom element`
);
}
domEl.innerHTML = "";
});
}

function chooseDomElementGetter(opts, props) {
if (props.domElement) {
return () => props.domElement;
} else if (props.domElementGetter) {
return props.domElementGetter;
} else if (opts.domElementGetter) {
return opts.domElementGetter;
} else {
return defaultDomElementGetter(props);
}
}

function defaultDomElementGetter(props) {
const htmlId = `single-spa-application:${props.appName || props.name}`;
if (!htmlId) {
throw Error(
`single-spa-html was not given an application name as a prop, so it can't make a unique dom element container for the ht l application`
);
}

return function defaultDomEl() {
let domElement = document.getElementById(htmlId);
if (!domElement) {
domElement = document.createElement("div");
domElement.id = htmlId;
document.body.appendChild(domElement);
}

return domElement;
};
}
68 changes: 68 additions & 0 deletions src/single-spa-html.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import singleSpaHtml from "./single-spa-html";

describe("single-spa-html", () => {
const domElementGetter = () => document.getElementById("test-div");
let props;

beforeEach(() => {
const div = document.createElement("div");
div.id = "test-div";
document.body.appendChild(div);

props = { name: "test" };
});

afterEach(() => {
document.getElementById("test-div").remove();
});

it("adds html to a provided dom element", () => {
const lifecycles = singleSpaHtml({
template: "<some-web-component></some-web-component>",
domElementGetter
});

const domEl = domElementGetter();
expect(domEl.innerHTML.trim()).toBe("");
return lifecycles
.bootstrap(props)
.then(() => lifecycles.mount(props))
.then(() => {
expect(domEl.innerHTML.trim()).toBe(
"<some-web-component></some-web-component>"
);
return lifecycles.unmount(props);
})
.then(() => {
expect(domEl.innerHTML.trim()).toBe("");
return lifecycles.mount(props);
})
.then(() => {
expect(domEl.innerHTML.trim()).toBe(
"<some-web-component></some-web-component>"
);
});
});

it(`throws if you don't provide a template`, () => {
expect(() => {
singleSpaHtml({});
}).toThrow();
});

it(`throws if you provide a non-string template`, () => {
expect(() => {
singleSpaHtml({
template: 123
});
}).toThrow();
});

it(`throws if you provide a domElementGetter that is not a function`, () => {
expect(() => {
singleSpaHtml({
template: 123
});
}).toThrow();
});
});
Loading

0 comments on commit 6849095

Please sign in to comment.