Skip to content
Merged
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
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
dist/
packages/common/src/vendor-libs
ultimate-crosscode-typedefs/
dist-ccmod-service-worker.js
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ dist/
*.tsbuildinfo

/ultimate-crosscode-typedefs/
/dist-ccmod-service-worker.js

ccloader_*_quick-install.*
ccloader_*_package.*
50 changes: 0 additions & 50 deletions build.mjs

This file was deleted.

108 changes: 108 additions & 0 deletions build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import * as esbuild from 'esbuild';
import copyStaticFiles from 'esbuild-copy-static-files';
import path from 'path';
import * as fs from 'fs';

const isWatch = process.argv[2] === 'watch';

function donePlugin(outfile: string): esbuild.Plugin {
return {
name: 'done plugin',
setup(build) {
build.onEnd(async (res) => {
let code = res.outputFiles![0]?.text;
if (!code) return; // when compile errors

await fs.promises.writeFile(outfile, code);

const bytes = code.length;
const kb = bytes / 1024;
console.log(outfile, `${kb.toFixed(1)}kb`);
});
},
};
}

const commonOptions = {
format: 'esm',
platform: 'node',

write: false,
bundle: true,
minify: false,
sourcemap: 'inline',
} as const;

function core(): esbuild.BuildOptions {
const outfile = path.join('./dist', `core.js`);

return {
entryPoints: ['packages/core/src/main.ts'],

...commonOptions,

plugins: [donePlugin(outfile)],
};
}

function runtime(): esbuild.BuildOptions {
const outfile = path.join('./dist', 'runtime', 'main.js');

return {
entryPoints: ['packages/runtime/src/_main.ts'],

...commonOptions,

plugins: [
copyStaticFiles({
src: `./packages/runtime/ccmod.json`,
dest: `./dist/runtime/ccmod.json`,
}),
copyStaticFiles({
src: `./packages/runtime/media`,
dest: `./dist/runtime/media`,
}),
copyStaticFiles({
src: `./packages/runtime/assets`,
dest: `./dist/runtime/assets`,
}),
donePlugin(outfile),
],
};
}

function ccmodServiceWorker(): esbuild.BuildOptions {
const outfile = path.join('./', 'dist-ccmod-service-worker.js');
return {
entryPoints: ['packages/core/src/service-worker.ts'],

...commonOptions,

plugins: [donePlugin(outfile)],
};
}

const modules: Array<() => esbuild.BuildOptions> = [core, runtime, ccmodServiceWorker];

