Skip to content
This repository has been archived by the owner on Dec 20, 2023. It is now read-only.

Convert to TypeScript #25

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .babelrc

This file was deleted.

2 changes: 1 addition & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ src
test
.travis.yml
.gitignore
.babelrc
tsconfig.json
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('./lib').default
2 changes: 1 addition & 1 deletion nodes.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require('./lib/nodes');
module.exports = require('./lib/nodes').default;
12 changes: 5 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,12 @@
},
"scripts": {
"prepublish": "npm run build",
"build": "babel src -d lib",
"build": "tsc",
"watch": "npm run build -- --watch",
"test": "npm run build && karma start test/karma.conf.js && mocha test/nodejs_tests.js"
},
"main": "lib/index.js",
"main": "index.js",
"devDependencies": {
"babel-cli": "^6.3.17",
"babel-loader": "^6.2.2",
"babel-preset-es2015": "^6.3.13",
"chai": "^3.5.0",
"jsdom": "^9.4.2",
"karma": "^0.13.19",
Expand All @@ -38,12 +35,13 @@
"mocha": "^2.4.5",
"sinon": "^1.17.3",
"sinon-chai": "^2.8.0",
"snabbdom": "~0",
"snabbdom": "~0.6.6",
"socket.io": "1.4.6",
"typescript": "^2.2.1",
"webpack": "^1.12.12"
},
"peerDependencies": {
"snabbdom": "~0"
"snabbdom": "~0.6.6"
},
"dependencies": {
"html-parse-stringify2": "^1.2.1"
Expand Down
5 changes: 4 additions & 1 deletion src/event-listeners.js → src/event-listeners.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// List from https://html.spec.whatwg.org/multipage/webappapis.html#globaleventhandlers.
export default [

const eventNames: Array<keyof HTMLElement> = [
'onabort',
'onautocomplete',
'onautocompleteerror',
Expand Down Expand Up @@ -63,3 +64,5 @@ export default [
'onvolumechange',
'onwaiting'
];

export default eventNames;
11 changes: 11 additions & 0 deletions src/html-element.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// because TypeScript's HTMLElement seems lacking: https://github.com/Microsoft/TSJS-lib-generator/issues/101
interface HTMLElement {
onautocomplete: (this: HTMLElement, ev: Event) => any
onautocompleteerror: (this: HTMLElement, ev: Event) => any
oncancel: (this: HTMLElement, ev: Event) => any
onclose: (this: HTMLElement, ev: Event) => any
ondragexit: (this: HTMLElement, ev: Event) => any
onresize: (this: HTMLElement, ev: Event) => any
onshow: (this: HTMLElement, ev: Event) => any
ontoggle: (this: HTMLElement, ev: Event) => any
}
14 changes: 14 additions & 0 deletions src/html-parse-stringify2.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// just enough to get us through
declare module 'html-parse-stringify2' {
export interface ASTNode {
type: string
content: string
name?: string
children?: ASTNode[]
attrs?: {
[key: string]: string
}
}

export const parse: (html: string) => ASTNode[]
}
3 changes: 2 additions & 1 deletion src/index.js → src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import virtualizeNode from './nodes';
import virtualizeString from './strings';
import { Options } from './interfaces'

export default function (el, options) {
export default function (el: string | Node, options: Options) {
if (typeof el === 'string') {
return virtualizeString(el, options);
}
Expand Down
11 changes: 11 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { VNode } from 'snabbdom/vnode'

interface Hooks {
create?: (vnode: VNode) => void
}

export interface Options {
context?: HTMLDocument
hooks?: Hooks
}

34 changes: 18 additions & 16 deletions src/nodes.js → src/nodes.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
import h from 'snabbdom/h';
import { VNode, VNodeData } from 'snabbdom/vnode'
import { createTextVNode, transformName } from './utils';
import listeners from './event-listeners';
import { Options } from './interfaces'

export default function virtualizeNodes(element, options = {}) {
export default function virtualizeNodes(node: Node, options: Options = {}) {

const context = options.context || document;

if (!element) {
if (!node) {
return null;
}

const createdVNodes = [];
const vnode = convertNode(element, createdVNodes, context);
const createdVNodes: VNode[] = [];
const vnode = convertNode(node, createdVNodes, context);
options.hooks && options.hooks.create && createdVNodes.forEach((node) => { options.hooks.create(node); });
return vnode;
}


function convertNode(element, createdVNodes, context) {
function convertNode(node: Node, createdVNodes: VNode[], context: HTMLDocument): VNode {
// If our node is a text node, then we only want to set the `text` part of
// the VNode.
if (element.nodeType === context.defaultView.Node.TEXT_NODE) {
const newNode = createTextVNode(element.textContent, context);
newNode.elm = element;
if (node.nodeType === context.defaultView.Node.TEXT_NODE) {
const newNode = createTextVNode(node.textContent, context);
newNode.elm = node;
createdVNodes.push(newNode);
return newNode
}
let element = node as HTMLElement

// If not a text node, then build up a VNode based on the element's tag
// name, class and style attributes, and remaining attributes.

// Special values: style, class. We don't include these in the attrs hash
// of the VNode.
const data = {};
const data: VNodeData = {};
const classes = getClasses(element);
if (Object.keys(classes).length !== 0) {
data.class = classes;
Expand All @@ -56,10 +58,10 @@ function convertNode(element, createdVNodes, context) {
}

// Check for event listeners.
const on = {};
const on: { [key: string]: EventListener } = {};
listeners.forEach((key) => {
if (element[key]) {
on[key.substring(2)] = element[key];
on[key.substring(2)] = element[key] as EventListener;
}
});
if (Object.keys(on).length > 0) {
Expand All @@ -82,9 +84,9 @@ function convertNode(element, createdVNodes, context) {
}

// Builds the class object for the VNode.
function getClasses(element) {
function getClasses(element: HTMLElement) {
const className = element.className;
const classes = {};
const classes: { [name: string]: boolean } = {};
if (className !== null && className.length > 0) {
className.split(' ').forEach((className) => {
classes[className] = true;
Expand All @@ -94,9 +96,9 @@ function getClasses(element) {
}

// Builds the style object for the VNode.
function getStyle(element) {
function getStyle(element: HTMLElement) {
const style = element.style;
const styles = {};
const styles: { [name: string]: string } = {};
for (let i = 0; i < style.length; i++) {
const name = style.item(i);
const transformedName = transformName(name);
Expand Down
26 changes: 14 additions & 12 deletions src/strings.js → src/strings.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import parse from 'html-parse-stringify2/lib/parse';
import { parse, ASTNode } from 'html-parse-stringify2';
import h from 'snabbdom/h';
import { VNode, VNodeData } from 'snabbdom/vnode'
import { createTextVNode, transformName, unescapeEntities } from './utils';
import { Options } from './interfaces'

export default function(html, options = {}) {
export default function(html: string, options: Options = {}) {

const context = options.context || document;

Expand All @@ -12,7 +14,7 @@ export default function(html, options = {}) {
}

// Maintain a list of created vnodes so we can call the create hook.
const createdVNodes = [];
const createdVNodes: VNode[] = [];

// Parse the string into the AST and convert to VNodes.
const vnodes = convertNodes(parse(html), createdVNodes, context);
Expand All @@ -38,7 +40,7 @@ export default function(html, options = {}) {
return res;
}

function convertNodes(nodes, createdVNodes, context) {
function convertNodes(nodes: ASTNode[], createdVNodes: VNode[], context: HTMLDocument): VNode[] | undefined {
if (nodes instanceof Array && nodes.length > 0) {
return nodes.map((node) => { return toVNode(node, createdVNodes, context); });
}
Expand All @@ -47,7 +49,7 @@ function convertNodes(nodes, createdVNodes, context) {
}
}

function toVNode(node, createdVNodes, context) {
function toVNode(node: ASTNode, createdVNodes: VNode[], context: HTMLDocument) {
let newNode;
if (node.type === 'text') {
newNode = createTextVNode(node.content, context);
Expand All @@ -59,8 +61,8 @@ function toVNode(node, createdVNodes, context) {
return newNode;
}

function buildVNodeData(node, context) {
const data = {};
function buildVNodeData(node: ASTNode, context: HTMLDocument) {
const data: VNodeData = {};
if (!node.attrs) {
return data;
}
Expand All @@ -71,7 +73,7 @@ function buildVNodeData(node, context) {
memo ? memo[name] = val : memo = { [name]: val };
}
return memo;
}, null);
}, null as null | { [key: string]: string });
if (attrs) {
data.attrs = attrs;
}
Expand All @@ -89,7 +91,7 @@ function buildVNodeData(node, context) {
return data;
}

function parseStyle(node) {
function parseStyle(node: ASTNode) {
try {
return node.attrs.style.split(';').reduce((memo, styleProp) => {
const res = styleProp.split(':');
Expand All @@ -99,22 +101,22 @@ function parseStyle(node) {
memo ? memo[name] = val : memo = { [name]: val };
}
return memo;
}, null);
}, null as null | { [key: string]: string });
}
catch (e) {
return null;
}
}

function parseClass(node) {
function parseClass(node: ASTNode) {
try {
return node.attrs.class.split(' ').reduce((memo, className) => {
className = className.trim();
if (className) {
memo ? memo[className] = true : memo = { [className]: true };
}
return memo;
}, null);
}, null as null | { [key: string]: boolean });
}
catch (e) {
return null;
Expand Down
12 changes: 6 additions & 6 deletions src/utils.js → src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import VNode from 'snabbdom/vnode';
import vnode from 'snabbdom/vnode';

export function createTextVNode(text, context) {
return VNode(undefined, undefined, undefined, unescapeEntities(text, context));
export function createTextVNode(text: string, context: HTMLDocument) {
return vnode(undefined, undefined, undefined, unescapeEntities(text, context), undefined);
}

export function transformName(name) {
export function transformName(name: string) {
// Replace -a with A to help camel case style property names.
name = name.replace( /-(\w)/g, function _replace( $1, $2 ) {
return $2.toUpperCase();
Expand All @@ -17,9 +17,9 @@ export function transformName(name) {
// Regex for matching HTML entities.
const entityRegex = new RegExp('&[a-z0-9]+;', 'gi')
// Element for setting innerHTML for transforming entities.
let el = null;
let el: HTMLDivElement | null = null;

export function unescapeEntities(text, context) {
export function unescapeEntities(text: string, context: HTMLDocument) {
// Create the element using the context if it doesn't exist.
if (!el) {
el = context.createElement('div');
Expand Down
3 changes: 3 additions & 0 deletions src/window.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
interface Window {
Node: Node
}
2 changes: 1 addition & 1 deletion strings.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require('./lib/strings');
module.exports = require('./lib/strings').default;
7 changes: 0 additions & 7 deletions test/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@ module.exports = function (config) {

webpack: {
devtool: 'inline-source-map',
module: {
loaders: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel'
}]
}
},

webpackMiddleware: {
Expand Down
18 changes: 9 additions & 9 deletions test/nodejs_tests.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use strict";
const expect = require('chai').expect;
const virtualizeString = require('../strings').default;
const virtualizeNodes = require('../nodes').default;
const h = require('snabbdom/h');
const virtualizeString = require('../strings')
const virtualizeNodes = require('../nodes')
const { h } = require('snabbdom/h');
const jsdom = require('jsdom').jsdom;
const extendVnode = require('./lib/helpers').extendVnode;
const VNode = require('snabbdom/vnode');
const { vnode } = require('snabbdom/vnode');


const opts = { context: (typeof document != 'undefined') ? document : jsdom('<html></html>') };
Expand Down Expand Up @@ -43,10 +43,10 @@ describe("In a nodejs environment", () => {
ul.innerHTML = "<li>One</li><li>Fish</li><li>Two</li><li>Fish</li>";
expect(virtualizeNodes(ul, opts)).to.deep.equal(
extendVnode(h('ul', [
extendVnode(h('li', [ extendVnode(VNode(undefined, undefined, undefined, 'One'), ul.childNodes[0].firstChild) ]), ul.childNodes[0]),
extendVnode(h('li', [ extendVnode(VNode(undefined, undefined, undefined, 'Fish'), ul.childNodes[1].firstChild) ]), ul.childNodes[1]),
extendVnode(h('li', [ extendVnode(VNode(undefined, undefined, undefined, 'Two'), ul.childNodes[2].firstChild) ]), ul.childNodes[2]),
extendVnode(h('li', [ extendVnode(VNode(undefined, undefined, undefined, 'Fish'), ul.childNodes[3].firstChild) ]), ul.childNodes[3])
extendVnode(h('li', [ extendVnode(vnode(undefined, undefined, undefined, 'One'), ul.childNodes[0].firstChild) ]), ul.childNodes[0]),
extendVnode(h('li', [ extendVnode(vnode(undefined, undefined, undefined, 'Fish'), ul.childNodes[1].firstChild) ]), ul.childNodes[1]),
extendVnode(h('li', [ extendVnode(vnode(undefined, undefined, undefined, 'Two'), ul.childNodes[2].firstChild) ]), ul.childNodes[2]),
extendVnode(h('li', [ extendVnode(vnode(undefined, undefined, undefined, 'Fish'), ul.childNodes[3].firstChild) ]), ul.childNodes[3])
]), ul)
);
});
Expand All @@ -55,7 +55,7 @@ describe("In a nodejs environment", () => {
const div = doc.createElement('div');
div.innerHTML = "&amp; is an ampersand! and &frac12; is 1/2!";
expect(virtualizeNodes(div, opts)).to.deep.equal(
extendVnode(h('div', [ extendVnode(VNode(undefined, undefined, undefined,'& is an ampersand! and ½ is 1/2!'), div.firstChild) ]), div)
extendVnode(h('div', [ extendVnode(vnode(undefined, undefined, undefined,'& is an ampersand! and ½ is 1/2!'), div.firstChild) ]), div)
);
});
});
Expand Down
Loading