storm-lua-minify 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) Mathias Bynens, 2024 Nona Takahara
3
+ Copyright (c) 2024 Nona Takahara, Mathias Bynens
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -3,3 +3,13 @@
3
3
  このプログラムは [mathiasbynens/luamin](https://github.com/mathiasbynens/luamin) をベースにした Stormworks: Build and Rescue 向けのLua minifierです。
4
4
 
5
5
  Stormworks: Build and Rescue 以外の用途にも使用可能です。
6
+
7
+ # 使い方
8
+
9
+ ```
10
+ npm i storm-lua-minify
11
+
12
+ npx storm-lua-minify script.lua
13
+ ```
14
+
15
+ - `-m`オプションを付加すると、モジュールの挙動をLuaの実際の挙動に近づけます
package/dist/ast2lua.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // based on "luamin": Copyright Mathias Bynens <https://mathiasbynens.be/>
3
3
  // SPDX-License-Identifier: MIT
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
- exports.Minifier = void 0;
5
+ exports.MinifyFile = void 0;
6
6
  const source_map_1 = require("source-map");
7
7
  const PRECEDENCE = {
8
8
  or: 1,
@@ -89,8 +89,6 @@ const IDENTIFIER_PARTS = [
89
89
  "Z",
90
90
  "_",
91
91
  ];
92
- const identifierMap = new Map();
93
- const identifiersInUse = new Set();
94
92
  function wrapArray(obj) {
95
93
  if (Array.isArray(obj)) {
96
94
  return obj;
@@ -206,25 +204,27 @@ function prependWithSeparator(val, prepending, separator = " ") {
206
204
  function insertSeparator(a, b, separator = " ") {
207
205
  return isNeedSeparator(a.toString(), b.toString()) ? separator : undefined;
208
206
  }
209
- class Minifier {
207
+ class MinifyFile {
210
208
  fileName;
211
209
  ast;
212
- requireHelper;
213
- constructor(fileName, ast, requireHelper) {
210
+ minifier;
211
+ mode;
212
+ constructor(fileName, ast, minifier, mode) {
214
213
  this.fileName = fileName;
215
214
  this.ast = ast;
216
- this.requireHelper = requireHelper;
217
- ast.globals?.map((v) => identifiersInUse.add(v.name));
215
+ this.minifier = minifier;
216
+ this.mode = mode;
218
217
  }
219
- parse() {
218
+ parse(noComment) {
220
219
  const body = this.formatStatementList(this.ast.body);
221
- /*if (this.ast.comments) {
222
- const comments = this.ast.comments as Comment[];
223
- comments.reverse().filter(v => v.raw.includes("--#") || v.raw.includes("[[#")).forEach(comment => {
224
- body.prepend(this.sourceNodeHelper(comment, comment.raw));
225
- })
226
- return body;
227
- } else*/ {
220
+ if (!noComment && this.ast.comments) {
221
+ const comments = this.ast.comments;
222
+ comments.reverse().filter(v => v.raw.includes("--#") || v.raw.includes("[[#")).forEach(comment => {
223
+ body.prepend([this.sourceNodeHelper(comment, comment.raw), "\n"]);
224
+ });
225
+ return body;
226
+ }
227
+ else {
228
228
  return body;
229
229
  }
230
230
  }
@@ -501,11 +501,21 @@ class Minifier {
501
501
  }
502
502
  else if (expression.type == "CallExpression") {
503
503
  // requireの展開モードは2種類: SLモード-その場に読み込み, FLモード-require相当の関数で呼び出し
504
- if (this.formatBase(expression.base).toString() == "require" &&
504
+ const callExpr = this.formatBase(expression.base).toString();
505
+ if ((callExpr === "require" || callExpr === "dofile") &&
505
506
  expression?.arguments[0].type === "StringLiteral") {
506
- const res = this.requireHelper(expression.arguments[0].raw.replaceAll('"', ""));
507
- if (res != undefined) {
508
- return res;
507
+ const moduleName = expression.arguments[0].raw
508
+ .replaceAll('"', "")
509
+ .replaceAll("'", "");
510
+ const res = this.minifier.parseModule(moduleName);
511
+ if (this.mode.moduleLikeLua) {
512
+ if (callExpr === "dofile") {
513
+ return (res || this.sourceNodeHelper(undefined, ""));
514
+ }
515
+ // requireならスキップ
516
+ }
517
+ else {
518
+ return (res || this.sourceNodeHelper(undefined, ""));
509
519
  }
510
520
  }
511
521
  const args = expression.arguments
@@ -632,7 +642,7 @@ class Minifier {
632
642
  if (nameItem.name === "self") {
633
643
  return this.sourceNodeHelper(nameItem, "self", "self");
634
644
  }
635
- const defined = identifierMap.get(nameItem.name);
645
+ const defined = this.minifier.identifierMap.get(nameItem.name);
636
646
  if (defined) {
637
647
  return this.sourceNodeHelper(nameItem, defined, nameItem.name); // 第3引数は要調査
638
648
  }
@@ -649,21 +659,21 @@ class Minifier {
649
659
  IDENTIFIER_PARTS[index + 1] +
650
660
  generateZeroes(length - (position + 1));
651
661
  if (isKeyword(this.currentIdentifier) ||
652
- identifiersInUse.has(this.currentIdentifier)) {
662
+ this.minifier.identifiersInUse.has(this.currentIdentifier)) {
653
663
  return this.generateIdentifier(nameItem, nested);
654
664
  }
655
- identifierMap.set(nameItem.name, this.currentIdentifier);
665
+ this.minifier.identifierMap.set(nameItem.name, this.currentIdentifier);
656
666
  return this.generateIdentifier(nameItem, nested);
657
667
  }
658
668
  --position;
659
669
  }
660
670
  this.currentIdentifier = "a" + generateZeroes(length);
661
- if (identifiersInUse.has(this.currentIdentifier)) {
671
+ if (this.minifier.identifiersInUse.has(this.currentIdentifier)) {
662
672
  return this.generateIdentifier(nameItem, nested);
663
673
  }
664
- identifierMap.set(nameItem.name, this.currentIdentifier);
674
+ this.minifier.identifierMap.set(nameItem.name, this.currentIdentifier);
665
675
  return this.generateIdentifier(nameItem, nested);
666
676
  // return this.sourceNodeHelper(nameItem, nameItem.name, nested ? nameItem.name : undefined);
667
677
  }
668
678
  }
669
- exports.Minifier = Minifier;
679
+ exports.MinifyFile = MinifyFile;
package/dist/cli.js CHANGED
@@ -7,10 +7,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const commander_1 = require("commander");
10
- const luaparse_1 = __importDefault(require("luaparse"));
11
- const ast2lua_1 = require("./ast2lua");
10
+ const minifier_1 = require("./minifier");
12
11
  const program = new commander_1.Command();
13
- program.version("0.1.0").description("A Lua minifier also outputs source map");
12
+ program
13
+ .version("0.1.2")
14
+ .description("A Lua minifier also outputs source map")
15
+ .option("-m, --module-like-lua", "require・dofileの動作を実際のLuaに近づけます");
14
16
  program.parse(process.argv);
15
17
  const luaFiles = program.args;
16
18
  const luaparseSetting = {
@@ -19,48 +21,25 @@ const luaparseSetting = {
19
21
  ranges: true,
20
22
  scope: true,
21
23
  };
24
+ const mode = program.opts();
22
25
  luaFiles.forEach((fileName) => {
23
- const includes = new Map();
24
26
  const parsedFileName = path_1.default.parse(fileName);
25
- function requireHelper(recursiveFilePath) {
26
- const resolvePath = path_1.default.relative(parsedFileName.dir, path_1.default.join(parsedFileName.dir, recursiveFilePath.replaceAll(".", path_1.default.sep) + ".lua"));
27
- const fullResolvePath = path_1.default.join(parsedFileName.dir, resolvePath);
28
- if (!includes.has(resolvePath) && fs_1.default.existsSync(fullResolvePath)) {
29
- const code = fs_1.default.readFileSync(fullResolvePath).toString();
30
- const ast = luaparse_1.default.parse(code, luaparseSetting);
31
- if ("globals" in ast) {
32
- includes.set(resolvePath, code);
33
- return new ast2lua_1.Minifier(resolvePath, ast, requireHelper).parse();
34
- }
35
- includes.set(resolvePath, "");
36
- return undefined;
37
- }
38
- }
39
27
  if (fs_1.default.existsSync(fileName)) {
40
- const code = fs_1.default.readFileSync(fileName).toString();
41
- const ast = luaparse_1.default.parse(code, luaparseSetting);
42
- if ("globals" in ast) {
43
- includes.set(parsedFileName.base, code);
44
- const map = new ast2lua_1.Minifier(parsedFileName.base, ast, requireHelper).parse();
45
- includes.forEach((v, k) => {
46
- console.log(k);
47
- map.setSourceContent(k, v);
48
- });
49
- const minFileName = path_1.default.format({
50
- dir: parsedFileName.dir,
51
- name: parsedFileName.name + ".min",
52
- ext: ".lua",
53
- });
54
- const mapFileName = path_1.default.format({
55
- dir: parsedFileName.dir,
56
- name: parsedFileName.name,
57
- ext: parsedFileName.ext + ".map",
58
- });
59
- map.add("\n--[[\n//# sourceMappingURL=" + mapFileName + "\n]]");
60
- const sourceAndMap = map.toStringWithSourceMap();
61
- fs_1.default.writeFileSync(minFileName, sourceAndMap.code);
62
- fs_1.default.writeFileSync(mapFileName, JSON.stringify(sourceAndMap.map));
63
- }
28
+ const map = new minifier_1.Minifier(fileName, luaparseSetting, mode).parse();
29
+ const minFileName = path_1.default.format({
30
+ dir: parsedFileName.dir,
31
+ name: parsedFileName.name + ".min",
32
+ ext: ".lua",
33
+ });
34
+ const mapFileName = path_1.default.format({
35
+ dir: parsedFileName.dir,
36
+ name: parsedFileName.name,
37
+ ext: parsedFileName.ext + ".map",
38
+ });
39
+ map.add("\n--[[\n//# sourceMappingURL=" + mapFileName + "\n]]");
40
+ const sourceAndMap = map.toStringWithSourceMap();
41
+ fs_1.default.writeFileSync(minFileName, sourceAndMap.code);
42
+ fs_1.default.writeFileSync(mapFileName, JSON.stringify(sourceAndMap.map));
64
43
  }
65
44
  else {
66
45
  console.error("No such file: " + fileName);
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Minifier = void 0;
7
+ const luaparse_1 = __importDefault(require("luaparse"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const source_map_1 = require("source-map");
11
+ const ast2lua_1 = require("./ast2lua");
12
+ class Minifier {
13
+ identifierMap;
14
+ identifiersInUse;
15
+ moduleSourceText;
16
+ moduleSourceNode;
17
+ moduleAST;
18
+ dir;
19
+ entryModule;
20
+ mode;
21
+ luaParseSettings;
22
+ constructor(entryFilePath, luaParseSettings, mode) {
23
+ this.identifierMap = new Map();
24
+ this.identifiersInUse = new Set();
25
+ this.moduleSourceText = new Map();
26
+ this.moduleSourceNode = new Map();
27
+ this.moduleAST = new Map();
28
+ this.luaParseSettings = luaParseSettings;
29
+ this.mode = mode;
30
+ const pn = path_1.default.parse(entryFilePath);
31
+ this.dir = pn.dir;
32
+ this.entryModule = pn.name;
33
+ }
34
+ parse() {
35
+ const sn = this.parseModule(this.entryModule);
36
+ if (this.mode.moduleLikeLua) {
37
+ // require関数を作成し、流し込む
38
+ /*
39
+ function require(m,r)
40
+ package=package or {loaded={}}
41
+ if package.loaded[m] then return package.loaded[m] end
42
+ if m=="<MODULE_NAME>" then r=(function()[[MODULE]]end)() end
43
+ package.loaded[m]=package.loaded[m] or r or true;return package.loaded[m]
44
+ end
45
+ */
46
+ sn.prepend("package.loaded[m]=package.loaded[m]or r or true;return package.loaded[m]end\n");
47
+ this.moduleSourceNode.forEach((v, k) => {
48
+ if (k !== this.entryModule) {
49
+ sn.prepend(["if m==\"", k, "\"then r=(function() ", v, " end)()end\n"]);
50
+ }
51
+ });
52
+ sn.prepend("function require(m,r)package=package or{loaded={}};if package.loaded[m]then return package.loaded[m]end\n");
53
+ }
54
+ // コメントの流し込み
55
+ const comments = this.moduleAST.get(this.entryModule)?.comments;
56
+ if (comments) {
57
+ comments
58
+ .reverse()
59
+ .filter((v) => v.raw.includes("--#") || v.raw.includes("[[#"))
60
+ .forEach((comment) => {
61
+ sn.prepend([
62
+ new source_map_1.SourceNode(comment.loc?.start.line || null, comment.loc?.start.column || null, this.entryModule, // 本当に自分のファイル名でよいかは要検討
63
+ comment.raw),
64
+ "\n"
65
+ ]);
66
+ });
67
+ }
68
+ return sn;
69
+ }
70
+ parseModule(moduleName) {
71
+ const resolvePath = moduleName.replaceAll(".", path_1.default.sep) + ".lua";
72
+ const fullResolvePath = path_1.default.join(this.dir, resolvePath);
73
+ if (this.moduleSourceNode.has(moduleName)) {
74
+ const res = this.moduleSourceNode.get(moduleName);
75
+ if (res) {
76
+ return res;
77
+ }
78
+ }
79
+ else if (fs_1.default.existsSync(fullResolvePath)) {
80
+ const code = fs_1.default.readFileSync(fullResolvePath).toString();
81
+ const ast = luaparse_1.default.parse(code, this.luaParseSettings);
82
+ if ("globals" in ast) {
83
+ ast.globals?.map((v) => this.identifiersInUse.add(v.name));
84
+ const sourceNode = new ast2lua_1.MinifyFile(resolvePath, ast, this, this.mode).parse(resolvePath === this.entryModule);
85
+ this.moduleSourceText.set(moduleName, code);
86
+ this.moduleAST.set(moduleName, ast);
87
+ this.moduleSourceNode.set(moduleName, sourceNode);
88
+ return sourceNode;
89
+ }
90
+ }
91
+ throw new Error(moduleName + " is not found");
92
+ }
93
+ }
94
+ exports.Minifier = Minifier;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "storm-lua-minify",
3
3
  "description": "A Lua minifier also outputs source map",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "dependencies": {
6
6
  "commander": "^12.1.0",
7
7
  "luamin": "^1.0.4",
package/src/ast2lua.ts CHANGED
@@ -4,6 +4,7 @@
4
4
  /* eslint-disable @typescript-eslint/no-unnecessary-condition */
5
5
  import Parser, { Comment } from "luaparse";
6
6
  import { SourceNode } from "source-map";
7
+ import { Minifier, MinifierMode } from "./minifier";
7
8
 
8
9
  export type Chunk = Parser.Chunk & {
9
10
  globals?: (Parser.Base<"Identifer"> & {
@@ -100,9 +101,6 @@ const IDENTIFIER_PARTS = [
100
101
  "_",
101
102
  ];
102
103
 
103
- const identifierMap = new Map<string, string>();
104
- const identifiersInUse = new Set<string>();
105
-
106
104
  function wrapArray<T>(obj: T | T[]): T[] {
107
105
  if (Array.isArray(obj)) {
108
106
  return obj;
@@ -262,31 +260,33 @@ function insertSeparator(
262
260
  return isNeedSeparator(a.toString(), b.toString()) ? separator : undefined;
263
261
  }
264
262
 
265
- export class Minifier {
263
+ export class MinifyFile {
266
264
  private fileName: string;
267
265
  private ast: Chunk;
268
- private requireHelper: (fileName: string) => SourceNode | undefined;
266
+ private minifier: Minifier;
267
+ private mode: MinifierMode;
269
268
 
270
269
  constructor(
271
270
  fileName: string,
272
271
  ast: Chunk,
273
- requireHelper: (fileName: string) => SourceNode | undefined
272
+ minifier: Minifier,
273
+ mode: MinifierMode
274
274
  ) {
275
275
  this.fileName = fileName;
276
276
  this.ast = ast;
277
- this.requireHelper = requireHelper;
278
- ast.globals?.map((v) => identifiersInUse.add(v.name));
277
+ this.minifier = minifier;
278
+ this.mode = mode;
279
279
  }
280
280
 
281
- parse() {
281
+ parse(noComment: boolean) {
282
282
  const body = this.formatStatementList(this.ast.body);
283
- /*if (this.ast.comments) {
283
+ if (!noComment && this.ast.comments) {
284
284
  const comments = this.ast.comments as Comment[];
285
285
  comments.reverse().filter(v => v.raw.includes("--#") || v.raw.includes("[[#")).forEach(comment => {
286
- body.prepend(this.sourceNodeHelper(comment, comment.raw));
286
+ body.prepend([this.sourceNodeHelper(comment, comment.raw), "\n"]);
287
287
  })
288
288
  return body;
289
- } else*/ {
289
+ } else {
290
290
  return body;
291
291
  }
292
292
  }
@@ -605,15 +605,28 @@ export class Minifier {
605
605
  return result;
606
606
  } else if (expression.type == "CallExpression") {
607
607
  // requireの展開モードは2種類: SLモード-その場に読み込み, FLモード-require相当の関数で呼び出し
608
+ const callExpr = this.formatBase(expression.base).toString();
608
609
  if (
609
- this.formatBase(expression.base).toString() == "require" &&
610
+ (callExpr === "require" || callExpr === "dofile") &&
610
611
  expression?.arguments[0].type === "StringLiteral"
611
612
  ) {
612
- const res = this.requireHelper(
613
- expression.arguments[0].raw.replaceAll('"', "")
614
- );
615
- if (res != undefined) {
616
- return res;
613
+ const moduleName = expression.arguments[0].raw
614
+ .replaceAll('"', "")
615
+ .replaceAll("'", "");
616
+
617
+ const res = this.minifier.parseModule(moduleName);
618
+
619
+ if (this.mode.moduleLikeLua) {
620
+ if (callExpr === "dofile") {
621
+ return (
622
+ res || this.sourceNodeHelper(undefined, "")
623
+ );
624
+ }
625
+ // requireならスキップ
626
+ } else {
627
+ return (
628
+ res || this.sourceNodeHelper(undefined, "")
629
+ );
617
630
  }
618
631
  }
619
632
  const args = expression.arguments
@@ -759,7 +772,7 @@ export class Minifier {
759
772
  return this.sourceNodeHelper(nameItem, "self", "self");
760
773
  }
761
774
 
762
- const defined = identifierMap.get(nameItem.name);
775
+ const defined = this.minifier.identifierMap.get(nameItem.name);
763
776
  if (defined) {
764
777
  return this.sourceNodeHelper(nameItem, defined, nameItem.name); // 第3引数は要調査
765
778
  }
@@ -778,20 +791,20 @@ export class Minifier {
778
791
  generateZeroes(length - (position + 1));
779
792
  if (
780
793
  isKeyword(this.currentIdentifier) ||
781
- identifiersInUse.has(this.currentIdentifier)
794
+ this.minifier.identifiersInUse.has(this.currentIdentifier)
782
795
  ) {
783
796
  return this.generateIdentifier(nameItem, nested);
784
797
  }
785
- identifierMap.set(nameItem.name, this.currentIdentifier);
798
+ this.minifier.identifierMap.set(nameItem.name, this.currentIdentifier);
786
799
  return this.generateIdentifier(nameItem, nested);
787
800
  }
788
801
  --position;
789
802
  }
790
803
  this.currentIdentifier = "a" + generateZeroes(length);
791
- if (identifiersInUse.has(this.currentIdentifier)) {
804
+ if (this.minifier.identifiersInUse.has(this.currentIdentifier)) {
792
805
  return this.generateIdentifier(nameItem, nested);
793
806
  }
794
- identifierMap.set(nameItem.name, this.currentIdentifier);
807
+ this.minifier.identifierMap.set(nameItem.name, this.currentIdentifier);
795
808
  return this.generateIdentifier(nameItem, nested);
796
809
 
797
810
  // return this.sourceNodeHelper(nameItem, nameItem.name, nested ? nameItem.name : undefined);
package/src/cli.ts CHANGED
@@ -3,12 +3,18 @@
3
3
  import fs from "fs";
4
4
  import path from "path";
5
5
  import { Command } from "commander";
6
- import Parser, { Options } from "luaparse";
7
- import { Chunk, Minifier } from "./ast2lua";
6
+ import { Options } from "luaparse";
7
+ import { Minifier, MinifierMode } from "./minifier";
8
8
 
9
9
  const program = new Command();
10
10
 
11
- program.version("0.1.0").description("A Lua minifier also outputs source map");
11
+ program
12
+ .version("0.1.2")
13
+ .description("A Lua minifier also outputs source map")
14
+ .option(
15
+ "-m, --module-like-lua",
16
+ "require・dofileの動作を実際のLuaに近づけます"
17
+ );
12
18
 
13
19
  program.parse(process.argv);
14
20
 
@@ -21,61 +27,29 @@ const luaparseSetting: Partial<Options> = {
21
27
  scope: true,
22
28
  };
23
29
 
30
+ const mode: MinifierMode = program.opts();
31
+
24
32
  luaFiles.forEach((fileName) => {
25
- const includes = new Map<string, string>();
26
33
  const parsedFileName = path.parse(fileName);
27
34
 
28
- function requireHelper(recursiveFilePath: string) {
29
- const resolvePath = path.relative(
30
- parsedFileName.dir,
31
- path.join(
32
- parsedFileName.dir,
33
- recursiveFilePath.replaceAll(".", path.sep) + ".lua"
34
- )
35
- );
36
- const fullResolvePath = path.join(parsedFileName.dir, resolvePath);
37
-
38
- if (!includes.has(resolvePath) && fs.existsSync(fullResolvePath)) {
39
- const code = fs.readFileSync(fullResolvePath).toString();
40
- const ast = Parser.parse(code, luaparseSetting) as Chunk;
41
- if ("globals" in ast) {
42
- includes.set(resolvePath, code);
43
- return new Minifier(resolvePath, ast, requireHelper).parse();
44
- }
45
- includes.set(resolvePath, "");
46
- return undefined;
47
- }
48
- }
49
-
50
35
  if (fs.existsSync(fileName)) {
51
- const code = fs.readFileSync(fileName).toString();
52
- const ast = Parser.parse(code, luaparseSetting) as Chunk;
53
- if ("globals" in ast) {
54
- includes.set(parsedFileName.base, code);
55
- const map = new Minifier(parsedFileName.base, ast, requireHelper).parse();
56
-
57
- includes.forEach((v, k) => {
58
- console.log(k);
59
- map.setSourceContent(k, v);
60
- });
61
-
62
- const minFileName = path.format({
63
- dir: parsedFileName.dir,
64
- name: parsedFileName.name + ".min",
65
- ext: ".lua",
66
- });
67
- const mapFileName = path.format({
68
- dir: parsedFileName.dir,
69
- name: parsedFileName.name,
70
- ext: parsedFileName.ext + ".map",
71
- });
72
- map.add("\n--[[\n//# sourceMappingURL=" + mapFileName + "\n]]");
73
-
74
- const sourceAndMap = map.toStringWithSourceMap();
75
-
76
- fs.writeFileSync(minFileName, sourceAndMap.code);
77
- fs.writeFileSync(mapFileName, JSON.stringify(sourceAndMap.map));
78
- }
36
+ const map = new Minifier(fileName, luaparseSetting, mode).parse();
37
+ const minFileName = path.format({
38
+ dir: parsedFileName.dir,
39
+ name: parsedFileName.name + ".min",
40
+ ext: ".lua",
41
+ });
42
+ const mapFileName = path.format({
43
+ dir: parsedFileName.dir,
44
+ name: parsedFileName.name,
45
+ ext: parsedFileName.ext + ".map",
46
+ });
47
+ map.add("\n--[[\n//# sourceMappingURL=" + mapFileName + "\n]]");
48
+
49
+ const sourceAndMap = map.toStringWithSourceMap();
50
+
51
+ fs.writeFileSync(minFileName, sourceAndMap.code);
52
+ fs.writeFileSync(mapFileName, JSON.stringify(sourceAndMap.map));
79
53
  } else {
80
54
  console.error("No such file: " + fileName);
81
55
  }
@@ -0,0 +1,114 @@
1
+ import Parser, { Comment, Options } from "luaparse";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ import { SourceNode } from "source-map";
5
+ import { Chunk, MinifyFile } from "./ast2lua";
6
+
7
+ export interface MinifierMode {
8
+ moduleLikeLua: boolean;
9
+ }
10
+
11
+ export class Minifier {
12
+ readonly identifierMap: Map<string, string>;
13
+ readonly identifiersInUse: Set<string>;
14
+ readonly moduleSourceText: Map<string, string>;
15
+ readonly moduleSourceNode: Map<string, SourceNode>;
16
+ readonly moduleAST: Map<string, Chunk>;
17
+ readonly dir: string;
18
+ readonly entryModule: string;
19
+ readonly mode: MinifierMode;
20
+ readonly luaParseSettings: Partial<Options>;
21
+
22
+ constructor(
23
+ entryFilePath: string,
24
+ luaParseSettings: Partial<Options>,
25
+ mode: MinifierMode
26
+ ) {
27
+ this.identifierMap = new Map<string, string>();
28
+ this.identifiersInUse = new Set<string>();
29
+ this.moduleSourceText = new Map<string, string>();
30
+ this.moduleSourceNode = new Map<string, SourceNode>();
31
+ this.moduleAST = new Map<string, Chunk>();
32
+ this.luaParseSettings = luaParseSettings;
33
+ this.mode = mode;
34
+ const pn = path.parse(entryFilePath);
35
+ this.dir = pn.dir;
36
+ this.entryModule = pn.name;
37
+ }
38
+
39
+ parse() {
40
+ const sn = this.parseModule(this.entryModule);
41
+
42
+ if (this.mode.moduleLikeLua) {
43
+ // require関数を作成し、流し込む
44
+ /*
45
+ function require(m,r)
46
+ package=package or {loaded={}}
47
+ if package.loaded[m] then return package.loaded[m] end
48
+ if m=="<MODULE_NAME>" then r=(function()[[MODULE]]end)() end
49
+ package.loaded[m]=package.loaded[m] or r or true;return package.loaded[m]
50
+ end
51
+ */
52
+ sn.prepend("package.loaded[m]=package.loaded[m]or r or true;return package.loaded[m]end\n");
53
+ this.moduleSourceNode.forEach((v, k) => {
54
+ if (k !== this.entryModule) {
55
+ sn.prepend(["if m==\"", k, "\"then r=(function() ", v ," end)()end\n"]);
56
+ }
57
+ });
58
+ sn.prepend("function require(m,r)package=package or{loaded={}};if package.loaded[m]then return package.loaded[m]end\n");
59
+ }
60
+
61
+ // コメントの流し込み
62
+ const comments = this.moduleAST.get(this.entryModule)?.comments as
63
+ | Comment[]
64
+ | undefined;
65
+ if (comments) {
66
+ comments
67
+ .reverse()
68
+ .filter((v) => v.raw.includes("--#") || v.raw.includes("[[#"))
69
+ .forEach((comment) => {
70
+ sn.prepend([
71
+ new SourceNode(
72
+ comment.loc?.start.line || null,
73
+ comment.loc?.start.column || null,
74
+ this.entryModule, // 本当に自分のファイル名でよいかは要検討
75
+ comment.raw
76
+ )
77
+ ,"\n"]);
78
+ });
79
+ }
80
+
81
+ return sn;
82
+ }
83
+
84
+ parseModule(moduleName: string): SourceNode {
85
+ const resolvePath = moduleName.replaceAll(".", path.sep) + ".lua";
86
+ const fullResolvePath = path.join(this.dir, resolvePath);
87
+
88
+ if (this.moduleSourceNode.has(moduleName)) {
89
+ const res = this.moduleSourceNode.get(moduleName);
90
+ if (res) {
91
+ return res;
92
+ }
93
+ } else if (fs.existsSync(fullResolvePath)) {
94
+ const code = fs.readFileSync(fullResolvePath).toString();
95
+ const ast = Parser.parse(code, this.luaParseSettings) as Chunk;
96
+ if ("globals" in ast) {
97
+ ast.globals?.map((v) => this.identifiersInUse.add(v.name));
98
+
99
+ const sourceNode = new MinifyFile(
100
+ resolvePath,
101
+ ast,
102
+ this,
103
+ this.mode
104
+ ).parse(resolvePath === this.entryModule);
105
+
106
+ this.moduleSourceText.set(moduleName, code);
107
+ this.moduleAST.set(moduleName, ast);
108
+ this.moduleSourceNode.set(moduleName, sourceNode);
109
+ return sourceNode;
110
+ }
111
+ }
112
+ throw new Error(moduleName + " is not found");
113
+ }
114
+ }