Improved the webpack script and using constants for build version detection

canary
WolverinDEV 2020-04-01 21:47:33 +02:00
parent b297147f98
commit c460d05ee5
25 changed files with 260 additions and 355 deletions

11
file.ts
View File

@ -37,8 +37,6 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
"path": "./", "path": "./",
"local-path": "./shared/html/" "local-path": "./shared/html/"
}, },
{ /* javascript files as manifest.json */ { /* javascript files as manifest.json */
"type": "js", "type": "js",
"search-pattern": /.*$/, "search-pattern": /.*$/,
@ -47,15 +45,6 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
"path": "js/", "path": "js/",
"local-path": "./dist/" "local-path": "./dist/"
}, },
{ /* loader javascript file */
"type": "js",
"search-pattern": /.*$/,
"build-target": "dev|rel",
"path": "js/",
"local-path": "./loader/dist/"
},
{ /* shared developer single css files */ { /* shared developer single css files */
"type": "css", "type": "css",
"search-pattern": /.*\.css$/, "search-pattern": /.*\.css$/,

View File

@ -1,6 +1,8 @@
import * as loader from "./targets/app"; import * as loader from "./targets/app";
import * as loader_base from "./loader/loader"; import * as loader_base from "./loader/loader";
export = loader_base; window["loader"] = loader_base;
/* let the loader register himself at the window first */ /* let the loader register himself at the window first */
setTimeout(loader.run, 0); setTimeout(loader.run, 0);
export {};

View File

@ -1,5 +1,3 @@
import {AppVersion} from "tc-loader";
import {LoadSyntaxError, script_name} from "./utils";
import * as script_loader from "./script_loader"; import * as script_loader from "./script_loader";
import * as style_loader from "./style_loader"; import * as style_loader from "./style_loader";
import * as template_loader from "./template_loader"; import * as template_loader from "./template_loader";
@ -75,38 +73,35 @@ const tasks: {[key:number]:Task[]} = {};
/* test if all files shall be load from cache or fetch again */ /* test if all files shall be load from cache or fetch again */
function loader_cache_tag() { function loader_cache_tag() {
const app_version = (() => { if(__build.mode === "debug") {
const version_node = document.getElementById("app_version");
if(!version_node) return undefined;
const version = version_node.hasAttribute("value") ? version_node.getAttribute("value") : undefined;
if(!version) return undefined;
if(!version || version == "unknown" || version.replace(/0+/, "").length == 0)
return undefined;
return version;
})();
if(config.verbose) console.log("Found current app version: %o", app_version);
if(!app_version) {
/* TODO add warning */
cache_tag = "?_ts=" + Date.now(); cache_tag = "?_ts=" + Date.now();
return; return;
} }
const cached_version = localStorage.getItem("cached_version"); const cached_version = localStorage.getItem("cached_version");
if(!cached_version || cached_version != app_version) { if(!cached_version || cached_version !== __build.version) {
register_task(Stage.LOADED, { register_task(Stage.LOADED, {
priority: 0, priority: 0,
name: "cached version updater", name: "cached version updater",
function: async () => { function: async () => {
localStorage.setItem("cached_version", app_version); localStorage.setItem("cached_version", __build.version);
} }
}); });
} }
cache_tag = "?_version=" + app_version; cache_tag = "?_version=" + __build.version;
} }
export type ModuleMapping = {
application: string,
modules: {
"id": string,
"context": string,
"resource": string
}[]
};
const module_mapping_: ModuleMapping[] = [];
export function module_mapping() : ModuleMapping[] { return module_mapping_; }
export function get_cache_version() { return cache_tag; } export function get_cache_version() { return cache_tag; }
export function finished() { export function finished() {
@ -259,12 +254,6 @@ export function hide_overlay() {
}); });
} }
/* versions management */
let version_: AppVersion;
export function version() : AppVersion { return version_; }
export function set_version(version: AppVersion) { version_ = version; }
/* critical error handler */ /* critical error handler */
export type ErrorHandler = (message: string, detail: string) => void; export type ErrorHandler = (message: string, detail: string) => void;
let _callback_critical_error: ErrorHandler; let _callback_critical_error: ErrorHandler;

View File

