Skip to content

Commit

Permalink
initial
Browse files Browse the repository at this point in the history
  • Loading branch information
sempostma committed Nov 11, 2018
0 parents commit 3715dfd
Show file tree
Hide file tree
Showing 14 changed files with 250 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .babelrc
@@ -0,0 +1,12 @@
{
"presets": [
[
"env",
{
"targets": {
"node": "4"
}
}
]
]
}
6 changes: 6 additions & 0 deletions .gitignore
@@ -0,0 +1,6 @@
.vscode/
node_modules/

logs/
*.log
npm-debug.log*
3 changes: 3 additions & 0 deletions index.js
@@ -0,0 +1,3 @@
import loader from './lib/loader';

export default loader;
3 changes: 3 additions & 0 deletions jest.config.js
@@ -0,0 +1,3 @@
module.exports = {
testEnvironment: 'node',
};
20 changes: 20 additions & 0 deletions lib/base.js
@@ -0,0 +1,20 @@

export const document_createDocumentFragment = () => {
return document.createDocumentFragment();
}

export const document_createElement = name => {
return document.createElement(name);
}

export const document_createTextNode = text => {
return document.createTextNode(text);
}

export const appendChild = (parent, child) => {
parent.appendChild(child);
}

export const setAttribute = (elem, key, value) => {
elem.setAttribute(key, value);
}
11 changes: 11 additions & 0 deletions lib/config.js
@@ -0,0 +1,11 @@
export default {
document: {
createElement: name => `document_createElement("${name}")`,
createTextNode: text => `document_createTextNode("${text}")`,
createDocumentFragment: () => 'document_createDocumentFragment()',
},
elem: {
appendChild: (parent, child) => `appendChild(${parent}, ${child})`,
setAttribute: (elem, key, value) => `setAttribute(${elem}, "${key}", "${value}")`
}
};
70 changes: 70 additions & 0 deletions lib/html2js.js
@@ -0,0 +1,70 @@
import { JSDOM } from 'jsdom';
import config from './config';

