diff --git a/ChangeLog.md b/ChangeLog.md index 872ebb4c..3ba79c0f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,7 +4,9 @@ - Saving last away state and message - Saving last query show state - Removing the hostbutton when we're disconnected from the server - + - Improved main pages loader speed as well inlining the initial js/css sources + This ensures that the error & loading animation loads properly regardless of any errors + * **04.03.20** - Implemented the new music bot playlist song list - Implemented the missing server log message builders diff --git a/file.ts b/file.ts index 0a59cbfa..f5003b4a 100644 --- a/file.ts +++ b/file.ts @@ -40,12 +40,28 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [ }, { /* javascript files as manifest.json */ "type": "js", - "search-pattern": /.*$/, + "search-pattern": /.*\.(js|json)$/, "build-target": "dev|rel", "path": "js/", "local-path": "./dist/" }, + { /* javascript files as manifest.json */ + "type": "html", + "search-pattern": /.*\.html$/, + "build-target": "dev|rel", + + "path": "./", + "local-path": "./dist/" + }, + { /* Loader css file (only required in dev mode. In release it gets inlined) */ + "type": "css", + "search-pattern": /.*\.css$/, + "build-target": "dev", + + "path": "css/", + "local-path": "./loader/css/" + }, { /* shared developer single css files */ "type": "css", "search-pattern": /.*\.css$/, diff --git a/loader/app/index.ts b/loader/app/index.ts index c020e98c..cfcb8bb5 100644 --- a/loader/app/index.ts +++ b/loader/app/index.ts @@ -1,6 +1,5 @@ import * as loader from "./targets/app"; import * as loader_base from "./loader/loader"; - window["loader"] = loader_base; /* let the loader register himself at the window first */ setTimeout(loader.run, 0); diff --git a/loader/app/loader/loader.ts b/loader/app/loader/loader.ts index 5f1a5991..977824cb 100644 --- a/loader/app/loader/loader.ts +++ b/loader/app/loader/loader.ts @@ -309,85 +309,78 @@ export const templates = template_loader; /* Hello World message */ { - const hello_world = () => { - const clog = console.log; - const print_security = () => { - { - const css = [ - "display: block", - "text-align: center", - "font-size: 42px", - "font-weight: bold", - "-webkit-text-stroke: 2px black", - "color: red" - ].join(";"); - clog("%c ", "font-size: 100px;"); - clog("%cSecurity warning:", css); - } - { - const css = [ - "display: block", - "text-align: center", - "font-size: 18px", - "font-weight: bold" - ].join(";"); - - clog("%cPasting anything in here could give attackers access to your data.", css); - clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css); - clog("%c ", "font-size: 100px;"); - } - }; - - /* print the hello world */ + const clog = console.log; + const print_security = () => { { const css = [ "display: block", "text-align: center", - "font-size: 72px", + "font-size: 42px", "font-weight: bold", "-webkit-text-stroke: 2px black", - "color: #18BC9C" + "color: red" ].join(";"); - clog("%cHey, hold on!", css); + clog("%c ", "font-size: 100px;"); + clog("%cSecurity warning:", css); } { const css = [ "display: block", "text-align: center", - "font-size: 26px", + "font-size: 18px", "font-weight: bold" ].join(";"); - const css_2 = [ - "display: block", - "text-align: center", - "font-size: 26px", - "font-weight: bold", - "color: blue" - ].join(";"); - - const display_detect = /./; - display_detect.toString = function() { print_security(); return ""; }; - - clog("%cLovely to see you using and debugging the TeaSpeak-Web client.", css); - clog("%cIf you have some good ideas or already done some incredible changes,", css); - clog("%cyou'll be may interested to share them here: %chttps://github.com/TeaSpeak/TeaWeb", css, css_2); - clog("%c ", display_detect); + clog("%cPasting anything in here could give attackers access to your data.", css); + clog("%cUnless you understand exactly what you are doing, close this window and stay safe.", css); + clog("%c ", "font-size: 100px;"); } }; - try { /* lets try to print it as VM code :)*/ - let hello_world_code = hello_world.toString(); - hello_world_code = hello_world_code.substr(hello_world_code.indexOf('() => {') + 8); - hello_world_code = hello_world_code.substring(0, hello_world_code.lastIndexOf("}")); - - //Look aheads are not possible with firefox - //hello_world_code = hello_world_code.replace(/(? { + const node = document.getElementById("load-error-image"); + const image = document.createElement("img"); + image.src = node.getAttribute("x-src"); + image.style.height = "12em"; + node.replaceWith(image); +}; +setTimeout(init_error_image, 100); \ No newline at end of file diff --git a/shared/css/loader/.gitignore b/loader/css/.gitignore similarity index 100% rename from shared/css/loader/.gitignore rename to loader/css/.gitignore diff --git a/shared/css/loader/loader.scss b/loader/css/loader.scss similarity index 100% rename from shared/css/loader/loader.scss rename to loader/css/loader.scss diff --git a/package-lock.json b/package-lock.json index cfef843b..6118d580 100644 --- a/package-lock.json +++ b/package-lock.json @@ -152,6 +152,15 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, + "@types/clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@types/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-A1HQhQ0hkvqqByJMgg+Wiv9p9XdoYEzuwm11SVo1mX2/4PSdhjcrUlilJQoqLscIheC51t1D5g+EFWCXZ2VTQQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/dompurify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-2.0.1.tgz", @@ -161,6 +170,12 @@ "@types/trusted-types": "*" } }, + "@types/ejs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.0.2.tgz", + "integrity": "sha512-+nriFZYDz+C+6SWzJp0jHrGvyzL3Sg63u1vRlH1AfViyaT4oTod+MtfZ0YMNYXzO7ybFkdx4ZaBwBtLwdYkReQ==", + "dev": true + }, "@types/emscripten": { "version": "1.39.2", "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.2.tgz", @@ -201,6 +216,17 @@ "@types/node": "*" } }, + "@types/html-minifier": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@types/html-minifier/-/html-minifier-3.5.3.tgz", + "integrity": "sha512-j1P/4PcWVVCPEy5lofcHnQ6BtXz9tHGiFPWzqm7TtGuWZEfCHEP446HlkSNc9fQgNJaJZ6ewPtp2aaFla/Uerg==", + "dev": true, + "requires": { + "@types/clean-css": "*", + "@types/relateurl": "*", + "@types/uglify-js": "*" + } + }, "@types/html-minifier-terser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.0.0.tgz", @@ -274,6 +300,12 @@ "@types/react": "*" } }, + "@types/relateurl": { + "version": "0.2.28", + "resolved": "https://registry.npmjs.org/@types/relateurl/-/relateurl-0.2.28.tgz", + "integrity": "sha1-a9p9uGU/piZD9e5p6facEaOS46Y=", + "dev": true + }, "@types/sha256": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@types/sha256/-/sha256-0.2.0.tgz", @@ -2385,9 +2417,9 @@ "dev": true }, "ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.0.2.tgz", + "integrity": "sha512-IncmUpn1yN84hy2shb0POJ80FWrfGNY0cxO9f4v+/sG7qcBvAtVWUA1IdzY/8EYUmOVhoKJVdJjNd3AZcnxOjA==", "dev": true }, "elliptic": { @@ -4940,6 +4972,57 @@ } } }, + "html-minifier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-4.0.0.tgz", + "integrity": "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==", + "dev": true, + "requires": { + "camel-case": "^3.0.0", + "clean-css": "^4.2.1", + "commander": "^2.19.0", + "he": "^1.2.0", + "param-case": "^2.1.1", + "relateurl": "^0.2.7", + "uglify-js": "^3.5.1" + }, + "dependencies": { + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "requires": { + "lower-case": "^1.1.1" + } + }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + } + } + }, "html-minifier-terser": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.0.5.tgz", @@ -9676,6 +9759,16 @@ "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", "dev": true }, + "uglify-js": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.1.tgz", + "integrity": "sha512-W7KxyzeaQmZvUFbGj4+YFshhVrMBGSg2IbcYAjGWGvx8DHvJMclbTDMpffdxFUGPBHjIytk7KJUR/KUXstUGDw==", + "dev": true, + "requires": { + "commander": "~2.20.3", + "source-map": "~0.6.1" + } + }, "unc-path-regex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", @@ -9815,6 +9908,12 @@ "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", "dev": true }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -10882,6 +10981,12 @@ "supports-color": "^5.3.0" } }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", diff --git a/package.json b/package.json index 2192db96..a36bc3c1 100644 --- a/package.json +++ b/package.json @@ -5,21 +5,17 @@ "main": "main.js", "directories": {}, "scripts": { - "compile-sass": "sass --update shared/css/:shared/css/ web/css/:web/css/ client/css/:client/css/ vendor/:vendor/", + "compile-sass": "sass --update shared/css/:shared/css/ loader/css/:loader/css/ web/css/:web/css/ client/css/:client/css/ vendor/:vendor/", "compile-project-base": "tsc -p tsbaseconfig.json", - "dtsgen": "node tools/dtsgen/index.js", "trgen": "node tools/trgen/index.js", "sass": "sass", "tsc": "tsc", - "start": "npm run compile-project-base && node file.js ndevelop", - "build-web": "webpack --config webpack-web.config.js", "watch-web": "webpack --watch --config webpack-web.config.js", "build-client": "webpack --config webpack-client.config.js", "watch-client": "webpack --watch --config webpack-client.config.js", - "generate-i18n-gtranslate": "node shared/generate_i18n_gtranslate.js" }, "author": "TeaSpeak (WolverinDEV)", @@ -27,8 +23,10 @@ "devDependencies": { "@google-cloud/translate": "^5.3.0", "@types/dompurify": "^2.0.1", + "@types/ejs": "^3.0.2", "@types/emscripten": "^1.38.0", "@types/fs-extra": "^8.0.1", + "@types/html-minifier": "^3.5.3", "@types/jquery": "^3.3.34", "@types/lodash": "^4.14.149", "@types/moment": "^2.13.0", @@ -42,10 +40,12 @@ "clean-webpack-plugin": "^3.0.0", "css-loader": "^3.4.2", "csso-cli": "^2.0.2", + "ejs": "^3.0.2", "exports-loader": "^0.7.0", "fs-extra": "latest", "gulp": "^4.0.2", "html-loader": "^1.0.0", + "html-minifier": "^4.0.0", "html-webpack-plugin": "^4.0.3", "mime-types": "^2.1.24", "mini-css-extract-plugin": "^0.9.0", diff --git a/shared/css/static/frame-chat.scss b/shared/css/static/frame-chat.scss index 30d887fc..4079787c 100644 --- a/shared/css/static/frame-chat.scss +++ b/shared/css/static/frame-chat.scss @@ -1,5 +1,5 @@ -@import "./mixin"; -@import "./properties"; +@import "mixin"; +@import "properties"; //$color_client_normal: #bebebe; $color_client_normal: #cccccc; diff --git a/shared/css/static/hostbanner.scss b/shared/css/static/hostbanner.scss index 2975eb60..f5c17b0f 100644 --- a/shared/css/static/hostbanner.scss +++ b/shared/css/static/hostbanner.scss @@ -1,4 +1,4 @@ -@import "./mixin.scss"; +@import "mixin"; .hostbanner { .container-hostbanner { diff --git a/shared/html/index.php b/shared/html/index.html.ejs similarity index 77% rename from shared/html/index.php rename to shared/html/index.html.ejs index f0587239..5d0c96a8 100644 --- a/shared/html/index.php +++ b/shared/html/index.html.ejs @@ -1,13 +1,10 @@ - - $WEB_CLIENT = (!isset($CLIENT) || !$CLIENT) && http_response_code() !== false || (defined("TEA_SERVE") && TEA_SERVE); - $localhost = false; - if(gethostname() == "WolverinDEV") - $localhost = true; -?> @@ -25,39 +22,22 @@ -TeaClient" . PHP_EOL; - echo "\t\t" . PHP_EOL; - } else { - echo "\t\tTeaSpeak-Web" . PHP_EOL; - echo "\t\t" . PHP_EOL; - echo "\t\t" . PHP_EOL; - // - } -?> + <% if(build_target === "client") { %> + TeaClient + + <% } else { %> + TeaSpeak-Web + + + + <% } %> - @@ -124,6 +104,8 @@ display: block; } + + <%- initial_css %> @@ -139,18 +121,8 @@ -
- -
- - -
- - -
+
+
@@ -172,13 +144,15 @@
- + +
+

