Skip to content

Commit

Permalink
feat(build): add node build file (#54)
Browse files Browse the repository at this point in the history
* feat(build): add node build file

* fix(build): add whitespace between source files in combined

* fix(build): get closure compiler step working!

* fix(build): fix shebang

* fix(build): remove `package-lock.json`
  • Loading branch information
ThatOneBro authored Jan 3, 2024
1 parent 4b3073e commit 8df371d
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
},
"homepage": "https://github.com/KilledByAPixel/LittleJS",
"scripts": {
"build": "cd src && engineBuild.bat"
"build": "node src/build/build.js"
},
"devDependencies": {
"google-closure-compiler": "~20230502.0.0",
Expand Down
126 changes: 126 additions & 0 deletions src/build/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env node

const start = performance.now();

const fs = require("node:fs");
const child_process = require("node:child_process");
const { Build } = require("./buildClass");

const BUILD_DIR = "build";
const ENGINE_NAME = "littlejs";

// Nuke build dir and recreate
fs.rmSync(BUILD_DIR, { recursive: true, force: true });
fs.mkdirSync(BUILD_DIR);

const closureCompilerStep = (filename) => {
fs.copyFileSync(filename, `${filename}.tmp`);
try {
child_process.execSync(
`npx google-closure-compiler --js=${filename}.tmp --js_output_file=${filename} --language_out=ECMASCRIPT_2021 --warning_level=VERBOSE --jscomp_off="*"`
);
} catch (e) {
console.error(e);
console.error(
"Failed to run Google Closure Compiler step... Make sure the file is valid before running?"
);
process.exit(1);
}
fs.rmSync(`${filename}.tmp`);
};

const uglifyBuildStep = (filename) => {
try {
child_process.execSync(`npx uglifyjs -o ${filename} -- ${filename}`);
} catch (e) {
console.error(e);
process.exit(1);
}
};

// Build engine -- all
{
const build = new Build();
build
.addSourceFile("src/engineDebug.js")
.addSourceFile("src/engineUtilities.js")
.addSourceFile("src/engineSettings.js")
.addSourceFile("src/engineObject.js")
.addSourceFile("src/engineDraw.js")
.addSourceFile("src/engineInput.js")
.addSourceFile("src/engineAudio.js")
.addSourceFile("src/engineTileLayer.js")
.addSourceFile("src/engineParticles.js")
.addSourceFile("src/engineMedals.js")
.addSourceFile("src/engineWebGL.js")
.addSourceFile("src/engine.js")
.setOutputFile(`build/${ENGINE_NAME}.js`)
.build();
}

// Build engine -- release
{
const build = new Build();
build
.addSourceFile("src/engineRelease.js")
.addSourceFile("src/engineUtilities.js")
.addSourceFile("src/engineSettings.js")
.addSourceFile("src/engine.js")
.addSourceFile("src/engineObject.js")
.addSourceFile("src/engineDraw.js")
.addSourceFile("src/engineInput.js")
.addSourceFile("src/engineAudio.js")
.addSourceFile("src/engineTileLayer.js")
.addSourceFile("src/engineParticles.js")
.addSourceFile("src/engineMedals.js")
.addSourceFile("src/engineWebGL.js")
.setOutputFile(`build/${ENGINE_NAME}.release.js`)
.build();
}

// Build engine -- minified
{
const build = new Build();
build
.addSourceFile(`build/${ENGINE_NAME}.release.js`)
.setOutputFile(`build/${ENGINE_NAME}.min.js`)
.addBuildStep(closureCompilerStep)
.addBuildStep(uglifyBuildStep)
.build();
}

// Build engine -- ESM
{
const build = new Build();
build
.addSourceFile(`build/${ENGINE_NAME}.js`)
.addSourceFile("src/engineExport.js")
.setOutputFile(`build/${ENGINE_NAME}.esm.js`)
.build();
}

// Build engine -- ESM minified / release
{
const build = new Build();
build
.addSourceFile(`build/${ENGINE_NAME}.min.js`)
.addSourceFile("src/engineExport.js")
.setOutputFile(`build/${ENGINE_NAME}.esm.min.js`)
.addBuildStep(uglifyBuildStep)
.build();
}

// Run tsc to create type definitions
try {
child_process.execSync(
`npx tsc build/${ENGINE_NAME}.esm.js --declaration --allowJs --emitDeclarationOnly --outFile build/${ENGINE_NAME}.d.ts`
);
} catch (e) {
console.error(e);
process.exit(1);
}

const end = performance.now();
const duration = end - start;

console.log(`Build completed in ${duration.toFixed(2)} ms.`);
84 changes: 84 additions & 0 deletions src/build/buildClass.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const fs = require("node:fs");

/**
A class representing a single build with its own source files, build steps, and output file
*/
class Build {
#files;
#outputFile;
#buildSteps;

constructor() {
this.#files = [];
this.#outputFile = "out.js";
this.#buildSteps = [];
}

addSourceFile(filename) {
this.#files.push(filename);
return this;
}

setOutputFile(filename) {
this.#outputFile = filename;
return this;
}

/**
*
* @param {buildStepCallback} cb - A callback that accepts a `filename` as its only argument representing the file to execute this step on.
* @returns {this}
*/
addBuildStep(cb) {
this.#buildSteps.push(cb);
return this;
}

/**
* @returns {boolean} Whether build completed successfully or not.
*/
build() {
// read files
const files = this.#files;
if (!files.length) {
console.error(
"No files given to build. Make sure you add files with `addSourceFile` before building."
);
return false; // no files, this should be an error
}

// concat files into one buffer
let buf = fs.readFileSync(files[0]);
buf += "\n";
// Start with i = 1 since we init buffer with 0th index
for (let i = 1; i < files.length; i += 1) {
buf += fs.readFileSync(files[i]);
buf += "\n";
}

// output file
fs.writeFileSync(this.#outputFile, buf, { flag: "w+" });

// go through build steps in order
const buildSteps = this.#buildSteps;
for (let i = 0; i < buildSteps.length; i += 1) {
buildSteps[i](this.#outputFile);
}
}

get sourceFiles() {
return this.#files;
}

get outputFile() {
return this.#outputFile;
}
}

/**
* Callback provided to run a build step in the Build class.
* @callback buildStepCallback
* @param {string} filename
*/

module.exports = { Build };

0 comments on commit 8df371d

Please sign in to comment.