Add tasks for building typescript-code

This commit is contained in:
Manuel Thalmann 2019-10-07 11:48:01 +00:00
parent bdb2e4e02f
commit f1906b03cd
14 changed files with 2179 additions and 4 deletions

View file

@ -2,5 +2,8 @@
"name": "SilverStripe Environment", "name": "SilverStripe Environment",
"dockerComposeFile": "docker-compose.yml", "dockerComposeFile": "docker-compose.yml",
"service": "silverstripe", "service": "silverstripe",
"workspaceFolder": "/vscode/src/mantra" "workspaceFolder": "/vscode/src/mantra",
"extensions": [
"ms-vscode.vscode-typescript-tslint-plugin"
]
} }

6
.gitignore vendored
View file

@ -51,6 +51,9 @@ typings/
# Output of 'npm pack' # Output of 'npm pack'
*.tgz *.tgz
# Output of 'composer archive'
*.tar
# Yarn Integrity file # Yarn Integrity file
.yarn-integrity .yarn-integrity
@ -77,3 +80,6 @@ vendor/
# MySQL cache # MySQL cache
.devcontainer/mysql-data .devcontainer/mysql-data
# Build files
/javascript/

126
.gulp/Settings.ts Normal file
View file

@ -0,0 +1,126 @@
import Path = require("upath");
/**
* Provides settings for building the project.
*/
export class Settings
{
/**
* A value indicating whether the project should be built in watched mode.
*/
public Watch = false;
/**
* The target of the project-build.
*/
public Target: string;
/**
* The path to the source-code root.
*/
private sourceRoot = "src";
/**
* The path to the root of the typescript-project.
*/
private typeScriptProjectRoot = "App";
/**
* The path to the root of the typescript-source.
*/
private typeScriptSourceRoot = "src";
/**
* The path to save the javascript-code to.
*/
private libraryPath = "javascript";
/**
* Initializes a new instance of the `Settings` class.
*
* @param target
* The target of the project-build.
*/
public constructor(target: string)
{
this.Target = target;
}
/**
* A value indicating whether the debug-mode is enabled.
*/
public get Debug()
{
return this.Target === "Debug";
}
/**
* Creates a path relative to the root of the solution.
*
* @param path
* The path to join.
*
* @return
* The joined path.
*/
public RootPath(...path: string[])
{
return Path.join(Path.dirname(__dirname), ...path);
}
/**
* Creates a path relative to the root of the source-code.
*
* @param path
* The path to join.
*
* @returns
* The joined path.
*/
public SourceRoot(...path: string[])
{
return this.RootPath(this.sourceRoot, ...path);
}
/**
* Creates a path relative to the root of the typescript-project.
*
* @param path
* The path to join.
*
* @returns
* The joined path.
*/
public TypeScriptProjectRoot(...path: string[])
{
return this.SourceRoot(this.typeScriptProjectRoot, ...path);
}
/**
* Creates a path relative to the root of the typescript-source.
*
* @param path
* The path to join.
*
* @returns
* The joined path.
*/
public TypeScriptSourceRoot(...path: string[])
{
return this.TypeScriptProjectRoot(this.typeScriptSourceRoot, ...path);
}
/**
* Creates a path relative to the directory to save the javascript-code to.
*
* @param path
* The path to join.
*
* @returns
* The joined path.
*/
public LibraryPath(...path: string[])
{
return this.RootPath(this.libraryPath, ...path);
}
}

View file

@ -5,6 +5,7 @@ import { TaskFunction } from "gulp";
*/ */
declare global declare global
{ {
// tslint:disable-next-line: completed-docs
interface Function extends TaskFunction interface Function extends TaskFunction
{ } { }
} }

14
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,14 @@
{
// See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.
// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"ms-vscode.vscode-typescript-tslint-plugin",
"ms-vscode-remote.remote-containers"
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
]
}

