Last active
March 7, 2023 15:29
-
-
Save Squareys/327f0243a394464a8635e1ac80d3ba9e to your computer and use it in GitHub Desktop.
Upgrade Wonderland Engine JavaScript component code from 0.9.5 to 1.0.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Convert Wonderland Engine 0.9.5 JavaScript code to Wonderland Engine 1.0.0 code. | |
* | |
* # Usage | |
* | |
* node wonderland-js-upgrade.mjs <your-file>.js | |
*/ | |
import {readFileSync, writeFileSync, existsSync} from 'fs'; | |
/* Parse filename argument and check existence */ | |
const filename = process.argv[2]; | |
if (!existsSync(filename)) { | |
console.log(filename, 'does not exist.'); | |
exit(1); | |
} | |
/** Convert a string to CamelCase */ | |
function toCamelCase(str) { | |
return str.replace(/(?:^\w|[A-Z-]|\b\w|\s+)/g, function (match, index) { | |
if (+match === 0) return ''; | |
if (/\s+|-/.test(match)) return ''; | |
return match.toUpperCase(); | |
}); | |
} | |
/** Convert `WL.Type.String` to `Type.String` */ | |
function convertParamTypes(params) { | |
return params.replaceAll('WL.Type', 'Type'); | |
} | |
/** Find scope end matching starting { at given index */ | |
function getScopeEnd(code, start) { | |
let count = 1; | |
for(let i = start + 1; i < code.length; ++i) { | |
if (code[i] == '{') ++count; | |
if (code[i] == '}') --count; | |
if (count == 0) return i; | |
} | |
throw new Error("Unmatched scope in code, starting at " + i.toString()); | |
} | |
/** Convert `init: async function(params)` to `async init(params)` */ | |
function convertFunctions(functions, apiImports) { | |
/* Move global types use to imports */ | |
const regex_Texture = /new\s*WL\.Texture\s*\(([^\);]*)\)\s*;/gm; | |
functions = functions.replace(regex_Texture, (m, i) => { | |
apiImports.push('Texture'); | |
return `new Texture(${m[1]});`; | |
}); | |
/* Replace global WL use that should be this.engine now */ | |
for (const symbol of [ | |
'canvas', | |
'scene', | |
'physics', | |
'xrSession', | |
'onXRSession', | |
'arSupported', | |
'vrSupported', | |
'onSceneLoaded', | |
'textures', | |
]) | |
functions = functions.replaceAll('WL.' + symbol, 'this.engine.' + symbol); | |
/* This regex matches `functionName: (async)? function(params) {` | |
* for replacement with ES6 class version */ | |
const regex_functions = | |
/(async\s*)?([a-zA-Z_]+)(?<!if|for|while|switch)\s*(?::\s*(async)?\s*function)?\s*\((.*)\)\s*\{/gm; | |
const matches = functions.matchAll(regex_functions); | |
if (!matches) return functions; | |
let result = ''; | |
let lastMatchEnd = 0; | |
for (const m of matches) { | |
const name = m[2]; | |
const isAsync = !!m[1] || !!m[3]; | |
const params = m[4]; | |
const converted = `${isAsync ? 'async ' : ''}${name}(${params || ''}) {`; | |
result += functions.substr(lastMatchEnd, m.index - lastMatchEnd) + converted; | |
lastMatchEnd = m.index + m[0].length; | |
/* Find closing bracket to remove ',' */ | |
let scopeEnd = getScopeEnd(functions, lastMatchEnd); | |
/* We only support ',' directly after closing bracket. | |
* Replace with newline to keep functions string length same, | |
* prettier cleans up after. | |
* Note: This function is never run for functions already in ES6 | |
* syntax, so removal will not happen. */ | |
if (functions[scopeEnd + 1] == ',') { | |
functions = | |
functions.substr(0, scopeEnd + 1) + | |
'\n' + | |
functions.substr(scopeEnd + 2, functions.length - (scopeEnd + 2)); | |
} | |
} | |
result += functions.substr(lastMatchEnd, functions.length - lastMatchEnd); | |
return result; | |
} | |
/** | |
* Convert `WL.registerComponent('my-comp', {}, {})` to `class MyComp extends Component { ... }` | |
*/ | |
function convertComponents(contents, apiImports) { | |
/* This monster regex matches the three parameters of the WL.registerComponent() calls into | |
* match groups 1-3 */ | |
const regex_registerComp = | |
/WL\.registerComponent\(\s*[\'\"]([^\'\"]+)[\"\']\s*,\s*\{(\s*(?:.*\s*.*\{.*\}\s*,?\s*)*)\}\s*,\s*\{/gm; | |
const matches = contents.matchAll(regex_registerComp); | |
if (!matches) return contents; | |
let lastMatchEnd = 0; | |
let result = ''; | |
for (const m of matches) { | |
const typeName = m[1]; | |
console.log('Migrating component', typeName); | |
const properties = convertParamTypes(m[2]); | |
const scopeStart = m.index + m[0].length; | |
const scopeEnd = getScopeEnd(contents, scopeStart); | |
const functions = convertFunctions(contents.substr( | |
scopeStart, scopeEnd - scopeStart), apiImports); | |
const converted = `export class ${toCamelCase(typeName)} extends Component { | |
static TypeName = '${typeName}'; | |
static Properties = {${properties}}; | |
${functions}}`; | |
result += contents.substr(lastMatchEnd, m.index - lastMatchEnd) + converted; | |
lastMatchEnd = scopeEnd + 3 /* Removes final }); */; | |
} | |
result += contents.substr(lastMatchEnd, contents.length - lastMatchEnd); | |
return result; | |
} | |
/* Keep track of imports from @wonderlandengine/api */ | |
const apiImports = ['Component', 'Type']; | |
/* Read the script and convert components */ | |
let contents = readFileSync(filename, 'utf8'); | |
contents = convertComponents(contents, apiImports); | |
/* Add imports */ | |
const uniqueApiImports = [...new Set(apiImports)].sort(); | |
contents = `import {${uniqueApiImports.join( | |
', ' | |
)}} from '@wonderlandengine/api';\n\n${contents}`; | |
/* Overwrite the input file with the result */ | |
writeFileSync(filename, contents); | |
console.log('Wrote', filename); | |
console.log('Running prettier on', filename); | |
/* Run prettier, if possible */ | |
import('prettier/cli.js') | |
.then((cli) => { | |
cli.default.run(['--write', filename]); | |
}) | |
.catch((e) => { | |
console.warn(e); | |
console.warn('Warning: Could not find prettier'); | |
console.warn('You will want to run prettier yourself.'); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment