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": "./",
"local-path": "./shared/html/"
},
{ /* javascript files as manifest.json */
"type": "js",
"search-pattern": /.*$/,
@ -47,15 +45,6 @@ const APP_FILE_LIST_SHARED_SOURCE: ProjectResource[] = [
"path": "js/",
"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 */
"type": "css",
"search-pattern": /.*\.css$/,

View File

@ -1,6 +1,8 @@
import * as loader from "./targets/app";
import * as loader_base from "./loader/loader";
export = loader_base;
/* let the loader register himself at the window first */
setTimeout(loader.run, 0);
window["loader"] = loader_base;
/* let the loader register himself at the window first */
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 style_loader from "./style_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 */
function loader_cache_tag() {
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 || 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 */
if(__build.mode === "debug") {
cache_tag = "?_ts=" + Date.now();
return;
}
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, {
priority: 0,
name: "cached version updater",
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 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 */
export type ErrorHandler = (message: string, detail: string) => void;
let _callback_critical_error: ErrorHandler;

View File

@ -31,38 +31,20 @@ interface Manifest {
version: number;
chunks: {[key: string]: {
hash: string,
file: string
}[]};
}
interface BuildDefinitions {
development: boolean,
version: string
}
declare global {
const __build: BuildDefinitions;
files: {
hash: string,
file: string
}[],
modules: {
id: string,
context: string,
resource: string
}[]
}};
}
/* all javascript loaders */
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 () => {
if(!window.require) {
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);
throw "failed to load manifest.json";
}
if(manifest.version !== 1)
if(manifest.version !== 2)
throw "invalid manifest version";
const chunk_name = loader.version().type === "web" ? "shared-app" : "client-app";
if(!Array.isArray(manifest.chunks[chunk_name])) {
const chunk_name = __build.entry_chunk_name;
if(typeof manifest.chunks[chunk_name] !== "object") {
loader.critical_error("Missing entry chunk in manifest.json", "Chunk " + chunk_name + " is missing.");
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,
max_parallel_requests: -1
});
@ -154,7 +140,7 @@ const loader_style = {
["vendor/highlight/styles/darcula.css", ""], /* empty string means not required */
], options);
if(loader.version().debug_mode) {
if(__build.mode === "debug") {
await loader_style.load_style_debug();
} else {
await loader_style.load_style_release();
@ -293,12 +279,6 @@ loader.register_task(loader.Stage.INITIALIZING, {
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, {
name: "javascript",
function: loader_javascript.load_scripts,
@ -398,9 +378,14 @@ loader.register_task(loader.Stage.SETUP, {
});
export function run() {
window["Module"] = (window["Module"] || {}) as any;
window["Module"] = (window["Module"] || {}) as any; /* Why? */
/* TeaClient */
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;
const path = node_require("path");
@ -415,6 +400,11 @@ export function run() {
priority: 40
});
} 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;
}

View File

@ -61,7 +61,15 @@ export enum Stage {
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 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",
"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",
"build-loader": "webpack --config loader/webpack.config.js",
"watch-loader": "webpack --watch --config loader/webpack.config.js"
"watch-client": "webpack --watch --config webpack-client.config.js"
},
"author": "TeaSpeak (WolverinDEV)",
"license": "ISC",

View File

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

View File

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

View File

@ -250,7 +250,7 @@ export class Group {
loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
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
});

View File

@ -144,7 +144,7 @@ async function initialize_app() {
try { //Initialize main template
const main = $("#tmpl_main").renderTag({
multi_session: !settings.static_global(Settings.KEY_DISABLE_MULTI_SESSION),
app_version: loader.version().ui
app_version: __build.version
}).dividerfy();
$("body").append(main);
@ -589,7 +589,7 @@ loader.register_task(loader.Stage.JAVASCRIPT_INITIALIZING, {
try {
await initialize();
if(loader.version().type == "web") {
if(__build.target == "web") {
loader.register_task(loader.Stage.LOADED, task_certificate_callback);
} else {
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;
}};
if(loader.version().type !== "web") {
if(__build.target !== "web") {
menu.append_hr();
item = menu.append_item(tr("Quit"));
@ -532,7 +532,7 @@ export function initialize() {
{
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.click(() => native_actions.check_native_update());
@ -546,7 +546,7 @@ export function initialize() {
item = menu.append_item(tr("Visit TeaSpeak forum"));
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();
item = menu.append_item(tr("Open developer tools"));
item.click(() => native_actions.open_dev_tools());
@ -556,7 +556,7 @@ export function initialize() {
}
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())
}

View File

@ -27,9 +27,9 @@ export function spawnAbout() {
header: tr("About"),
body: () => {
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_timestamp: !!app_version ? format_date(Date.now()) : "--"
@ -43,7 +43,7 @@ export function spawnAbout() {
connectModal.htmlTag.find(".modal-body").addClass("modal-about");
connectModal.open();
if(loader.version().type !== "web") {
if(__build.target !== "web") {
(window as any).native.client_version().then(version => {
connectModal.htmlTag.find(".version-client").text(version);
}).catch(error => {

View File

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

View File

@ -24,9 +24,9 @@ const last_step: {[key: string]:string} = (() => {
export function openModalNewcomer() : Modal {
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({
is_web: loader.version().type === "web"
is_web: __build.version === "web"
}).children(),
footer: null,

View File

@ -331,7 +331,7 @@ function settings_general_language(container: JQuery, modal: Modal) {
}
container.find(".button-restart").on('click', () => {
if(loader.version().type === "web") {
if(__build.target === "web") {
location.reload();
} else {
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> {
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))));
});
});

View File

@ -64,7 +64,7 @@ function _generate(config: Configuration, node: ts.Node, result: TranslationEntr
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 node_path = (node: ts.Node) => {
@ -90,10 +90,10 @@ function create_unique_check(source_file: ts.SourceFile, variable: ts.Expression
/* 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()))));
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()))));
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_path = ts.createLoopVariable();
const for_declaration = ts.createVariableDeclarationList([ts.createVariableDeclaration(ts.createObjectBindingPattern([
ts.createBindingElement(undefined, "name", for_variable_name, undefined),
ts.createBindingElement(undefined, "path", for_variable_path, undefined)])
ts.createBindingElement(undefined, config.optimized ? "n": "name", for_variable_name, undefined),
ts.createBindingElement(undefined, config.optimized ? "p": "path", for_variable_path, undefined)])
, undefined, undefined)]);
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,
for_declaration, ts.createArrayLiteral(
[...variables.map(e => ts.createObjectLiteral([
ts.createPropertyAssignment("name", ts.createLiteral(e.name)),
ts.createPropertyAssignment("path", ts.createLiteral(node_path(e.node)))
ts.createPropertyAssignment(config.optimized ? "n": "name", ts.createLiteral(e.name)),
ts.createPropertyAssignment(config.optimized ? "p": "path", ts.createLiteral(node_path(e.node)))
]))
])
, for_block);
@ -166,52 +166,69 @@ export function transform(config: Configuration, context: ts.TransformationConte
const cache: VolatileTransformConfig = {} as any;
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
const extra_nodes: ts.Node[] = [];
{
cache.nodes = {} as any;
if(config.use_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());
let translation_map_init: ts.Expression = ts.createBinary(translation_map, ts.SyntaxKind.BarBarToken, new_translations);
translation_map_init = ts.createParen(translation_map_init);
extra_nodes.push(translation_map_init);
cache.nodes = {
translation_map: translation_map,
translation_map_init: translation_map_init
translation_map: translation_map
};
} 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()));
cache.nodes = {
translation_map: variable_map,
translation_map_init: variable_map
};
//ts.createVariableDeclarationList([ts.createVariableDeclaration(variable_name)], ts.NodeFlags.Let)
extra_nodes.push(inline_if);
}
}
const used_names = [config.variables.declarations, config.variables.declare_files];
const generated_names: { name: string, node: ts.Node }[] = [];
let generator_base = 0;
cache.name_generator = (config, node, message) => {
const characters = "0123456789_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
let name = "";
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) || "";
}
let name;
do {
name = "";
if(config.module) {
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});
return name;
};
@ -221,7 +238,10 @@ export function transform(config: Configuration, context: ts.TransformationConte
return replace_processor(config, cache, node, source_file);
}
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);
@ -262,7 +282,7 @@ export function replace_processor(config: Configuration, cache: VolatileTransfor
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_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 new_variable = ts.createAssignment(variable, call);
@ -284,6 +304,15 @@ export interface Configuration {
use_window?: boolean;
replace_cache?: boolean;
verbose?: boolean;
optimized?: boolean;
module?: boolean;
variables?: {
base: string,
declarations: string,
declare_files: string
}
}
export interface TransformResult {
@ -294,7 +323,6 @@ export interface TransformResult {
interface VolatileTransformConfig {
nodes: {
translation_map: ts.Expression;
translation_map_init: ts.Expression;
};
name_generator: (config: Configuration, node: ts.Node, message: string) => string;

View File

@ -9,6 +9,7 @@ import {TranslationEntry} from "./generator";
export interface Config {
target_file?: string;
verbose?: boolean;
optimized?: boolean;
}
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;
}
let processed = [];
const translations: TranslationEntry[] = [];
const transformer = (context: ts.TransformationContext) =>
(rootNode: ts.Node) => {
@ -51,10 +53,17 @@ const transformer = (context: ts.TransformationContext) =>
} else if(rootNode.kind == ts.SyntaxKind.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);
const result = ts_generator.transform({
use_window: false,
replace_cache: true
replace_cache: true,
module: true,
optimized: process_config.optimized
}, context, file);
translations.push(...result.translations);
return result.node;

View File

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

View File

@ -1,115 +1,16 @@
import * as ts from "typescript";
import trtransformer, {Config} from "./tools/trgen/ts_transformer";
import * as path from "path";
import * as config_base from "./webpack.config";
const path = require('path');
const webpack = require("webpack");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
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');
const config = config_base.config();
Object.assign(config.entry, {
"shared-app": "./web/js/index.ts"
});
let isDevelopment = process.env.NODE_ENV === 'development';
isDevelopment = true;
module.exports = {
entry: {
"shared-app": "./web/js/index.ts"
},
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/,
Object.assign(config.resolve.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"),
});
loader: [
{
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()]
}
};
export = config;

View File

@ -1,5 +1,6 @@
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 webpack = require("webpack");
@ -10,10 +11,32 @@ const WorkerPlugin = require('worker-plugin');
const TerserPlugin = require('terser-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;
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,
mode: isDevelopment ? "development" : "production",
@ -24,7 +47,8 @@ export = {
chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css'
}),
new ManifestGenerator({
file: path.join(__dirname, "dist/manifest.json")
file: path.join(__dirname, "dist/manifest.json"),
base: __dirname
}),
new WorkerPlugin(),
//new BundleAnalyzerPlugin()
@ -39,7 +63,8 @@ export = {
isDevelopment ? undefined : new webpack.optimize.AggressiveSplittingPlugin({
minSize: 1024 * 8,
maxSize: 1024 * 128
})
}),
new webpack.DefinePlugin(generate_definitions())
].filter(e => !!e),
module: {
rules: [
@ -73,7 +98,9 @@ export = {
transpileOnly: true,
getCustomTransformers: (prog: ts.Program) => {
return {
before: [trtransformer(prog, {})]
before: [trtransformer(prog, {
optimized: true
})]
};
}
}
@ -90,19 +117,21 @@ export = {
},
resolve: {
extensions: ['.tsx', '.ts', '.js', ".scss"],
alias: { }, /* will be individually set */
},
externals: {
"tc-loader": "window loader"
alias: { },
},
externals: [
{"tc-loader": "window loader"}
] as any[],
output: {
filename: isDevelopment ? '[name].js' : '[contenthash].js',
path: path.resolve(__dirname, 'dist'),
publicPath: "js/"
},
optimization: {
splitChunks: { },
splitChunks: {
},
minimize: !isDevelopment,
minimizer: [new TerserPlugin()]
}
};
}};

View File

@ -1,8 +1,10 @@
import * as webpack from "webpack";
import * as fs from "fs";
import * as path from "path";
interface Options {
file?: string;
base: string;
}
class ManifestGenerator {
@ -10,32 +12,51 @@ class ManifestGenerator {
readonly options: Options;
constructor(options: Options) {
this.options = options || {};
this.options = options || { base: __dirname };
}
apply(compiler: webpack.Compiler) {
compiler.hooks.afterCompile.tap(this.constructor.name, compilation => {
const chunks_data = {};
for(const chunk_group of compilation.chunkGroups) {
console.log(chunk_group.options.name);
const js_files = [];
const modules = [];
for(const chunk of chunk_group.chunks) {
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({
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 = {
version: 1,
version: 2,
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 {};