import browserify = require("browserify"); import log = require("fancy-log"); import gulp = require("gulp"); import { TaskFunction } from "gulp"; import gulpIf = require("gulp-if"); import terser = require("gulp-terser"); import merge = require("merge-stream"); import minimist = require("minimist"); import { Server, Socket } from "net"; import PromiseQueue = require("promise-queue"); import { parseArgsStringToArgv } from "string-argv"; import Path = require("upath"); import buffer = require("vinyl-buffer"); import vinylSourceStream = require("vinyl-source-stream"); import Watchify = require("watchify"); import { Settings } from "./.gulp/Settings"; import "./.gulp/TaskFunction"; /** * The port to listen for stop-requests. */ const watchConnectorPort = 50958; /** * The message that is printed when starting the compilation in watch mode. */ const watchStartMessage = "Starting compilation in watch mode..."; /** * The message that is printed when starting an incremental compilation. */ const incrementalMessage = "File change detected. Starting incremental compilation..."; /** * Generates the message that is printed after finishing a compilation in watch mode. * * @param errorCount * The number of errors which occurred. */ const watchFinishMessage = (errorCount: number) => { return `Found ${errorCount} errors. Watching for file changes.`; }; /** * The arguments passed by the user. */ let options = ParseArgs(process.argv.slice(2)); /** * Parses the specified arguments. * * @param args * The arguments to parse. */ function ParseArgs(args: string[]) { return minimist( args, { string: [ "target" ], alias: { target: "t" }, default: { target: "Debug" } }); } /** * The settings for building the project. */ let settings = new Settings(options["target"]); /** * Initializes the project. */ export function Initialize(done: () => void) { done(); } Initialize.description = "Initializes the project."; /** * Builds the project in watched mode. */ export let Watch: TaskFunction = (done) => { settings.Watch = true; Build(); let server = new Server( (socket) => { socket.on( "data", (data) => { let args = parseArgsStringToArgv(data.toString()); socket.destroy(); if (args[0] === "stop") { let options = ParseArgs(args.slice(1)); if (options["target"] === settings.Target) { server.close(); done(); process.exit(); } } }); }); server.listen(watchConnectorPort); }; Watch.description = "Builds the project in watched mode."; /** * Builds the project. */ export async function Build() { if (settings.Watch) { log.info(watchStartMessage); } Library(); } /** * Builds the TypeScript- and JavaScript-library. */ export async function Library() { let streams: Array> = []; let queue = new PromiseQueue(); let tsConfigFile = settings.TypeScriptProjectRoot("tsconfig.json"); let tsConfig = require(tsConfigFile); let optionBase: browserify.Options = { ...Watchify.args, node: true, ignoreMissing: true, debug: settings.Debug }; { let errorMessages: string[] = []; let files = (tsConfig.files as string[]).map( (file) => Path.relative(settings.TypeScriptSourceRoot(), settings.TypeScriptProjectRoot(file))); for (let file of files) { let bundler = browserify( { ...optionBase, basedir: settings.LibraryPath(Path.dirname(file)), entries: [ settings.TypeScriptSourceRoot(file) ], standalone: Path.join(Path.dirname(file), Path.parse(file).name) }); if (settings.Watch) { bundler = Watchify(bundler); } bundler.plugin( require("tsify"), { project: tsConfigFile }); let bundle = async () => { return new Promise( (resolve) => { let stream = bundler.bundle().on( "error", (error) => { let message: string = error.message; if (!errorMessages.includes(message)) { errorMessages.push(message); log.error(message); } } ).pipe( vinylSourceStream(Path.changeExt(file, "js")) ).pipe( buffer() ).pipe( gulpIf( settings.Debug, terser() ) ).pipe( gulp.dest(settings.LibraryPath()) ); stream.on( "end", () => { if (settings.Watch && ((queue.getQueueLength() + queue.getPendingLength()) === 1)) { log.info(watchFinishMessage(errorMessages.length)); } errorMessages.splice(0, errorMessages.length); resolve(stream); }); }); }; if (settings.Watch) { bundler.on( "update", () => { if ((queue.getQueueLength() + queue.getPendingLength()) === 0) { log.info(incrementalMessage); } queue.add( async () => { return bundle(); }); }); } let build = () => queue.add(bundle); build.displayName = Build.displayName; build.description = Build.description; streams.push(build()); } } return merge(await Promise.all(streams)) as NodeJS.ReadWriteStream; } Library.description = "Builds the TypeScript- and JavaScript-library."; /** * Stops a watch-task. */ export async function Stop() { try { await new Promise( (resolve, reject) => { let client = new Socket(); client.connect( watchConnectorPort, "localhost", async () => { client.write(`stop -t ${settings.Target}`); }); client.on("close", resolve); client.on("error", reject); }); } catch { log.info("The specified task is not running."); } } Stop.description = "Stops a watch-task";