Skip to content

Commit 411ea45

Browse files
committed
initial commit
0 parents  commit 411ea45

7 files changed

+617
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.DS_Store
2+
node_modules
3+
/three.js
4+
/dist

package.json

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "convert-threejs-to-classes",
3+
"version": "0.0.1",
4+
"author": "Rich Harris",
5+
"scripts": {
6+
"convert": "node dist",
7+
"preconvert": "npm run build",
8+
"build": "rollup -c",
9+
"dev": "rollup -cw",
10+
"pull": "rm -rf three.js && git clone git@github.com:mrdoob/three.js.git && cp -r three.js/src three.js/src.original"
11+
},
12+
"devDependencies": {
13+
"rollup": "^0.63.5",
14+
"rollup-plugin-commonjs": "^9.1.4",
15+
"rollup-plugin-node-resolve": "^3.3.0",
16+
"rollup-plugin-typescript": "^0.8.1",
17+
"typescript": "^3.0.1"
18+
},
19+
"dependencies": {
20+
"acorn": "^5.7.1",
21+
"estree-walker": "^0.5.2",
22+
"magic-string": "^0.25.0",
23+
"tiny-glob": "^0.2.2"
24+
}
25+
}

rollup.config.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import resolve from 'rollup-plugin-node-resolve';
2+
import commonjs from 'rollup-plugin-commonjs';
3+
import typescript from 'rollup-plugin-typescript';
4+
5+
export default {
6+
input: 'src/index.ts',
7+
output: {
8+
file: 'dist/index.js',
9+
format: 'cjs'
10+
},
11+
plugins: [
12+
resolve(),
13+
commonjs(),
14+
typescript({
15+
typescript: require('typescript')
16+
})
17+
],
18+
external: [
19+
'fs',
20+
'path'
21+
]
22+
};