async function run(): Promise<void> {
fs.promises.mkdir('./dist', { recursive: true });

if (isWatch) {
console.clear();
await Promise.all(
modules.map(async (module) => {
const ctx = await esbuild.context(module());
await ctx.watch();
}),
);
} else {
await Promise.all(
modules.map(async (module) => {
await esbuild.build(module());
}),
);
// eslint-disable-next-line no-process-exit
process.exit(); // because esbuild keeps the process alive for some reason
}
}
run();
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"devDependencies": {
"@types/archiver": "^6.0.3",
"@types/esbuild-copy-static-files": "^0.1.4",
"@types/jquery": "^1.10.31",
"@types/node": ">=24.0",
"@types/semver": "6.2.x || 6.3.x",
Expand All @@ -27,7 +28,8 @@
"ultimate-crosscode-typedefs": "github:CCDirectLink/ultimate-crosscode-typedefs"
},
"scripts": {
"build": "node build.mjs",
"build": "node --experimental-strip-types build.ts",
"watch": "node --experimental-strip-types build.ts watch",
"lint": "eslint . --ext .js,.ts --ignore-path .eslintignore",
"check-fmt": "prettier --check '**/*.{js,ts,json,css,html}'",
"format": "prettier --write **/*.{js,ts,json,css,html}"
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/console.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as utils from './utils';

const nodejsUtil = window.require('util') as typeof import('util');
const nodejsUtil = window.require?.('util') as typeof import('util');

export enum LogLevel {
LOG = 2,
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/require.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import * as utils from './utils';

let requireFixed: NodeRequire = null!;

if (typeof require === 'function') {
const paths = require('path') as typeof import('path');
if (typeof window.require === 'function') {
const paths = window.require('path') as typeof import('path');

requireFixed = ((id) => {
try {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
"dependencies": {
"@ccloader3/common": "workspace:*",
"@ccloader3/runtime": "workspace:*",
"fflate": "^0.8.2",
"ultimate-crosscode-typedefs": "github:CCDirectLink/ultimate-crosscode-typedefs"
},
"devDependencies": {
"@types/serviceworker": "^0.0.145"
}
}
2 changes: 1 addition & 1 deletion packages/core/src/files.android.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as filesBrowser from './files.browser';
import { Config } from './config';

export { isReadable, loadText } from './files.browser';
export { isReadable, readFile, loadText } from './files.browser';

export async function getModDirectoriesIn(dir: string, config: Config): Promise<string[]> {
if (dir === `${config.gameAssetsDir}mods/`) {
Expand Down
15 changes: 12 additions & 3 deletions packages/core/src/files.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import * as utils from '@ccloader3/common/utils';
import * as paths from '@ccloader3/common/paths';
import { Config } from './config';

export async function loadText(path: string): Promise<string> {
async function request(path: string): Promise<Response> {
try {
let res = await fetch(utils.cwdFilePathToURL(path).href);
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`);
return await res.text();
return res;
} catch (err) {
if (utils.errorHasMessage(err)) {
err.message = `Failed to load file '${path}': ${err.message}`;
Expand All @@ -15,6 +15,14 @@ export async function loadText(path: string): Promise<string> {
}
}

export async function readFile(path: string): Promise<ArrayBuffer> {
return (await request(path)).arrayBuffer();
}

export async function loadText(path: string): Promise<string> {
return (await request(path)).text();
}

export async function isReadable(path: string): Promise<boolean> {
try {
let res = await fetch(utils.cwdFilePathToURL(path).href, { method: 'HEAD' });
Expand Down Expand Up @@ -57,6 +65,7 @@ export async function getInstalledExtensions(config: Config): Promise<string[]>
if (utils.errorHasMessage(err)) {
err.message = `Failed to send request to '${extensionsApiUrl}': ${err.message}`;
}
throw err;
// throw err;
return [];
}
}
82 changes: 82 additions & 0 deletions packages/core/src/files.ccmod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { type Unzipped, unzipSync } from 'fflate/browser';
import { addFetchHandler } from './service-worker-bridge';
import * as files from './files';

const fileMap = new Map<string, Unzipped>();

function findPrefix(path: string): [string, string] | null {
if (path.startsWith('/')) path = path.substring(1);
const prefix = [...fileMap.keys()].find((dir) => path.startsWith(dir));

if (!prefix) return null;

path = path.substring(prefix.length + 1);
return [path, prefix];
}

// eslint-disable-next-line @typescript-eslint/require-await
export async function isReadable(path: string): Promise<boolean> {
return Boolean(findPrefix(path));
}

export async function loadText(path: string): Promise<string | null> {
const buf = await readFile(path);
if (!buf) return null;

return new TextDecoder('utf-8').decode(buf);
}

// eslint-disable-next-line @typescript-eslint/require-await
export async function readFile(path: string): Promise<ArrayBuffer | null> {
const prefixObj = findPrefix(path);
if (!prefixObj) return null;
const [relativePath, prefix] = prefixObj;

const unzipped = fileMap.get(prefix)!;

const data = unzipped[relativePath];

return data.buffer;
}

// eslint-disable-next-line @typescript-eslint/require-await
export async function findRecursively(dir: string): Promise<string[] | null> {
const prefixObj = findPrefix(dir);
if (!prefixObj) return null;
const [relativePath, prefix] = prefixObj;

const unzipped = fileMap.get(prefix)!;

const dirs = Object.keys(unzipped)
.filter((path) => !path.endsWith('/') && path.startsWith(relativePath))
.map((dir) => dir.substring(relativePath.length));

return dirs;
}

export async function loadCCMods(
allModsList: Array<{ parentDir: string; dir: string }>,
): Promise<void> {
const ccmods: typeof allModsList = allModsList.filter((mod) => mod.dir.endsWith('.ccmod'));
const ccmodArrayBuffers = await Promise.all(
ccmods.map(async (mod) => {
const url = `./${mod.dir}`;
return new Uint8Array(await files.readFile(url));
}),
);

// console.time('uncompress');
const uncompressed = ccmodArrayBuffers.map((buf) => unzipSync(buf));
// console.timeEnd('uncompress');

for (let i = 0; i < ccmods.length; i++) {
const mod = ccmods[i];
const buf = uncompressed[i];
fileMap.set(mod.dir, buf);
}

addFetchHandler(
ccmods.map((mod) => mod.dir),
readFile,
);
}
Loading