@pixi/animate
Version:
PIXI plugin for the PixiAnimate Extension
395 lines (369 loc) • 13.3 kB
JavaScript
/* eslint-disable no-console, newline-after-var, camelcase, prefer-template */
const fs = require('fs');
const path = require('path');
const files = process.argv.slice(2);
const MODE_COMMONJS = 'CJS';
const MODE_ES6 = 'ES6';
const MODE_ES6_AUTORUN = 'ES6_A';
let moduleMode = MODE_COMMONJS;
for (let file of files)
{
if (file === '-h')
{
console.log(`Usage: animate-upgrade <mode> path/to/file1.js path/to/file2.js
Modes:
-c (Default) CommonJS/Node style export
-e ES6 style export (export default)
-a ES6 style export with auto import of @pixi/animate and run of setup(), exports library items as well as the default.
`);
return;
}
else if (file === '-e')
{
moduleMode = MODE_ES6;
continue;
}
else if (file === '-c')
{
moduleMode = MODE_COMMONJS;
continue;
}
else if (file === '-a')
{
moduleMode = MODE_ES6_AUTORUN;
continue;
}
if (!path.isAbsolute(file))
{
file = path.resolve(process.cwd(), file);
}
let orig;
try
{
orig = fs.readFileSync(file, 'utf8');
}
catch (e)
{
console.log(e.message);
continue;
}
const libSetup = (/\(function \(([^)]+)\) {((?:.|[\n\r])+)}\)\(/m).exec(orig);
/* LIB ARGS (Match 1):
PIXI, lib
*/
/* LIB SETUP (Match 2) (mandatory):
var MovieClip = PIXI.animate.MovieClip;
var Graphics = PIXI.Graphics;
var shapes = PIXI.animate.ShapesCache;
var Graphic1 = MovieClip.extend(function (mode) {
MovieClip.call(this, { mode: mode, duration: 2, loop: false });
var instance1 = new Graphics()
.drawCommands(shapes.graphic_singleframe[0]);
this.addTimedChild(instance1);
});
var Graphic2 = MovieClip.extend(function (mode) {
MovieClip.call(this, { mode: mode, duration: 2, loop: false });
var instance1 = new Graphic1(MovieClip.SYNCHED)
.setTransform(-2, -2);
this.addTimedChild(instance1);
});
lib.graphic_singleframe = MovieClip.extend(function () {
MovieClip.call(this, {
duration: 2,
framerate: 24
});
var instance1 = new Graphic2(MovieClip.SYNCHED)
.setTransform(16, 16);
this.addTimedChild(instance1);
});
lib.graphic_singleframe.assets = {
"graphic_singleframe": "images/graphic_singleframe.shapes.json"
};
*/
if (!libSetup)
{
console.log(`Unable to parse library setup method from ${file}`);
continue;
}
const [arg_PIXI, arg_lib] = libSetup[1].split(', ');
let foundAssets = new RegExp(`${arg_lib}\\.([a-zA-Z_$0-9]+)\\.assets = ({[^}]*});`).exec(orig);
if (!foundAssets)
{
console.log('Warning: Unable to parse library assets (and which item is the stage) from ' + file);
console.log('You will need to ensure correct loading of assets and stage instantiation.');
console.log('At any point before instantiation, you will need to overwrite \'getTexture\' on the asset data with \'Texture.fromFrame\' from PixiJS\'s Texture class');
foundAssets = [null, null, null];
}
const [fullAssetSetup, stageName, assets] = foundAssets;
/* STAGE DATA (optional):
module.exports = {
stage: lib.graphic_singleframe,
background: 0xffffff,
width: 32,
height: 32,
framerate: 24,
totalFrames: 2,
library: lib
};
*/
const stageData = (/module\.exports = {([^}]+)};/m).exec(orig);
let data = `const data = {
stage: null,
`;
// if we are doing an autorun, then we need to put that at the top
if (moduleMode === MODE_ES6_AUTORUN)
{
data = 'import animate from \'@pixi/animate\';\n' + data;
}
if (stageData)
{
data += stageData[1].trim().split(/[\n\r]+/)
.map((s) => s.trim())
.filter((s) => !s.startsWith('stage') && !s.startsWith('library'))
.map((s) => ' ' + s)
.join('\n') + '\n';
}
else
{
data += ` background: 0x000000,
width: 0,
height: 0,
framerate: 24,
totalFrames: 1,
`;
}
data += ` assets: ${assets || '{}'},
lib: {},
shapes: {},
textures: {},
spritesheets: [],
getTexture: function(id) {
if (data.textures[id]) {
return data.textures[id];
}
const atlas = data.spritesheets.find(atlas => !!atlas.textures[id]);
return atlas ? atlas.textures[id] : null;
},
`;
let setup = libSetup[2];
if (fullAssetSetup && stageName)
{
// replace assets setup with stage reference setup
setup = setup.replace(fullAssetSetup, `data.stage = data.lib.${stageName};`);
}
// remove the shapes cache variable, if present
setup = setup.replace(new RegExp(`^\\s+var shapes = ${arg_PIXI}\\.animate\\.ShapesCache;\\r?\\n?`, 'm'), '');
// remove the fromFrame variable, if present
setup = setup.replace(new RegExp(`^\\s+var fromFrame = ${arg_PIXI}\\.Texture\\.fromFrame;\\r?\\n?`, 'm'), '');
// replace MovieClip variable
setup = setup.replace(new RegExp(`${arg_PIXI}\\.animate.MovieClip`), 'animate.MovieClip');
// replace Container variable
setup = setup.replace(new RegExp(`${arg_PIXI}\\.Container`), 'animate.Container');
// replace Sprite variable
setup = setup.replace(new RegExp(`${arg_PIXI}\\.Sprite`), 'animate.Sprite');
// replace Text variable
setup = setup.replace(new RegExp(`${arg_PIXI}\\.Text`), 'animate.Text');
// replace Graphics variable
setup = setup.replace(new RegExp(`${arg_PIXI}\\.Graphics`), 'animate.Graphics');
// fix lib usage
setup = setup.replace(new RegExp(`(\\s)(${arg_lib})(?=\\.)`, 'g'), '$1data.lib');
// fix shapes usage
setup = setup.replace(new RegExp(`(\\W)(shapes)(?=\\.)`, 'g'), '$1data.shapes');
// fix fromFrame usage
setup = setup.replace(new RegExp(`(\\W)(fromFrame)(?=\\()`, 'g'), '$1data.getTexture');
// list of classes that need to be exported if in MODE_ES6_AUTORUN
const classList = [];
// fix MovieClip extension
const MCFinder = /^([ \t]+)((data\.lib\.|var )[a-zA-Z_$0-9]+ = )MovieClip.extend\(function \(([^)]*)\) {/gm;
let found = MCFinder.exec(setup);
while (found)
{
const [fullMatch, indent, libItem, libOrVar, args] = found;
const { index } = found;
// replace extend() with class declaration & constructor
setup = setup.replace(fullMatch, `${indent}${libItem}class extends MovieClip {
${indent}constructor(${args}) {`);
// replace super call with real super()
const superFinder = /MovieClip.call\(this, /;
superFinder.lastIndex = index;
setup = setup.replace(superFinder, 'super(');
// replace the closing of the extend() function with closing of constructor & class
const endFinder = new RegExp(`^${indent}}\\);`, 'm');
endFinder.lastIndex = index;
setup = setup.replace(endFinder, `${indent}}\n${indent}}`);
// add to class list if not a var
if (libOrVar === 'data.lib.')
{
// extract just the class name
const [, className] = (/([^. ]+) =/).exec(libItem);
classList.push(className);
}
// find next
found = MCFinder.exec(setup);
}
// fix Container extension
const ContainerFinder = /^([ \t]+)((data\.lib\.|var )[a-zA-Z_$0-9]+ = )Container.extend\(function \(([^)]*)\) {/gm;
found = ContainerFinder.exec(setup);
while (found)
{
const [fullMatch, indent, libItem, libOrVar] = found;
const { index } = found;
// replace extend() with class declaration & constructor
setup = setup.replace(fullMatch, `${indent}${libItem}class extends Container {
${indent}constructor() {`);
// replace super call with real super()
const superFinder = /Container.call\(this/;
superFinder.lastIndex = index;
setup = setup.replace(superFinder, 'super(');
// replace the closing of the extend() function with closing of constructor & class
const endFinder = new RegExp(`^${indent}}\\);`, 'm');
endFinder.lastIndex = index;
setup = setup.replace(endFinder, `${indent}}\n${indent}}`);
// add to class list if not a var
if (libOrVar === 'data.lib.')
{
// extract just the class name
const [, className] = (/([^. ]+) =/).exec(libItem);
classList.push(className);
}
// find next
found = ContainerFinder.exec(setup);
}
// replace var with const
setup = setup.replace(/(\s)(var)(?=\s)/g, '$1const');
// now insert setup
data += ` setup: function(animate) {
${setup}
}
`;
// close data object
data += '};\n';
// if in autorun mode, run setup and do individual exports
if (moduleMode === MODE_ES6_AUTORUN)
{
data += 'data.setup(animate);\n';
for (const className of classList)
{
data += `const ${className} = data.lib.${className};
export {${className}};
`;
}
}
// do default export
data += `
${moduleMode === MODE_COMMONJS ? 'module.exports =' : 'export default'} data;`;
fs.writeFileSync(file, data, 'utf8');
// *********************************************
// SHAPE UPDATING
// *********************************************
// now look at shapes so that we can update them
const shapeFileFinder = /"([^"]+.shapes.(?:json|txt))"/g;
let foundShape;
// eslint-disable-next-line no-cond-assign
while (foundShape = shapeFileFinder.exec(assets))
{
let [, shapeFile] = foundShape;
if (!path.isAbsolute(shapeFile))
{
shapeFile = path.resolve(path.dirname(file), shapeFile);
}
let orig;
try
{
orig = fs.readFileSync(shapeFile, 'utf8');
}
catch (e)
{
console.log(e.message);
continue;
}
let shapeData = null;
if (shapeFile.endsWith('txt'))
{
shapeData = orig.split('\n').map((line) => line.split(' '));
}
else if (shapeFile.endsWith('json'))
{
shapeData = JSON.parse(orig);
}
/* at this point shapeData should be an array of arrays - example:
[[
"f", "#345ffa", 1,
"m", 3, 6,
"l", 3, 22,
"l", 19, 22,
"l", 19, 6,
"l", 3, 6,
"c"],
[
"f", "#eb34fa", 1,
"m", 11, 12,
"l", 11, 28,
"l", 27, 28,
"l", 27, 12,
"l", 11, 12,
"c"],
[
"f", "#060", 1,
"m", 13, 2,
"l", 13, 18,
"l", 29, 18,
"l", 29, 2,
"l", 13, 2,
"c"]]
*/
if (!Array.isArray(shapeData) || !Array.isArray(shapeData[0]))
{
console.log(`Unexpected format for ${shapeFile}`);
continue;
}
// go through the list of shapes/distinct drawing commands
for (let s = 0; s < shapeData.length; ++s)
{
const shape = shapeData[s];
// go through each individual command
for (let i = 0; i < shape.length; ++i)
{
if (shape[i] === 'c')
{
// if it was the old close path, replace it with the new close path
shape[i] = 'cp';
}
else if (shape[i] === 'h')
{
// if it was the old addHole, then replace it with endHold and go back to
// insert a begin hole
shape[i] = 'eh';
// start going backwards to find what we need
let prevIndex = i - 1;
for (; prevIndex >= 0; --prevIndex)
{
/* eslint-disable max-depth */
if (shape[prevIndex] === 'm')
{
break;
}
/* eslint-enable max-depth */
}
// at this point prevIndex is the location of the ending of the shape
// before our hole, or -1 if there was somehow nothing but a hole
// so we can insert beginHole right after that index
shape.splice(prevIndex/* + 1 */, 0, 'bh');
// increment i because we inserted a thing earlier into the array
++i;
}
}
}
let shapeOut = null;
if (shapeFile.endsWith('txt'))
{
shapeOut = shapeData.map((line) => line.join(' ')).join('\n');
}
else if (shapeFile.endsWith('json'))
{
shapeOut = JSON.stringify(shapeData, null, 2);
}
fs.writeFileSync(shapeFile, shapeOut, 'utf8');
}
}