Инструмент: релизная сборка tools/minify.mjs (index.html −35%). (1) Методы: JS — terser compress passes:2 + mangle.toplevel:false (глобалы из буллета «Подводные камни» держатся именами — артефакт прогоняется обычной тестовой суитой); CSS — csso в каждом <style> КРОМЕ блока EMOJI-FONT (base64 woff2 держится побайтово, csso мог бы перенормировать кавычки); FISH-SVG — вырезаны HTML-комменты (~50 КБ описаний анатомии) и whitespace между тегами (в SVG текст-ноды не рендерятся — безопасно); прочие HTML-комменты — кроме маркеров EMOJI-FONT/FISH-SVG BEGIN/END (нельзя: whitespace внутри отрицательного lookaхeda, иначе <!-- EMOJI-FONT:BEGIN съедался бэктреком); whitespace DOM — только между блок-тегами (div/section/… + canvas/svg), inline-текст и пробелы между <b>…</b> и словом не трогаются. (2) Самопроверка 6 пунктов при любом FAIL exit 1: (a) размер, (b) node --check вынутого JS (паттерн tools/check.mjs), (c) маркеры по одному, (d) набор id идентичен (104), (e) видимый текст DOM идентичен, (f) блок EMOJI-FONT побайтово. (3) Артефакт dist/index.release.html: 541029 → 349886 байт (−35,3%), gzip 183366 → 128565; разбивка релиза: JS 112 КБ, SVG 120 КБ, шрифт 93 КБ, CSS 18 КБ, DOM ~5 КБ. (4) Валидация: ПОЛНАЯ тестовая суита на самом артефакте (подставлен под index.html) — 222/222 PASS, exit 0, headless Chromium. (5) Зависимости tools/: terser 5.51.2 + csso 5.0.5 (package.json + package-lock.json, npm i --prefix tools; tools/node_modules не коммитится — .gitignore +node_modules/, заодно прикрыл tests/node_modules). (6) index.html, balance, тесты, скриншоты НЕ тронуты. Доки: README (секция «Релизная (уменьшенная) сборка» в «Публикация в Яндекс Играх»), AGENTS.md (пункт 6 в «Проверка»: команда + методы + обязательный прогон tests/test.js на артефакте перед публикацией).
This commit is contained in:
@@ -6,6 +6,9 @@ tests/shot-*.png
|
||||
tools/fonts-src/
|
||||
tools/.venv/
|
||||
|
||||
# node_modules (tests/ — puppeteer-core, tools/ — terser+csso для minify.mjs)
|
||||
node_modules/
|
||||
|
||||
# Служебное
|
||||
.opencode/
|
||||
.po-handoff.md
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
Проходит тот же геймплей «вживую» в браузере (не headless) и пишет `orca-*.png` в каталог запуска. Если Orca app не запущен — пропустить и НЕ блокировать (главный рубеж — пункт 2). Подробности и подводные камни: `docs/BROWSER-TESTING.md`.
|
||||
4. Скриншоты: `node tests/gen-screenshots.js [каталог-сборки] [папка-вывода] [язык]` — 15 кадров × 3 вьюпорта (1280×720 / 360×640@2x / 800×[450@1.6x](mailto:450@1.6x); кадры sea/ocean — этап 43, swamp/swamp-night — этап 45, aquarium — этап 46), 24-битный PNG без альфа. Без аргументов — русские в `screenshots-wiki/`, с `en` — английские в `screenshots-wiki-en/`. README ссылается на `screenshots-wiki/mobp-*` (мобильный портрет). Emoji рисует встроенный шрифт (раздел «Встроенный emoji-шрифт») — системные emoji-шрифты для скриншотов больше не нужны.
|
||||
5. Публикация в Яндекс Играх: `node tools/pack.mjs` (сборка dist/fishing-yandex.zip + самопроверка 5 пунктов) → `node tests/pack-smoke.js` (smoke играбельности КОПИИ из архива, 6 чек) → загрузить zip в консоль. Доки: раздел README «Публикация в Яндекс Играх», запись Этап 27 в DEVLOG.
|
||||
6. Релизная (уменьшенная) сборка: `node tools/minify.mjs` (зависимости: `npm i --prefix tools` — terser+csso) → `dist/index.release.html` (−35% к размеру) + самопроверка 6 пунктов (размер, node --check, маркеры EMOJI-FONT/FISH-SVG, набор id, видимый текст DOM, base64 шрифта). Методы: terser с `mangle.toplevel:false` (глобалы из буллета «Подводные камни» — «Тесты гоняют игру через внутренние глобалы» — держатся ИМЯМИ, поэтому артефакт можно прогнать тем же tests/test.js), csso в каждом `<style>` кроме блока шрифта (держится побайтово), в FISH-SVG удаляются комменты и whitespace между тегами, прочие HTML-комменты — кроме маркеров, whitespace DOM — только между блок-тегами. ПЕРЕД публикацией артефакта: подставить его под `index.html` и прогнать `node tests/test.js` (полную суиту) и/или цепочку пункта 5.
|
||||
|
||||
## Подводные камни
|
||||
|
||||
|
||||
@@ -68,6 +68,12 @@ node tools/pack.mjs # → dist/fishing-yandex.zip
|
||||
node tests/pack-smoke.js # перед загрузкой в консоль: smoke упакованной копии (игра из архива, 6 проверок)
|
||||
```
|
||||
|
||||
### Релизная (уменьшенная) сборка
|
||||
|
||||
Игра — один файл (~528 КБ), из них ~200 КБ — встроенные SVG-портреты рыб и ~93 КБ — вшитый emoji-шрифт. Для публикации «поменьше» есть минификатор: `node tools/minify.mjs` (зависимости: `npm i --prefix tools` — terser+csso). Он пишет `dist/index.release.html` (−35%: 528→342 КБ, gzip 179→126 КБ) и гоняет самопроверку (6 пунктов: размер, синтаксис JS, маркеры блоков, набор id, видимый текст DOM, base64 шрифта). Что сжимает: JS (terser, имена глобалов НЕ трогаются — артефакт прогоняется обычной тестовой суитой), CSS (csso), комменты и whitespace в SVG-портретах и DOM. Блок emoji-шрифта не трогается (base64 woff2 уже сжат).
|
||||
|
||||
Перед публикацией: подставь артефакт под `index.html` и прогони `node tests/test.js` (222 проверки) и/или цепочку выше (pack → pack-smoke).
|
||||
|
||||
SDK подхватывается автоматически на площадке; локально (file://) игра работает без него (встроенный стаб). Локальная отладка с SDK: открыть игру с параметром `?sdk=1` и положить `sdk.js` рядом с игрой (или поднять локальный прокси SDK Яндекса). В консоли разработчика (games.yandex.ru): включить монетизацию, опция облачных сейвов — выключена (игра сама хранит сейв в `localStorage`), платформы — без Android TV, `debug-mode=16` — для проверки лоадера.
|
||||
|
||||
## Тесты
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
#!/usr/bin/env node
|
||||
// Минификация Релизной сборки index.html → dist/index.release.html (для публикации).
|
||||
//
|
||||
// Что делает (по частям файла, маркеры блоков не трогаются):
|
||||
// 1. <script> — terser: compress passes:2, mangle с toplevel:false.
|
||||
// ВАЖНО: toplevel:false — имена глобалов (state, save, SPECIES, aqOpen, …) НЕ
|
||||
// переименовываются: их пинит тестовая суита (tests/test.js через page.evaluate)
|
||||
// и раздел AGENTS.md «Глобалы, которых нельзя переименовывать».
|
||||
// 2. <style> — csso (каждый блок; EMOJI-FONT блок csso тоже проходит, но base64
|
||||
// внутри src:url() остаётся побайтово — самопроверка (f) это гарантирует).
|
||||
// 3. FISH-SVG — удаляются HTML-комменты (русс. описания анатомии рыб, ~50 КБ) и
|
||||
// whitespace между тегами. В SVG текст-ноды не рендерятся, поэтому `>\s+<`→`><`
|
||||
// безопасно; path-данные и атрибуты не трогаются.
|
||||
// 4. Прочие HTML-комменты — удаляются, КРОМЕ маркеров EMOJI-FONT/FISH-SVG
|
||||
// (BEGIN/END парами — нужны, чтобы блок оставался распознаваемым).
|
||||
// 5. Whitespace DOM — схлопывается ТОЛЬКО между тегами блок-уровня (div/section/…).
|
||||
// Inline-текст (в т.ч. пробелы между <b>…</b> и словом) не трогаем — самопроверка (e).
|
||||
//
|
||||
// Самопроверка (при любом FAIL — exit 1, артефакт не считается готовым):
|
||||
// (a) размер выходного файла < входного;
|
||||
// (b) синтаксис вынутого JS — node --check (тот же паттерн, что в tools/check.mjs);
|
||||
// (c) маркеры EMOJI-FONT/FISH-SVG BEGIN/END сохранены (по 2 штуки);
|
||||
// (d) набор id="…" в HTML идентичен оригиналу;
|
||||
// (e) видимый текст DOM (вне script/style/SVG/комментов) идентичен оригиналу побайтово;
|
||||
// (f) блок EMOJI-FONT (base64 woff2) идентичен оригиналу побайтово.
|
||||
//
|
||||
// Зависимости: terser, csso (npm i --prefix tools; package.json в tools/).
|
||||
// Запуск: node tools/minify.mjs [путь-к-исходному-html] (по умолчанию index.html в корне репо)
|
||||
//
|
||||
// Дальше по желанию:
|
||||
// - публикация веб-хостинга: положить dist/index.release.html как index.html;
|
||||
// серверу включить gzip/brotli (тогда по сети пойдёт ещё меньше — см. вывод скрипта);
|
||||
// - Яндекс Игры: заменить index.html на релизную версию и `node tools/pack.mjs`
|
||||
// (pack сам проверит JS-синтаксис из архива), либо прогнать tests/test.js на копии.
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { createRequire } from 'node:module';
|
||||
import { existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { gzipSync } from 'node:zlib';
|
||||
import { minify } from 'terser';
|
||||
const require = createRequire(import.meta.url);
|
||||
const csso = require('csso');
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const htmlPath = process.argv[2] || join(root, 'index.html');
|
||||
const distDir = join(root, 'dist');
|
||||
const outPath = join(distDir, 'index.release.html');
|
||||
|
||||
if (!existsSync(htmlPath)) {
|
||||
console.error(`minify: не найден ${htmlPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
const src = readFileSync(htmlPath, 'utf8');
|
||||
|
||||
// ---------- 1. JS: terser ----------
|
||||
const JS_RE = /<script>([\s\S]*?)<\/script>/;
|
||||
const jsM = src.match(JS_RE);
|
||||
if (!jsM) {
|
||||
console.error('minify: не найден <script>-блок с JS');
|
||||
process.exit(1);
|
||||
}
|
||||
let jsMin;
|
||||
try {
|
||||
jsMin = (await minify(jsM[1], {
|
||||
compress: { passes: 2, module: false },
|
||||
mangle: { toplevel: false }, // глобалы держим — см. шапку
|
||||
format: { comments: false },
|
||||
})).code;
|
||||
} catch (e) {
|
||||
console.error(`minify: terser не смог минифицировать JS: ${e.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
let out = src.replace(jsM[1], () => jsMin); // function-замена: спецсимволы в JS-коде не влияют
|
||||
|
||||
// ---------- 2. CSS: csso в каждом <style>, КРОМЕ блока EMOJI-FONT ----------
|
||||
// Блок шрифта — сгенерированный артефакт (tools/emoji-font.py), csso там всё равно
|
||||
// ничего не сжимает (base64 не трогается), зато мог бы перенормировать кавычки —
|
||||
// проходим мимо, держим побайтово (самопроверка (f) это ловит).
|
||||
{
|
||||
const fb = out.indexOf('EMOJI-FONT:BEGIN');
|
||||
const fe = out.indexOf('EMOJI-FONT:END');
|
||||
out = out.replace(/<style>([\s\S]*?)<\/style>/g, (all, css, off) => {
|
||||
if (fb !== -1 && fe !== -1 && off > fb && off < fe) return all;
|
||||
return '<style>' + csso.minify(css).css + '</style>';
|
||||
});
|
||||
}
|
||||
|
||||
// ---------- 3. FISH-SVG: без комментов, без whitespace между тегами ----------
|
||||
const SVG_RE = /(<!-- FISH-SVG:BEGIN[^>]*-->)([\s\S]*?)(<!-- FISH-SVG:END -->)/;
|
||||
const svgM = out.match(SVG_RE);
|
||||
if (!svgM) {
|
||||
console.error('minify: не найден блок FISH-SVG (маркеры BEGIN/END)');
|
||||
process.exit(1);
|
||||
}
|
||||
const svgInner = svgM[2].replace(/<!--[\s\S]*?-->/g, '').replace(/>\s+</g, '><');
|
||||
out = out.replace(svgM[0], () => svgM[1] + svgInner + svgM[3]);
|
||||
|
||||
// ---------- 4. Прочие HTML-комменты (кроме маркеров EMOJI-FONT/FISH-SVG) ----------
|
||||
// Нюанс: whitespace идёт ВНУТРИ отрицательного lookахeda (`[^>]*`), а не `\s*` после
|
||||
// `<!--` — иначе `\s*` откатывалось бы назад и маркер `<!-- EMOJI-FONT:BEGIN…` съедался.
|
||||
out = out.replace(/<!--(?![^>]*(?:EMOJI-FONT|FISH-SVG):)[\s\S]*?-->[ \t]*\n?/g, '');
|
||||
|
||||
// ---------- 5. Whitespace DOM: только между блок-тегами ----------
|
||||
const BLOCK = /^(div|body|section|header|footer|main|nav|ul|ol|li|table|canvas|svg)$/i;
|
||||
{
|
||||
let prevOpen = ''; // имя последнего открытого (и ещё не закрытого… упрощённо: последнего встреченного) тега
|
||||
let outDom = '';
|
||||
let i = 0;
|
||||
while (i < out.length) {
|
||||
const lt = out.indexOf('<', i);
|
||||
if (lt < 0) { outDom += out.slice(i); break; }
|
||||
outDom += out.slice(i, lt);
|
||||
const gt = out.indexOf('>', lt);
|
||||
if (gt < 0) { outDom += out.slice(lt); break; }
|
||||
const tag = out.slice(lt, gt + 1);
|
||||
if (tag.startsWith('</')) {
|
||||
const nm = tag.slice(2, -1).trim().split(/\s/)[0].toLowerCase();
|
||||
prevOpen = nm;
|
||||
} else if (!tag.startsWith('<!--')) {
|
||||
const nm = (tag.match(/^<([a-zA-Z][a-zA-Z0-9]*)/) || [])[1];
|
||||
if (nm) prevOpen = nm.toLowerCase();
|
||||
}
|
||||
outDom += tag;
|
||||
i = gt + 1;
|
||||
// whitespace сразу после тега: съедаем, если закрыт блок-тег и дальше блок-тег/конец
|
||||
let j = i;
|
||||
while (j < out.length && /[\r\n \t]/.test(out[j])) j++;
|
||||
let eat = false;
|
||||
if (j > i && BLOCK.test(prevOpen)) {
|
||||
if (j >= out.length) eat = true;
|
||||
else if (out[j] === '<') {
|
||||
if (out.startsWith('<!', j)) eat = false; // перед комментом не трогаем (маркер!)
|
||||
else {
|
||||
const head = out.slice(j, out.indexOf('>', j));
|
||||
const nname = ((head.match(/^<\/?([a-zA-Z][a-zA-Z0-9]*)/) || [])[1] || '').toLowerCase();
|
||||
eat = BLOCK.test(nname) || head.startsWith('</');
|
||||
}
|
||||
}
|
||||
}
|
||||
if (eat) i = j;
|
||||
}
|
||||
out = outDom;
|
||||
}
|
||||
|
||||
// ---------- Запись ----------
|
||||
mkdirSync(distDir, { recursive: true });
|
||||
writeFileSync(outPath, out);
|
||||
|
||||
// ---------- Самопроверка ----------
|
||||
let failed = false;
|
||||
const fail = (code, msg) => { failed = true; console.error(`FAIL (${code}) ${msg}`); };
|
||||
const pass = (code, msg) => console.log(`PASS (${code}) ${msg}`);
|
||||
|
||||
const srcBytes = Buffer.byteLength(src, 'utf8');
|
||||
const outBytes = Buffer.byteLength(out, 'utf8');
|
||||
|
||||
// (a) стало меньше
|
||||
if (outBytes < srcBytes) pass('a', `размер: ${fmt(srcBytes)} → ${fmt(outBytes)} (−${(100 * (1 - outBytes / srcBytes)).toFixed(1)}%)`);
|
||||
else fail('a', `минификат (${fmt(outBytes)}) не меньше оригинала (${fmt(srcBytes)})`);
|
||||
|
||||
// (b) синтаксис JS из ВЫХОДНОГО файла (паттерн tools/check.mjs)
|
||||
{
|
||||
const blocks = [];
|
||||
for (const m of out.matchAll(/<script\b([^>]*)>([\s\S]*?)<\/script>/gi)) {
|
||||
if (/\bsrc\s*=/i.test(m[1])) continue;
|
||||
if (m[2].trim()) blocks.push(m[2]);
|
||||
}
|
||||
if (!blocks.length) fail('b', 'в выходном файле не найдено <script>-блоков с JS');
|
||||
else {
|
||||
const tmp = join(tmpdir(), `minify-check-${process.pid}`);
|
||||
mkdirSync(tmp, { recursive: true });
|
||||
let synOk = true;
|
||||
try {
|
||||
blocks.forEach((code, i) => {
|
||||
const f = join(tmp, `block-${i + 1}.js`);
|
||||
writeFileSync(f, code);
|
||||
const r = spawnSync(process.execPath, ['--check', f], { encoding: 'utf8' });
|
||||
if (r.status !== 0) {
|
||||
synOk = false;
|
||||
fail('b', `синтаксис JS в блоке ${i + 1}: ${((r.stderr || r.stdout || '').trim()).split('\n')[0]}`);
|
||||
}
|
||||
});
|
||||
if (synOk) pass('b', `синтаксис JS корректен (node --check, блоков: ${blocks.length})`);
|
||||
} finally {
|
||||
rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (c) маркеры сохранены
|
||||
{
|
||||
const cnt = (re) => (out.match(re) || []).length;
|
||||
const okC =
|
||||
cnt(/EMOJI-FONT:BEGIN/g) === 1 && cnt(/EMOJI-FONT:END/g) === 1 &&
|
||||
cnt(/FISH-SVG:BEGIN/g) === 1 && cnt(/FISH-SVG:END/g) === 1;
|
||||
if (okC) pass('c', 'маркеры EMOJI-FONT/FISH-SVG BEGIN/END сохранены (по одному)');
|
||||
else fail('c', `маркеры потеряны: EMOJI-FONT BEGIN/END=${cnt(/EMOJI-FONT:BEGIN/g)}/${cnt(/EMOJI-FONT:END/g)}, FISH-SVG BEGIN/END=${cnt(/FISH-SVG:BEGIN/g)}/${cnt(/FISH-SVG:END/g)}`);
|
||||
}
|
||||
|
||||
// (d) набор id идентичен
|
||||
{
|
||||
const ids = (s) => new Set([...s.matchAll(/id="([^"]+)"/g)].map((m) => m[1]));
|
||||
const a = ids(src), b = ids(out);
|
||||
const missing = [...a].filter((x) => !b.has(x));
|
||||
const extra = [...b].filter((x) => !a.has(x));
|
||||
if (missing.length === 0 && extra.length === 0) pass('d', `набор id идентичен (${a.size} шт)`);
|
||||
else fail('d', `id расходятся: потеряно=[${missing}] лишнее=[${extra}]`);
|
||||
}
|
||||
|
||||
// (e) видимый текст DOM идентичен (вне script/style/SVG/комментов)
|
||||
{
|
||||
const domText = (s) => {
|
||||
let t = s;
|
||||
t = t.replace(/<style>[\s\S]*?<\/style>/g, ' ');
|
||||
t = t.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, ' ');
|
||||
t = t.replace(/<!--[\s\S]*?-->/g, ' ');
|
||||
const i = t.search(/FISH-SVG:BEGIN/);
|
||||
const j = t.indexOf('FISH-SVG:END');
|
||||
if (i !== -1 && j !== -1) t = t.slice(0, i) + t.slice(j);
|
||||
t = t.replace(/<[^>]+>/g, ' ');
|
||||
return t.replace(/\s+/g, ' ').trim();
|
||||
};
|
||||
if (domText(src) === domText(out)) pass('e', 'видимый текст DOM идентичен оригиналу');
|
||||
else fail('e', 'видимый текст DOM ИЗМЕНИЛСЯ (минификация съела текст)');
|
||||
}
|
||||
|
||||
// (f) блок EMOJI-FONT побайтово
|
||||
{
|
||||
const font = (s) => {
|
||||
const i = s.indexOf('EMOJI-FONT:BEGIN');
|
||||
const j = s.indexOf('EMOJI-FONT:END');
|
||||
return (i === -1 || j === -1) ? null : s.slice(i, j + 'EMOJI-FONT:END'.length);
|
||||
};
|
||||
const a = font(src), b = font(out);
|
||||
if (a && b && a === b) pass('f', 'блок EMOJI-FONT (base64 woff2) идентичен оригиналу');
|
||||
else fail('f', 'блок EMOJI-FONT изменён/потерян');
|
||||
}
|
||||
|
||||
// ---------- Итог ----------
|
||||
if (failed) {
|
||||
console.error(`minify: FAIL — ${outPath} НЕ готов к публикации (см. пункты выше)`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`minify: OK — ${outPath}`);
|
||||
console.log(` размер: ${fmt(srcBytes)} → ${fmt(outBytes)} (−${(100 * (1 - outBytes / srcBytes)).toFixed(1)}%)`);
|
||||
console.log(` gzip (оценка): ${fmt(gzipSync(Buffer.from(src, 'utf8')).length)} → ${fmt(gzipSync(Buffer.from(out, 'utf8')).length)}`);
|
||||
console.log(' проверка работоспособности: подставь артефакт под index.html и прогони node tests/test.js (и/или tests/pack-smoke.js через pack.mjs)');
|
||||
|
||||
function fmt(bytes) {
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' КБ';
|
||||
return (bytes / (1024 * 1024)).toFixed(2) + ' МБ';
|
||||
}
|
||||
Generated
+162
@@ -0,0 +1,162 @@
|
||||
{
|
||||
"name": "fishing-game-tools",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "fishing-game-tools",
|
||||
"dependencies": {
|
||||
"csso": "^5.0.5",
|
||||
"terser": "^5.48.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/resolve-uri": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.11",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
|
||||
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
|
||||
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.31",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.18.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/buffer-from": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
|
||||
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.28",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csso": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
|
||||
"integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"css-tree": "~2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.28",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
|
||||
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==",
|
||||
"license": "CC0-1.0"
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-js": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.5.21",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
|
||||
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.51.2",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz",
|
||||
"integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==",
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.15.0",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "fishing-game-tools",
|
||||
"private": true,
|
||||
"description": "Инструменты сборки: minify.mjs (terser + csso)",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"csso": "^5.0.5",
|
||||
"terser": "^5.48.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user