diff --git a/lib/extend.js b/lib/extend.js
index 2840bc3..0d1f956 100644
--- a/lib/extend.js
+++ b/lib/extend.js
@@ -1,28 +1,22 @@
const utils = require('./utils');
-module.exports = (dom, extendingAppManifest) => {
- const doc = dom.window.document;
- const _$ = doc.querySelector.bind(doc);
- const _$$ = doc.querySelectorAll.bind(doc);
- utils.injectBundleIntoDom(dom, extendingAppManifest.bundle);
- _$('[export]').setAttribute('export', extendingAppManifest.appName);
- Array.from(_$$('[resource]')).forEach(e =>
- e.setAttribute('resource', 'resource')
- );
- Array.from(_$$('script:not([resource]),style:not([resource]),link:not([resource])')).forEach(e =>
- e.parentElement.removeChild(e)
- );
+module.exports = ($, extendingAppManifest) => {
+ utils.injectBundleIntoDom($, extendingAppManifest.bundle);
+ $('[export]').attr('export', extendingAppManifest.appName);
+
+ $('script:not([resource]),style:not([resource]),link:not([resource])').remove();
+
if (extendingAppManifest.overrides && extendingAppManifest.overrides.length) {
extendingAppManifest.overrides.forEach(override => {
- const elem = _$(`${override.tag}[public="${override.target}"]`);
+ const selector = `${override.tag}[public="${override.target}"]`;
+ const $elem = $(selector);
let content = override.content;
const superTagPattern = /()|(.*<\/\s*super>)/g;
if (superTagPattern.test(content)) {
- content = content.replace(superTagPattern, elem.innerHTML);
+ content = content.replace(superTagPattern, $elem.html());
}
const tagName = override.target && override.target !== '' ? override.target : override.tag;
- utils.replaceElement(dom, elem, tagName, content);
+ utils.replaceElement($, selector, tagName, content);
});
}
- return dom;
};
diff --git a/lib/import.js b/lib/import.js
index f5d0bd7..eccbe12 100644
--- a/lib/import.js
+++ b/lib/import.js
@@ -1,27 +1,21 @@
const utils = require('./utils');
-module.exports = (dom, importedApps = {}) => {
+module.exports = ($, importedApps = {}) => {
const injected = [];
- const doc = dom.window.document;
- const _$$ = doc.querySelectorAll.bind(doc);
- Array.from(_$$('head [import]')).forEach(elem =>
- elem.parentNode.removeChild(elem)
- );
- Array.from(_$$('fragment')).forEach(elem => {
- const appName = utils.getAttr(elem, 'name');
+
+ $('head [import]').remove();
+
+ $('fragment').each((index, elem) => {
+ const appName = $(elem).attr('name');
if (importedApps[appName] && importedApps[appName].content) {
// inject content
- utils.replaceElement(dom, elem, appName, importedApps[appName].content);
- if (injected.indexOf(appName) === -1) {
- utils.injectBundleIntoDom(dom, importedApps[appName].bundle);
+ utils.replaceElement($, elem, appName, importedApps[appName].content);
+ if (!injected.includes(appName)) {
+ utils.injectBundleIntoDom($, importedApps[appName].bundle);
injected.push(appName);
}
} else {
- elem.parentNode.removeChild(elem);
+ $(elem).remove(elem);
}
});
- Array.from(_$$('[resource]')).forEach(e =>
- e.setAttribute('resource', 'resource')
- );
- return dom;
};
diff --git a/lib/manifest.js b/lib/manifest.js
index 73e738e..dfa0fea 100644
--- a/lib/manifest.js
+++ b/lib/manifest.js
@@ -1,38 +1,40 @@
const utils = require('./utils');
-const JSDOM = require('JSDOM').JSDOM;
module.exports = html => {
if (!html || typeof html !== 'string') {
return;
}
- const dom = utils.parse(html);
- const _$ = dom.window.document.querySelector.bind(dom.window.document);
- const _$$ = dom.window.document.querySelectorAll.bind(dom.window.document);
- const manifest = utils.cleanObject({
- appName: utils.getAttr(_$('head meta[export]'), 'export'),
- baseUrl: utils.getAttr(_$('head meta[public-url]'), 'public-url'),
- extending: utils.getAttr(_$('head meta[export]'), 'extends') || undefined,
- alias: utils.getAttr(_$('head meta[export-as]'), 'export-as') || undefined,
- type: utils.getAttr(_$('head meta[of-type]'), 'of-type') || 'page',
- uses: Array.from(_$$('head meta[import]')).map(elem =>
- utils.getAttr(elem, 'import')
- ),
- bundle: Array.from(_$$('[resource]')).map(elem =>
- utils.cleanObject(utils.toBundleItem(elem))
- ),
- overrides: Array.from(_$$('[override]')).map(elem =>
- utils.cleanObject(utils.toOverrideItem(elem))
- ),
- publics: Array.from(_$$('[public]')).map(
- elem => utils.getAttr(elem, 'public') || elem.tagName
- ),
- content: utils.toContent(new JSDOM(dom.serialize())) || '',
+ const $ = utils.parse(html);
+
+ const manifest = {
+ appName: $('head meta[export]').attr('export'),
+ baseUrl: $('head meta[public-url]').attr('public-url'),
+ extending: $('head meta[extends]').attr('extends'),
+ alias: $('head meta[export-as]').attr('export-as'),
+ type: $('head meta[of-type]').attr('of-type') || 'page',
+ uses: [],
+ bundle: [],
+ overrides: [],
+ publics: [],
+ content: utils.toContent(utils.parse(utils.serialize($))) || '',
raw: utils.minify(html),
- });
+ };
+
+ $('meta[import]').each((index, elem) => {
+ manifest.uses.push($(elem).attr('import'))});
+ $('[resource]').each((index, elem) =>
+ manifest.bundle.push(utils.toBundleItem($, elem))
+ );
+ $('[override]').each((index, elem) =>
+ manifest.overrides.push(utils.toOverrideItem($, elem))
+ );
+ $('[public]').each((index, elem) =>
+ manifest.publics.push($(elem).attr('public') || elem.name)
+ );
if (!manifest.appName) {
throw new Error(' is not available');
}
- return manifest;
+ return utils.cleanObject(manifest);
};
diff --git a/lib/mfhtml.js b/lib/mfhtml.js
index e0a0b30..fee030e 100644
--- a/lib/mfhtml.js
+++ b/lib/mfhtml.js
@@ -29,7 +29,7 @@ class MFHTML {
const manifest = generateManifest(html);
this.graph[manifest.appName] = manifest;
} catch (e) {
- throw new Error('Not able to generate manifest for given html');
+ throw new Error(e);
}
} else {
throw new Error('mfhml.register requires an html');
@@ -39,26 +39,30 @@ class MFHTML {
/**
* Searches and retrieves the app by name and returns the generated html
* @param {string} appName
- * @param {null | string} baseUrl
+ * @param scriptToInject
*/
- get(appName, baseUrl = null) {
+ get(appName, scriptToInject) {
const foundAppManifest = this.getManifest(appName);
- const appBaseUrl = baseUrl || foundAppManifest.baseUrl;
- let appDom = UrlFix(utils.parse(foundAppManifest.raw), appBaseUrl);
+ const appBaseUrl = foundAppManifest.baseUrl || '';
+ let appDom = utils.parse(foundAppManifest.raw);
+ UrlFix(appDom, appBaseUrl);
if (foundAppManifest.extending) {
if (this.getAppNames().includes(foundAppManifest.extending)) {
const superDom = utils.parse(this.get(foundAppManifest.extending));
foundAppManifest.bundle = foundAppManifest.bundle.map(bundle => ({
...bundle,
- path: bundle.path ? utils.combineUrl(appBaseUrl, bundle.path) : bundle.path,
+ path: bundle.path
+ ? utils.combineUrl(appBaseUrl, bundle.path)
+ : bundle.path,
}));
- appDom = Extend(superDom, foundAppManifest);
+ Extend(superDom, foundAppManifest);
+ appDom = superDom;
} else {
throw new Error(`undefined super '${foundAppManifest.extending}'`);
}
}
if (foundAppManifest.uses && foundAppManifest.uses.length) {
- appDom = Import(
+ Import(
appDom,
foundAppManifest.uses.reduce((importedAppManifest, importedAppName) => {
if (this.getAppNames().includes(importedAppName)) {
@@ -72,7 +76,11 @@ class MFHTML {
}, {})
);
}
- return utils.serialize(utils.cleanPrivates(appDom));
+ utils.cleanPrivates(appDom);
+ if (scriptToInject) {
+ utils.injectScript(appDom, scriptToInject);
+ }
+ return utils.serialize(appDom);
}
/**
diff --git a/lib/url-fixer.js b/lib/url-fixer.js
index 7035ebf..ba59692 100644
--- a/lib/url-fixer.js
+++ b/lib/url-fixer.js
@@ -37,19 +37,18 @@ const ElementsWithUrlSelectors = {
*/
// will ignore inline styles
-module.exports = (dom, baseUri) => {
+module.exports = ($, baseUri) => {
if (baseUri) {
- _$$ = dom.window.document.querySelectorAll.bind(dom.window.document);
Object.keys(ElementsWithUrlSelectors).forEach(attr => {
const tags = ElementsWithUrlSelectors[attr];
const selector = tags.map(tag => `${tag}[${attr}]`).join(', ');
- Array.from(_$$(selector)).forEach(elem => {
- const attrValue = elem.getAttribute(attr);
+ $(selector).each((index, elem) => {
+ const $elem = $(elem);
+ const attrValue = $elem.attr(attr);
if (attrValue && /https?:\/\//g.test(attrValue) !== true) {
- elem.setAttribute(attr, utils.combineUrl(baseUri, attrValue));
+ $elem.attr(attr, utils.combineUrl(baseUri, attrValue));
}
});
});
}
- return dom;
};
diff --git a/lib/utils.js b/lib/utils.js
index 2f9a57c..4f5038c 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,4 +1,4 @@
-const JSDOM = require('jsdom').JSDOM;
+const cheerio = require('cheerio');
const htmlMinify = require('html-minifier').minify;
const minifyOptions = {
collapseWhitespace: true,
@@ -7,22 +7,16 @@ const minifyOptions = {
collapseBooleanAttributes: true,
};
-const parse = html => new JSDOM(html);
+const parse = html => cheerio.load(html);
const minify = html => htmlMinify(html, minifyOptions);
-const serialize = dom => dom && dom.serialize && minify(dom.serialize(dom));
+const serialize = $ => $ && minify($.html());
-const getAttr = (elem, attr) => elem && elem.getAttribute(attr);
-
-const setAttr = (elem, attr, value) => elem.setAttribute(attr, value);
-
-const replaceElement = (dom, elem, tag, content) => {
+const replaceElement = ($, elemSelector, tag, content) => {
if (content) {
- const doc = dom.window.document;
- const wrapper = doc.createElement(tag);
- wrapper.innerHTML = content;
- elem && elem.parentNode.replaceChild(wrapper, elem);
+ const wrapper = `<${tag}>${content}${tag}>`;
+ $(elemSelector).replaceWith(wrapper);
}
};
@@ -43,32 +37,21 @@ const cleanObject = obj =>
return res;
}, {});
-const toBundleItem = elem => ({
- type: elem.tagName,
- path:
- elem.tagName === 'LINK'
- ? elem.getAttribute('href')
- : elem.getAttribute('src'),
- content: minify(elem.innerHTML),
- attributes: Object.assign(
- {},
- ...Array.from(elem.attributes, ({ name, value }) => ({ [name]: value }))
- ),
+const toBundleItem = ($, elem) => ({
+ type: elem.name.toUpperCase(),
+ content: minify($(elem).html()),
+ attributes: cleanObject(Object.assign({}, elem.attribs || {})),
});
-const toOverrideItem = elem => ({
- tag: elem.tagName,
- target: elem.getAttribute('override') || '',
- content: elem.innerHTML,
+const toOverrideItem = ($, elem) => ({
+ tag: elem.name.toUpperCase(),
+ target: $(elem).attr('override'),
+ content: $(elem).html(),
});
-const toContent = dom => {
- Array.from(dom.window.document.getElementsByTagName('script')).forEach(
- elem => {
- elem.parentNode.removeChild(elem);
- }
- );
- return minify(dom.window.document.body.innerHTML);
+const toContent = $ => {
+ $('script').remove();
+ return minify($('body').html());
};
const combineUrl = (prefix, urlPart) =>
@@ -76,59 +59,49 @@ const combineUrl = (prefix, urlPart) =>
.join('/')
.replace('/./', '/');
-const injectBundleIntoDom = (dom, bundle) => {
+const attributesToString = attributes =>
+ Object.entries(attributes)
+ .map(([atr, value]) => `${atr}="${value}"`)
+ .join(' ');
+
+const injectBundleIntoDom = ($, bundle) => {
if (!bundle || !bundle.length) {
return;
}
- const doc = dom.window.document;
- const head = doc.getElementsByTagName('head')[0];
- const body = doc.body;
+ const headerTypes = ['LINK', 'STYLE'];
+ const bodyTypes = ['SCRIPT'];
+ const headContent = bundle
+ .filter(({ type }) => headerTypes.includes(type))
+ .map(
+ ({ type, content, attributes }) =>
+ `<${type} ${attributesToString(attributes)}>${content}${type}>`
+ )
+ .join('');
+ const bodyContent = bundle
+ .filter(({ type }) => bodyTypes.includes(type))
+ .map(
+ ({ type, content, attributes }) =>
+ `<${type} ${attributesToString(attributes)}>${content}${type}>`
+ )
+ .join('');
+ $('head').append(headContent);
+ $('body').append(bodyContent);
+};
- bundle.forEach(item => {
- const typesWithContent = ['SCRIPT', 'STYLE'];
- const typesWithPath = ['SCRIPT', 'LINK'];
- const { type, path, content, attributes } = item;
- const itemElem = doc.createElement(type);
- Object.entries(attributes).forEach(([attr, value]) => {
- itemElem.setAttribute(attr, value);
- });
- itemElem.setAttribute('resource', 'resource');
- if (typesWithPath.some(t => t === type) && path) {
- itemElem.setAttribute(type === 'LINK' ? 'href' : 'src', path);
- }
- if (typesWithContent.some(t => t === type) && content) {
- const textNode = doc.createTextNode(content);
- itemElem.appendChild(textNode);
- }
- switch (type) {
- case 'LINK':
- case 'STYLE':
- head.appendChild(itemElem);
- break;
- case 'SCRIPT':
- body.appendChild(itemElem);
- break;
- default:
- throw new Error('Unexpected type for bundle item ' + type);
- }
- });
+const cleanPrivates = $ => {
+ $('[private]').remove();
};
-const cleanPrivates = dom => {
- const doc = dom.window.document;
- const _$$ = doc.querySelectorAll.bind(doc);
- Array.from(_$$('[private]')).forEach(elem =>
- elem.parentNode.removeChild(elem)
- );
- return dom;
+const injectScript = ($, script) => {
+ $('script')
+ .first()
+ .before(script);
};
module.exports = {
parse,
minify,
serialize,
- getAttr,
- setAttr,
replaceElement,
cleanObject,
toBundleItem,
@@ -137,4 +110,5 @@ module.exports = {
combineUrl,
injectBundleIntoDom,
cleanPrivates,
+ injectScript,
};
diff --git a/package.json b/package.json
index b64ae3d..896e3b6 100644
--- a/package.json
+++ b/package.json
@@ -10,8 +10,8 @@
"test": "mocha --watch --recursive"
},
"dependencies": {
- "html-minifier": "^3.5.21",
- "jsdom": "^13.2.0"
+ "cheerio": "^1.0.0-rc.3",
+ "html-minifier": "^3.5.21"
},
"devDependencies": {
"chai": "^4.2.0",
diff --git a/test/extend/app.html.js b/test/extend/app.html.js
index fb97c72..9514865 100644
--- a/test/extend/app.html.js
+++ b/test/extend/app.html.js
@@ -1,4 +1,4 @@
-exports.SomeApp = /*html*/ `
+exports.SomeApp = `
@@ -12,7 +12,7 @@ exports.SomeApp = /*html*/ `
`;
-exports.SomeApp1 = /*html*/ `
+exports.SomeApp1 = `
@@ -31,18 +31,18 @@ exports.SomeApp1 = /*html*/ `
`;
-exports.Expected = /*html*/ `
+exports.Expected = `
SomeApp Title
-
-
+
+
Header Override
Content Override
-
-
+
+
`;
diff --git a/test/extend/extend.js b/test/extend/extend.js
index d51cf5f..8ce44f7 100644
--- a/test/extend/extend.js
+++ b/test/extend/extend.js
@@ -11,8 +11,8 @@ const someAppManifest = manifest(someApp);
describe('Extend', () => {
it('should extend SomeApp1 with SomeApp and produced expected html', () => {
- const processed = extend(someApp1Parsed, someAppManifest);
- const serialized = utils.serialize(processed);
+ extend(someApp1Parsed, someAppManifest);
+ const serialized = utils.serialize(someApp1Parsed);
expect(serialized).to.be.equal(utils.minify(Expected));
});
});
diff --git a/test/import/app.html.js b/test/import/app.html.js
index b5e12c9..b5791df 100644
--- a/test/import/app.html.js
+++ b/test/import/app.html.js
@@ -1,4 +1,4 @@
-exports.SomeApp = /*html*/ `
+exports.SomeApp = `
@@ -19,7 +19,7 @@ exports.SomeApp = /*html*/ `
`;
-exports.SomeApp1 = /*html*/ `
+exports.SomeApp1 = `
@@ -32,13 +32,13 @@ exports.SomeApp1 = /*html*/ `
`;
-exports.Expected = /*html*/ `
+exports.Expected = `
SomeApp Title
-
-
+
+
@@ -50,10 +50,10 @@ exports.Expected = /*html*/ `
Some App 1 body
-
-
-
+
+
`;
diff --git a/test/import/import.js b/test/import/import.js
index ac5d4d9..612a26c 100644
--- a/test/import/import.js
+++ b/test/import/import.js
@@ -11,8 +11,8 @@ const someApp1Manifest = manifest(someApp1);
describe('Import', () => {
it('should replace fragment and produce expected html', () => {
- const processed = _import(someAppParsed, { SomeApp1: someApp1Manifest });
- const serialized = utils.serialize(processed);
+ _import(someAppParsed, { SomeApp1: someApp1Manifest });
+ const serialized = utils.serialize(someAppParsed);
expect(serialized).to.be.equal(utils.minify(Expected));
});
});
diff --git a/test/manifest/app.html.js b/test/manifest/app.html.js
index 9d897e9..5515991 100644
--- a/test/manifest/app.html.js
+++ b/test/manifest/app.html.js
@@ -1,4 +1,4 @@
-exports.app = /*html*/ `
+exports.app = `
diff --git a/test/mfhtml/mfhtml.js b/test/mfhtml/mfhtml.js
index 219aaed..a6d8ed2 100644
--- a/test/mfhtml/mfhtml.js
+++ b/test/mfhtml/mfhtml.js
@@ -78,7 +78,7 @@ describe('MFHTML runtime', () => {
it('should throw error on register with wrong html', () => {
expect(() => mfhtml.register(mock.badHtml)).to.throw(
- 'Not able to generate manifest for given html'
+ ' is not available'
);
});
diff --git a/test/mfhtml/mock.html.js b/test/mfhtml/mock.html.js
index 6256f05..e4efd09 100644
--- a/test/mfhtml/mock.html.js
+++ b/test/mfhtml/mock.html.js
@@ -1,13 +1,13 @@
const utils = require('../../lib/utils');
-exports.badHtml = /*html*/ `
+exports.badHtml = `
Bad Html
Bad Html Body
`;
-const noDependencyApp = /*html*/ `
+const noDependencyApp = `
No dependency App
@@ -41,7 +41,7 @@ exports.noDependencyAppManifest = {
raw: utils.minify(noDependencyApp),
};
-exports.App = /*html*/ `
+exports.App = `
@@ -72,7 +72,7 @@ exports.App = /*html*/ `
`;
-exports.SomeApp1 = /*html*/ `
+exports.SomeApp1 = `
@@ -85,7 +85,7 @@ exports.SomeApp1 = /*html*/ `
`;
-exports.SomeApp2 = /*html*/ `
+exports.SomeApp2 = `
@@ -99,7 +99,7 @@ exports.SomeApp2 = /*html*/ `
`;
-exports.SomeApp3 = /*html*/ `
+exports.SomeApp3 = `
@@ -113,7 +113,7 @@ exports.SomeApp3 = /*html*/ `
`;
-exports.ExtendableApp = /*html*/ `
+exports.ExtendableApp = `
diff --git a/test/url-fixer/app.html.js b/test/url-fixer/app.html.js
index ca78ae7..f74fdd3 100644
--- a/test/url-fixer/app.html.js
+++ b/test/url-fixer/app.html.js
@@ -14,12 +14,12 @@ exports.SomeApp = `
exports.Expected = `
-
-
+
+
-
+
`;
diff --git a/test/url-fixer/url-fixer.js b/test/url-fixer/url-fixer.js
index c2e4690..f532515 100644
--- a/test/url-fixer/url-fixer.js
+++ b/test/url-fixer/url-fixer.js
@@ -6,52 +6,52 @@ const Expected = require('./app.html').Expected;
describe('Url Fix', () => {
it('should fix img url', () => {
- const dom = utils.parse(someApp);
- urlFix(dom, 'http://some.domain.name');
- expect(
- dom.window.document.getElementById('img1').getAttribute('src')
- ).to.be.equal('http://some.domain.name/assets/someimage.jpg');
+ const $ = utils.parse(someApp);
+ urlFix($, 'http://some.domain.name');
+ expect($('#img1').attr('src')).to.be.equal(
+ 'http://some.domain.name/assets/someimage.jpg'
+ );
});
it('should fix link url', () => {
- const dom = utils.parse(someApp);
- urlFix(dom, 'http://some.domain.name');
- expect(
- dom.window.document.getElementById('link1').getAttribute('href')
- ).to.be.equal('http://some.domain.name/someapp.css');
+ const $ = utils.parse(someApp);
+ urlFix($, 'http://some.domain.name');
+ expect($('#link1').attr('href')).to.be.equal(
+ 'http://some.domain.name/someapp.css'
+ );
});
it('should not fix link absolute url', () => {
- const dom = utils.parse(someApp);
- urlFix(dom, 'http://some.domain.name');
- expect(
- dom.window.document.getElementById('link2').getAttribute('href')
- ).to.be.equal('https://cdn.server.com/someapp.css?13456');
+ const $ = utils.parse(someApp);
+ urlFix($, 'http://some.domain.name');
+ expect($('#link2').attr('href')).to.be.equal(
+ 'https://cdn.server.com/someapp.css?13456'
+ );
});
it('should fix script url', () => {
- const dom = utils.parse(someApp);
- urlFix(dom, 'http://some.domain.name');
- expect(
- dom.window.document.getElementById('script1').getAttribute('src')
- ).to.be.equal('http://some.domain.name/js/someapp.js');
+ const $ = utils.parse(someApp);
+ urlFix($, 'http://some.domain.name');
+ expect($('#script1').attr('src')).to.be.equal(
+ 'http://some.domain.name/js/someapp.js'
+ );
});
it('should fix video src and poster url', () => {
- const dom = utils.parse(someApp);
- urlFix(dom, 'http://some.domain.name');
- expect(
- dom.window.document.getElementById('video1').getAttribute('poster')
- ).to.be.equal('http://some.domain.name/someposter.png');
- expect(
- dom.window.document.getElementById('video1').getAttribute('src')
- ).to.be.equal('http://some.domain.name/somevideo.mpeg');
+ const $ = utils.parse(someApp);
+ urlFix($, 'http://some.domain.name');
+ expect($('#video1').attr('poster')).to.be.equal(
+ 'http://some.domain.name/someposter.png'
+ );
+ expect($('#video1').attr('src')).to.be.equal(
+ 'http://some.domain.name/somevideo.mpeg'
+ );
});
it('should fix SomeApp with http://some.domain.name and produce expected html', () => {
- const dom = utils.parse(someApp);
- urlFix(dom, 'http://some.domain.name');
- const serialized = utils.serialize(dom);
+ const $ = utils.parse(someApp);
+ urlFix($, 'http://some.domain.name');
+ const serialized = utils.serialize($);
expect(serialized).to.be.equal(utils.minify(Expected));
});
});