src/Module.ts

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import MagicString from "../node_modules/magic-string";
2+
import * as assert from 'assert';
3+
import * as fs from "fs";
4+
import * as acorn from 'acorn';
5+
6+
export default class Module {
7+
file: string;
8+
source: string;
9+
code: MagicString;
10+
ast: any;
11+
12+
superclasses: Map<string, string>;
13+
methodBlocks: Map<string, string>;
14+
constructors: Map<string, string>;
15+
16+
constructor(file: string) {
17+
this.file = file;
18+
this.source = fs.readFileSync(file, 'utf-8');
19+
this.ast = acorn.parse(this.source, {
20+
ecmaVersion: 9,
21+
sourceType: 'module'
22+
});
23+
24+
this.code = new MagicString(this.source);
25+
26+
this.superclasses = new Map();
27+
this.methodBlocks = new Map();
28+
this.constructors = new Map();
29+
30+
this.convert();
31+
}
32+
33+
findSuperclasses() {
34+
const { superclasses } = this;
35+
}
36+
37+
findAndConvertMethods() {
38+
this.ast.body.forEach(node => {
39+
if (node.type !== 'ExpressionStatement') return;
40+
if (node.expression.type !== 'CallExpression') return;
41+
42+
// check if this is an Object.assign( SomeClass.prototype, { methods })
43+
const callee = this.snip(node.expression.callee);
44+
if (callee !== 'Object.assign') return;
45+
46+
assert.equal(node.expression.arguments.length, 2);
47+
48+
const [targetNode, methodsNode] = node.expression.arguments;
49+
50+
const target = this.snip(targetNode);
51+
const match = /^(\w+)\.prototype$/.exec(target);
52+
53+
assert.ok(match, target);
54+
55+
const name = match[1];
56+
57+
// check we're actually assigning methods
58+
assert.ok(methodsNode.properties.every(node => node.value.type === 'FunctionExpression'));
59+
60+
methodsNode.properties.forEach((prop, i) => {
61+
// `foo: function()` -> `foo()`
62+
let c = prop.key.end;
63+
while (this.source[c] !== '(') c += 1;
64+
this.code.overwrite(prop.key.end, c, ' ');
65+
66+
if (i < methodsNode.properties.length - 1) {
67+
// remove comma
68+
assert.equal(this.source[prop.end], ',');
69+
this.code.remove(prop.end, prop.end + 1);
70+
}
71+
});
72+
73+
const methodBlock = `\t${this.code.slice(methodsNode.start + 1, methodsNode.end - 1).trim()}`;
74+
75+
this.methodBlocks.set(name, methodBlock);
76+
77+
let c = node.start;
78+
while (/\s/.test(this.source[c - 1])) c -= 1;
79+
80+
this.code.remove(c, node.end);
81+
});
82+
}
83+
84+
findAndConvertConstructors() {
85+
this.ast.body.forEach(node => {
86+
if (node.type === 'ExportNamedDeclaration') {
87+
node = node.declaration;
88+
}
89+
90+
if (!node || node.type !== 'FunctionDeclaration') return;
91+
const { name } = node.id;
92+
93+
if (name[0].toUpperCase() !== name[0]) return; // not a class
94+
95+
console.log('converting constructor', { name });
96+
97+
const superclass = this.superclasses.get(name);
98+
99+
const declaration = superclass
100+
? `class ${name} extends ${superclass}`
101+
: `class ${name}`;
102+
103+
const constructorArgsAndBody = this.code
104+
.slice(node.id.end, node.body.end)
105+
.replace(/^/gm, '\t')
106+
.slice(1);
107+
108+
const methodBlock = this.methodBlocks.get(name);
109+
110+
this.code.overwrite(node.start, node.end, `${declaration} {\n\tconstructor ${constructorArgsAndBody}\n\n${methodBlock}\n}`);
111+
});
112+
}
113+
114+
convert() {
115+
this.findSuperclasses();
116+
this.findAndConvertMethods();
117+
this.findAndConvertConstructors();
118+
119+
// function handleDeclaration(node) {
120+
// const { name } = node.id;
121+
// const superclass = superclasses.get(name);
122+
123+
// const declaration = superclass
124+
// ? `class ${name} extends ${superclass}`
125+
// : `class ${name}`;
126+
127+
// console.log(node.start, node.id.end);
128+
129+
// code.overwrite(node.start, node.id.end, `${declaration} {\n\tconstructor`);
130+
131+
132+
// }
133+
134+
// ast.body.forEach(statement => {
135+
// if (statement.type === 'FunctionDeclaration') {
136+
// handleDeclaration(statement);
137+
// }
138+
139+
// if (statement.type === 'ExportNamedDeclaration') {
140+
// // TODO handle export named functions
141+
// console.log(statement);
142+
// }
143+
// });
144+
}
145+
146+
snip({ start, end }) {
147+
return this.source.slice(start, end);
148+
}
149+
150+
toString() {
151+
return this.code.toString();
152+
}
153+
}

src/index.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import * as fs from 'fs';
2+
import glob from 'tiny-glob/sync';
3+
import Module from './Module';
4+
5+
const SRC = 'three.js/src.original';
6+
const DEST = 'three.js/src';
7+
8+
const files = glob('**/*.js', { cwd: SRC })
9+
.filter((file: string) => /AnimationAction/.test(file));
10+
11+
files.forEach((file: string) => {
12+
const mod = new Module(`${SRC}/${file}`);
13+
fs.writeFileSync(`${DEST}/${file}`, mod.toString());
14+
});

tsconfig.json

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"compilerOptions": {
3+
"noImplicitAny": true,
4+
"diagnostics": true,
5+
"noImplicitThis": true,
6+
"noEmitOnError": true,
7+
"lib": ["es5", "es6", "dom"]
8+
},
9+
"target": "ES5",
10+
"module": "ES6",
11+
"include": [
12+
"src"
13+
],
14+
"exclude": [
15+
"node_modules"
16+
]
17+
}

0 commit comments

Comments
 (0)