Skip to content

Commit d88c930

Browse files
authored
Alternative bundling (#750)
* security audit fix * Alternative bundling approach * Changed default lualib inline to require, support for lualib require in bundling. Added diagnostics and more tests * Added failing test for entry point resolution * Fixed bundle transpile test, broke all other bundle tests * Fix bundle tests * Added basic project transpilation test * Fixed test paths * Moved resolution modules to resolve.ts, rewrote them to use configFilePath and rootDir, addressed other PR comments * Fixed bug with getProjectDir * Use getCommonSourcesDir in all cases * Changed path resolution again, using confFilePath again * Renamed transpile/basic test to transpile/project, normalized output path of bundle * Reverted toHaveDiagnostics change * Reverted some more forgotten stuff * Final stuff to revert * Do not emit lualib bundle when bundling * Updated emit lualib_bundle logic
1 parent c7b3810 commit d88c930

29 files changed

+620
-135
lines changed

package-lock.json

Lines changed: 14 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/CompilerOptions.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ export interface TransformerImport {
2020
export type CompilerOptions = OmitIndexSignature<ts.CompilerOptions> & {
2121
noImplicitSelf?: boolean;
2222
noHeader?: boolean;
23+
luaBundle?: string;
24+
luaBundleEntry?: string;
2325
luaTarget?: LuaTarget;
2426
luaLibImport?: LuaLibImportKind;
2527
noHoisting?: boolean;
@@ -41,3 +43,42 @@ export enum LuaTarget {
4143
Lua53 = "5.3",
4244
LuaJIT = "JIT",
4345
}
46+
47+
export function validateOptions(options: CompilerOptions): ts.Diagnostic[] {
48+
const diagnostics: ts.Diagnostic[] = [];
49+
50+
if (options.luaBundle && !options.luaBundleEntry) {
51+
diagnostics.push(configErrorDiagnostic(`'luaBundleEntry' is required when 'luaBundle' is enabled.`));
52+
}
53+
54+
if (options.luaBundle && options.luaLibImport === LuaLibImportKind.Inline) {
55+
diagnostics.push(
56+
configWarningDiagnostic(
57+
`Using 'luaBundle' with 'luaLibImport: "inline"' might generate duplicate code. ` +
58+
`It is recommended to use 'luaLibImport: "require"'`
59+
)
60+
);
61+
}
62+
63+
return diagnostics;
64+
}
65+
66+
const configErrorDiagnostic = (message: string): ts.Diagnostic => ({
67+
file: undefined,
68+
start: undefined,
69+
length: undefined,
70+
category: ts.DiagnosticCategory.Error,
71+
code: 0,
72+
source: "typescript-to-lua",
73+
messageText: message,
74+
});
75+
76+
const configWarningDiagnostic = (message: string): ts.Diagnostic => ({
77+
file: undefined,
78+
start: undefined,
79+
length: undefined,
80+
category: ts.DiagnosticCategory.Warning,
81+
code: 0,
82+
source: "typescript-to-lua",
83+
messageText: message,
84+
});

src/Emit.ts

Lines changed: 32 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import * as path from "path";
22
import * as ts from "typescript";
3-
import { CompilerOptions, LuaLibImportKind } from "./CompilerOptions";
3+
import { LuaLibImportKind } from "./CompilerOptions";
44
import { EmitHost, TranspiledFile } from "./Transpile";
5-
import { normalizeSlashes } from "./utils";
6-
7-
const trimExt = (filePath: string) => filePath.slice(0, -path.extname(filePath).length);
5+
import { normalizeSlashes, trimExtension } from "./utils";
86

97
export interface OutputFile {
108
name: string;
@@ -13,18 +11,15 @@ export interface OutputFile {
1311

1412
let lualibContent: string;
1513
export function emitTranspiledFiles(
16-
options: CompilerOptions,
14+
program: ts.Program,
1715
transpiledFiles: TranspiledFile[],
1816
emitHost: EmitHost = ts.sys
1917
): OutputFile[] {
20-
let { rootDir, outDir, outFile, luaLibImport } = options;
21-
22-
const configFileName = options.configFilePath as string | undefined;
23-
// TODO: Use getCommonSourceDirectory
24-
const baseDir = configFileName ? path.dirname(configFileName) : process.cwd();
18+
const options = program.getCompilerOptions();
19+
let { outDir, luaLibImport, luaBundle } = options;
2520

26-
rootDir = rootDir || baseDir;
27-
outDir = outDir ? path.resolve(baseDir, outDir) : rootDir;
21+
const rootDir = program.getCommonSourceDirectory();
22+
outDir = outDir || rootDir;
2823

2924
const files: OutputFile[] = [];
3025
for (const { fileName, lua, sourceMap, declaration, declarationMap } of transpiledFiles) {
@@ -33,14 +28,8 @@ export function emitTranspiledFiles(
3328
outPath = path.resolve(outDir, path.relative(rootDir, fileName));
3429
}
3530

36-
// change extension or rename to outFile
37-
if (outFile) {
38-
outPath = path.isAbsolute(outFile) ? outFile : path.resolve(baseDir, outFile);
39-
} else {
40-
outPath = trimExt(outPath) + ".lua";
41-
}
42-
43-
outPath = normalizeSlashes(outPath);
31+
// change extension
32+
outPath = normalizeSlashes(trimExtension(outPath) + ".lua");
4433

4534
if (lua !== undefined) {
4635
files.push({ name: outPath, text: lua });
@@ -51,30 +40,38 @@ export function emitTranspiledFiles(
5140
}
5241

5342
if (declaration !== undefined) {
54-
files.push({ name: trimExt(outPath) + ".d.ts", text: declaration });
43+
files.push({ name: trimExtension(outPath) + ".d.ts", text: declaration });
5544
}
5645

5746
if (declarationMap !== undefined) {
58-
files.push({ name: trimExt(outPath) + ".d.ts.map", text: declarationMap });
47+
files.push({ name: trimExtension(outPath) + ".d.ts.map", text: declarationMap });
5948
}
6049
}
6150

62-
if (luaLibImport === LuaLibImportKind.Require || luaLibImport === LuaLibImportKind.Always) {
63-
if (lualibContent === undefined) {
64-
const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"));
65-
if (lualibBundle !== undefined) {
66-
lualibContent = lualibBundle;
67-
} else {
68-
throw new Error("Could not load lualib bundle from ./dist/lualib/lualib_bundle.lua");
51+
if (
52+
!luaBundle &&
53+
(luaLibImport === undefined ||
54+
luaLibImport === LuaLibImportKind.Require ||
55+
luaLibImport === LuaLibImportKind.Always)
56+
) {
57+
const lualibRequired = files.some(f => f.text && f.text.includes(`require("lualib_bundle")`));
58+
if (lualibRequired) {
59+
if (lualibContent === undefined) {
60+
const lualibBundle = emitHost.readFile(path.resolve(__dirname, "../dist/lualib/lualib_bundle.lua"));
61+
if (lualibBundle !== undefined) {
62+
lualibContent = lualibBundle;
63+
} else {
64+
throw new Error("Could not load lualib bundle from ./dist/lualib/lualib_bundle.lua");
65+
}
6966
}
70-
}
7167

72-
let outPath = path.resolve(rootDir, "lualib_bundle.lua");
73-
if (outDir !== rootDir) {
74-
outPath = path.join(outDir, path.relative(rootDir, outPath));
75-
}
68+
let outPath = path.resolve(rootDir, "lualib_bundle.lua");
69+
if (outDir !== rootDir) {
70+
outPath = path.join(outDir, path.relative(rootDir, outPath));
71+
}
7672

77-
files.push({ name: normalizeSlashes(outPath), text: lualibContent });
73+
files.push({ name: normalizeSlashes(outPath), text: lualibContent });
74+
}
7875
}
7976

8077
return files;

src/LuaPrinter.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ export class LuaPrinter {
4343

4444
public constructor(private options: CompilerOptions, private emitHost: EmitHost) {}
4545

46-
public print(block: tstl.Block, luaLibFeatures?: Set<LuaLibFeature>, sourceFile = ""): [string, string] {
46+
public print(
47+
block: tstl.Block,
48+
luaLibFeatures?: Set<LuaLibFeature>,
49+
sourceFile = ""
50+
): [string, string, SourceNode] {
4751
// Add traceback lualib if sourcemap traceback option is enabled
4852
if (this.options.sourceMapTraceback) {
4953
if (luaLibFeatures === undefined) {
@@ -71,7 +75,7 @@ export class LuaPrinter {
7175
codeResult = codeResult.replace("{#SourceMapTraceback}", stackTraceOverride);
7276
}
7377

74-
return [codeResult, sourceMap.toString()];
78+
return [codeResult, sourceMap.toString(), rootSourceNode];
7579
}
7680

7781
private printInlineSourceMap(sourceMap: SourceMapGenerator): string {
@@ -113,7 +117,7 @@ export class LuaPrinter {
113117
}
114118

115119
if (luaLibFeatures) {
116-
const luaLibImport = this.options.luaLibImport || LuaLibImportKind.Inline;
120+
const luaLibImport = this.options.luaLibImport || LuaLibImportKind.Require;
117121
// Require lualib bundle
118122
if (
119123
(luaLibImport === LuaLibImportKind.Require && luaLibFeatures.size > 0) ||

src/TSHelper.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Decorator, DecoratorKind } from "./Decorator";
55
import * as tstl from "./LuaAST";
66
import * as TSTLErrors from "./TSTLErrors";
77
import { EmitResolver } from "./LuaTransformer";
8+
import { formatPathToLuaPath } from "./utils";
89

910
export enum ContextType {
1011
None,
@@ -1060,15 +1061,6 @@ export function getExportPath(fileName: string, options: ts.CompilerOptions): st
10601061
return formatPathToLuaPath(absolutePath.replace(absoluteRootDirPath, "").slice(1));
10611062
}
10621063

1063-
export function formatPathToLuaPath(filePath: string): string {
1064-
filePath = filePath.replace(/\.json$/, "");
1065-
if (process.platform === "win32") {
1066-
// Windows can use backslashes
1067-
filePath = filePath.replace(/\.\\/g, "").replace(/\\/g, ".");
1068-
}
1069-
return filePath.replace(/\.\//g, "").replace(/\//g, ".");
1070-
}
1071-
10721064
export function isBuiltinErrorTypeName(name: string): boolean {
10731065
return builtinErrorTypeNames.has(name);
10741066
}

src/Transpile.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { SourceNode } from "source-map";
12
import * as ts from "typescript";
2-
import { CompilerOptions } from "./CompilerOptions";
3+
import { bundleTranspiledFiles } from "./bundle";
4+
import { CompilerOptions, validateOptions } from "./CompilerOptions";
35
import * as diagnosticFactories from "./diagnostics";
46
import { Block } from "./LuaAST";
57
import { LuaPrinter } from "./LuaPrinter";
@@ -14,6 +16,8 @@ export interface TranspiledFile {
1416
sourceMap?: string;
1517
declaration?: string;
1618
declarationMap?: string;
19+
/** @internal */
20+
sourceMapNode?: SourceNode;
1721
}
1822

1923
export interface TranspileResult {
@@ -31,6 +35,7 @@ export interface TranspileOptions {
3135
}
3236

3337
export interface EmitHost {
38+
getCurrentDirectory(): string;
3439
readFile(path: string): string | undefined;
3540
}
3641

@@ -44,7 +49,7 @@ export function transpile({
4449
}: TranspileOptions): TranspileResult {
4550
const options = program.getCompilerOptions() as CompilerOptions;
4651

47-
const diagnostics: ts.Diagnostic[] = [];
52+
const diagnostics = validateOptions(options);
4853
let transpiledFiles: TranspiledFile[] = [];
4954

5055
const updateTranspiledFile = (fileName: string, update: Omit<TranspiledFile, "fileName">) => {
@@ -57,7 +62,11 @@ export function transpile({
5762
};
5863

5964
if (options.noEmitOnError) {
60-
const preEmitDiagnostics = [...program.getOptionsDiagnostics(), ...program.getGlobalDiagnostics()];
65+
const preEmitDiagnostics = [
66+
...diagnostics,
67+
...program.getOptionsDiagnostics(),
68+
...program.getGlobalDiagnostics(),
69+
];
6170

6271
if (targetSourceFiles) {
6372
for (const sourceFile of targetSourceFiles) {
@@ -82,8 +91,8 @@ export function transpile({
8291
try {
8392
const [luaAst, lualibFeatureSet] = transformer.transform(sourceFile);
8493
if (!options.noEmit && !options.emitDeclarationOnly) {
85-
const [lua, sourceMap] = printer.print(luaAst, lualibFeatureSet, sourceFile.fileName);
86-
updateTranspiledFile(sourceFile.fileName, { luaAst, lua, sourceMap });
94+
const [lua, sourceMap, sourceNode] = printer.print(luaAst, lualibFeatureSet, sourceFile.fileName);
95+
updateTranspiledFile(sourceFile.fileName, { luaAst, lua, sourceMap, sourceMapNode: sourceNode });
8796
}
8897
} catch (err) {
8998
if (!(err instanceof TranspileError)) throw err;
@@ -144,5 +153,17 @@ export function transpile({
144153
transpiledFiles = [];
145154
}
146155

156+
if (options.luaBundle && options.luaBundleEntry) {
157+
const [bundleDiagnostics, bundle] = bundleTranspiledFiles(
158+
options.luaBundle,
159+
options.luaBundleEntry,
160+
transpiledFiles,
161+
program,
162+
emitHost
163+
);
164+
diagnostics.push(...bundleDiagnostics);
165+
transpiledFiles = [bundle];
166+
}
167+
147168
return { diagnostics, transpiledFiles };
148169
}

0 commit comments

Comments
 (0)