storm-lua-minify 0.1.3 → 0.2.0
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/.github/workflows/ci.yml +39 -0
- package/.github/workflows/publish.yml +39 -0
- package/.prettierignore +3 -0
- package/.prettierrc.json +1 -0
- package/LICENSE +21 -21
- package/README.md +37 -15
- package/dist/ast2lua.js +188 -85
- package/dist/cli.js +16 -6
- package/dist/index.js +3 -19
- package/dist/linker.js +96 -0
- package/dist/minifier.js +174 -53
- package/dist/output.js +33 -0
- package/dist/renamer.js +87 -0
- package/dist/resolver.js +303 -0
- package/eslint.config.js +29 -0
- package/package.json +33 -27
- package/src/ast2lua.ts +940 -812
- package/src/cli.ts +86 -56
- package/src/linker.ts +108 -0
- package/src/minifier.ts +284 -118
- package/src/output.ts +71 -0
- package/src/renamer.ts +134 -0
- package/src/resolver.ts +378 -0
- package/test/circular-require.test.ts +19 -0
- package/test/fixtures/bare-require/main.lua +2 -0
- package/test/fixtures/bare-require/mod.lua +1 -0
- package/test/fixtures/bitwise-precedence/main.lua +10 -0
- package/test/fixtures/circular-require/a.lua +2 -0
- package/test/fixtures/circular-require/b.lua +2 -0
- package/test/fixtures/circular-require/main.lua +2 -0
- package/test/fixtures/dofile/greet.lua +1 -0
- package/test/fixtures/dofile/main.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_alpha.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_bravo.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_charlie.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_delta.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_echo.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_foxtrot.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_golf.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_hotel.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_india.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_juliet.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/dep_kilo.lua +2 -0
- package/test/fixtures/entry-scope-many-requires/main.lua +25 -0
- package/test/fixtures/multi-require/common.lua +1 -0
- package/test/fixtures/multi-require/main.lua +3 -0
- package/test/fixtures/nested-module/main.lua +2 -0
- package/test/fixtures/nested-module/sub/deep.lua +1 -0
- package/test/fixtures/require-call/main.lua +2 -0
- package/test/fixtures/require-call/mod.lua +5 -0
- package/test/fixtures/require-in-expression/main.lua +1 -0
- package/test/fixtures/require-in-expression/mod.lua +1 -0
- package/test/fixtures/require-string-call/main.lua +2 -0
- package/test/fixtures/require-string-call/mod.lua +5 -0
- package/test/fixtures/single-file/main.lua +15 -0
- package/test/identifier-collision.test.ts +28 -0
- package/test/lib/collision.ts +131 -0
- package/test/lib/helpers.ts +124 -0
- package/test/no-rename.test.ts +19 -0
- package/test/output.test.ts +95 -0
- package/test/precedence.test.ts +28 -0
- package/test/renamer.test.ts +130 -0
- package/test/resolver.test.ts +206 -0
- package/test/roundtrip.test.ts +26 -0
- package/test/snapshot.test.ts +27 -0
- package/test/snapshots/bare-require.sl.lua +1 -0
- package/test/snapshots/bitwise-precedence.sl.lua +2 -0
- package/test/snapshots/dofile.sl.lua +1 -0
- package/test/snapshots/entry-scope-many-requires.m.lua +14 -0
- package/test/snapshots/multi-require.m.lua +4 -0
- package/test/snapshots/multi-require.sl.lua +1 -0
- package/test/snapshots/nested-module.m.lua +4 -0
- package/test/snapshots/require-call.m.lua +5 -0
- package/test/snapshots/require-call.sl.lua +1 -0
- package/test/snapshots/require-in-expression.sl.lua +1 -0
- package/test/snapshots/require-string-call.m.lua +5 -0
- package/test/snapshots/single-file.sl.lua +6 -0
- package/test/sourcemap.test.ts +105 -0
- package/tsconfig.eslint.json +8 -0
- package/tsconfig.json +111 -109
- package/.eslintrc.json +0 -20
package/dist/linker.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.staticStringArgument = staticStringArgument;
|
|
4
|
+
exports.findModuleReferences = findModuleReferences;
|
|
5
|
+
/**
|
|
6
|
+
* Chunk配下を型を問わず再帰的に走査するジェネリックウォーカー。
|
|
7
|
+
* printerとは独立に、AST全体からrequire/dofile呼び出しを見つけ出すために使う(#18)。
|
|
8
|
+
*/
|
|
9
|
+
function walk(node, visit) {
|
|
10
|
+
if (node === null || typeof node !== "object") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (Array.isArray(node)) {
|
|
14
|
+
node.forEach((child) => {
|
|
15
|
+
walk(child, visit);
|
|
16
|
+
});
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const obj = node;
|
|
20
|
+
if (typeof obj.type === "string") {
|
|
21
|
+
visit(obj);
|
|
22
|
+
}
|
|
23
|
+
for (const key of Object.keys(obj)) {
|
|
24
|
+
if (key === "loc" || key === "range") {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
walk(obj[key], visit);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// luaparseはデフォルト設定(encodingMode: "none")ではStringLiteral.valueを
|
|
31
|
+
// 常にnullにする(discardStrings)ため、rawから引用符を取り除いて文字列値を得る。
|
|
32
|
+
// require/dofileのモジュール名として使う簡単な文字列リテラルのみを想定しており、
|
|
33
|
+
// エスケープシーケンスの解釈までは行わない。
|
|
34
|
+
function unquoteRaw(raw) {
|
|
35
|
+
if (raw.length >= 2) {
|
|
36
|
+
const first = raw.charAt(0);
|
|
37
|
+
const last = raw.charAt(raw.length - 1);
|
|
38
|
+
if ((first === '"' || first === "'") && first === last) {
|
|
39
|
+
return raw.slice(1, -1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return raw;
|
|
43
|
+
}
|
|
44
|
+
function staticStringArgument(node) {
|
|
45
|
+
if (node !== null &&
|
|
46
|
+
typeof node === "object" &&
|
|
47
|
+
node.type === "StringLiteral" &&
|
|
48
|
+
typeof node.raw === "string") {
|
|
49
|
+
return unquoteRaw(node.raw);
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
function calleeName(node) {
|
|
54
|
+
if (node !== null &&
|
|
55
|
+
typeof node === "object" &&
|
|
56
|
+
node.type === "Identifier" &&
|
|
57
|
+
typeof node.name === "string") {
|
|
58
|
+
return node.name;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* ASTを走査してrequire/dofile呼び出し(CallExpression / StringCallExpression の両構文)を
|
|
64
|
+
* 静的な文字列引数付きのものに限って列挙する。同一モジュールへの参照は重複したまま返す
|
|
65
|
+
* (呼び出し側で重複排除する)。
|
|
66
|
+
*/
|
|
67
|
+
function findModuleReferences(ast) {
|
|
68
|
+
const refs = [];
|
|
69
|
+
walk(ast, (node) => {
|
|
70
|
+
if (node.type === "CallExpression") {
|
|
71
|
+
const name = calleeName(node.base);
|
|
72
|
+
if (name !== "require" && name !== "dofile") {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const args = node.arguments;
|
|
76
|
+
if (!Array.isArray(args) || args.length === 0) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const moduleName = staticStringArgument(args[0]);
|
|
80
|
+
if (moduleName !== undefined) {
|
|
81
|
+
refs.push({ kind: name, moduleName });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (node.type === "StringCallExpression") {
|
|
85
|
+
const name = calleeName(node.base);
|
|
86
|
+
if (name !== "require" && name !== "dofile") {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const moduleName = staticStringArgument(node.argument);
|
|
90
|
+
if (moduleName !== undefined) {
|
|
91
|
+
refs.push({ kind: name, moduleName });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return refs;
|
|
96
|
+
}
|
package/dist/minifier.js
CHANGED
|
@@ -9,22 +9,31 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const source_map_1 = require("source-map");
|
|
11
11
|
const ast2lua_1 = require("./ast2lua");
|
|
12
|
+
const linker_1 = require("./linker");
|
|
13
|
+
const resolver_1 = require("./resolver");
|
|
14
|
+
const renamer_1 = require("./renamer");
|
|
15
|
+
const NO_RENAME = {
|
|
16
|
+
nameOf: () => undefined,
|
|
17
|
+
usedNames: new Set(),
|
|
18
|
+
};
|
|
12
19
|
class Minifier {
|
|
13
|
-
identifierMap;
|
|
14
20
|
identifiersInUse;
|
|
15
21
|
moduleSourceText;
|
|
16
|
-
moduleSourceNode;
|
|
17
22
|
moduleAST;
|
|
18
23
|
moduleNameAndFileName;
|
|
19
24
|
dir;
|
|
20
25
|
entryModule;
|
|
21
26
|
mode;
|
|
22
27
|
luaParseSettings;
|
|
28
|
+
// Linkパスで解決されたモジュール名を、依存されている側が先に来る順序で並べたもの
|
|
29
|
+
linkOrder = [];
|
|
30
|
+
// モジュールごとのResolveパスの結果(Linkパスで一度だけ計算し使い回す)
|
|
31
|
+
moduleResolve = new Map();
|
|
32
|
+
// モジュールごとのRenameパスの結果(初回アクセス時に計算しキャッシュする)
|
|
33
|
+
renameCache = new Map();
|
|
23
34
|
constructor(entryFilePath, luaParseSettings, mode) {
|
|
24
|
-
this.identifierMap = new Map();
|
|
25
35
|
this.identifiersInUse = new Set();
|
|
26
36
|
this.moduleSourceText = new Map();
|
|
27
|
-
this.moduleSourceNode = new Map();
|
|
28
37
|
this.moduleAST = new Map();
|
|
29
38
|
this.moduleNameAndFileName = new Map();
|
|
30
39
|
this.luaParseSettings = luaParseSettings;
|
|
@@ -34,65 +43,177 @@ class Minifier {
|
|
|
34
43
|
this.entryModule = pn.name;
|
|
35
44
|
}
|
|
36
45
|
parse() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if package.loaded[m] then return package.loaded[m] end
|
|
44
|
-
if m=="<MODULE_NAME>" then r=(function()[[MODULE]]end)() end
|
|
45
|
-
package.loaded[m]=package.loaded[m] or r or true;return package.loaded[m]
|
|
46
|
-
end
|
|
47
|
-
*/
|
|
48
|
-
sn.prepend("package.loaded[m]=package.loaded[m]or r or true;return package.loaded[m]end\n");
|
|
49
|
-
this.moduleSourceNode.forEach((v, k) => {
|
|
50
|
-
if (k !== this.entryModule) {
|
|
51
|
-
sn.prepend(["if m==\"", k, "\"then r=(function() ", v, " end)()end\n"]);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
sn.prepend("function require(m,r)package=package or{loaded={}};if package.loaded[m]then return package.loaded[m]end\n");
|
|
55
|
-
}
|
|
56
|
-
// コメントの流し込み
|
|
57
|
-
const comments = this.moduleAST.get(this.entryModule)?.comments;
|
|
58
|
-
if (comments) {
|
|
59
|
-
comments
|
|
60
|
-
.reverse()
|
|
46
|
+
this.link();
|
|
47
|
+
this.renameAll();
|
|
48
|
+
const parts = [];
|
|
49
|
+
const entryComments = this.moduleAST.get(this.entryModule)?.comments;
|
|
50
|
+
if (entryComments) {
|
|
51
|
+
entryComments
|
|
61
52
|
.filter((v) => v.raw.includes("--#") || v.raw.includes("[[#"))
|
|
62
53
|
.forEach((comment) => {
|
|
63
|
-
|
|
64
|
-
new source_map_1.SourceNode(comment.loc?.start.line || null, comment.loc?.start.column || null, this.entryModule, // 本当に自分のファイル名でよいかは要検討
|
|
65
|
-
comment.raw),
|
|
66
|
-
"\n"
|
|
67
|
-
]);
|
|
54
|
+
parts.push(new source_map_1.SourceNode(comment.loc?.start.line ?? null, comment.loc?.start.column ?? null, this.moduleNameAndFileName.get(this.entryModule) ?? null, comment.raw), "\n");
|
|
68
55
|
});
|
|
69
56
|
}
|
|
70
|
-
this.
|
|
71
|
-
|
|
57
|
+
if (this.mode.moduleLikeLua) {
|
|
58
|
+
parts.push(this.buildRequireWrapper());
|
|
59
|
+
}
|
|
60
|
+
parts.push(this.printModule(this.entryModule));
|
|
61
|
+
const result = new source_map_1.SourceNode(null, null, null, parts);
|
|
62
|
+
this.moduleSourceText.forEach((v, k) => {
|
|
63
|
+
const fileName = this.moduleNameAndFileName.get(k);
|
|
64
|
+
if (fileName) {
|
|
65
|
+
result.setSourceContent(fileName, v);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* dofileの呼び出し箇所ごとに、キャッシュ済みASTから新規にSourceNodeを作り直す。
|
|
72
|
+
* 同じSourceNodeインスタンスを複数箇所へ挿入すると壊れるため、常に作り直す(#18)。
|
|
73
|
+
*/
|
|
74
|
+
printModuleInline(moduleName) {
|
|
75
|
+
return this.printModule(moduleName);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* requireを式(IIFE)ではなく文として展開できる場合に使う。モジュール本体が
|
|
79
|
+
* 「単一の式を返すreturn文」で終わっている場合のみ結果を返す。それ以外は
|
|
80
|
+
* undefinedを返すので、呼び出し側は従来のIIFE方式にフォールバックする(#29)。
|
|
81
|
+
*/
|
|
82
|
+
splitModuleForStatementSplice(moduleName) {
|
|
83
|
+
const ast = this.moduleAST.get(moduleName);
|
|
84
|
+
const fileName = this.moduleNameAndFileName.get(moduleName);
|
|
85
|
+
if (!ast || !fileName) {
|
|
86
|
+
throw new Error(moduleName + " is not found");
|
|
87
|
+
}
|
|
88
|
+
return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parseAsStatementsAndFinalExpression(moduleName === this.entryModule);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 指定モジュールのRenameパス結果を返す。`renameAll`で事前に計算済みの
|
|
92
|
+
* ものをそのまま返すだけの参照用アクセサ。
|
|
93
|
+
*/
|
|
94
|
+
getRenameResult(moduleName) {
|
|
95
|
+
if (this.mode.rename === false) {
|
|
96
|
+
return NO_RENAME;
|
|
97
|
+
}
|
|
98
|
+
const cached = this.renameCache.get(moduleName);
|
|
99
|
+
if (!cached) {
|
|
100
|
+
throw new Error(moduleName + " is not found");
|
|
101
|
+
}
|
|
102
|
+
return cached;
|
|
72
103
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
104
|
+
/**
|
|
105
|
+
* Renameパス(#20): linkOrder(依存されている側が先)の順にモジュールごとの
|
|
106
|
+
* 短縮名を割り当てる。
|
|
107
|
+
*
|
|
108
|
+
* dofileやSLモードのrequireその場展開は、呼び出し元と同じLuaスコープに
|
|
109
|
+
* 関数で包まずに直接展開されるため、モジュールをまたいで同じ短縮名を
|
|
110
|
+
* 再利用すると本来無関係な変数同士が衝突しうる(#12)。これを安全に防ぐため、
|
|
111
|
+
* あるモジュールが実際に使った短縮名は、後続モジュールを処理する前に
|
|
112
|
+
* `identifiersInUse`(予約名の集合)へ積み増す。これにより短縮名は
|
|
113
|
+
* プログラム全体で重複しなくなる(モジュール間での再利用による圧縮は
|
|
114
|
+
* 犠牲になるが、モジュール内でのスコープに基づく再利用は維持される)。
|
|
115
|
+
*/
|
|
116
|
+
renameAll() {
|
|
117
|
+
if (this.mode.rename === false) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.linkOrder.forEach((moduleName) => {
|
|
121
|
+
const resolved = this.moduleResolve.get(moduleName);
|
|
122
|
+
if (!resolved) {
|
|
123
|
+
throw new Error(moduleName + " is not found");
|
|
80
124
|
}
|
|
125
|
+
const result = (0, renamer_1.assignRenames)(resolved, this.identifiersInUse);
|
|
126
|
+
this.renameCache.set(moduleName, result);
|
|
127
|
+
result.usedNames.forEach((name) => this.identifiersInUse.add(name));
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
printModule(moduleName) {
|
|
131
|
+
const ast = this.moduleAST.get(moduleName);
|
|
132
|
+
const fileName = this.moduleNameAndFileName.get(moduleName);
|
|
133
|
+
if (!ast || !fileName) {
|
|
134
|
+
throw new Error(moduleName + " is not found");
|
|
81
135
|
}
|
|
82
|
-
|
|
136
|
+
return new ast2lua_1.MinifyFile(fileName, moduleName, ast, this, this.mode).parse(moduleName === this.entryModule);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* エントリファイルから到達可能な全モジュールをASTレベルで解決するLinkパス(#18)。
|
|
140
|
+
* - ファイルごとのパースは一度だけ行う(同一モジュールの多重require/dofileの重複排除)
|
|
141
|
+
* - require/dofileの参照グラフに循環があればエラーを投げる
|
|
142
|
+
* - 出力(Print)を開始する前に、必要なモジュール解決をすべて完了させる
|
|
143
|
+
*/
|
|
144
|
+
link() {
|
|
145
|
+
const visiting = new Set();
|
|
146
|
+
const stack = [];
|
|
147
|
+
const visit = (moduleName) => {
|
|
148
|
+
if (visiting.has(moduleName)) {
|
|
149
|
+
const cycleStart = stack.indexOf(moduleName);
|
|
150
|
+
const cycle = [...stack.slice(cycleStart), moduleName];
|
|
151
|
+
throw new Error("Circular require/dofile detected: " + cycle.join(" -> "));
|
|
152
|
+
}
|
|
153
|
+
if (this.moduleAST.has(moduleName)) {
|
|
154
|
+
// 解決済み(このモジュールは複数箇所から参照されていても一度しかパースしない)
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
visiting.add(moduleName);
|
|
158
|
+
stack.push(moduleName);
|
|
159
|
+
const fullResolvePath = path_1.default.join(this.dir, ...moduleName.split(".")) + ".lua";
|
|
160
|
+
if (!fs_1.default.existsSync(fullResolvePath)) {
|
|
161
|
+
throw new Error(moduleName + " is not found");
|
|
162
|
+
}
|
|
83
163
|
const code = fs_1.default.readFileSync(fullResolvePath).toString();
|
|
84
164
|
const ast = luaparse_1.default.parse(code, this.luaParseSettings);
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
165
|
+
// Resolveパス(#19): このモジュールのスコープ/シンボルを解析し、Renameパスの
|
|
166
|
+
// 入力として使い回せるようキャッシュする。グローバル参照はプログラム全体で
|
|
167
|
+
// 予約すべき名前(identifiersInUse)としてここで集計する。
|
|
168
|
+
const resolved = (0, resolver_1.resolveScopes)(ast);
|
|
169
|
+
this.moduleResolve.set(moduleName, resolved);
|
|
170
|
+
resolved.globals.forEach((binding) => this.identifiersInUse.add(binding.name));
|
|
171
|
+
this.moduleSourceText.set(moduleName, code);
|
|
172
|
+
this.moduleAST.set(moduleName, ast);
|
|
173
|
+
// Source Mapの`sources`はURLとして解釈されるため、OS依存のpath.sepではなく
|
|
174
|
+
// 常に"/"区切りで保持する(Windows上でのビルドでも壊れないように)。
|
|
175
|
+
this.moduleNameAndFileName.set(moduleName, moduleName.replaceAll(".", "/") + ".lua");
|
|
176
|
+
(0, linker_1.findModuleReferences)(ast).forEach((ref) => {
|
|
177
|
+
visit(ref.moduleName);
|
|
178
|
+
});
|
|
179
|
+
visiting.delete(moduleName);
|
|
180
|
+
stack.pop();
|
|
181
|
+
this.linkOrder.push(moduleName);
|
|
182
|
+
};
|
|
183
|
+
visit(this.entryModule);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* require()(dofileは除く)で参照されているモジュール名の集合を求める。
|
|
187
|
+
* dofileは呼び出しごとに毎回展開しなおすため、キャッシュ/ホイストの対象にしない。
|
|
188
|
+
*/
|
|
189
|
+
collectRequireTargets() {
|
|
190
|
+
const targets = new Set();
|
|
191
|
+
this.linkOrder.forEach((moduleName) => {
|
|
192
|
+
const ast = this.moduleAST.get(moduleName);
|
|
193
|
+
if (!ast) {
|
|
194
|
+
return;
|
|
93
195
|
}
|
|
94
|
-
|
|
95
|
-
|
|
196
|
+
(0, linker_1.findModuleReferences)(ast).forEach((ref) => {
|
|
197
|
+
if (ref.kind === "require") {
|
|
198
|
+
targets.add(ref.moduleName);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
return targets;
|
|
203
|
+
}
|
|
204
|
+
buildRequireWrapper() {
|
|
205
|
+
const targets = this.collectRequireTargets();
|
|
206
|
+
const parts = [
|
|
207
|
+
"function require(m,r)package=package or{loaded={}};if package.loaded[m]then return package.loaded[m]end\n",
|
|
208
|
+
];
|
|
209
|
+
this.linkOrder.forEach((moduleName) => {
|
|
210
|
+
if (moduleName === this.entryModule || !targets.has(moduleName)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
parts.push('if m=="', moduleName, '"then r=(function() ', this.printModule(moduleName), " end)()end\n");
|
|
214
|
+
});
|
|
215
|
+
parts.push("package.loaded[m]=package.loaded[m]or r or true;return package.loaded[m]end\n");
|
|
216
|
+
return new source_map_1.SourceNode(null, null, null, parts);
|
|
96
217
|
}
|
|
97
218
|
}
|
|
98
219
|
exports.Minifier = Minifier;
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
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.buildMinifiedOutput = buildMinifiedOutput;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
/**
|
|
9
|
+
* ミニファイ済みSourceNodeに、sourceMappingURLアノテーションとSource Mapを
|
|
10
|
+
* 付加した最終的な出力を組み立てる。アノテーションの出力形式は
|
|
11
|
+
* `BuildMinifiedOutputOptions.sourceMappingUrlStyle`で選択する。
|
|
12
|
+
*/
|
|
13
|
+
function buildMinifiedOutput(sourceNode, minFileName, mapFileName, options = {}) {
|
|
14
|
+
const style = options.sourceMappingUrlStyle ?? "legacy";
|
|
15
|
+
const marker = "//# sourceMappingURL=" + path_1.default.basename(mapFileName);
|
|
16
|
+
if (style === "legacy") {
|
|
17
|
+
// 旧storm-lua-minifyと完全に同じ出力(末尾に改行は付加しない)。
|
|
18
|
+
sourceNode.add("\n--[[\n" + marker + "\n]]");
|
|
19
|
+
}
|
|
20
|
+
else if (style === "line") {
|
|
21
|
+
sourceNode.add("\n-- " + marker + "\n");
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
sourceNode.add("\n" + marker + "\n");
|
|
25
|
+
}
|
|
26
|
+
const sourceAndMap = sourceNode.toStringWithSourceMap({
|
|
27
|
+
file: path_1.default.basename(minFileName),
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
code: sourceAndMap.code,
|
|
31
|
+
map: JSON.stringify(sourceAndMap.map),
|
|
32
|
+
};
|
|
33
|
+
}
|
package/dist/renamer.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assignRenames = assignRenames;
|
|
4
|
+
const ast2lua_1 = require("./ast2lua");
|
|
5
|
+
function isAvailable(id, reserved) {
|
|
6
|
+
return id !== "self" && !(0, ast2lua_1.isKeyword)(id) && !reserved.has(id);
|
|
7
|
+
}
|
|
8
|
+
// 0始まりのカウンタから短縮名候補を生成する(バイジェクティブ基数記数法)。
|
|
9
|
+
// 通常の位取り記数法と違い同じ文字列を2つのカウンタ値が指すことがないため、
|
|
10
|
+
// カウンタを増やし続けるだけで重複なく識別子候補を列挙できる。
|
|
11
|
+
function generateCandidate(counter) {
|
|
12
|
+
const l = ast2lua_1.IDENTIFIER_PARTS.length;
|
|
13
|
+
let num = counter + 1;
|
|
14
|
+
let id = "";
|
|
15
|
+
while (num > 0) {
|
|
16
|
+
const rem = (num - 1) % l;
|
|
17
|
+
id = ast2lua_1.IDENTIFIER_PARTS[rem] + id;
|
|
18
|
+
num = Math.floor((num - 1) / l);
|
|
19
|
+
}
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* スコープ木のDFSでシンボルごとにスロット番号を割り当てる。
|
|
24
|
+
* `active`は祖先スコープ(自分を含む)で既に使われているスロットの集合。
|
|
25
|
+
* 兄弟スコープには同じ`active`のコピーが渡されるため、互いの割当は影響しない。
|
|
26
|
+
*/
|
|
27
|
+
function assignSlots(scope, active) {
|
|
28
|
+
const slotOf = new Map();
|
|
29
|
+
const used = new Set(active);
|
|
30
|
+
scope.symbols.forEach((symbol) => {
|
|
31
|
+
let slot = 0;
|
|
32
|
+
while (used.has(slot)) {
|
|
33
|
+
slot++;
|
|
34
|
+
}
|
|
35
|
+
slotOf.set(symbol, slot);
|
|
36
|
+
used.add(slot);
|
|
37
|
+
});
|
|
38
|
+
scope.children.forEach((child) => {
|
|
39
|
+
assignSlots(child, used).forEach((slot, symbol) => {
|
|
40
|
+
slotOf.set(symbol, slot);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
return slotOf;
|
|
44
|
+
}
|
|
45
|
+
function assignRenames(resolveResult, reserved) {
|
|
46
|
+
const slotOf = assignSlots(resolveResult.chunkScope, new Set());
|
|
47
|
+
// スロットの通算参照回数(宣言自体も1回として数える)を集計する。
|
|
48
|
+
const weightOfSlot = new Map();
|
|
49
|
+
resolveResult.symbols.forEach((symbol) => {
|
|
50
|
+
const slot = slotOf.get(symbol);
|
|
51
|
+
if (slot === undefined) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const weight = symbol.references.length + 1;
|
|
55
|
+
weightOfSlot.set(slot, (weightOfSlot.get(slot) ?? 0) + weight);
|
|
56
|
+
});
|
|
57
|
+
const orderedSlots = [...weightOfSlot.keys()].sort((a, b) => (weightOfSlot.get(b) ?? 0) - (weightOfSlot.get(a) ?? 0));
|
|
58
|
+
const nameOfSlot = new Map();
|
|
59
|
+
let counter = 0;
|
|
60
|
+
orderedSlots.forEach((slot) => {
|
|
61
|
+
let candidate;
|
|
62
|
+
do {
|
|
63
|
+
candidate = generateCandidate(counter++);
|
|
64
|
+
} while (!isAvailable(candidate, reserved));
|
|
65
|
+
nameOfSlot.set(slot, candidate);
|
|
66
|
+
});
|
|
67
|
+
const nameOfSymbol = new Map();
|
|
68
|
+
slotOf.forEach((slot, symbol) => {
|
|
69
|
+
const name = nameOfSlot.get(slot);
|
|
70
|
+
if (name !== undefined) {
|
|
71
|
+
nameOfSymbol.set(symbol, name);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
nameOf: (identifier) => {
|
|
76
|
+
// メソッド定義の暗黙のselfパラメータは慣習的な名前のため常に維持する
|
|
77
|
+
// (呼び出し側から見える名前ではないため短縮しても安全ではあるが、
|
|
78
|
+
// 可読性のために元の名前のままにする)。
|
|
79
|
+
if (identifier.name === "self") {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
const symbol = resolveResult.symbolOf(identifier);
|
|
83
|
+
return symbol ? nameOfSymbol.get(symbol) : undefined;
|
|
84
|
+
},
|
|
85
|
+
usedNames: new Set(nameOfSlot.values()),
|
|
86
|
+
};
|
|
87
|
+
}
|