8
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,8 @@
{
"javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
"javascript.format.placeOpenBraceOnNewLineForControlBlocks": true,
"javascript.format.placeOpenBraceOnNewLineForFunctions": true,
"typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
"typescript.format.placeOpenBraceOnNewLineForControlBlocks": true,
"typescript.format.placeOpenBraceOnNewLineForFunctions": true
}

View file

@ -36,12 +36,18 @@
"archive": { "archive": {
"exclude": [ "exclude": [
"/*.tar", "/*.tar",
".devcontainer",
".gulp",
".gitignore", ".gitignore",
".vscode/",
"gulpfile.ts", "gulpfile.ts",
"node_modules/", "node_modules/",
"package-lock.json", "package-lock.json",
"package.json", "package.json",
"test/" "src/",
"test/",
"tsconfig.json",
"tslint.json"
] ]
}, },
"extra": { "extra": {

View file

@ -1,5 +1,80 @@
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"; 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. * Initializes the project.
*/ */
@ -9,3 +84,206 @@ export function Initialize(done: () => void)
} }
Initialize.description = "Initializes the project."; 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<Promise<NodeJS.ReadWriteStream>> = [];
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<NodeJS.ReadWriteStream>(
(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";

1660
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,9 +5,42 @@
}, },
"dependencies": {}, "dependencies": {},
"devDependencies": { "devDependencies": {
"@manuth/tsconfig": "^1.2.2",
"@manuth/tslint-presets": "^1.0.4",
"@types/bootstrap": "^4.3.1",
"@types/browserify": "^12.0.36",
"@types/fancy-log": "^1.3.1",
"@types/gulp": "^4.0.6", "@types/gulp": "^4.0.6",
"@types/gulp-if": "0.0.33",
"@types/gulp-terser": "^1.2.0",
"@types/jquery": "^3.3.31",
"@types/merge-stream": "^1.1.2",
"@types/minimist": "^1.2.0",
"@types/node": "^12.7.11",
"@types/promise-queue": "^2.2.0",
"@types/vinyl-buffer": "^1.0.0",
"@types/vinyl-source-stream": "0.0.30",
"@types/watchify": "^3.7.4",
"bootstrap": "^4.3.1",
"browserify": "^16.5.0",
"fancy-log": "^1.3.3",
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-if": "^3.0.0",
"gulp-terser": "^1.2.0",
"jquery": "^3.4.1",
"merge-stream": "^2.0.0",
"minimist": "^1.2.0",
"popper.js": "^1.15.0",
"promise-queue": "^2.2.5",
"string-argv": "^0.3.1",
"ts-node": "^8.4.1", "ts-node": "^8.4.1",
"typescript": "^3.6.3" "tsify": "^4.0.1",
"tslint": "^5.20.0",
"typescript": "^3.6.3",
"typescript-tslint-plugin": "^0.5.4",
"upath": "^1.2.0",
"vinyl-buffer": "^1.0.1",
"vinyl-source-stream": "^2.0.0",
"watchify": "^3.11.1"
} }
} }

3
src/App/src/main.ts Normal file
View file

@ -0,0 +1,3 @@
import "bootstrap";
import "jquery";
import "popper.js";

13
src/App/tsconfig.json Normal file
View file

@ -0,0 +1,13 @@
{
"files": [
"src/main.ts"
],
"compilerOptions": {
"rootDir": "src",
"lib": [
"es7",
"dom"
],
"target": "es5"
}
}

18
tsconfig.json Normal file
View file

@ -0,0 +1,18 @@
{
"extends": "@manuth/tsconfig/recommended",
"compilerOptions": {
"module": "commonjs",
"lib": [
"es7"
],
"plugins": [
{
"name": "typescript-tslint-plugin"
}
],
"target": "es5"
},
"files": [
"./gulpfile.ts"
]
}

6
tslint.json Normal file
View file

@ -0,0 +1,6 @@
{
"extends": "@manuth/tslint-presets/recommended",
"rules": {
"interface-name": false
}
}