Mostly using direct assets instead of stored files somewhere within the project.
parent
66021b125b
commit
61da22895f
52
file.ts
52
file.ts
|
@ -30,49 +30,26 @@ type ProjectResource = {
|
|||
}
|
||||
|
||||
const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
|
||||
{ /* javascript files as manifest.json */
|
||||
"type": "js",
|
||||
"search-pattern": /.*\.(js|json|svg|png|css|html)$/,
|
||||
"build-target": "dev|rel",
|
||||
|
||||
"path": "js/",
|
||||
"local-path": "./dist/"
|
||||
},
|
||||
|
||||
{ /* shared html files */
|
||||
"type": "html",
|
||||
"search-pattern": /^.*([a-zA-Z]+)\.(html|json)$/,
|
||||
"build-target": "dev|rel",
|
||||
|
||||
"path": "./",
|
||||
"local-path": "./shared/html/"
|
||||
},
|
||||
{ /* javascript files as manifest.json */
|
||||
"type": "js",
|
||||
"search-pattern": /.*\.(js|json|svg|png)$/,
|
||||
"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 sound files */
|
||||
"type": "wav",
|
||||
"search-pattern": /.*\.wav$/,
|
||||
"build-target": "dev|rel",
|
||||
|
||||
"path": "audio/",
|
||||
"local-path": "./shared/audio/"
|
||||
},
|
||||
{ /* shared data sound files */
|
||||
"type": "json",
|
||||
"search-pattern": /.*\.json/,
|
||||
"search-pattern": /.*\.(wav|json)$/,
|
||||
"build-target": "dev|rel",
|
||||
|
||||
"path": "audio/",
|
||||
|
@ -87,15 +64,6 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
|
|||
"path": "img/",
|
||||
"local-path": "./shared/img/"
|
||||
},
|
||||
{ /* assembly files */
|
||||
"web-only": true,
|
||||
"type": "wasm",
|
||||
"search-pattern": /.*\.(wasm)/,
|
||||
"build-target": "dev|rel",
|
||||
|
||||
"path": "js/",
|
||||
"local-path": "./dist/"
|
||||
}
|
||||
];
|
||||
|
||||
const APP_FILE_LIST_SHARED_VENDORS: ProjectResource[] = [];
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
import * as path from "path";
|
||||
import EJSGenerator = require("../webpack/EJSGenerator");
|
||||
|
||||
class IndexGenerator extends EJSGenerator {
|
||||
constructor(options: {
|
||||
buildTarget: string;
|
||||
output: string,
|
||||
isDevelopment: boolean
|
||||
}) {
|
||||
super({
|
||||
variables: {
|
||||
build_target: options.buildTarget
|
||||
},
|
||||
output: options.output,
|
||||
initialJSEntryChunk: "loader",
|
||||
input: path.join(__dirname, "html/index.html.ejs"),
|
||||
minify: !options.isDevelopment,
|
||||
|
||||
embedInitialJSEntryChunk: !options.isDevelopment,
|
||||
embedInitialCSSFile: !options.isDevelopment,
|
||||
|
||||
initialCSSFile: {
|
||||
localFile: path.join(__dirname, "css/index.css"),
|
||||
publicFile: "css/index.css"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export = IndexGenerator;
|
|
@ -9,6 +9,3 @@ body {
|
|||
box-sizing: border-box;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@import "loader";
|
||||
@import "overlay";
|
|
@ -0,0 +1,3 @@
|
|||
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./index.scss";
|
||||
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./loader.scss";
|
||||
import "!style-loader!css-loader?url=false!sass-loader?sourceMap=true!./overlay.css";
|
|
@ -80,7 +80,7 @@ $loop-time-halloween: 25s / 24;
|
|||
|
||||
width: 249px;
|
||||
height: 125px;
|
||||
background: url("img/loader/steam.png") 0 0, url("../img/loader/steam.png") 0 0;
|
||||
background: url("../images/steam.png") 0 0;
|
||||
|
||||
animation: sprite-steam 2.5s steps(50) forwards infinite;
|
||||
}
|
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 188 KiB |
|
@ -1,10 +1,14 @@
|
|||
import "core-js/stable";
|
||||
import "./polifill";
|
||||
import "./css";
|
||||
|
||||
import * as loader from "./loader/loader";
|
||||
import {ApplicationLoader} from "./loader/loader";
|
||||
import {getUrlParameter} from "./loader/utils";
|
||||
|
||||
if(window["loader"]) {
|
||||
throw "an loader instance has already been defined";
|
||||
}
|
||||
window["loader"] = loader;
|
||||
/* let the loader register himself at the window first */
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as script_loader from "./script_loader";
|
||||
import * as template_loader from "./template_loader";
|
||||
import * as style_loader from "./style_loader";
|
||||
import * as Animation from "../animation";
|
||||
import {getUrlParameter} from "./utils";
|
||||
|
||||
|
@ -111,8 +111,6 @@ export type ModuleMapping = {
|
|||
const module_mapping_: ModuleMapping[] = [];
|
||||
export function module_mapping() : ModuleMapping[] { return module_mapping_; }
|
||||
|
||||
export function get_cache_version() { return cache_tag; }
|
||||
|
||||
export function finished() {
|
||||
return currentStage == Stage.DONE;
|
||||
}
|
||||
|
@ -360,7 +358,7 @@ export type DependSource = {
|
|||
export type SourcePath = string | DependSource | string[];
|
||||
|
||||
export const scripts = script_loader;
|
||||
export const templates = template_loader;
|
||||
export const style = style_loader;
|
||||
|
||||
/* Hello World message */
|
||||
{
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
import {config, critical_error, SourcePath} from "./loader";
|
||||
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||
|
||||
let _style_promises: {[key: string]: Promise<void>} = {};
|
||||
|
||||
function load_style_url(url: string) : Promise<void> {
|
||||
if(typeof _style_promises[url] === "object")
|
||||
return _style_promises[url];
|
||||
|
||||
return (_style_promises[url] = new Promise((resolve, reject) => {
|
||||
const tag: HTMLLinkElement = document.createElement("link");
|
||||
|
||||
let error = false;
|
||||
const error_handler = (event: ErrorEvent) => {
|
||||
if(config.verbose) console.log("msg: %o, url: %o, line: %o, col: %o, error: %o", event.message, event.filename, event.lineno, event.colno, event.error);
|
||||
if(event.filename == tag.href) { //FIXME!
|
||||
window.removeEventListener('error', error_handler as any);
|
||||
|
||||
reject(new SyntaxError(event.error));
|
||||
event.preventDefault();
|
||||
error = true;
|
||||
}
|
||||
};
|
||||
window.addEventListener('error', error_handler as any);
|
||||
|
||||
tag.type = "text/css";
|
||||
tag.rel = "stylesheet";
|
||||
|
||||
const cleanup = () => {
|
||||
tag.onerror = undefined;
|
||||
tag.onload = undefined;
|
||||
|
||||
clearTimeout(timeout_handle);
|
||||
window.removeEventListener('error', error_handler as any);
|
||||
};
|
||||
|
||||
const timeout_handle = setTimeout(() => {
|
||||
cleanup();
|
||||
reject("timeout");
|
||||
}, 5000);
|
||||
|
||||
tag.onerror = error => {
|
||||
cleanup();
|
||||
tag.remove();
|
||||
if(config.error)
|
||||
console.error("File load error for file %s: %o", url, error);
|
||||
reject("failed to load file " + url);
|
||||
};
|
||||
tag.onload = () => {
|
||||
cleanup();
|
||||
{
|
||||
const css: CSSStyleSheet = tag.sheet as CSSStyleSheet;
|
||||
const rules = css.cssRules;
|
||||
const rules_remove: number[] = [];
|
||||
const rules_add: string[] = [];
|
||||
|
||||
for(let index = 0; index < rules.length; index++) {
|
||||
const rule = rules.item(index);
|
||||
let rule_text = rule.cssText;
|
||||
|
||||
if(rule.cssText.indexOf("%%base_path%%") != -1) {
|
||||
rules_remove.push(index);
|
||||
rules_add.push(rule_text.replace("%%base_path%%", document.location.origin + document.location.pathname));
|
||||
}
|
||||
}
|
||||
|
||||
for(const index of rules_remove.sort((a, b) => b > a ? 1 : 0)) {
|
||||
if(css.removeRule)
|
||||
css.removeRule(index);
|
||||
else
|
||||
css.deleteRule(index);
|
||||
}
|
||||
for(const rule of rules_add)
|
||||
css.insertRule(rule, rules_remove[0]);
|
||||
}
|
||||
|
||||
if(config.verbose) console.debug("Style sheet %o loaded", url);
|
||||
setTimeout(resolve, 100);
|
||||
};
|
||||
|
||||
document.getElementById("style").appendChild(tag);
|
||||
tag.href = config.baseUrl + url;
|
||||
})).then(result => {
|
||||
/* cleanup memory */
|
||||
_style_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
|
||||
return _style_promises[url];
|
||||
}).catch(error => {
|
||||
/* cleanup memory */
|
||||
_style_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
|
||||
return _style_promises[url];
|
||||
});
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
cache_tag?: string;
|
||||
}
|
||||
|
||||
export async function load(path: SourcePath, options: Options) : Promise<void> {
|
||||
if(Array.isArray(path)) { //We have fallback scripts
|
||||
return load(path[0], options).catch(error => {
|
||||
if(error instanceof LoadSyntaxError)
|
||||
return Promise.reject(error);
|
||||
|
||||
if(path.length > 1)
|
||||
return load(path.slice(1), options);
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
} else {
|
||||
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
|
||||
if(source.url.length == 0) return Promise.resolve();
|
||||
|
||||
/* await depends */
|
||||
for(const depend of source.depends) {
|
||||
if(!_style_promises[depend])
|
||||
throw "Missing dependency " + depend;
|
||||
await _style_promises[depend];
|
||||
}
|
||||
await load_style_url(source.url + (options.cache_tag || ""));
|
||||
}
|
||||
}
|
||||
|
||||
export type MultipleOptions = Options | ParallelOptions;
|
||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
|
||||
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
|
||||
if(result.failed.length > 0) {
|
||||
if(config.error) {
|
||||
console.error("Failed to load the following style sheets:");
|
||||
for(const style of result.failed) {
|
||||
const sname = script_name(style.request, false);
|
||||
if(style.error instanceof LoadSyntaxError) {
|
||||
console.log(" - %s: %o", sname, style.error.source);
|
||||
} else {
|
||||
console.log(" - %s: %o", sname, style.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
critical_error("Failed to load style " + script_name(result.failed[0].request, true) + " <br>" + "View the browser console for more information!");
|
||||
throw "failed to load style " + script_name(result.failed[0].request, false);
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
import {config, critical_error, SourcePath} from "./loader";
|
||||
import {load_parallel, LoadCallback, LoadSyntaxError, ParallelOptions, script_name} from "./utils";
|
||||
|
||||
let _template_promises: {[key: string]: Promise<void>} = {};
|
||||
|
||||
function load_template_url(url: string) : Promise<void> {
|
||||
if(typeof _template_promises[url] === "object")
|
||||
return _template_promises[url];
|
||||
|
||||
return (_template_promises[url] = (async () => {
|
||||
const response = await (await fetch(config.baseUrl + url)).text();
|
||||
|
||||
let node = document.createElement("html");
|
||||
node.innerHTML = response;
|
||||
let tags: HTMLCollection;
|
||||
if(node.getElementsByTagName("body").length > 0)
|
||||
tags = node.getElementsByTagName("body")[0].children;
|
||||
else
|
||||
tags = node.children;
|
||||
|
||||
let root = document.getElementById("templates");
|
||||
if(!root) {
|
||||
critical_error("Failed to find template tag!");
|
||||
throw "Failed to find template tag";
|
||||
}
|
||||
while(tags.length > 0){
|
||||
let tag = tags.item(0);
|
||||
root.appendChild(tag);
|
||||
|
||||
}
|
||||
})()).then(result => {
|
||||
/* cleanup memory */
|
||||
_template_promises[url] = Promise.resolve(); /* this promise does not holds the whole script tag and other memory */
|
||||
return _template_promises[url];
|
||||
}).catch(error => {
|
||||
/* cleanup memory */
|
||||
_template_promises[url] = Promise.reject(error); /* this promise does not holds the whole script tag and other memory */
|
||||
return _template_promises[url];
|
||||
});
|
||||
}
|
||||
|
||||
export interface Options {
|
||||
cache_tag?: string;
|
||||
}
|
||||
|
||||
export async function load(path: SourcePath, options: Options) : Promise<void> {
|
||||
if(Array.isArray(path)) { //We have fallback scripts
|
||||
return load(path[0], options).catch(error => {
|
||||
if(error instanceof LoadSyntaxError)
|
||||
return Promise.reject(error);
|
||||
|
||||
if(path.length > 1)
|
||||
return load(path.slice(1), options);
|
||||
|
||||
return Promise.reject(error);
|
||||
});
|
||||
} else {
|
||||
const source = typeof(path) === "string" ? {url: path, depends: []} : path;
|
||||
if(source.url.length == 0) return Promise.resolve();
|
||||
|
||||
/* await depends */
|
||||
for(const depend of source.depends) {
|
||||
if(!_template_promises[depend])
|
||||
throw "Missing dependency " + depend;
|
||||
await _template_promises[depend];
|
||||
}
|
||||
await load_template_url(source.url + (options.cache_tag || ""));
|
||||
}
|
||||
}
|
||||
|
||||
export type MultipleOptions = Options | ParallelOptions;
|
||||
export async function load_multiple(paths: SourcePath[], options: MultipleOptions, callback?: LoadCallback<SourcePath>) : Promise<void> {
|
||||
const result = await load_parallel<SourcePath>(paths, e => load(e, options), e => script_name(e, false), options, callback);
|
||||
if(result.failed.length > 0) {
|
||||
if(config.error) {
|
||||
console.error("Failed to load the following template files:");
|
||||
for(const style of result.failed) {
|
||||
const sname = script_name(style.request, false);
|
||||
if(style.error instanceof LoadSyntaxError) {
|
||||
console.log(" - %s: %o", sname, style.error.source);
|
||||
} else {
|
||||
console.log(" - %s: %o", sname, style.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
critical_error("Failed to load template file " + script_name(result.failed[0].request, true) + " <br>" + "View the browser console for more information!");
|
||||
throw "failed to load template file " + script_name(result.failed[0].request, false);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,10 @@ export interface TeaManifest {
|
|||
hash: string,
|
||||
file: string
|
||||
}[],
|
||||
css_files: {
|
||||
hash: string,
|
||||
file: string
|
||||
}[],
|
||||
modules: {
|
||||
id: string,
|
||||
context: string,
|
||||
|
@ -52,12 +56,24 @@ export async function loadManifestTarget(chunkName: string, taskId: number) {
|
|||
modules: manifest.chunks[chunkName].modules
|
||||
});
|
||||
|
||||
loader.style.load_multiple(manifest.chunks[chunkName].css_files.map(e => "js/" + e.file), {
|
||||
cache_tag: undefined,
|
||||
max_parallel_requests: 4
|
||||
}, (entry, state) => {
|
||||
if(state !== "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
loader.setCurrentTaskName(taskId, script_name(entry, false));
|
||||
});
|
||||
|
||||
await loader.scripts.load_multiple(manifest.chunks[chunkName].files.map(e => "js/" + e.file), {
|
||||
cache_tag: undefined,
|
||||
max_parallel_requests: 4
|
||||
}, (script, state) => {
|
||||
if(state !== "loading")
|
||||
if(state !== "loading") {
|
||||
return;
|
||||
}
|
||||
|
||||
loader.setCurrentTaskName(taskId, script_name(script, false));
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* IE11 and safari */
|
||||
if(Element.prototype.remove === undefined)
|
||||
if(Element.prototype.remove === undefined) {
|
||||
Object.defineProperty(Element.prototype, "remove", {
|
||||
enumerable: false,
|
||||
configurable: false,
|
||||
|
@ -8,6 +8,7 @@ if(Element.prototype.remove === undefined)
|
|||
this.parentElement.removeChild(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* IE11 */
|
||||
function ReplaceWithPolyfill() {
|
||||
|
@ -28,14 +29,17 @@ function ReplaceWithPolyfill() {
|
|||
parent.insertBefore(currentNode, this.nextSibling);
|
||||
}
|
||||
}
|
||||
if (!Element.prototype.replaceWith)
|
||||
if (!Element.prototype.replaceWith) {
|
||||
Element.prototype.replaceWith = ReplaceWithPolyfill;
|
||||
}
|
||||
|
||||
if (!CharacterData.prototype.replaceWith)
|
||||
if (!CharacterData.prototype.replaceWith) {
|
||||
CharacterData.prototype.replaceWith = ReplaceWithPolyfill;
|
||||
}
|
||||
|
||||
if (!DocumentType.prototype.replaceWith)
|
||||
if (!DocumentType.prototype.replaceWith) {
|
||||
DocumentType.prototype.replaceWith = ReplaceWithPolyfill;
|
||||
}
|
||||
|
||||
// Source: https://github.com/jserz/js_piece/blob/master/DOM/ParentNode/append()/append().md
|
||||
(function (arr) {
|
||||
|
|
|
@ -1,26 +1,8 @@
|
|||
import "./shared";
|
||||
import * as loader from "../loader/loader";
|
||||
import {ApplicationLoader, SourcePath} from "../loader/loader";
|
||||
import {script_name} from "../loader/utils";
|
||||
import {ApplicationLoader} from "../loader/loader";
|
||||
import {loadManifest, loadManifestTarget} from "../maifest";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
native_client: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
function getCacheTag() {
|
||||
return "?_ts=" + (__build.mode === "debug" ? Date.now() : __build.timestamp);
|
||||
}
|
||||
|
||||
const LoaderTaskCallback = taskId => (script: SourcePath, state) => {
|
||||
if(state !== "loading")
|
||||
return;
|
||||
|
||||
loader.setCurrentTaskName(taskId, script_name(script, false));
|
||||
};
|
||||
|
||||
/* all javascript loaders */
|
||||
const loader_javascript = {
|
||||
load_scripts: async taskId => {
|
||||
|
@ -30,33 +12,12 @@ const loader_javascript = {
|
|||
}
|
||||
};
|
||||
|
||||
const loader_webassembly = {
|
||||
test_webassembly: async () => {
|
||||
/* We dont required WebAssembly anymore for fundamental functions, only for auto decoding
|
||||
if(typeof (WebAssembly) === "undefined" || typeof (WebAssembly.compile) === "undefined") {
|
||||
console.log(navigator.browserSpecs);
|
||||
if (navigator.browserSpecs.name == 'Safari') {
|
||||
if (parseInt(navigator.browserSpecs.version) < 11) {
|
||||
displayCriticalError("You require Safari 11 or higher to use the web client!<br>Safari " + navigator.browserSpecs.version + " does not support WebAssambly!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Do something for all other browsers.
|
||||
}
|
||||
displayCriticalError("You require WebAssembly for TeaSpeak-Web!");
|
||||
throw "Missing web assembly";
|
||||
}
|
||||
*/
|
||||
}
|
||||
};
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "secure tester",
|
||||
function: async () => {
|
||||
/* we need https or localhost to use some things like the storage API */
|
||||
if(typeof isSecureContext === "undefined")
|
||||
(<any>window)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost';
|
||||
(window as any)["isSecureContext"] = location.protocol !== 'https:' || location.hostname === 'localhost';
|
||||
|
||||
if(!isSecureContext) {
|
||||
loader.critical_error("TeaWeb cant run on unsecured sides.", "App requires to be loaded via HTTPS!");
|
||||
|
@ -66,33 +27,12 @@ loader.register_task(loader.Stage.INITIALIZING, {
|
|||
priority: 20
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.INITIALIZING, {
|
||||
name: "webassembly tester",
|
||||
function: loader_webassembly.test_webassembly,
|
||||
priority: 20
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.JAVASCRIPT, {
|
||||
name: "scripts",
|
||||
function: loader_javascript.load_scripts,
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.TEMPLATES, {
|
||||
name: "templates",
|
||||
function: async taskId => {
|
||||
await loader.templates.load_multiple([
|
||||
"templates.html",
|
||||
"templates/modal/musicmanage.html",
|
||||
"templates/modal/newcomer.html",
|
||||
], {
|
||||
cache_tag: getCacheTag(),
|
||||
max_parallel_requests: -1
|
||||
}, LoaderTaskCallback(taskId));
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.SETUP, {
|
||||
name: "page setup",
|
||||
function: async () => {
|
||||
|
|
|
@ -45,19 +45,6 @@ export default class implements ApplicationLoader {
|
|||
priority: 10
|
||||
});
|
||||
|
||||
loader.register_task(loader.Stage.TEMPLATES, {
|
||||
name: "templates",
|
||||
function: async () => {
|
||||
await loader.templates.load_multiple([
|
||||
"templates.html"
|
||||
], {
|
||||
cache_tag: "?22",
|
||||
max_parallel_requests: -1
|
||||
});
|
||||
},
|
||||
priority: 10
|
||||
});
|
||||
|
||||
loader.execute_managed(false);
|
||||
}
|
||||
}
|
|
@ -13,14 +13,11 @@ loader.register_task(Stage.SETUP, {
|
|||
}
|
||||
|
||||
window.__native_client_init_hook();
|
||||
window.native_client = true;
|
||||
} 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;
|
||||
}
|
||||
},
|
||||
priority: 1000
|
||||
|
@ -50,7 +47,6 @@ loader.register_task(Stage.SETUP, {
|
|||
case "ie":
|
||||
loader.critical_error("Browser not supported", "We're sorry, but your browser isn't supported.");
|
||||
throw "unsupported browser";
|
||||
|
||||
}
|
||||
},
|
||||
priority: 50
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
**/*.css
|
||||
**/*.css.map
|
|
@ -1,10 +1,3 @@
|
|||
<%
|
||||
/* given on compile time */
|
||||
var build_target;
|
||||
var initial_script;
|
||||
var initial_css;
|
||||
%>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -16,20 +9,20 @@ var initial_css;
|
|||
|
||||
<meta name="og:description" content="The TeaSpeak Web client is a in the browser running client for the VoIP communication software TeaSpeak." />
|
||||
<meta name="og:url" content="https://web.teaspeak.de/">
|
||||
<%# TODO: Put in an appropirate image <meta name="og:image" content="https://www.whatsapp.com/img/whatsapp-promo.png"> %>
|
||||
<% /* TODO: Put in an appropriate image <meta name="og:image" content="https://www.whatsapp.com/img/whatsapp-promo.png"> */ %>
|
||||
|
||||
<%# Using an absolute path here since the manifest.json works only with such. %>
|
||||
<% /* Using an absolute path here since the manifest.json works only with such. */ %>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<% if(build_target === "client") { %>
|
||||
<% if(buildTarget === "client") { %>
|
||||
<title>TeaClient</title>
|
||||
<meta name='og:title' content='TeaClient'>
|
||||
<% } else { %>
|
||||
<% } else { %>
|
||||
<title>TeaSpeak-Web</title>
|
||||
<meta name='og:title' content='TeaSpeak-Web'>
|
||||
<link rel='shortcut icon' href='img/favicon/teacup.png' type='image/x-icon' id="favicon">
|
||||
<%# <link rel="apple-touch-icon" sizes="194x194" href="/apple-touch-icon.png" type="image/png"> %>
|
||||
<% } %>
|
||||
<% /* <link rel="apple-touch-icon" sizes="194x194" href="/apple-touch-icon.png" type="image/png"> */ %>
|
||||
<% } %>
|
||||
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
@ -49,10 +42,10 @@ var initial_css;
|
|||
|
||||
<link rel="preload" as="image" href="img/loader/initial-sequence.gif">
|
||||
<link rel="preload" as="image" href="img/loader/bowl.png">
|
||||
<%# We don't preload the bowl since it's only a div background %>
|
||||
<% /* We don't preload the bowl since it's only a div background */ %>
|
||||
<link rel="preload" as="image" href="img/loader/text.png">
|
||||
|
||||
<%- initial_css %>
|
||||
<%= htmlWebpackPlugin.tags.headTags %>
|
||||
</head>
|
||||
<body>
|
||||
<!-- No javascript error -->
|
||||
|
@ -101,6 +94,6 @@ var initial_css;
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<%- initial_script %>
|
||||
<%= htmlWebpackPlugin.tags.bodyTags %>
|
||||
</body>
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
28
package.json
28
package.json
|
@ -12,7 +12,8 @@
|
|||
"build-client": "webpack --config webpack-client.config.js",
|
||||
"webpack-web": "webpack --config webpack-web.config.js",
|
||||
"webpack-client": "webpack --config webpack-client.config.js",
|
||||
"generate-i18n-gtranslate": "node shared/generate_i18n_gtranslate.js"
|
||||
"generate-i18n-gtranslate": "node shared/generate_i18n_gtranslate.js",
|
||||
"dev-server": "webpack serve --config webpack-web.config.js"
|
||||
},
|
||||
"author": "TeaSpeak (WolverinDEV)",
|
||||
"license": "ISC",
|
||||
|
@ -29,12 +30,13 @@
|
|||
"@types/html-minifier": "^3.5.3",
|
||||
"@types/jquery": "^3.3.34",
|
||||
"@types/jsrender": "^1.0.5",
|
||||
"@types/loader-utils": "^1.1.3",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/moment": "^2.13.0",
|
||||
"@types/node": "^12.7.2",
|
||||
"@types/react-color": "^3.0.4",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/react-grid-layout": "^1.1.1",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"@types/remarkable": "^1.7.4",
|
||||
"@types/sdp-transform": "^2.4.4",
|
||||
"@types/sha256": "^0.2.0",
|
||||
|
@ -43,13 +45,11 @@
|
|||
"@types/xml-parser": "^1.2.29",
|
||||
"@wasm-tool/wasm-pack-plugin": "^1.3.1",
|
||||
"babel-loader": "^8.1.0",
|
||||
"chunk-manifest-webpack-plugin": "^1.1.2",
|
||||
"circular-dependency-plugin": "^5.2.0",
|
||||
"clean-css": "^4.2.1",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.6.0",
|
||||
"csso-cli": "^3.0.0",
|
||||
"ejs": "^3.0.2",
|
||||
"css-minimizer-webpack-plugin": "^1.3.0",
|
||||
"exports-loader": "^0.7.0",
|
||||
"fast-xml-parser": "^3.17.4",
|
||||
"file-loader": "^6.0.0",
|
||||
|
@ -57,13 +57,16 @@
|
|||
"gulp": "^4.0.2",
|
||||
"html-loader": "^1.0.0",
|
||||
"html-minifier": "^4.0.0",
|
||||
"html-webpack-plugin": "^4.0.3",
|
||||
"html-webpack-inline-source-plugin": "0.0.10",
|
||||
"html-webpack-plugin": "^5.3.1",
|
||||
"inline-chunks-html-webpack-plugin": "^1.3.1",
|
||||
"mime-types": "^2.1.24",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"mini-css-extract-plugin": "^1.3.9",
|
||||
"mkdirp": "^0.5.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"potpack": "^1.0.1",
|
||||
"raw-loader": "^4.0.0",
|
||||
"react-dev-utils": "^11.0.4",
|
||||
"sass": "1.22.10",
|
||||
"sass-loader": "^8.0.2",
|
||||
"sha256": "^0.2.0",
|
||||
|
@ -76,12 +79,11 @@
|
|||
"typescript": "^3.7.0",
|
||||
"url-loader": "^4.1.1",
|
||||
"wabt": "^1.0.13",
|
||||
"webpack": "^4.42.1",
|
||||
"webpack": "^5.26.1",
|
||||
"webpack-bundle-analyzer": "^3.6.1",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-svg-sprite-generator": "^1.0.17",
|
||||
"worker-plugin": "^4.0.3",
|
||||
"xml-parser": "^1.2.1"
|
||||
"webpack-cli": "^4.5.0",
|
||||
"webpack-dev-server": "^3.11.2",
|
||||
"webpack-svg-sprite-generator": "^5.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -92,8 +94,6 @@
|
|||
},
|
||||
"homepage": "https://www.teaspeak.de",
|
||||
"dependencies": {
|
||||
"@types/react-grid-layout": "^1.1.1",
|
||||
"@types/react-transition-group": "^4.4.0",
|
||||
"broadcastchannel-polyfill": "^1.0.1",
|
||||
"detect-browser": "^5.2.0",
|
||||
"dompurify": "^2.0.8",
|
||||
|
|
|
@ -43,6 +43,8 @@ if [[ "$1" == "full" ]]; then
|
|||
echo "Full cleanup. Deleting generated javascript and css files"
|
||||
cleanup_files "shared/js" "*.js" "JavaScript"
|
||||
cleanup_files "shared/js" "*.js.map" "JavaScript-Mapping"
|
||||
cleanup_files "shared/js" "*.css" "JavaScript - CSS"
|
||||
cleanup_files "shared/js" "*.css.map" "JavaScript - CSS - Mapping"
|
||||
cleanup_files "shared/css/static/" "*.css" "CSS" # We only use SCSS, not CSS
|
||||
cleanup_files "shared/css/static/" "*.css.map" "CSS-Mapping"
|
||||
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
cd "$(dirname "$0")" || exit 1
|
||||
#find css/static/ -name '*.css' -exec cat {} \; | npm run csso -- --output `pwd`/generated/static/base.css
|
||||
|
||||
|
||||
#File order
|
||||
files=(
|
||||
"css/static/properties.css"
|
||||
"css/static/main-layout.css"
|
||||
"css/static/general.css"
|
||||
"css/static/channel-tree.css"
|
||||
"css/static/connection_handlers.css"
|
||||
"css/static/context_menu.css"
|
||||
"css/static/frame-chat.css"
|
||||
"css/static/server-log.css"
|
||||
"css/static/scroll.css"
|
||||
"css/static/hostbanner.css"
|
||||
"css/static/htmltags.css"
|
||||
"css/static/menu-bar.css"
|
||||
"css/static/mixin.css"
|
||||
"css/static/modal.css"
|
||||
"css/static/modals.css"
|
||||
"css/static/modal-about.css"
|
||||
"css/static/modal-avatar.css"
|
||||
"css/static/modal-banclient.css"
|
||||
"css/static/modal-banlist.css"
|
||||
"css/static/modal-bookmarks.css"
|
||||
"css/static/modal-channel.css"
|
||||
"css/static/modal-channelinfo.css"
|
||||
"css/static/modal-clientinfo.css"
|
||||
"css/static/modal-connect.css"
|
||||
"css/static/modal-group-assignment.css"
|
||||
"css/static/modal-icons.css"
|
||||
"css/static/modal-identity.css"
|
||||
"css/static/modal-newcomer.css"
|
||||
"css/static/modal-invite.css"
|
||||
"css/static/modal-keyselect.css"
|
||||
"css/static/modal-poke.css"
|
||||
"css/static/modal-query.css"
|
||||
"css/static/modal-server.css"
|
||||
"css/static/modal-musicmanage.css"
|
||||
"css/static/modal-serverinfobandwidth.css"
|
||||
"css/static/modal-serverinfo.css"
|
||||
"css/static/modal-settings.css"
|
||||
"css/static/overlay-image-preview.css"
|
||||
|
||||
"css/static/ts/tab.css"
|
||||
"css/static/ts/chat.css"
|
||||
"css/static/ts/icons.css"
|
||||
"css/static/ts/icons_em.css"
|
||||
"css/static/ts/country.css"
|
||||
)
|
||||
|
||||
target_file=`pwd`/../generated/static/base.css
|
||||
|
||||
if [[ ! -d $(dirname ${target_file}) ]]; then
|
||||
echo "Creating target path ($(dirname "${target_file}"))"
|
||||
mkdir -p $(dirname "${target_file}")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to create target path!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "/* Auto generated merged CSS file */" > "${target_file}"
|
||||
for file in "${files[@]}"; do
|
||||
if [[ ${file} =~ css/* ]]; then
|
||||
file="./${file:4}"
|
||||
fi
|
||||
cat "${file}" >> "${target_file}"
|
||||
done
|
||||
|
||||
cat "${target_file}" | npm run csso -- --output "$(pwd)/../generated/static/base.css"
|
|
@ -2,3 +2,8 @@ declare module "*.png" {
|
|||
const value: any;
|
||||
export = value;
|
||||
}
|
||||
|
||||
declare module "*.html" {
|
||||
const value: any;
|
||||
export = value;
|
||||
}
|
|
@ -294,7 +294,8 @@ class IdentityPOWWorker {
|
|||
private _initialized = false;
|
||||
|
||||
async initialize(key: string) {
|
||||
this._worker = new Worker("tc-shared/workers/pow", { type: "module" });
|
||||
// @ts-ignore
|
||||
this._worker = new Worker(new URL("tc-shared/workers/pow", import.meta.url));
|
||||
|
||||
/* initialize */
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
|
|
|
@ -228,21 +228,17 @@ if(typeof ($) !== "undefined") {
|
|||
if(!$.fn.renderTag) {
|
||||
$.fn.renderTag = function (this: JQuery, values?: any) : JQuery {
|
||||
let result;
|
||||
if(this.render) {
|
||||
result = $(this.render(values));
|
||||
} else {
|
||||
const template = jsrender.render[this.attr("id")];
|
||||
if(!template) {
|
||||
console.error("Tried to render template %o, but template is not available!", this.attr("id"));
|
||||
throw "missing template " + this.attr("id");
|
||||
}
|
||||
/*
|
||||
result = window.jsrender.templates("tmpl_permission_entry", $("#tmpl_permission_entry").html());
|
||||
result = window.jsrender.templates("xxx", this.html());
|
||||
*/
|
||||
result = template(values);
|
||||
result = $(result);
|
||||
const template = $.views.templates[this.attr("id")];
|
||||
if(!template) {
|
||||
console.error("Tried to render template %o, but template is not available!", this.attr("id"));
|
||||
throw "missing template " + this.attr("id");
|
||||
}
|
||||
/*
|
||||
result = window.jsrender.templates("tmpl_permission_entry", $("#tmpl_permission_entry").html());
|
||||
result = window.jsrender.templates("xxx", this.html());
|
||||
*/
|
||||
result = template(values);
|
||||
result = $(result);
|
||||
result.find("node").each((index, element) => {
|
||||
$(element).replaceWith(values[$(element).attr("key")] || (values[0] || [])[$(element).attr("key")]);
|
||||
});
|
||||
|
|
|
@ -20,7 +20,6 @@ import {ChannelTreeRenderer} from "tc-shared/ui/tree/Renderer";
|
|||
import {ChannelTreeUIEvents} from "tc-shared/ui/tree/Definitions";
|
||||
|
||||
const cssStyle = require("./AppRenderer.scss");
|
||||
|
||||
const VideoFrame = React.memo((props: { events: Registry<AppUiEvents> }) => {
|
||||
const refElement = React.useRef<HTMLDivElement>();
|
||||
const [ container, setContainer ] = useState<HTMLDivElement | undefined>(() => {
|
||||
|
|
|
@ -2,6 +2,26 @@ import * as loader from "tc-loader";
|
|||
import moment from "moment";
|
||||
import {LogCategory, logError, logTrace} from "../log";
|
||||
import {tr} from "tc-shared/i18n/localize";
|
||||
import TemplateFile from "../../html/templates.html";
|
||||
import TemplateMusicManage from "../../html/templates/modal/musicmanage.html";
|
||||
import TemplateNewComer from "../../html/templates/modal/newcomer.html";
|
||||
|
||||
function initializeHtml(html: string) {
|
||||
const hangingPoint = document.getElementById("templates");
|
||||
|
||||
const node = document.createElement("html");
|
||||
node.innerHTML = html;
|
||||
for(const element of node.getElementsByClassName("jsrender-template")) {
|
||||
if(!$.templates(element.id, element.innerHTML)) {
|
||||
logError(LogCategory.GENERAL, tr("Failed to setup cache for js renderer template %s!"), element.id);
|
||||
} else {
|
||||
logTrace(LogCategory.GENERAL, tr("Successfully loaded jsrender template %s"), element.id);
|
||||
|
||||
const elem = document.createElement("div");
|
||||
elem.id = element.id;
|
||||
hangingPoint?.appendChild(elem); }
|
||||
}
|
||||
}
|
||||
|
||||
export function setupJSRender() : boolean {
|
||||
if(!$.views) {
|
||||
|
@ -25,11 +45,8 @@ export function setupJSRender() : boolean {
|
|||
return /* @tr-ignore */ tr(args[0]);
|
||||
});
|
||||
|
||||
$(".jsrender-template").each((idx, _entry) => {
|
||||
if(!$.templates(_entry.id, _entry.innerHTML)) {
|
||||
logError(LogCategory.GENERAL, tr("Failed to setup cache for js renderer template %s!"), _entry.id);
|
||||
} else
|
||||
logTrace(LogCategory.GENERAL, tr("Successfully loaded jsrender template %s"), _entry.id);
|
||||
});
|
||||
initializeHtml(TemplateFile);
|
||||
initializeHtml(TemplateMusicManage);
|
||||
initializeHtml(TemplateNewComer);
|
||||
return true;
|
||||
}
|
|
@ -14,12 +14,9 @@
|
|||
"webpack-web.config.ts",
|
||||
|
||||
"webpack/build-definitions.d.ts",
|
||||
"webpack/ManifestPlugin.ts",
|
||||
"webpack/EJSGenerator.ts",
|
||||
"webpack/HtmlWebpackInlineSource.ts",
|
||||
"webpack/WatLoader.ts",
|
||||
"webpack/DevelBlocks.ts",
|
||||
|
||||
"loader/IndexGenerator.ts",
|
||||
"webpack/ManifestPlugin.ts",
|
||||
|
||||
"babel.config.ts",
|
||||
"file.ts"
|
||||
|
|
|
@ -25,6 +25,7 @@ async function initializeFaviconRenderer() {
|
|||
|
||||
iconImage = new Image();
|
||||
iconImage.src = kClientSpriteUrl;
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
iconImage.onload = resolve;
|
||||
iconImage.onerror = () => reject("failed to load client icon sprite");
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as config_base from "./webpack.config";
|
|||
|
||||
export = () => config_base.config("client").then(config => {
|
||||
Object.assign(config.entry, {
|
||||
"client-app": "./client/app/index.ts"
|
||||
"client-app": ["./client/app/index.ts"]
|
||||
});
|
||||
|
||||
Object.assign(config.resolve.alias, {
|
||||
|
@ -15,7 +15,7 @@ export = () => config_base.config("client").then(config => {
|
|||
if(!Array.isArray(config.externals))
|
||||
throw "invalid config";
|
||||
|
||||
config.externals.push((context, request, callback) => {
|
||||
config.externals.push(({ context, request }, callback) => {
|
||||
if (request.startsWith("tc-backend/")) {
|
||||
return callback(null, `window["backend-loader"].require("${request}")`);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ import * as config_base from "./webpack.config";
|
|||
|
||||
export = () => config_base.config("web").then(config => {
|
||||
Object.assign(config.entry, {
|
||||
"shared-app": "./web/app/index.ts",
|
||||
"modal-external": "./web/app/index-external.ts"
|
||||
"shared-app": ["./web/app/index.ts"],
|
||||
"modal-external": ["./web/app/index-external.ts"]
|
||||
});
|
||||
|
||||
Object.assign(config.resolve.alias, {
|
||||
|
@ -13,7 +13,5 @@ export = () => config_base.config("web").then(config => {
|
|||
"tc-backend": path.resolve(__dirname, "web/app"),
|
||||
});
|
||||
|
||||
config.node = config.node || {};
|
||||
config.node["fs"] = "empty";
|
||||
return Promise.resolve(config);
|
||||
});
|
|
@ -3,20 +3,23 @@ import * as fs from "fs";
|
|||
import trtransformer from "./tools/trgen/ts_transformer";
|
||||
import {exec} from "child_process";
|
||||
import * as util from "util";
|
||||
import { Plugin as SvgSpriteGenerator } from "webpack-svg-sprite-generator";
|
||||
|
||||
import LoaderIndexGenerator from "./loader/IndexGenerator";
|
||||
import {Configuration} from "webpack";
|
||||
|
||||
const path = require('path');
|
||||
const webpack = require("webpack");
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
import { Plugin as SvgSpriteGenerator } from "webpack-svg-sprite-generator";
|
||||
const ManifestGenerator = require("./webpack/ManifestPlugin");
|
||||
const WorkerPlugin = require('worker-plugin');
|
||||
const InlineChunkHtmlPlugin = require("react-dev-utils/InlineChunkHtmlPlugin");
|
||||
|
||||
import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
|
||||
|
||||
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
|
||||
|
||||
export let isDevelopment = process.env.NODE_ENV === 'development';
|
||||
console.log("Webpacking for %s (%s)", isDevelopment ? "development" : "production", process.env.NODE_ENV || "NODE_ENV not specified");
|
||||
|
@ -63,34 +66,60 @@ const isLoaderFile = (file: string) => {
|
|||
return false;
|
||||
};
|
||||
|
||||
export const config = async (target: "web" | "client"): Promise<Configuration> => ({
|
||||
const generateIndexPlugin = (target: "web" | "client"): HtmlWebpackPlugin => {
|
||||
const options: HtmlWebpackPlugin.Options & { inlineSource?: RegExp | string } = {};
|
||||
|
||||
options.cache = true;
|
||||
options.chunks = ["loader"];
|
||||
options.inject = false;
|
||||
options.template = path.join(__dirname, "loader", "index.ejs");
|
||||
options.templateParameters = { buildTarget: target };
|
||||
options.scriptLoading = "defer";
|
||||
|
||||
if(!isDevelopment) {
|
||||
options.minify = {
|
||||
html5: true,
|
||||
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeTagWhitespace: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
minifyURLs: true,
|
||||
};
|
||||
|
||||
options.inlineSource = /\.(js|css)$/;
|
||||
}
|
||||
return new HtmlWebpackPlugin(options);
|
||||
}
|
||||
|
||||
export const config = async (target: "web" | "client"): Promise<Configuration & { devServer: any }> => ({
|
||||
entry: {
|
||||
"loader": "./loader/app/index.ts",
|
||||
"modal-external": "./shared/js/ui/react-elements/external-modal/PopoutEntrypoint.ts",
|
||||
"loader": ["./loader/app/index.ts"],
|
||||
"modal-external": ["./shared/js/ui/react-elements/external-modal/PopoutEntrypoint.ts"],
|
||||
//"devel-main": "./shared/js/devel_main.ts"
|
||||
},
|
||||
|
||||
devtool: isDevelopment ? "inline-source-map" : undefined,
|
||||
mode: isDevelopment ? "development" : "production",
|
||||
plugins: [
|
||||
//new CleanWebpackPlugin(),
|
||||
new CleanWebpackPlugin(),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: isDevelopment ? '[name].css' : '[name].[hash].css',
|
||||
chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css'
|
||||
filename: isDevelopment ? '[name].css' : '[name].[contenthash].css',
|
||||
chunkFilename: isDevelopment ? '[id].css' : '[id].[contenthash].css',
|
||||
ignoreOrder: true
|
||||
}),
|
||||
new ManifestGenerator({
|
||||
file: path.join(__dirname, "dist/manifest.json"),
|
||||
base: __dirname
|
||||
outputFileName: "manifest.json",
|
||||
context: __dirname
|
||||
}),
|
||||
new WorkerPlugin(),
|
||||
//new BundleAnalyzerPlugin(),
|
||||
isDevelopment ? undefined : new webpack.optimize.AggressiveSplittingPlugin({
|
||||
minSize: 1024 * 8,
|
||||
maxSize: 1024 * 128
|
||||
}),
|
||||
new webpack.DefinePlugin(await generateDefinitions(target)),
|
||||
new SvgSpriteGenerator({
|
||||
dtsOutputFolder: path.join(__dirname, "shared", "svg-sprites"),
|
||||
publicPath: "js/",
|
||||
configurations: {
|
||||
"client-icons": {
|
||||
folder: path.join(__dirname, "shared", "img", "client-icons"),
|
||||
|
@ -125,20 +154,22 @@ export const config = async (target: "web" | "client"): Promise<Configuration> =
|
|||
}
|
||||
}
|
||||
}),
|
||||
new LoaderIndexGenerator({
|
||||
buildTarget: target,
|
||||
output: path.join(__dirname, "dist/index.html"),
|
||||
isDevelopment: isDevelopment
|
||||
})
|
||||
generateIndexPlugin(target),
|
||||
new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/.*/]),
|
||||
].filter(e => !!e),
|
||||
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(s[ac]|c)ss$/,
|
||||
loader: [
|
||||
'style-loader',
|
||||
//MiniCssExtractPlugin.loader,
|
||||
use: [
|
||||
//'style-loader',
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
options: {
|
||||
esModule: false
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
|
@ -161,7 +192,7 @@ export const config = async (target: "web" | "client"): Promise<Configuration> =
|
|||
test: (module: string) => module.match(/\.tsx?$/) && !isLoaderFile(module),
|
||||
exclude: /node_modules/,
|
||||
|
||||
loader: [
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
|
@ -185,7 +216,7 @@ export const config = async (target: "web" | "client"): Promise<Configuration> =
|
|||
test: (module: string) => module.match(/\.tsx?$/) && isLoaderFile(module),
|
||||
exclude: /(node_modules|bower_components)/,
|
||||
|
||||
loader: [
|
||||
use: [
|
||||
{
|
||||
loader: "babel-loader",
|
||||
options: {
|
||||
|
@ -202,24 +233,26 @@ export const config = async (target: "web" | "client"): Promise<Configuration> =
|
|||
},
|
||||
{
|
||||
test: /\.was?t$/,
|
||||
loader: [
|
||||
use: [
|
||||
"./webpack/WatLoader.js"
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
loader: 'svg-inline-loader'
|
||||
use: 'svg-inline-loader'
|
||||
},
|
||||
{
|
||||
test: /ChangeLog\.md$/i,
|
||||
loader: "raw-loader",
|
||||
options: {
|
||||
esModule: false
|
||||
}
|
||||
test: /ChangeLog\.md$|\.html$/i,
|
||||
use: {
|
||||
loader: "raw-loader",
|
||||
options: {
|
||||
esModule: false
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|jpeg|gif)?$/,
|
||||
loader: 'file-loader',
|
||||
use: 'file-loader',
|
||||
},
|
||||
]
|
||||
} as any,
|
||||
|
@ -245,9 +278,19 @@ export const config = async (target: "web" | "client"): Promise<Configuration> =
|
|||
},
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks: "all"
|
||||
chunks: "all",
|
||||
maxSize: 512 * 1024
|
||||
},
|
||||
minimize: !isDevelopment,
|
||||
minimizer: [new TerserPlugin()]
|
||||
}
|
||||
minimizer: [
|
||||
new TerserPlugin(),
|
||||
new CssMinimizerPlugin()
|
||||
]
|
||||
},
|
||||
devServer: {
|
||||
publicPath: "/",
|
||||
contentBase: path.join(__dirname, 'dist'),
|
||||
writeToDisk: true,
|
||||
compress: true
|
||||
},
|
||||
});
|
|
@ -1,107 +0,0 @@
|
|||
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 = webpack.compilation.Compilation;
|
||||
|
||||
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 generateEntryJsTag(compilation: Compilation) {
|
||||
const entry_group = compilation.chunkGroups.find(e => e.options.name === this.options.initialJSEntryChunk);
|
||||
if(!entry_group) return; /* not the correct compilation */
|
||||
|
||||
const tags = entry_group.chunks.map(chunk => {
|
||||
if(chunk.files.length !== 1)
|
||||
throw "invalid chunk file count";
|
||||
|
||||
const file = chunk.files[0];
|
||||
if(path.extname(file) !== ".js")
|
||||
throw "Entry chunk file has unknown extension";
|
||||
|
||||
if(!this.options.embedInitialJSEntryChunk) {
|
||||
return '<script type="application/javascript" src=' + compilation.compiler.options.output.publicPath + file + ' async defer></script>';
|
||||
} else {
|
||||
const script = fs.readFileSync(path.join(compilation.compiler.outputPath, file));
|
||||
return `<script type="application/javascript">${script}</script>`;
|
||||
}
|
||||
});
|
||||
return tags.join("\n");
|
||||
}
|
||||
|
||||
private async generateEntryCssTag() {
|
||||
if(this.options.embedInitialCSSFile) {
|
||||
const style = await util.promisify(fs.readFile)(this.options.initialCSSFile.localFile);
|
||||
return `<style>${style}</style>`
|
||||
} else {
|
||||
return `<link rel="stylesheet" href="${this.options.initialCSSFile.publicFile}">`
|
||||
}
|
||||
}
|
||||
|
||||
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.generateEntryJsTag(compilation);
|
||||
variables["initial_css"] = await this.generateEntryCssTag();
|
||||
|
||||
let generated = await ejs.render(input.toString(), variables, {
|
||||
beautify: false, /* uglify is a bit dump and does not understands ES6 */
|
||||
context: this
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
compiler.hooks.afterCompile.tapPromise(this.constructor.name, async compilation => {
|
||||
const file = path.resolve(this.options.input);
|
||||
if(compilation.fileDependencies.has(file))
|
||||
return;
|
||||
|
||||
console.log("Adding additional watch to %s", file);
|
||||
compilation.fileDependencies.add(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export = EJSGenerator;
|
|
@ -1,65 +1,64 @@
|
|||
import * as webpack from "webpack";
|
||||
import * as fs from "fs-extra";
|
||||
import * as path from "path";
|
||||
|
||||
interface Options {
|
||||
file?: string;
|
||||
base: string;
|
||||
outputFileName?: string;
|
||||
context: string;
|
||||
}
|
||||
|
||||
class ManifestGenerator {
|
||||
private manifest_content;
|
||||
private readonly options: Options;
|
||||
|
||||
readonly options: Options;
|
||||
constructor(options: Options) {
|
||||
this.options = options || { base: __dirname };
|
||||
this.options = options || { context: __dirname };
|
||||
}
|
||||
|
||||
apply(compiler: webpack.Compiler) {
|
||||
compiler.hooks.afterCompile.tap(this.constructor.name, compilation => {
|
||||
const chunks_data = {};
|
||||
compiler.hooks.emit.tap(this.constructor.name, compilation => {
|
||||
const chunkData = {};
|
||||
for(const chunkGroup of compilation.chunkGroups) {
|
||||
const fileJs = [];
|
||||
const filesCss = [];
|
||||
const modules = [];
|
||||
|
||||
for(const chunk of chunkGroup.chunks) {
|
||||
if(!chunk.files.length)
|
||||
if(!chunk.files.size) {
|
||||
continue;
|
||||
|
||||
/*
|
||||
if(chunk.files.length !== 1) {
|
||||
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";
|
||||
}
|
||||
*/
|
||||
|
||||
for(const file of chunk.files) {
|
||||
const extension = path.extname(file);
|
||||
if(extension === ".js") {
|
||||
fileJs.push({
|
||||
hash: chunk.hash,
|
||||
file: file
|
||||
});
|
||||
} else if(extension === ".css") {
|
||||
filesCss.push({
|
||||
hash: chunk.hash,
|
||||
file: file
|
||||
});
|
||||
} else if(extension === ".wasm") {
|
||||
/* do nothing */
|
||||
} else {
|
||||
throw "Unknown chunk file with extension " + extension;
|
||||
switch (extension) {
|
||||
case ".js":
|
||||
fileJs.push({
|
||||
hash: chunk.hash,
|
||||
file: file
|
||||
});
|
||||
break;
|
||||
|
||||
case ".css":
|
||||
filesCss.push({
|
||||
hash: chunk.hash,
|
||||
file: file
|
||||
});
|
||||
break;
|
||||
|
||||
case ".wasm":
|
||||
break;
|
||||
|
||||
default:
|
||||
throw "Unknown chunk file with extension " + extension;
|
||||
}
|
||||
}
|
||||
|
||||
for(const module of chunk.getModules()) {
|
||||
if(!module.type.startsWith("javascript/"))
|
||||
for(const module of chunk.getModules() as any[]) {
|
||||
if(!module.type.startsWith("javascript/")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!module.context)
|
||||
if(!module.context) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(module.context.startsWith("svg-sprites/")) {
|
||||
/* custom svg sprite handler */
|
||||
|
@ -71,37 +70,39 @@ class ManifestGenerator {
|
|||
continue;
|
||||
}
|
||||
|
||||
if(!module.resource)
|
||||
if(!module.resource) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(module.context !== path.dirname(module.resource))
|
||||
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, "/"),
|
||||
context: path.relative(this.options.context, module.context).replace(/\\/g, "/"),
|
||||
resource: path.basename(module.resource)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
chunks_data[chunkGroup.options.name] = {
|
||||
chunkData[chunkGroup.options.name] = {
|
||||
files: fileJs,
|
||||
css_files: filesCss,
|
||||
modules: modules
|
||||
};
|
||||
}
|
||||
|
||||
this.manifest_content = {
|
||||
const payload = JSON.stringify({
|
||||
version: 2,
|
||||
chunks: chunks_data
|
||||
};
|
||||
});
|
||||
chunks: chunkData
|
||||
});
|
||||
|
||||
compiler.hooks.done.tap(this.constructor.name, () => {
|
||||
const file = this.options.file || "manifest.json";
|
||||
fs.mkdirpSync(path.dirname(file));
|
||||
fs.writeFileSync(this.options.file || "manifest.json", JSON.stringify(this.manifest_content));
|
||||
const fileName = this.options.outputFileName || "manifest.json";
|
||||
compilation.assets[fileName] = {
|
||||
size() { return payload.length; },
|
||||
source() { return payload; }
|
||||
} as any;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
import * as webpack from "webpack";
|
||||
import {RawSourceMap} from "source-map";
|
||||
import LoaderContext = webpack.loader.LoaderContext;
|
||||
|
||||
const wabt = require("wabt")();
|
||||
|
||||
const filename = "module.wast";
|
||||
|
||||
export default function loader(this: LoaderContext, source: string | Buffer, sourceMap?: RawSourceMap): string | Buffer | void | undefined {
|
||||
export default function loader(source: string | Buffer): string | Buffer | void | undefined {
|
||||
this.cacheable();
|
||||
|
||||
const module = wabt.parseWat(filename, source);
|
||||
|
|
Loading…
Reference in New Issue