mirror of
https://github.com/unanmed/HumanBreak.git
synced 2026-11-02 09:15:05 +08:00
feat: 构建版本加载
This commit is contained in:
1 parent
684fba1475
commit
4d5f14e8a5
14 files changed
+444
-391
No files matched your search
+9
-18
@@ -8,12 +8,12 @@ import rollupBabel from '@rollup/plugin-babel';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import { splitResorce } from './resource.js';
|
||||
import { splitResource } from './resource.js';
|
||||
import compressing from 'compressing';
|
||||
|
||||
const type = process.argv[2];
|
||||
const map = false;
|
||||
const resorce = type !== 'dev';
|
||||
const resorce = true;
|
||||
const compress = type === 'dist';
|
||||
|
||||
(async function () {
|
||||
@@ -115,14 +115,6 @@ const compress = type === 'dist';
|
||||
})()
|
||||
)
|
||||
]);
|
||||
await Promise.all([
|
||||
...fonts.map(v => {
|
||||
return fs.rename(
|
||||
`./dist/project/fonts/${v}.ttf`,
|
||||
`./dist/project/fonts/${v}-${timestamp}.ttf`
|
||||
);
|
||||
})
|
||||
]);
|
||||
} catch (e) {
|
||||
console.log('字体压缩失败');
|
||||
console.log(e);
|
||||
@@ -133,7 +125,7 @@ const compress = type === 'dist';
|
||||
await fs.remove('./dist/project/plugin.min.js');
|
||||
|
||||
const build = await rollup.rollup({
|
||||
input: 'src/plugin/game/index.js',
|
||||
input: 'src/game/index.ts',
|
||||
plugins: [
|
||||
typescript({
|
||||
sourceMap: false
|
||||
@@ -163,12 +155,7 @@ const compress = type === 'dist';
|
||||
// 4. 压缩main.js
|
||||
try {
|
||||
// 先获取不能压缩的部分
|
||||
const main = (await fs.readFile('./public/main.js', 'utf-8'))
|
||||
.replace(
|
||||
/this.pluginUseCompress\s*=\s*false\;/,
|
||||
'this.pluginUseCompress = true;'
|
||||
)
|
||||
.replace('this.timestamp = 0', `this.timestamp = ${timestamp};`);
|
||||
const main = await fs.readFile('./public/main.js', 'utf-8');
|
||||
|
||||
const endIndex = main.indexOf('// >>>> body end');
|
||||
const nonCompress = main.slice(0, endIndex);
|
||||
@@ -190,7 +177,11 @@ const compress = type === 'dist';
|
||||
|
||||
// 6. 资源分离
|
||||
if (resorce) {
|
||||
await splitResorce(type);
|
||||
await splitResource();
|
||||
}
|
||||
|
||||
if (!compress) {
|
||||
await fs.copy('./script/template/启动服务.exe', './dist/启动服务.exe');
|
||||
}
|
||||
|
||||
// 7. 压缩
|
||||
|
||||
+214
-275
@@ -1,297 +1,236 @@
|
||||
import fs from 'fs-extra';
|
||||
import { uniqueSymbol } from './utils.js';
|
||||
import { resolve } from 'path';
|
||||
import motaConfig from '../mota.config.js';
|
||||
import compressing from 'compressing';
|
||||
import fs, { Stats } from 'fs-extra';
|
||||
import JSZip from 'jszip';
|
||||
import { formatSize, uniqueSymbol } from './utils.js';
|
||||
|
||||
const SYMBOL = uniqueSymbol();
|
||||
const MAX_SIZE = 100 * (1 << 20) - 20 * (1 << 10);
|
||||
const sourceIndex: Record<string, string> = {};
|
||||
const toMove: Stats[] = [];
|
||||
const all = [
|
||||
'bgms',
|
||||
'sounds',
|
||||
'autotiles',
|
||||
'images',
|
||||
'materials',
|
||||
'tilesets',
|
||||
'animates',
|
||||
'fonts'
|
||||
];
|
||||
// 资源拆分模块,可以加快在线加载速度
|
||||
|
||||
type Stats = fs.Stats & { name?: string };
|
||||
type ResourceType =
|
||||
| 'text'
|
||||
| 'buffer'
|
||||
| 'image'
|
||||
| 'material'
|
||||
| 'audio'
|
||||
| 'json'
|
||||
| 'zip';
|
||||
interface CompressedLoadListItem {
|
||||
type: ResourceType;
|
||||
name: string;
|
||||
usage: string;
|
||||
}
|
||||
type CompressedLoadList = Record<string, CompressedLoadListItem[]>;
|
||||
|
||||
export async function splitResorce(type: string) {
|
||||
await fs.ensureDir('./dist-resource');
|
||||
await fs.emptyDir('./dist-resource');
|
||||
await readySplit();
|
||||
|
||||
await zipResource();
|
||||
await split(type === 'dist' ? MAX_SIZE : void 0);
|
||||
|
||||
await endSplit(type);
|
||||
interface MainData {
|
||||
main: {
|
||||
images: string[];
|
||||
tilesets: string[];
|
||||
animates: string[];
|
||||
sounds: string[];
|
||||
fonts: string[];
|
||||
};
|
||||
}
|
||||
|
||||
async function readySplit() {
|
||||
await fs.ensureDir('./_temp');
|
||||
await fs.emptyDir('./_temp');
|
||||
await fs.ensureDir('./_temp/origin');
|
||||
await copyAll();
|
||||
}
|
||||
/** 单包大小,2M */
|
||||
const SPLIT_SIZE = 2 ** 20 * 2;
|
||||
|
||||
async function endSplit(type: string) {
|
||||
await rewriteMain(type);
|
||||
await fs.emptyDir('./_temp');
|
||||
await fs.rmdir('./_temp');
|
||||
}
|
||||
export async function splitResource() {
|
||||
const splitResult: CompressedLoadList = {};
|
||||
let now: CompressedLoadListItem[] = [];
|
||||
let totalSize: number = 0;
|
||||
let nowZip: JSZip = new JSZip();
|
||||
|
||||
async function zipResource() {
|
||||
const zip = motaConfig.zip;
|
||||
if (!zip) return;
|
||||
for await (const [name, files] of Object.entries(zip)) {
|
||||
const stream = new compressing.zip.Stream();
|
||||
const dirs: string[] = [];
|
||||
await fs.ensureDir('./dist/resource/');
|
||||
await fs.emptyDir('./dist/resource');
|
||||
|
||||
for await (const file of files) {
|
||||
if (/^.+\/\*$/.test(file)) {
|
||||
const dir = file.split('/')[0];
|
||||
dirs.push(dir);
|
||||
await fs.copy(`./_temp/origin/${dir}`, `./_temp/${dir}`);
|
||||
} else if (file.startsWith('!')) {
|
||||
const dir = file.slice(1);
|
||||
await fs.remove(`./_temp/${dir}`);
|
||||
} else {
|
||||
const [dir, name] = file.split('/');
|
||||
if (dirs.includes(dir)) dirs.push(dir);
|
||||
await fs.ensureDir(`./_temp/${dir}`);
|
||||
await fs.copyFile(
|
||||
`./_temp/origin/${dir}/${name}`,
|
||||
`./_temp/${dir}/${name}`
|
||||
const pushItem = async (
|
||||
type: ResourceType,
|
||||
name: string,
|
||||
usage: string,
|
||||
file: Stats,
|
||||
content: any
|
||||
) => {
|
||||
totalSize += file.size;
|
||||
|
||||
if (totalSize > SPLIT_SIZE) {
|
||||
if (file.size > SPLIT_SIZE) {
|
||||
const symbol = uniqueSymbol() + `.h5data`;
|
||||
console.warn(
|
||||
`file ${type}/${name}(${formatSize(
|
||||
file.size
|
||||
)}) is larger than split limit (${formatSize(
|
||||
SPLIT_SIZE
|
||||
)}), single zip will be generated.`
|
||||
);
|
||||
splitResult['resource/' + symbol] = [{ type, name, usage }];
|
||||
const zip = new JSZip();
|
||||
addZippedFile(zip, type, name, content);
|
||||
await writeZip(zip, `./dist/resource/${symbol}`, file.size);
|
||||
totalSize -= file.size;
|
||||
return;
|
||||
} else {
|
||||
const symbol = uniqueSymbol() + `.h5data`;
|
||||
splitResult['resource/' + symbol] = now;
|
||||
await writeZip(
|
||||
nowZip,
|
||||
`./dist/resource/${symbol}`,
|
||||
totalSize - file.size
|
||||
);
|
||||
nowZip = new JSZip();
|
||||
totalSize = 0;
|
||||
now = [];
|
||||
await pushItem(type, name, usage, file, content);
|
||||
}
|
||||
}
|
||||
|
||||
dirs.forEach(v => stream.addEntry(`./_temp/${v}`));
|
||||
const dest = fs.createWriteStream(`./_temp/${name}`);
|
||||
await new Promise<void>(res =>
|
||||
stream.pipe(dest).on('finish', () => {
|
||||
res();
|
||||
})
|
||||
);
|
||||
const stat = await fs.stat(`./_temp/${name}`);
|
||||
toMove.push({ ...stat, name: `./_temp/${name}` });
|
||||
}
|
||||
}
|
||||
|
||||
async function getRemainReource() {
|
||||
const zip = motaConfig.zip;
|
||||
if (!zip) return;
|
||||
const values = Object.values(zip);
|
||||
for await (const one of all) {
|
||||
if (values.some(v => v.includes(`${one}/*`))) continue;
|
||||
const list = await fs.readdir(`./_temp/origin/${one}`);
|
||||
for await (const name of list) {
|
||||
if (!values.some(vv => vv.includes(`${one}/${name}`))) {
|
||||
const stat = await fs.stat(`./_temp/origin/${one}/${name}`);
|
||||
toMove.push({
|
||||
...stat,
|
||||
name: `./_temp/origin/${one}/${name}`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
toMove.sort((a, b) => {
|
||||
if (a.name?.endsWith('.zip') && b.name?.endsWith('.zip')) {
|
||||
return b.size - a.size;
|
||||
}
|
||||
if (a.name?.endsWith('.zip')) return -1;
|
||||
if (b.name?.endsWith('.zip')) return 1;
|
||||
return b.size - a.size;
|
||||
});
|
||||
}
|
||||
|
||||
async function split(max?: number) {
|
||||
await getRemainReource();
|
||||
|
||||
const doSplit = async (index: string | number) => {
|
||||
const base =
|
||||
typeof index === 'string' ? index : `./dist-resource/${index}`;
|
||||
|
||||
await fs.ensureDir(base);
|
||||
await generatePublishStructure(
|
||||
base,
|
||||
typeof index === 'string' ? 0 : index
|
||||
);
|
||||
|
||||
let size = (await fs.stat(base)).size;
|
||||
// 计算出要移动多少资源
|
||||
const res = (() => {
|
||||
if (!max) return toMove.splice(0, toMove.length);
|
||||
let remain = max - size;
|
||||
for (let i = 0; i < toMove.length; i++) {
|
||||
const ele = toMove[i];
|
||||
remain -= ele.size;
|
||||
if (remain <= 0) {
|
||||
return toMove.splice(0, i);
|
||||
}
|
||||
}
|
||||
return toMove.splice(0, toMove.length);
|
||||
})();
|
||||
|
||||
if (base.endsWith('dist')) {
|
||||
await fs.ensureDir(resolve(base, 'resource'));
|
||||
}
|
||||
|
||||
// 执行移动
|
||||
await Promise.all(
|
||||
res.map(async v => {
|
||||
if (!v.name) return;
|
||||
// 压缩包
|
||||
if (v.name.endsWith('.zip')) {
|
||||
const [, , name] = v.name.split('/');
|
||||
const split = name.split('.');
|
||||
const target = `${split
|
||||
.slice(0, -1)
|
||||
.join('.')}-${SYMBOL}.${split.at(-1)}`;
|
||||
if (base.endsWith('dist')) {
|
||||
await fs.ensureDir(resolve(base, 'resource/zip'));
|
||||
return fs.copyFile(
|
||||
v.name,
|
||||
resolve(base, 'resource', 'zip', target)
|
||||
);
|
||||
} else {
|
||||
await fs.ensureDir(resolve(base, 'zip'));
|
||||
return fs.copyFile(
|
||||
v.name,
|
||||
resolve(base, 'zip', target)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 非压缩包
|
||||
if (!v.name.endsWith('.zip')) {
|
||||
const [, , , type, name] = v.name.split('/');
|
||||
const split = name.split('.');
|
||||
const target = `${split
|
||||
.slice(0, -1)
|
||||
.join('.')}-${SYMBOL}.${split.at(-1)}`;
|
||||
if (base.endsWith('dist')) {
|
||||
await fs.ensureDir(resolve(base, 'resource', type));
|
||||
} else {
|
||||
await fs.ensureDir(resolve(base, type));
|
||||
}
|
||||
if (base.endsWith('dist')) {
|
||||
return fs.copyFile(
|
||||
v.name,
|
||||
resolve(base, 'resource', type, target)
|
||||
);
|
||||
} else {
|
||||
return fs.copyFile(v.name, resolve(base, type, target));
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// 标记资源索引
|
||||
res.forEach(v => {
|
||||
if (!v.name) return;
|
||||
// 压缩包
|
||||
if (v.name.endsWith('.zip')) {
|
||||
const [, , name] = v.name.split('/');
|
||||
sourceIndex[`zip.${name}`] = index.toString();
|
||||
}
|
||||
// 非压缩包
|
||||
if (!v.name.endsWith('.zip')) {
|
||||
const [, , , type, name] = v.name.split('/');
|
||||
sourceIndex[`${type}.${name}`] = index.toString();
|
||||
}
|
||||
});
|
||||
|
||||
if (toMove.length > 0) {
|
||||
await doSplit(typeof index === 'string' ? 0 : index + 1);
|
||||
} else {
|
||||
now.push({ type, name, usage });
|
||||
addZippedFile(nowZip, type, name, content);
|
||||
}
|
||||
};
|
||||
await doSplit('dist');
|
||||
}
|
||||
|
||||
async function copyAll() {
|
||||
await Promise.all(
|
||||
all.map(v => {
|
||||
return fs.move(`./dist/project/${v}`, `./_temp/origin/${v}`);
|
||||
})
|
||||
);
|
||||
}
|
||||
const writeZip = (zip: JSZip, name: string, size: number) => {
|
||||
return new Promise<void>(res => {
|
||||
zip.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
|
||||
.pipe(fs.createWriteStream(name))
|
||||
.once('finish', function () {
|
||||
console.log(
|
||||
`Generated ${name}. Unzipped size: ${formatSize(size)}`
|
||||
);
|
||||
res();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
async function rewriteMain(type: string) {
|
||||
const main = await fs.readFile('./dist/main.js', 'utf-8');
|
||||
const res = main
|
||||
.replace(
|
||||
/this\.RESOURCE_TYPE\s*\=\s*.*;/,
|
||||
`this.RESOURCE_TYPE = '${type}';`
|
||||
)
|
||||
.replace(
|
||||
/this\.RESOURCE_URL\s*\=\s*'.*'/,
|
||||
`this.RESOURCE_URL = '/games/${motaConfig.resourceName}'`
|
||||
)
|
||||
.replace(
|
||||
/this\.RESOURCE_SYMBOL\s*\=\s*'.*'/,
|
||||
`this.RESOURCE_SYMBOL = '${SYMBOL}'`
|
||||
)
|
||||
.replace(
|
||||
/this\.RESOURCE_INDEX\s*\=\s*\{.*\}/,
|
||||
`this.RESOURCE_INDEX = ${JSON.stringify(sourceIndex, void 0, 8)}`
|
||||
);
|
||||
await fs.writeFile('./dist/main.js', res, 'utf-8');
|
||||
}
|
||||
const addZippedFile = (
|
||||
zip: JSZip,
|
||||
type: ResourceType,
|
||||
name: string,
|
||||
content: any
|
||||
) => {
|
||||
zip.file(`${type}/${name}`, content);
|
||||
};
|
||||
|
||||
/**
|
||||
* 生成可发布目录
|
||||
*/
|
||||
async function generatePublishStructure(dir: string, index: number) {
|
||||
await fs.ensureDir(resolve(dir, 'libs'));
|
||||
await fs.ensureDir(resolve(dir, 'libs/thirdparty'));
|
||||
await fs.ensureDir(resolve(dir, 'project'));
|
||||
await Promise.all(
|
||||
all.map(v => {
|
||||
fs.ensureDir(resolve(dir, 'project', v));
|
||||
fs.emptyDir(resolve(dir, 'project', v));
|
||||
})
|
||||
);
|
||||
const file = await fs.readFile('./dist/project/data.js', 'utf-8');
|
||||
const data = JSON.parse(file.split('\n').slice(1).join('')) as MainData;
|
||||
|
||||
if (!dir.endsWith('dist')) {
|
||||
await fs.writeFile(
|
||||
resolve(dir, 'project/icons.js'),
|
||||
`var icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1 =
|
||||
{"autotile": {}}
|
||||
`,
|
||||
'utf-8'
|
||||
);
|
||||
await fs.writeFile(
|
||||
resolve(dir, 'project/floors/none.js'),
|
||||
'"none"',
|
||||
'utf-8'
|
||||
);
|
||||
await fs.writeFile(resolve(dir, 'libs/none.js'), '"none"', 'utf-8');
|
||||
// images
|
||||
for (const image of data.main.images) {
|
||||
const path = `./dist/project/images/${image}`;
|
||||
const stat = await fs.stat(path);
|
||||
await pushItem('image', image, 'image', stat, await fs.readFile(path));
|
||||
}
|
||||
|
||||
await fs.copyFile('./script/template/main.js', resolve(dir, 'main.js'));
|
||||
const data = await fs.readFile('./script/template/data.js', 'utf-8');
|
||||
await fs.writeFile(
|
||||
resolve(dir, 'project/data.js'),
|
||||
data.replace('@name', `${motaConfig.resourceName}${index}`)
|
||||
);
|
||||
|
||||
await fs.copyFile(
|
||||
'./script/template/lz-string.min.js',
|
||||
resolve(dir, 'libs/thirdparty/lz-string.min.js')
|
||||
// tileset
|
||||
for (const tileset of data.main.tilesets) {
|
||||
const path = `./dist/project/tilesets/${tileset}`;
|
||||
const stat = await fs.stat(path);
|
||||
await pushItem(
|
||||
'image',
|
||||
tileset,
|
||||
'tileset',
|
||||
stat,
|
||||
await fs.readFile(path)
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
['animates', 'images', 'materials', 'sounds', 'tilesets'].map(v => {
|
||||
fs.copyFile(
|
||||
'./script/template/.h5data',
|
||||
resolve(dir, `project/${v}/${v}.h5data`)
|
||||
);
|
||||
})
|
||||
// animates
|
||||
for (const ani of data.main.animates) {
|
||||
const path = `./dist/project/animates/${ani}.animate`;
|
||||
const stat = await fs.stat(path);
|
||||
await pushItem(
|
||||
'text',
|
||||
ani + '.animate',
|
||||
'animate',
|
||||
stat,
|
||||
await fs.readFile(path, 'utf-8')
|
||||
);
|
||||
}
|
||||
|
||||
// sounds
|
||||
for (const sound of data.main.sounds) {
|
||||
const path = `./dist/project/sounds/${sound}`;
|
||||
const stat = await fs.stat(path);
|
||||
await pushItem('audio', sound, 'sound', stat, await fs.readFile(path));
|
||||
}
|
||||
|
||||
// fonts
|
||||
for (const font of data.main.fonts) {
|
||||
const path = `./dist/project/fonts/${font}.ttf`;
|
||||
const stat = await fs.stat(path);
|
||||
await pushItem(
|
||||
'buffer',
|
||||
font + '.ttf',
|
||||
'font',
|
||||
stat,
|
||||
await fs.readFile(path)
|
||||
);
|
||||
}
|
||||
|
||||
// autotiles
|
||||
const autotiles = await fs.readdir('./dist/project/autotiles');
|
||||
for (const a of autotiles) {
|
||||
const path = `./dist/project/autotiles/${a}`;
|
||||
const stat = await fs.stat(path);
|
||||
await pushItem('image', a, 'autotile', stat, await fs.readFile(path));
|
||||
}
|
||||
|
||||
// materials
|
||||
const materials = await fs.readdir('./dist/project/materials');
|
||||
for (const m of materials) {
|
||||
const path = `./dist/project/materials/${m}`;
|
||||
const stat = await fs.stat(path);
|
||||
await pushItem(
|
||||
'material',
|
||||
m,
|
||||
'material',
|
||||
stat,
|
||||
await fs.readFile(path)
|
||||
);
|
||||
}
|
||||
|
||||
const symbol = uniqueSymbol() + `.h5data`;
|
||||
splitResult['resource/' + symbol] = now;
|
||||
await writeZip(nowZip, `./dist/resource/${symbol}`, totalSize);
|
||||
|
||||
// 添加资源映射
|
||||
await fs.writeFile(
|
||||
'./dist/loadList.json',
|
||||
JSON.stringify(splitResult),
|
||||
'utf-8'
|
||||
);
|
||||
|
||||
// 删除原资源
|
||||
await fs.emptyDir('./dist/project/images');
|
||||
await fs.emptyDir('./dist/project/tilesets');
|
||||
await fs.emptyDir('./dist/project/animates');
|
||||
await fs.emptyDir('./dist/project/fonts');
|
||||
await fs.emptyDir('./dist/project/materials');
|
||||
await fs.emptyDir('./dist/project/sounds');
|
||||
await fs.emptyDir('./dist/project/autotiles');
|
||||
// 然后加入填充内容
|
||||
await fs.copy(
|
||||
'./script/template/.h5data',
|
||||
'./dist/project/images/images.h5data'
|
||||
);
|
||||
await fs.copy(
|
||||
'./script/template/.h5data',
|
||||
'./dist/project/tilesets/tilesets.h5data'
|
||||
);
|
||||
await fs.copy(
|
||||
'./script/template/.h5data',
|
||||
'./dist/project/animates/animates.h5data'
|
||||
);
|
||||
await fs.copy(
|
||||
'./script/template/.h5data',
|
||||
'./dist/project/fonts/fonts.h5data'
|
||||
);
|
||||
await fs.copy(
|
||||
'./script/template/.h5data',
|
||||
'./dist/project/materials/materials.h5data'
|
||||
);
|
||||
await fs.copy(
|
||||
'./script/template/.h5data',
|
||||
'./dist/project/sounds/sounds.h5data'
|
||||
);
|
||||
await fs.copy(
|
||||
'./script/template/.h5data',
|
||||
'./dist/project/autotiles/autotiles.h5data'
|
||||
);
|
||||
}
|
||||
Binary file not shown.
Reference in new issue
Block a user