@ -31,38 +31,20 @@ interface Manifest {
version: number; version: number;
chunks: {[key: string]: { chunks: {[key: string]: {
hash: string, files: {
file: string hash: string,
}[]}; file: string
} }[],
modules: {
interface BuildDefinitions { id: string,
development: boolean, context: string,
version: string resource: string
} }[]
declare global { }};
const __build: BuildDefinitions;
} }
/* all javascript loaders */ /* all javascript loaders */
const loader_javascript = { const loader_javascript = {
detect_type: async () => {
if(window.native_client) {
loader.set_version({
backend: "-",
ui: ui_version(),
debug_mode: __build.development,
type: "native"
});
} else {
loader.set_version({
backend: "-",
ui: ui_version(),
debug_mode: __build.development,
type: "web"
});
}
},
load_scripts: async () => { load_scripts: async () => {
if(!window.require) { if(!window.require) {
await loader.scripts.load(["vendor/jquery/jquery.min.js"], { cache_tag: cache_tag() }); await loader.scripts.load(["vendor/jquery/jquery.min.js"], { cache_tag: cache_tag() });
@ -101,15 +83,19 @@ const loader_javascript = {
loader.critical_error("Failed to load manifest.json", error); loader.critical_error("Failed to load manifest.json", error);
throw "failed to load manifest.json"; throw "failed to load manifest.json";
} }
if(manifest.version !== 1) if(manifest.version !== 2)
throw "invalid manifest version"; throw "invalid manifest version";
const chunk_name = loader.version().type === "web" ? "shared-app" : "client-app"; const chunk_name = __build.entry_chunk_name;
if(!Array.isArray(manifest.chunks[chunk_name])) { if(typeof manifest.chunks[chunk_name] !== "object") {
loader.critical_error("Missing entry chunk in manifest.json", "Chunk " + chunk_name + " is missing."); loader.critical_error("Missing entry chunk in manifest.json", "Chunk " + chunk_name + " is missing.");
throw "missing entry chunk"; throw "missing entry chunk";
} }
await loader.scripts.load_multiple(manifest.chunks[chunk_name].map(e => "js/" + e.file), { loader.module_mapping().push({
application: chunk_name,
modules: manifest.chunks[chunk_name].modules
});
await loader.scripts.load_multiple(manifest.chunks[chunk_name].files.map(e => "js/" + e.file), {
cache_tag: undefined, cache_tag: undefined,
max_parallel_requests: -1 max_parallel_requests: -1
}); });
@ -154,7 +140,7 @@ const loader_style = {
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */ ["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */
], options); ], options);
if(loader.version().debug_mode) { if(__build.mode === "debug") {
await loader_style.load_style_debug(); await loader_style.load_style_debug();
} else { } else {
await loader_style.load_style_release(); await loader_style.load_style_release();
@ -293,12 +279,6 @@ loader.register_task(loader.Stage.INITIALIZING, {
priority: 20 priority: 20
}); });
loader.register_task(loader.Stage.INITIALIZING, {
name: "app type test",
function: loader_javascript.detect_type,
priority: 20
});
loader.register_task(loader.Stage.JAVASCRIPT, { loader.register_task(loader.Stage.JAVASCRIPT, {
name: "javascript", name: "javascript",
function: loader_javascript.load_scripts, function: loader_javascript.load_scripts,
@ -398,9 +378,14 @@ loader.register_task(loader.Stage.SETUP, {
}); });
export function run() { export function run() {
window["Module"] = (window["Module"] || {}) as any; window["Module"] = (window["Module"] || {}) as any; /* Why? */
/* TeaClient */ /* TeaClient */
if(node_require) { if(node_require) {
if(__build.target !== "client") {
loader.critical_error("App seems not to be compiled for the client.", "This app has been compiled for " + __build.target);
return;
}
window.native_client = true; window.native_client = true;
const path = node_require("path"); const path = node_require("path");
@ -415,6 +400,11 @@ export function run() {
priority: 40 priority: 40
}); });
} else { } else {
if(__build.target !== "web") {
loader.critical_error("App seems not to be compiled for the web.", "This app has been compiled for " + __build.target);
return;
}
window.native_client = false; window.native_client = false;
} }

View File

@ -61,7 +61,15 @@ export enum Stage {
DONE DONE
} }
export function version() : AppVersion; export type ModuleMapping = {
application: string,
modules: {
"id": string,
"context": string,
"resource": string
}[]
};
export function module_mapping() : ModuleMapping;
export function finished(); export function finished();
export function running(); export function running();

View File

@ -1,71 +0,0 @@
const webpack = require("webpack");
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
let isDevelopment = process.env.NODE_ENV === 'development';
isDevelopment = true;
module.exports = {
entry: path.join(__dirname, "app/index.ts"),
devtool: 'inline-source-map',
mode: "development",
plugins: [
new MiniCssExtractPlugin({
filename: isDevelopment ? '[name].css' : '[name].[hash].css',
chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css'
}),
new webpack.DefinePlugin({
__build: {
development: isDevelopment,
version: '0000' //TODO!
}
})
],
module: {
rules: [
{
test: /\.s[ac]ss$/,
loader: [
//isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
sourceMap: isDevelopment
}
},
{
loader: 'sass-loader',
options: {
sourceMap: isDevelopment
}
}
]
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: [
{
loader: 'ts-loader',
options: {
transpileOnly: true
}
}
]
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js', ".scss"],
alias: { }
},
output: {
filename: 'loader.js',
path: path.resolve(__dirname, 'dist'),
library: "loader",
libraryTarget: "window" //"var" | "assign" | "this" | "window" | "self" | "global" | "commonjs" | "commonjs2" | "commonjs-module" | "amd" | "amd-require" | "umd" | "umd2" | "jsonp" | "system"
},
optimization: { }
};

View File

@ -21,9 +21,7 @@
"build-web": "webpack --config webpack-web.config.js", "build-web": "webpack --config webpack-web.config.js",
"watch-web": "webpack --watch --config webpack-web.config.js", "watch-web": "webpack --watch --config webpack-web.config.js",
"build-client": "webpack --config webpack-client.config.js", "build-client": "webpack --config webpack-client.config.js",
"watch-client": "webpack --watch --config webpack-client.config.js", "watch-client": "webpack --watch --config webpack-client.config.js"
"build-loader": "webpack --config loader/webpack.config.js",
"watch-loader": "webpack --watch --config loader/webpack.config.js"
}, },
"author": "TeaSpeak (WolverinDEV)", "author": "TeaSpeak (WolverinDEV)",
"license": "ISC", "license": "ISC",

View File

@ -1,4 +1,5 @@
import * as log from "tc-shared/log"; import * as log from "tc-shared/log";
import * as hex from "tc-shared/crypto/hex";
import {LogCategory} from "tc-shared/log"; import {LogCategory} from "tc-shared/log";
import {ChannelEntry} from "tc-shared/ui/channel"; import {ChannelEntry} from "tc-shared/ui/channel";
import {ConnectionHandler} from "tc-shared/ConnectionHandler"; import {ConnectionHandler} from "tc-shared/ConnectionHandler";

View File

@ -126,7 +126,7 @@ export namespace bbcode {
}, },
name: tr("Open URL in Browser"), name: tr("Open URL in Browser"),
type: contextmenu.MenuEntryType.ENTRY, type: contextmenu.MenuEntryType.ENTRY,
visible: loader.version().type === "native" && false // Currently not possible visible: __build.target === "client" && false // Currently not possible
}, contextmenu.Entry.HR(), { }, contextmenu.Entry.HR(), {
callback: () => copy_to_clipboard(url), callback: () => copy_to_clipboard(url),
name: tr("Copy URL to clipboard"), name: tr("Copy URL to clipboard"),

View File

@ -250,7 +250,7 @@ export class Group {
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, { loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
name: "log enabled initialisation", name: "log enabled initialisation",
function: async () => initialize(loader.version().debug_mode ? LogType.TRACE : LogType.INFO), function: async () => initialize(__build.mode === "debug" ? LogType.TRACE : LogType.INFO),
priority: 150 priority: 150
}); });

