Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions build-lualib.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ const tstl = require("./src");
const { loadLuaLibFeatures } = require("./src/LuaLib");

const configFileName = path.resolve(__dirname, "src/lualib/tsconfig.json");
const { emitResult, diagnostics } = tstl.transpileProject(configFileName);
emitResult.forEach(({ name, text }) => ts.sys.writeFile(name, text));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is no longer emitted? Doesn't this break lualib inlining?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transpileProject now emits files itself (unless writeFile callback parameter is provided)


const reportDiagnostic = tstl.createDiagnosticReporter(true);
diagnostics.forEach(reportDiagnostic);
const { diagnostics } = tstl.transpileProject(configFileName);
diagnostics.forEach(tstl.createDiagnosticReporter(true));

const bundlePath = path.join(__dirname, "dist/lualib/lualib_bundle.lua");
if (fs.existsSync(bundlePath)) {
Expand Down
3 changes: 3 additions & 0 deletions src/CompilerOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ export enum LuaTarget {
LuaJIT = "JIT",
}

export const isBundleEnabled = (options: CompilerOptions) =>
options.luaBundle !== undefined && options.luaBundleEntry !== undefined;

export function validateOptions(options: CompilerOptions): ts.Diagnostic[] {
const diagnostics: ts.Diagnostic[] = [];

Expand Down
99 changes: 44 additions & 55 deletions src/transpilation/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,101 +2,90 @@ import * as path from "path";
import { SourceNode } from "source-map";
import * as ts from "typescript";
import { CompilerOptions } from "../CompilerOptions";
import { getLuaLibBundle } from "../LuaLib";
import { escapeString } from "../LuaPrinter";
import { formatPathToLuaPath, normalizeSlashes, trimExtension } from "../utils";
import * as diagnosticFactories from "./diagnostics";
import { EmitHost, TranspiledFile } from "./transpile";
import { cast, formatPathToLuaPath, isNonNull, normalizeSlashes, trimExtension } from "../utils";
import { couldNotFindBundleEntryPoint } from "./diagnostics";
import { EmitFile, EmitHost, ProcessedFile } from "./utils";

const createModulePath = (baseDir: string, pathToResolve: string) =>
escapeString(formatPathToLuaPath(trimExtension(path.relative(baseDir, pathToResolve))));

export function bundleTranspiledFiles(
bundleFile: string,
entryModule: string,
transpiledFiles: TranspiledFile[],
// Override `require` to read from ____modules table.
const requireOverride = `
local ____modules = {}
local ____moduleCache = {}
local ____originalRequire = require
local function require(file)
if ____moduleCache[file] then
return ____moduleCache[file]
end
if ____modules[file] then
____moduleCache[file] = ____modules[file]()
return ____moduleCache[file]
else
if ____originalRequire then
return ____originalRequire(file)
else
error("module '" .. file .. "' not found")
end
end
end
`;

export function getBundleResult(
program: ts.Program,
emitHost: EmitHost
): [ts.Diagnostic[], TranspiledFile] {
emitHost: EmitHost,
files: ProcessedFile[]
): [ts.Diagnostic[], EmitFile] {
const diagnostics: ts.Diagnostic[] = [];

const options = program.getCompilerOptions() as CompilerOptions;
const bundleFile = cast(options.luaBundle, isNonNull);
const entryModule = cast(options.luaBundleEntry, isNonNull);

const rootDir = program.getCommonSourceDirectory();
const outDir = options.outDir ?? rootDir;
const projectRootDir = options.configFilePath
? path.dirname(options.configFilePath)
: emitHost.getCurrentDirectory();

// Resolve project settings relative to project file.
const resolvedEntryModule = path.resolve(projectRootDir, entryModule);
const resolvedBundleFile = path.resolve(projectRootDir, bundleFile);
const outputPath = normalizeSlashes(path.resolve(projectRootDir, bundleFile));

// Resolve source files relative to common source directory.
const sourceRootDir = program.getCommonSourceDirectory();
if (!transpiledFiles.some(f => path.resolve(sourceRootDir, f.fileName) === resolvedEntryModule)) {
return [[diagnosticFactories.couldNotFindBundleEntryPoint(entryModule)], { fileName: bundleFile }];
if (!files.some(f => f.fileName === resolvedEntryModule)) {
diagnostics.push(couldNotFindBundleEntryPoint(entryModule));
return [diagnostics, { outputPath, code: "" }];
}

// For each file: ["<module path>"] = function() <lua content> end,
const moduleTableEntries: SourceChunk[] = transpiledFiles.map(f =>
moduleSourceNode(f, createModulePath(sourceRootDir, f.fileName))
);

// If any of the modules contains a require for lualib_bundle, add it to the module table.
const lualibRequired = transpiledFiles.some(f => f.lua?.includes('require("lualib_bundle")'));
if (lualibRequired) {
moduleTableEntries.push(`["lualib_bundle"] = function() ${getLuaLibBundle(emitHost)} end,\n`);
}
const moduleTableEntries = files.map(f => moduleSourceNode(f, createModulePath(outDir, f.fileName)));

// Create ____modules table containing all entries from moduleTableEntries
const moduleTable = createModuleTableNode(moduleTableEntries);

// Override `require` to read from ____modules table.
const requireOverride = `
local ____modules = {}
local ____moduleCache = {}
local ____originalRequire = require
local function require(file)
if ____moduleCache[file] then
return ____moduleCache[file]
end
if ____modules[file] then
____moduleCache[file] = ____modules[file]()
return ____moduleCache[file]
else
if ____originalRequire then
return ____originalRequire(file)
else
error("module '" .. file .. "' not found")
end
end
end\n`;

// return require("<entry module path>")
const entryPoint = `return require(${createModulePath(sourceRootDir, resolvedEntryModule)})\n`;
const entryPoint = `return require(${createModulePath(outDir, resolvedEntryModule)})\n`;

const bundleNode = joinSourceChunks([requireOverride, moduleTable, entryPoint]);
const { code, map } = bundleNode.toStringWithSourceMap();

return [
diagnostics,
{
fileName: normalizeSlashes(resolvedBundleFile),
lua: code,
outputPath,
code,
sourceMap: map.toString(),
sourceMapNode: moduleTable,
sourceFiles: files.flatMap(x => x.sourceFiles ?? []),
},
];
}

function moduleSourceNode(transpiledFile: TranspiledFile, modulePath: string): SourceNode {
function moduleSourceNode({ code, sourceMapNode }: ProcessedFile, modulePath: string): SourceNode {
const tableEntryHead = `[${modulePath}] = function() `;
const tableEntryTail = "end,\n";

if (transpiledFile.lua && transpiledFile.sourceMapNode) {
return joinSourceChunks([tableEntryHead, transpiledFile.sourceMapNode, tableEntryTail]);
} else {
return joinSourceChunks([tableEntryHead, tableEntryTail]);
}
return joinSourceChunks([tableEntryHead, sourceMapNode ?? code, tableEntryTail]);
}

function createModuleTableNode(fileChunks: SourceChunk[]): SourceNode {
Expand Down
69 changes: 0 additions & 69 deletions src/transpilation/emit.ts

This file was deleted.

78 changes: 48 additions & 30 deletions src/transpilation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,41 @@ import * as path from "path";
import * as ts from "typescript";
import { parseConfigFileWithSystem } from "../cli/tsconfig";
import { CompilerOptions } from "../CompilerOptions";
import { emitTranspiledFiles, OutputFile } from "./emit";
import { transpile, TranspiledFile, TranspileResult } from "./transpile";
import { createEmitOutputCollector, TranspiledFile } from "./output-collector";
import { EmitResult, Transpiler } from "./transpiler";

export * from "./emit";
export { Plugin } from "./plugins";
export * from "./transpile";

export interface TranspileFilesResult {
diagnostics: ts.Diagnostic[];
emitResult: OutputFile[];
}

export function transpileFiles(rootNames: string[], options: CompilerOptions = {}): TranspileFilesResult {
export * from "./transpiler";
export { EmitHost } from "./utils";
export { TranspiledFile };

export function transpileFiles(
rootNames: string[],
options: CompilerOptions = {},
writeFile?: ts.WriteFileCallback
): EmitResult {
const program = ts.createProgram(rootNames, options);
const { transpiledFiles, diagnostics: transpileDiagnostics } = transpile({ program });
const emitResult = emitTranspiledFiles(program, transpiledFiles);

const { diagnostics: transpileDiagnostics, emitSkipped } = new Transpiler().emit({ program, writeFile });
const diagnostics = ts.sortAndDeduplicateDiagnostics([
...ts.getPreEmitDiagnostics(program),
...transpileDiagnostics,
]);

return { diagnostics: [...diagnostics], emitResult };
return { diagnostics: [...diagnostics], emitSkipped };
}

export function transpileProject(configFileName: string, optionsToExtend?: CompilerOptions): TranspileFilesResult {
export function transpileProject(
configFileName: string,
optionsToExtend?: CompilerOptions,
writeFile?: ts.WriteFileCallback
): EmitResult {
const parseResult = parseConfigFileWithSystem(configFileName, optionsToExtend);
if (parseResult.errors.length > 0) {
return { diagnostics: parseResult.errors, emitResult: [] };
return { diagnostics: parseResult.errors, emitSkipped: true };
}

return transpileFiles(parseResult.fileNames, parseResult.options);
return transpileFiles(parseResult.fileNames, parseResult.options, writeFile);
}

const libCache: { [key: string]: ts.SourceFile } = {};
Expand All @@ -51,33 +54,45 @@ export function createVirtualProgram(input: Record<string, string>, options: Com
useCaseSensitiveFileNames: () => false,
writeFile() {},

getSourceFile(filename) {
if (filename in input) {
return ts.createSourceFile(filename, input[filename], ts.ScriptTarget.Latest, false);
getSourceFile(fileName) {
if (fileName in input) {
return ts.createSourceFile(fileName, input[fileName], ts.ScriptTarget.Latest, false);
}

if (filename.startsWith("lib.")) {
if (libCache[filename]) return libCache[filename];
if (fileName.startsWith("lib.")) {
if (libCache[fileName]) return libCache[fileName];
const typeScriptDir = path.dirname(require.resolve("typescript"));
const filePath = path.join(typeScriptDir, filename);
const filePath = path.join(typeScriptDir, fileName);
const content = fs.readFileSync(filePath, "utf8");

libCache[filename] = ts.createSourceFile(filename, content, ts.ScriptTarget.Latest, false);
libCache[fileName] = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, false);

return libCache[filename];
return libCache[fileName];
}
},
};

return ts.createProgram(Object.keys(input), options, compilerHost);
}

export function transpileVirtualProject(files: Record<string, string>, options: CompilerOptions = {}): TranspileResult {
export interface TranspileVirtualProjectResult {
diagnostics: ts.Diagnostic[];
transpiledFiles: TranspiledFile[];
}

export function transpileVirtualProject(
files: Record<string, string>,
options: CompilerOptions = {}
): TranspileVirtualProjectResult {
const program = createVirtualProgram(files, options);
const result = transpile({ program });
const diagnostics = ts.sortAndDeduplicateDiagnostics([...ts.getPreEmitDiagnostics(program), ...result.diagnostics]);
const collector = createEmitOutputCollector();
const { diagnostics: transpileDiagnostics } = new Transpiler().emit({ program, writeFile: collector.writeFile });
const diagnostics = ts.sortAndDeduplicateDiagnostics([
...ts.getPreEmitDiagnostics(program),
...transpileDiagnostics,
]);

return { ...result, diagnostics: [...diagnostics] };
return { diagnostics: [...diagnostics], transpiledFiles: collector.files };
}

export interface TranspileStringResult {
Expand All @@ -87,5 +102,8 @@ export interface TranspileStringResult {

export function transpileString(main: string, options: CompilerOptions = {}): TranspileStringResult {
const { diagnostics, transpiledFiles } = transpileVirtualProject({ "main.ts": main }, options);
return { diagnostics, file: transpiledFiles.find(({ fileName }) => fileName === "main.ts") };
return {
diagnostics,
file: transpiledFiles.find(({ sourceFiles }) => sourceFiles.some(f => f.fileName === "main.ts")),
};
}
Loading