const escapeRegExp = text => text.replace(/\r?\n\s*/gm, '\\n').replace(/"/g, '\\"');

export const parse = (html, { functionName = 'createNode' } = {}) => {
const { document, Node } = new JSDOM().window;

var div = document.createElement('div');
div.innerHTML = html;
div.normalize();
var res = 'function ' + functionName + '() {\n';
var vi = 0;

if (div.childNodes.length > 1) {
res += 'var container = ' + config.document.createDocumentFragment() + ';\n';
res += parseRecursive(div, 'container');
res += 'return container;\n';
} else {
res += parseRecursive(div.childNodes[0], null);
}
res + '}';
return res;

function parseRecursive(elem, parent) {
var ret = '';
switch (elem.nodeType) {
case Node.ELEMENT_NODE:
break;
case Node.TEXT_NODE:
if (elem.textContent.trim() === '') return '';
const textNodeInstruction = config.document.createTextNode(escapeRegExp(elem.textContent));
if (parent) ret += config.elem.appendChild(parent, textNodeInstruction) + ';\n';
else ret += 'return ' + textNodeInstruction + ';\n';
return ret;
default: throw 'element with node type ' + elem.nodeType + ' should not be in loaded with this loader';
}
var name = 'e_' + vi++;
ret += ('var ');
ret += (name);
ret += ' = ' + config.document.createElement(elem.tagName.toLowerCase()) + ';\n';
var attrs = Array.prototype.slice.apply(elem.attributes);
for (var i = 0; i < attrs.length; i++) {
ret += config.elem.setAttribute(name, attrs[i].name, attrs[i].value) + ';\n';
}
var children = Array.prototype.slice.apply(elem.childNodes);
for (var j = 0; j < children.length; j++) {
ret += parseRecursive(children[j], name);
}
if (parent) ret += config.elem.appendChild(parent, name) + ';\n';
else ret += 'return ' + name + ';\n';
return ret;
}
}

const parserError = parsedDocument => {
// parser and parsererrorNS could be cached on startup for efficiency
var parser = new DOMParser(),
errorneousParse = parser.parseFromString('<', 'text/xml'),
parsererrorNS = errorneousParse.getElementsByTagName("parsererror")[0].namespaceURI;

if (parsererrorNS === 'http://www.w3.org/1999/xhtml') {
// In PhantomJS the parseerror element doesn't seem to have a special namespace, so we are just guessing here :(
return parsedDocument.getElementsByTagName("parsererror").length > 0
? parsedDocument.children[0] : false;
}

return parsedDocument.getElementsByTagNameNS(parsererrorNS, 'parsererror').length > 0
? parsedDocument.children[0] : false;
};
28 changes: 28 additions & 0 deletions lib/loader.js
@@ -0,0 +1,28 @@
import { getOptions, stringifyRequest } from 'loader-utils';
import validateOptions from 'schema-utils';

import { parse } from './html2js';

const schema = {
type: 'object',
properties: {
name: {
type: 'string'
}
}
};

export default function (source) {
const options = getOptions(this);

validateOptions(schema, options, 'HTML2JS Loader');

source = parse(source);

const common = "import { document_createElement, document_createTextNode, "
+ "appendChild, setAttribute, document_createDocumentFragment } from " +
stringifyRequest(this, "!" + require.resolve("./base")) +
";\n\n";

return common + `export default ${source} }`;
}
27 changes: 27 additions & 0 deletions package.json
@@ -0,0 +1,27 @@
{
"name": "html2js-loader",
"version": "1.0.0",
"description": "",
"scripts": {
"test": "jest",
"webpack": "webpack"
},
"main": "index.js",
"author": "Sem Postma",
"license": "ISC",
"dependencies": {
"jsdom": "^13.0.0",
"loader-utils": "^1.1.0",
"schema-utils": "^1.0.0"
},
"devDependencies": {
"babel-core": "^6.26.3",
"babel-jest": "^23.6.0",
"babel-preset-env": "^1.7.0",
"jest": "^23.6.0",
"memory-fs": "^0.4.1",
"regenerator-runtime": "^0.12.1",
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2"
}
}
21 changes: 21 additions & 0 deletions test/compiler.js
@@ -0,0 +1,21 @@
import path from 'path';
import webpack from 'webpack';
import memoryfs from 'memory-fs';
import webpackConfig from './webpack.config.babel';

export default (fixture, options = {}) => {
const compiler = webpack(webpackConfig);

compiler.outputFileSystem = new memoryfs();

return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (stats.hasErrors()) return reject(stats.toString())
else if (err) return reject(err);
else if (stats.hasWarnings()) console.warn(stats.toString())

const result = compiler.outputFileSystem.data['bundle.js'].toString()
resolve({ stats, result });
});
});
};
5 changes: 5 additions & 0 deletions test/example.html
@@ -0,0 +1,5 @@
<article>
<h2>HTML2JS is Awesome</h2>
<img src="https://html2js.esstudio.site/android-chrome-256x256.png" alt="HTML Logo">
<a href="https://html2js.esstudio.site/">View More</a>
</article>
4 changes: 4 additions & 0 deletions test/example.js
@@ -0,0 +1,4 @@

import createExample from './example.html';

(global || window).output = createExample();
20 changes: 20 additions & 0 deletions test/loader.test.js
@@ -0,0 +1,20 @@
import compiler from './compiler.js';
import { JSDOM } from 'jsdom';
import fs from 'fs';
import path from 'path';

test('Inserts name and outputs JavaScript', async () => {
const { stats, result } = await compiler('./example.js');

const { document, Node } = new JSDOM().window; /* expose for eval */
const refHTML = fs.readFileSync(path.resolve(__dirname, './example.html')).toString();
const refParent = document.createElement('div');
refParent.innerHTML = refHTML;
const ref = refParent.firstChild;
eval(result); /* sets global output */

console.log(ref)
expect(output.tagName).toBe(ref.tagName);
expect(output.children.length).toBe(ref.children.length);
expect(output.querySelector('img').src).toBe(ref.querySelector('img').src);
});
20 changes: 20 additions & 0 deletions test/webpack.config.babel.js
@@ -0,0 +1,20 @@
import path from 'path';

export default {
context: __dirname,
entry: './example.js',
mode: 'development',
output: {
path: '/',
filename: 'bundle.js',
},
module: {
rules: [{
test: /\.html$/,
use: {
loader: path.resolve(__dirname, '../index'),
options: { }
}
}]
}
}

0 comments on commit 3715dfd

Please sign in to comment.