View File

@ -144,7 +144,7 @@ async function initialize_app() {
try { //Initialize main template try { //Initialize main template
const main = $("#tmpl_main").renderTag({ const main = $("#tmpl_main").renderTag({
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION), multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
app_version: loader.version().ui app_version: __build.version
}).dividerfy(); }).dividerfy();
$("body").append(main); $("body").append(main);
@ -589,7 +589,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
try { try {
await initialize(); await initialize();
if(loader.version().type == "web") { if(__build.target == "web") {
loader.register_task(loader.Stage.LOADED, task_certificate_callback); loader.register_task(loader.Stage.LOADED, task_certificate_callback);
} else { } else {
loader.register_task(loader.Stage.LOADED, task_teaweb_starter); loader.register_task(loader.Stage.LOADED, task_teaweb_starter);

View File

@ -515,4 +515,4 @@ export class ServerSettings extends SettingsBase {
} }
} }
export let settings: Settings; export let settings: Settings = null;

View File

@ -370,7 +370,7 @@ export function initialize() {
return true; return true;
}}; }};
if(loader.version().type !== "web") { if(__build.target !== "web") {
menu.append_hr(); menu.append_hr();
item = menu.append_item(tr("Quit")); item = menu.append_item(tr("Quit"));
@ -532,7 +532,7 @@ export function initialize() {
{ {
const menu = driver_.append_item(tr("Help")); const menu = driver_.append_item(tr("Help"));
if(loader.version().type !== "web") { if(__build.target !== "web") {
item = menu.append_item(tr("Check for updates")); item = menu.append_item(tr("Check for updates"));
item.click(() => native_actions.check_native_update()); item.click(() => native_actions.check_native_update());
@ -546,7 +546,7 @@ export function initialize() {
item = menu.append_item(tr("Visit TeaSpeak forum")); item = menu.append_item(tr("Visit TeaSpeak forum"));
item.click(() => window.open('https://forum.teaspeak.de/', '_blank')); item.click(() => window.open('https://forum.teaspeak.de/', '_blank'));
if(loader.version().type !== "web" && typeof(native_actions.show_dev_tools) === "function" && native_actions.show_dev_tools()) { if(__build.target !== "web" && typeof(native_actions.show_dev_tools) === "function" && native_actions.show_dev_tools()) {
menu.append_hr(); menu.append_hr();
item = menu.append_item(tr("Open developer tools")); item = menu.append_item(tr("Open developer tools"));
item.click(() => native_actions.open_dev_tools()); item.click(() => native_actions.open_dev_tools());
@ -556,7 +556,7 @@ export function initialize() {
} }
menu.append_hr(); menu.append_hr();
item = menu.append_item(loader.version().type === "web" ? tr("About TeaWeb") : tr("About TeaClient")); item = menu.append_item(__build.target === "web" ? tr("About TeaWeb") : tr("About TeaClient"));
item.click(() => spawnAbout()) item.click(() => spawnAbout())
} }

View File

@ -27,9 +27,9 @@ export function spawnAbout() {
header: tr("About"), header: tr("About"),
body: () => { body: () => {
let tag = $("#tmpl_about").renderTag({ let tag = $("#tmpl_about").renderTag({
client: loader.version().type !== "web", client: __build.target !== "web",
version_client: loader.version().type === "web" ? app_version || "in-dev" : "loading...", version_client: __build.target === "web" ? app_version || "in-dev" : "loading...",
version_ui: app_version || "in-dev", version_ui: app_version || "in-dev",
version_timestamp: !!app_version ? format_date(Date.now()) : "--" version_timestamp: !!app_version ? format_date(Date.now()) : "--"
@ -43,7 +43,7 @@ export function spawnAbout() {
connectModal.htmlTag.find(".modal-body").addClass("modal-about"); connectModal.htmlTag.find(".modal-body").addClass("modal-about");
connectModal.open(); connectModal.open();
if(loader.version().type !== "web") { if(__build.target !== "web") {
(window as any).native.client_version().then(version => { (window as any).native.client_version().then(version => {
connectModal.htmlTag.find(".version-client").text(version); connectModal.htmlTag.find(".version-client").text(version);
}).catch(error => { }).catch(error => {

View File

@ -34,7 +34,7 @@ export function spawnKeySelect(callback: (key?: KeyEvent) => void) {
button_save.on('click', () => { button_save.on('click', () => {
if(loader.version().type !== "web") { if(__build.version !== "web") {
/* Because pressing the close button is also a mouse action */ /* Because pressing the close button is also a mouse action */
if(current_key_age + 1000 > Date.now() && current_key.key_code == "MOUSE2") if(current_key_age + 1000 > Date.now() && current_key.key_code == "MOUSE2")
current_key = last_key; current_key = last_key;

View File

@ -24,9 +24,9 @@ const last_step: {[key: string]:string} = (() => {
export function openModalNewcomer() : Modal { export function openModalNewcomer() : Modal {
let modal = createModal({ let modal = createModal({
header: tra("Welcome to the {}", loader.version().type === "web" ? "TeaSpeak - Web client" : "TeaSpeak - Client"), header: tra("Welcome to the {}", __build.version === "web" ? "TeaSpeak - Web client" : "TeaSpeak - Client"),
body: () => $("#tmpl_newcomer").renderTag({ body: () => $("#tmpl_newcomer").renderTag({
is_web: loader.version().type === "web" is_web: __build.version === "web"
}).children(), }).children(),
footer: null, footer: null,

View File

@ -331,7 +331,7 @@ function settings_general_language(container: JQuery, modal: Modal) {
} }
container.find(".button-restart").on('click', () => { container.find(".button-restart").on('click', () => {
if(loader.version().type === "web") { if(__build.target === "web") {
location.reload(); location.reload();
} else { } else {
createErrorModal(tr("Not implemented"), tr("Client restart isn't implemented.<br>Please do it manually!")).open(); createErrorModal(tr("Not implemented"), tr("Client restart isn't implemented.<br>Please do it manually!")).open();

View File

@ -1,6 +1,8 @@
import * as sha1 from "../crypto/sha";
export function hashPassword(password: string) : Promise<string> { export function hashPassword(password: string) : Promise<string> {
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
sha.sha1(password).then(result => { sha1.sha1(password).then(result => {
resolve(btoa(String.fromCharCode.apply(null, new Uint8Array(result)))); resolve(btoa(String.fromCharCode.apply(null, new Uint8Array(result))));
}); });
}); });

View File

@ -64,7 +64,7 @@ function _generate(config: Configuration, node: ts.Node, result: TranslationEntr
node.forEachChild(n => _generate(config, n, result)); node.forEachChild(n => _generate(config, n, result));
} }
function create_unique_check(source_file: ts.SourceFile, variable: ts.Expression, variables: { name: string, node: ts.Node }[]) : ts.Node[] { function create_unique_check(config: Configuration, source_file: ts.SourceFile, variable: ts.Expression, variables: { name: string, node: ts.Node }[]) : ts.Node[] {
const nodes: ts.Node[] = [], blocked_nodes: ts.Statement[] = []; const nodes: ts.Node[] = [], blocked_nodes: ts.Statement[] = [];
const node_path = (node: ts.Node) => { const node_path = (node: ts.Node) => {
@ -90,10 +90,10 @@ function create_unique_check(source_file: ts.SourceFile, variable: ts.Expression
/* initialization */ /* initialization */
{ {
const declarations = ts.createElementAccess(variable, ts.createLiteral("declared")); const declarations = ts.createElementAccess(variable, ts.createLiteral(config.variables.declarations));
nodes.push(ts.createAssignment(declarations, ts.createBinary(declarations, SyntaxKind.BarBarToken, ts.createAssignment(declarations, ts.createObjectLiteral())))); nodes.push(ts.createAssignment(declarations, ts.createBinary(declarations, SyntaxKind.BarBarToken, ts.createAssignment(declarations, ts.createObjectLiteral()))));
declarations_file = ts.createElementAccess(variable, ts.createLiteral("declared_files")); declarations_file = ts.createElementAccess(variable, ts.createLiteral(config.variables.declare_files));
nodes.push(ts.createAssignment(declarations_file, ts.createBinary(declarations_file, SyntaxKind.BarBarToken, ts.createAssignment(declarations_file, ts.createObjectLiteral())))); nodes.push(ts.createAssignment(declarations_file, ts.createBinary(declarations_file, SyntaxKind.BarBarToken, ts.createAssignment(declarations_file, ts.createObjectLiteral()))));
variable = declarations; variable = declarations;
@ -126,8 +126,8 @@ function create_unique_check(source_file: ts.SourceFile, variable: ts.Expression
const for_variable_name = ts.createLoopVariable(); const for_variable_name = ts.createLoopVariable();
const for_variable_path = ts.createLoopVariable(); const for_variable_path = ts.createLoopVariable();
const for_declaration = ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createObjectBindingPattern([ const for_declaration = ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createObjectBindingPattern([
ts.createBindingElement(undefined, "name", for_variable_name, undefined), ts.createBindingElement(undefined, config.optimized ? "n": "name", for_variable_name, undefined),
ts.createBindingElement(undefined, "path", for_variable_path, undefined)]) ts.createBindingElement(undefined, config.optimized ? "p": "path", for_variable_path, undefined)])
, undefined, undefined)]); , undefined, undefined)]);
let for_block: ts.Statement; let for_block: ts.Statement;
@ -151,8 +151,8 @@ function create_unique_check(source_file: ts.SourceFile, variable: ts.Expression
let block = ts.createForOf(undefined, let block = ts.createForOf(undefined,
for_declaration, ts.createArrayLiteral( for_declaration, ts.createArrayLiteral(
[...variables.map(e => ts.createObjectLiteral([ [...variables.map(e => ts.createObjectLiteral([
ts.createPropertyAssignment("name", ts.createLiteral(e.name)), ts.createPropertyAssignment(config.optimized ? "n": "name", ts.createLiteral(e.name)),
ts.createPropertyAssignment("path", ts.createLiteral(node_path(e.node))) ts.createPropertyAssignment(config.optimized ? "p": "path", ts.createLiteral(node_path(e.node)))
])) ]))
]) ])
, for_block); , for_block);
@ -166,52 +166,69 @@ export function transform(config: Configuration, context: ts.TransformationConte
const cache: VolatileTransformConfig = {} as any; const cache: VolatileTransformConfig = {} as any;
cache.translations = []; cache.translations = [];
config.variables = (config.variables || {}) as any;
config.variables.base = config.variables.base || (config.optimized ? "__tr" : "_translations");
config.variables.declare_files = config.variables.declare_files || (config.optimized ? "f" : "declare_files");
config.variables.declarations = config.variables.declarations || (config.optimized ? "d" : "definitions");
//Initialize nodes //Initialize nodes
const extra_nodes: ts.Node[] = []; const extra_nodes: ts.Node[] = [];
{ {
cache.nodes = {} as any; cache.nodes = {} as any;
if(config.use_window) { if(config.use_window) {
const window = ts.createIdentifier("window"); const window = ts.createIdentifier("window");
let translation_map = ts.createPropertyAccess(window, ts.createIdentifier("_translations")); let translation_map = ts.createPropertyAccess(window, ts.createIdentifier(config.variables.base));
const new_translations = ts.createAssignment(translation_map, ts.createObjectLiteral()); const new_translations = ts.createAssignment(translation_map, ts.createObjectLiteral());
let translation_map_init: ts.Expression = ts.createBinary(translation_map, ts.SyntaxKind.BarBarToken, new_translations); let translation_map_init: ts.Expression = ts.createBinary(translation_map, ts.SyntaxKind.BarBarToken, new_translations);
translation_map_init = ts.createParen(translation_map_init); translation_map_init = ts.createParen(translation_map_init);
extra_nodes.push(translation_map_init);
cache.nodes = { cache.nodes = {
translation_map: translation_map, translation_map: translation_map
translation_map_init: translation_map_init };
} else if(config.module) {
cache.nodes = {
translation_map: ts.createIdentifier(config.variables.base)
}; };
} else {
const variable_name = "_translations";
const variable_map = ts.createIdentifier(variable_name);
extra_nodes.push(ts.createVariableDeclarationList([
ts.createVariableDeclaration(config.variables.base, undefined, ts.createObjectLiteral())
], ts.NodeFlags.Const), ts.createToken(SyntaxKind.SemicolonToken));
} else {
const variable_map = ts.createIdentifier(config.variables.base);
const inline_if = ts.createBinary(ts.createBinary(ts.createTypeOf(variable_map), SyntaxKind.ExclamationEqualsEqualsToken, ts.createLiteral("undefined")), ts.SyntaxKind.BarBarToken, ts.createAssignment(variable_map, ts.createObjectLiteral())); const inline_if = ts.createBinary(ts.createBinary(ts.createTypeOf(variable_map), SyntaxKind.ExclamationEqualsEqualsToken, ts.createLiteral("undefined")), ts.SyntaxKind.BarBarToken, ts.createAssignment(variable_map, ts.createObjectLiteral()));
cache.nodes = { cache.nodes = {
translation_map: variable_map, translation_map: variable_map,
translation_map_init: variable_map
}; };
//ts.createVariableDeclarationList([ts.createVariableDeclaration(variable_name)], ts.NodeFlags.Let)
extra_nodes.push(inline_if); extra_nodes.push(inline_if);
} }
} }
const used_names = [config.variables.declarations, config.variables.declare_files];
const generated_names: { name: string, node: ts.Node }[] = []; const generated_names: { name: string, node: ts.Node }[] = [];
let generator_base = 0;
cache.name_generator = (config, node, message) => { cache.name_generator = (config, node, message) => {
const characters = "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const characters = "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let name = ""; let name;
while(name.length < 8) { do {
const char = characters[Math.floor(Math.random() * characters.length)]; name = "";
name = name + char;
if(name[0] >= '0' && name[0] <= '9') if(config.module) {
name = name.substr(1) || ""; name = "_" + generator_base++;
} } else {
/* Global namespace. We've to generate a random name so no duplicates happen */
while(name.length < 8) {
const char = characters[Math.floor(Math.random() * characters.length)];
name = name + char;
if(name[0] >= '0' && name[0] <= '9')
name = name.substr(1) || "";
}
}
} while(used_names.findIndex(e => e === name) !== -1);
//FIXME
//if(generated_names.indexOf(name) != -1)
// return cache.name_generator(config, node, message);
generated_names.push({name: name, node: node}); generated_names.push({name: name, node: node});
return name; return name;
}; };
@ -221,7 +238,10 @@ export function transform(config: Configuration, context: ts.TransformationConte
return replace_processor(config, cache, node, source_file); return replace_processor(config, cache, node, source_file);
} }
source_file = ts.visitNode(source_file, visit); source_file = ts.visitNode(source_file, visit);
extra_nodes.push(...create_unique_check(source_file, cache.nodes.translation_map_init, generated_names)); if(!config.module) {
/* we don't need a unique check because we're just in our scope */
extra_nodes.push(...create_unique_check(config, source_file, cache.nodes.translation_map, generated_names));
}
source_file = ts.updateSourceFileNode(source_file, [...(extra_nodes as any[]), ...source_file.statements], source_file.isDeclarationFile, source_file.referencedFiles, source_file.typeReferenceDirectives, source_file.hasNoDefaultLib, source_file.referencedFiles); source_file = ts.updateSourceFileNode(source_file, [...(extra_nodes as any[]), ...source_file.statements], source_file.isDeclarationFile, source_file.referencedFiles, source_file.typeReferenceDirectives, source_file.hasNoDefaultLib, source_file.referencedFiles);
@ -262,7 +282,7 @@ export function replace_processor(config: Configuration, cache: VolatileTransfor
console.log("Message: %o", object.text || object.getText(source_file)); console.log("Message: %o", object.text || object.getText(source_file));
const variable_name = ts.createIdentifier(cache.name_generator(config, node, object.text || object.getText(source_file))); const variable_name = ts.createIdentifier(cache.name_generator(config, node, object.text || object.getText(source_file)));
const variable_init = ts.createPropertyAccess(cache.nodes.translation_map_init, variable_name); const variable_init = ts.createPropertyAccess(cache.nodes.translation_map, variable_name);
const variable = ts.createPropertyAccess(cache.nodes.translation_map, variable_name); const variable = ts.createPropertyAccess(cache.nodes.translation_map, variable_name);
const new_variable = ts.createAssignment(variable, call); const new_variable = ts.createAssignment(variable, call);
@ -284,6 +304,15 @@ export interface Configuration {
use_window?: boolean; use_window?: boolean;
replace_cache?: boolean; replace_cache?: boolean;
verbose?: boolean; verbose?: boolean;
optimized?: boolean;
module?: boolean;
variables?: {
base: string,
declarations: string,
declare_files: string
}
} }
export interface TransformResult { export interface TransformResult {
@ -294,7 +323,6 @@ export interface TransformResult {
interface VolatileTransformConfig { interface VolatileTransformConfig {
nodes: { nodes: {
translation_map: ts.Expression; translation_map: ts.Expression;
translation_map_init: ts.Expression;
}; };
name_generator: (config: Configuration, node: ts.Node, message: string) => string; name_generator: (config: Configuration, node: ts.Node, message: string) => string;

View File

@ -9,6 +9,7 @@ import {TranslationEntry} from "./generator";
export interface Config { export interface Config {
target_file?: string; target_file?: string;
verbose?: boolean; verbose?: boolean;
optimized?: boolean;
} }
let process_config: Config; let process_config: Config;
@ -37,6 +38,7 @@ export default function(program: ts.Program, config?: Config) : (context: ts.Tra
return ctx => transformer(ctx) as any; return ctx => transformer(ctx) as any;
} }
let processed = [];
const translations: TranslationEntry[] = []; const translations: TranslationEntry[] = [];
const transformer = (context: ts.TransformationContext) => const transformer = (context: ts.TransformationContext) =>
(rootNode: ts.Node) => { (rootNode: ts.Node) => {
@ -51,10 +53,17 @@ const transformer = (context: ts.TransformationContext) =>
} else if(rootNode.kind == ts.SyntaxKind.SourceFile) { } else if(rootNode.kind == ts.SyntaxKind.SourceFile) {
const file = rootNode as ts.SourceFile; const file = rootNode as ts.SourceFile;
if(processed.findIndex(e => e === file.fileName) !== -1) {
console.log("Skipping %s (already processed)", file.fileName);
return rootNode;
}
processed.push(file.fileName);
console.log("Processing " + file.fileName); console.log("Processing " + file.fileName);
const result = ts_generator.transform({ const result = ts_generator.transform({
use_window: false, use_window: false,
replace_cache: true replace_cache: true,
module: true,
optimized: process_config.optimized
}, context, file); }, context, file);
translations.push(...result.translations); translations.push(...result.translations);
return result.node; return result.node;

View File

@ -1,28 +1,21 @@
import * as path from "path"; import * as path from "path";
const config = require("./webpack.config"); import * as config_base from "./webpack.config";
let isDevelopment = process.env.NODE_ENV === 'development'; const config = config_base.config();
isDevelopment = true; Object.assign(config.entry, {
config.entry = {
"client-app": "./client/js/index.ts" "client-app": "./client/js/index.ts"
}; });
config.resolve.alias = { Object.assign(config.resolve.alias, {
"tc-shared": path.resolve(__dirname, "shared/js"), "tc-shared": path.resolve(__dirname, "shared/js"),
/* backend hasn't declared but its available via "require()" */ /* backend hasn't declared but its available via "require()" */
"tc-backend": path.resolve(__dirname, "shared/backend.d"), "tc-backend": path.resolve(__dirname, "shared/backend.d"),
}; });
config.externals = [ config.externals.push((context, request: string, callback) => {
{ if (request.startsWith("tc-backend/"))
"tc-loader": "window loader" return callback(null, `window["backend-loader"].require("${request}")`);
}, callback();
(context, request: string, callback) => { });
if (request.startsWith("tc-backend/"))
return callback(null, `window["backend-loader"].require("${request}")`);
callback();
}
];
export = config; export = config;

View File

@ -1,115 +1,16 @@
import * as ts from "typescript"; import * as path from "path";
import trtransformer, {Config} from "./tools/trgen/ts_transformer"; import * as config_base from "./webpack.config";
const path = require('path'); const config = config_base.config();
const webpack = require("webpack"); Object.assign(config.entry, {
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); "shared-app": "./web/js/index.ts"
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; });
const ManifestGenerator = require("./webpack/ManifestPlugin");
const WorkerPlugin = require('worker-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
let isDevelopment = process.env.NODE_ENV === 'development'; Object.assign(config.resolve.alias, {
isDevelopment = true; "tc-shared": path.resolve(__dirname, "shared/js"),
module.exports = { "tc-backend/web": path.resolve(__dirname, "web/js"),
entry: { "tc-backend": path.resolve(__dirname, "web/js"),
"shared-app": "./web/js/index.ts" "tc-generated/codec/opus": path.resolve(__dirname, "asm/generated/TeaWeb-Worker-Codec-Opus.js"),
}, });
devtool: isDevelopment ? "inline-source-map" : undefined,
mode: isDevelopment ? "development" : "production",
plugins: [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin({
filename: isDevelopment ? '[name].css' : '[name].[hash].css',
chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css'
}),
new ManifestGenerator({
file: path.join(__dirname, "dist/manifest.json")
}),
new WorkerPlugin(),
//new BundleAnalyzerPlugin()
/*
new CircularDependencyPlugin({
//exclude: /a\.js|node_modules/,
failOnError: true,
allowAsyncCycles: false,
cwd: process.cwd(),
})
*/
new webpack.optimize.AggressiveSplittingPlugin({
minSize: 1024 * 8,
maxSize: 1024 * 128
})
],
module: {
rules: [
{
test: /\.s[ac]ss$/,
loader: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
modules: true,
sourceMap: isDevelopment
}
},
{
loader: 'sass-loader',
options: {
sourceMap: isDevelopment
}
}
]
},
{
test: /\.tsx?$/,
exclude: /node_modules/,
loader: [ export = config;
{
loader: 'ts-loader',
options: {
transpileOnly: true,
getCustomTransformers: (prog: ts.Program) => {
return {
before: [trtransformer(prog, {})]
};
}
}
}
]
},
{
test: /\.was?t$/,
loader: [
"./webpack/WatLoader.js"
]
}
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js', ".scss"],
alias: {
"tc-shared": path.resolve(__dirname, "shared/js"),
"tc-backend/web": path.resolve(__dirname, "web/js"),
"tc-backend": path.resolve(__dirname, "web/js"),
"tc-generated/codec/opus": path.resolve(__dirname, "asm/generated/TeaWeb-Worker-Codec-Opus.js"),
//"tc-backend": path.resolve(__dirname, "shared/backend.d"),
},
},
externals: {
"tc-loader": "window loader"
},
output: {
filename: '[contenthash].js',
path: path.resolve(__dirname, 'dist'),
publicPath: "js/"
},
optimization: {
splitChunks: { },
minimize: !isDevelopment,
minimizer: [new TerserPlugin()]
}
};

View File

@ -1,5 +1,6 @@
import * as ts from "typescript"; import * as ts from "typescript";
import trtransformer, {Config} from "./tools/trgen/ts_transformer"; import * as fs from "fs";
import trtransformer from "./tools/trgen/ts_transformer";
const path = require('path'); const path = require('path');
const webpack = require("webpack"); const webpack = require("webpack");
@ -10,10 +11,32 @@ const WorkerPlugin = require('worker-plugin');
const TerserPlugin = require('terser-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin');
let isDevelopment = process.env.NODE_ENV === 'development'; export let isDevelopment = process.env.NODE_ENV === 'development';
isDevelopment = true; isDevelopment = true;
export = {
entry: {}, /* will be individually set */ const generate_definitions = () => {
const git_rev = fs.readFileSync(path.join(__dirname, ".git", "HEAD")).toString();
let version;
if(git_rev.indexOf("/") === -1)
version = git_rev;
else
version = fs.readFileSync(path.join(__dirname, ".git", git_rev.substr(5).trim())).toString().substr(0, 7);
return {
"__build": {
target: JSON.stringify("web"),
mode: JSON.stringify(isDevelopment ? "debug" : "release"),
version: JSON.stringify(version),
timestamp: Date.now(),
entry_chunk_name: JSON.stringify("shared-app")
} as BuildDefinitions
} as any;
};
export const config = () => { return {
entry: {
"loader": "./loader/app/index.ts"
},
devtool: isDevelopment ? "inline-source-map" : undefined, devtool: isDevelopment ? "inline-source-map" : undefined,
mode: isDevelopment ? "development" : "production", mode: isDevelopment ? "development" : "production",
@ -24,7 +47,8 @@ export = {
chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css' chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css'
}), }),
new ManifestGenerator({ new ManifestGenerator({
file: path.join(__dirname, "dist/manifest.json") file: path.join(__dirname, "dist/manifest.json"),
base: __dirname
}), }),
new WorkerPlugin(), new WorkerPlugin(),
//new BundleAnalyzerPlugin() //new BundleAnalyzerPlugin()
@ -39,7 +63,8 @@ export = {
isDevelopment ? undefined : new webpack.optimize.AggressiveSplittingPlugin({ isDevelopment ? undefined : new webpack.optimize.AggressiveSplittingPlugin({
minSize: 1024 * 8, minSize: 1024 * 8,
maxSize: 1024 * 128 maxSize: 1024 * 128
}) }),
new webpack.DefinePlugin(generate_definitions())
].filter(e => !!e), ].filter(e => !!e),
module: { module: {
rules: [ rules: [
@ -73,7 +98,9 @@ export = {
transpileOnly: true, transpileOnly: true,
getCustomTransformers: (prog: ts.Program) => { getCustomTransformers: (prog: ts.Program) => {
return { return {
before: [trtransformer(prog, {})] before: [trtransformer(prog, {
optimized: true
})]
}; };
} }
} }
@ -90,19 +117,21 @@ export = {
}, },
resolve: { resolve: {
extensions: ['.tsx', '.ts', '.js', ".scss"], extensions: ['.tsx', '.ts', '.js', ".scss"],
alias: { }, /* will be individually set */ alias: { },
},
externals: {
"tc-loader": "window loader"
}, },
externals: [
{"tc-loader": "window loader"}
] as any[],
output: { output: {
filename: isDevelopment ? '[name].js' : '[contenthash].js', filename: isDevelopment ? '[name].js' : '[contenthash].js',
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
publicPath: "js/" publicPath: "js/"
}, },
optimization: { optimization: {
splitChunks: { }, splitChunks: {
},
minimize: !isDevelopment, minimize: !isDevelopment,
minimizer: [new TerserPlugin()] minimizer: [new TerserPlugin()]
} }
}; }};

View File

@ -1,8 +1,10 @@
import * as webpack from "webpack"; import * as webpack from "webpack";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path";
interface Options { interface Options {
file?: string; file?: string;
base: string;
} }
class ManifestGenerator { class ManifestGenerator {
@ -10,32 +12,51 @@ class ManifestGenerator {
readonly options: Options; readonly options: Options;
constructor(options: Options) { constructor(options: Options) {
this.options = options || {}; this.options = options || { base: __dirname };
} }
apply(compiler: webpack.Compiler) { apply(compiler: webpack.Compiler) {
compiler.hooks.afterCompile.tap(this.constructor.name, compilation => { compiler.hooks.afterCompile.tap(this.constructor.name, compilation => {
const chunks_data = {}; const chunks_data = {};
for(const chunk_group of compilation.chunkGroups) { for(const chunk_group of compilation.chunkGroups) {
console.log(chunk_group.options.name);
const js_files = []; const js_files = [];
const modules = [];
for(const chunk of chunk_group.chunks) { for(const chunk of chunk_group.chunks) {
if(chunk.files.length !== 1) throw "expected only one file per chunk"; if(chunk.files.length !== 1) throw "expected only one file per chunk";
const file = chunk.files[0];
console.log("Chunk: %s - %s - %s", chunk.id, chunk.hash, file);
//console.log(chunk);
//console.log(" - %s - %o", chunk.id, chunk);
js_files.push({ js_files.push({
hash: chunk.hash, hash: chunk.hash,
file: file file: chunk.files[0]
}) });
for(const module of chunk._modules) {
if(!module.type.startsWith("javascript/"))
continue;
if(!module.resource || !module.context)
continue;
if(module.context !== path.dirname(module.resource))
throw "invalid context/resource relation";
modules.push({
id: module.id,
context: path.relative(this.options.base, module.context).replace(/\\/g, "/"),
resource: path.basename(module.resource)
});
}
} }
chunks_data[chunk_group.options.name] = js_files;
chunks_data[chunk_group.options.name] = {
files: js_files,
modules: modules
};
} }
this.manifest_content = { this.manifest_content = {
version: 1, version: 2,
chunks: chunks_data chunks: chunks_data
}; };
}); });

16
webpack/build-definitions.d.ts vendored Normal file
View File

@ -0,0 +1,16 @@
declare global {
interface BuildDefinitions {
target: "web" | "client";
mode: "release" | "debug";
/* chunk for the loader to load initially */
entry_chunk_name: string;
version: string;
timestamp: number;
}
const __build: BuildDefinitions;
}
export {};