- +<%#
@@ -241,6 +215,7 @@ }; setTimeout(() => init($), 1000); - +%> + <%- initial_script %> \ No newline at end of file diff --git a/shared/js/ui/modal/ModalAbout.ts b/shared/js/ui/modal/ModalAbout.ts index 828a36b5..bc72a981 100644 --- a/shared/js/ui/modal/ModalAbout.ts +++ b/shared/js/ui/modal/ModalAbout.ts @@ -1,5 +1,4 @@ import {createModal} from "tc-shared/ui/elements/Modal"; -import * as loader from "tc-loader"; import {LogCategory} from "tc-shared/log"; import * as log from "tc-shared/log"; @@ -10,29 +9,16 @@ function format_date(date: number) { } export function spawnAbout() { - const app_version = (() => { - 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 == "unknown" || version.replace(/0+/, "").length == 0) - return undefined; - - return version; - })(); - const connectModal = createModal({ header: tr("About"), body: () => { let tag = $("#tmpl_about").renderTag({ client: __build.target !== "web", - version_client: __build.target === "web" ? app_version || "in-dev" : "loading...", - version_ui: app_version || "in-dev", + version_client: __build.target === "web" ? __build.version || "in-dev" : "loading...", + version_ui: __build.version || "in-dev", - version_timestamp: !!app_version ? format_date(Date.now()) : "--" + version_timestamp: format_date(__build.timestamp) }); return tag; }, diff --git a/tsbaseconfig.json b/tsbaseconfig.json index 2b8684bd..167763c2 100644 --- a/tsbaseconfig.json +++ b/tsbaseconfig.json @@ -14,6 +14,7 @@ "webpack/build-definitions.d.ts", "webpack/ManifestPlugin.ts", + "webpack/EJSGenerator.ts", "webpack/WatLoader.ts", "file.ts" diff --git a/webpack.config.ts b/webpack.config.ts index 5fdd49c1..cf1ad3ff 100644 --- a/webpack.config.ts +++ b/webpack.config.ts @@ -3,6 +3,7 @@ import * as fs from "fs"; import trtransformer from "./tools/trgen/ts_transformer"; import {exec} from "child_process"; import * as util from "util"; +import EJSGenerator = require("./webpack/EJSGenerator"); const path = require('path'); const webpack = require("webpack"); @@ -74,7 +75,24 @@ export const config = async (target: "web" | "client") => { return { minSize: 1024 * 8, maxSize: 1024 * 128 }), - new webpack.DefinePlugin(await generate_definitions(target)) + new webpack.DefinePlugin(await generate_definitions(target)), + + new EJSGenerator({ + variables: { + build_target: target + }, + input: path.join(__dirname, "shared/html/index.html.ejs"), + output: path.join(__dirname, "dist/index.html"), + initialJSEntryChunk: "loader", + minify: !isDevelopment, + embedInitialJSEntryChunk: !isDevelopment, + + embedInitialCSSFile: !isDevelopment, + initialCSSFile: { + localFile: path.join(__dirname, "loader/css/loader.css"), + publicFile: "css/loader.css" + } + }) ].filter(e => !!e), module: { rules: [ @@ -142,13 +160,8 @@ export const config = async (target: "web" | "client") => { return { {"tc-loader": "window loader"} ] as any[], output: { - filename: (chunkData) => { - if(chunkData.chunk.name === "loader") - return "loader.js"; - - return '[name].js'; - }, - chunkFilename: "[name].js", + filename: isDevelopment ? "[name].[contenthash].js" : "[contenthash].js", + chunkFilename: isDevelopment ? "[name].[contenthash].js" : "[contenthash].js", path: path.resolve(__dirname, 'dist'), publicPath: "js/" }, diff --git a/webpack/EJSGenerator.ts b/webpack/EJSGenerator.ts new file mode 100644 index 00000000..7695b8c0 --- /dev/null +++ b/webpack/EJSGenerator.ts @@ -0,0 +1,94 @@ +import * as webpack from "webpack"; +import * as ejs from "ejs"; +import * as fs from "fs"; +import * as util from "util"; +import * as minifier from "html-minifier"; +import * as path from "path"; +import {Compilation} from "webpack"; + +interface Options { + input: string, + output: string, + + minify?: boolean, + + initialJSEntryChunk: string, + embedInitialJSEntryChunk?: boolean, + + initialCSSFile: { + localFile: string, + publicFile: string + }, + embedInitialCSSFile?: boolean, + + variables?: {[name: string]: any}; +} + +class EJSGenerator { + readonly options: Options; + + constructor(options: Options) { + this.options = options; + } + + private async generate_entry_js_tag(compilation: Compilation) { + const entry_group = compilation.chunkGroups.find(e => e.options.name === this.options.initialJSEntryChunk); + if(!entry_group) return; /* not the correct compilation */ + if(entry_group.chunks.length !== 1) throw "Unsupported entry chunk size. We only support one at the moment."; + if(entry_group.chunks[0].files.length !== 1) + throw "Entry chunk has too many files. We only support to inline one!"; + const file = entry_group.chunks[0].files[0]; + if(path.extname(file) !== ".js") + throw "Entry chunk file has unknown extension"; + + if(!this.options.embedInitialJSEntryChunk) { + return ''; + } else { + const script = await util.promisify(fs.readFile)(path.join(compilation.compiler.outputPath, file)); + return ``; + } + } + + private async generate_entry_css_tag() { + if(this.options.embedInitialCSSFile) { + const style = await util.promisify(fs.readFile)(this.options.initialCSSFile.localFile); + return `` + } else { + // + return `` + } + } + + apply(compiler: webpack.Compiler) { + compiler.hooks.afterEmit.tapPromise(this.constructor.name, async compilation => { + const input = await util.promisify(fs.readFile)(this.options.input); + const variables = Object.assign({}, this.options.variables); + + variables["initial_script"] = await this.generate_entry_js_tag(compilation); + variables["initial_css"] = await this.generate_entry_css_tag(); + + let generated = await ejs.render(input.toString(), variables, { + beautify: false /* uglify is a bit dump and does not understands ES6 */ + }); + + if(this.options.minify) { + generated = minifier.minify(generated, { + html5: true, + + collapseWhitespace: true, + removeComments: true, + removeRedundantAttributes: true, + removeScriptTypeAttributes: true, + removeTagWhitespace: true, + minifyCSS: true, + minifyJS: true, + minifyURLs: true + }); + } + + await util.promisify(fs.writeFile)(this.options.output, generated); + }); + } +} + +export = EJSGenerator; \ No newline at end of file diff --git a/webpack/ManifestPlugin.ts b/webpack/ManifestPlugin.ts index d74fa12b..d0e3d0a3 100644 --- a/webpack/ManifestPlugin.ts +++ b/webpack/ManifestPlugin.ts @@ -24,8 +24,9 @@ class ManifestGenerator { const modules = []; for(const chunk of chunk_group.chunks) { + if(!chunk.files.length) continue; if(chunk.files.length !== 1) { - console.error("Expected only on file per chunk but got " + chunk.files.length); + console.error("Expected only one file per chunk but got " + chunk.files.length); chunk.files.forEach(e => console.log(" - %s", e)); throw "expected only one file per chunk"; }