import * as ts from "typescript"; 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 = require("./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; const ManifestGenerator = require("./webpack/ManifestPlugin"); const WorkerPlugin = require('worker-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); 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"); const generate_definitions = async (target: string) => { const git_rev = fs.readFileSync(path.join(__dirname, ".git", "HEAD")).toString(); let version; if(git_rev.indexOf("/") === -1) version = (git_rev || "0000000").substr(0, 7); else version = fs.readFileSync(path.join(__dirname, ".git", git_rev.substr(5).trim())).toString().substr(0, 7); let timestamp; try { const { stdout } = await util.promisify(exec)("git show -s --format=%ct"); timestamp = parseInt(stdout.toString()); if(isNaN(timestamp)) throw "failed to parse timestamp '" + stdout.toString() + "'"; } catch (error) { console.error("Failed to get commit timestamp: %o", error); throw "failed to get commit timestamp"; } return { "__build": { target: JSON.stringify(target), mode: JSON.stringify(isDevelopment ? "debug" : "release"), version: JSON.stringify(version), timestamp: timestamp, entry_chunk_name: JSON.stringify(target === "web" ? "shared-app" : "client-app") } as BuildDefinitions } as any; }; const isLoaderFile = (file: string) => { if(file.startsWith(__dirname)) { const path = file.substr(__dirname.length).replace(/\\/g, "/"); if(path.startsWith("/loader/")) { return true; } } return false; }; export const config = async (target: "web" | "client"): Promise => { return { entry: { "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 MiniCssExtractPlugin({ filename: isDevelopment ? '[name].css' : '[name].[hash].css', chunkFilename: isDevelopment ? '[id].css' : '[id].[hash].css' }), new ManifestGenerator({ file: path.join(__dirname, "dist/manifest.json"), base: __dirname }), new WorkerPlugin(), //new BundleAnalyzerPlugin(), isDevelopment ? undefined : new webpack.optimize.AggressiveSplittingPlugin({ minSize: 1024 * 8, maxSize: 1024 * 128 }), new webpack.DefinePlugin(await generate_definitions(target)), new SvgSpriteGenerator({ dtsOutputFolder: path.join(__dirname, "shared", "svg-sprites"), configurations: { "client-icons": { folder: path.join(__dirname, "shared", "img", "client-icons"), cssClassPrefix: "client-", cssOptions: [ { scale: 1, selector: ".icon", unit: "px" }, { scale: 1.5, selector: ".icon_x24", unit: "px" }, { scale: 2, selector: ".icon_x32", unit: "px" }, { scale: 1, selector: ".icon_em", unit: "em" } ], dtsOptions: { enumName: "ClientIcon", classUnionName: "ClientIconClass", module: false } } } }), new LoaderIndexGenerator({ buildTarget: target, output: path.join(__dirname, "dist/index.html"), isDevelopment: isDevelopment }) ].filter(e => !!e), module: { rules: [ { test: /\.(s[ac]|c)ss$/, loader: [ 'style-loader', /* { loader: MiniCssExtractPlugin.loader, options: { esModule: true, }, }, */ { loader: 'css-loader', options: { modules: { mode: "local", localIdentName: isDevelopment ? "[path][name]__[local]--[hash:base64:5]" : "[hash:base64]", }, sourceMap: isDevelopment } }, { loader: 'sass-loader', options: { sourceMap: isDevelopment } } ] }, { test: (module: string) => module.match(/\.tsx?$/) && !isLoaderFile(module), exclude: /node_modules/, loader: [ { loader: 'ts-loader', options: { context: __dirname, colors: true, getCustomTransformers: (prog: ts.Program) => { return { before: [trtransformer(prog, { optimized: false, verbose: true, target_file: path.join(__dirname, "dist", "translations.json") })] }; }, transpileOnly: isDevelopment } }, { loader: "./webpack/DevelBlocks.js", options: { enabled: true } } ] }, { test: (module: string) => module.match(/\.tsx?$/) && isLoaderFile(module), exclude: /(node_modules|bower_components)/, loader: [ { loader: "babel-loader", options: { presets: ["@babel/preset-env"] //Preset used for env setup } }, { loader: "ts-loader", options: { transpileOnly: true } } ] }, { test: /\.was?t$/, loader: [ "./webpack/WatLoader.js" ] }, { test: /\.svg$/, loader: 'svg-inline-loader' }, { test: /ChangeLog\.md$/i, loader: "raw-loader", options: { esModule: false } } ] } as any, resolve: { extensions: ['.tsx', '.ts', '.js', ".scss", ".css", ".wasm"], alias: { "vendor/xbbcode": path.resolve(__dirname, "vendor/xbbcode/src") }, }, externals: [ {"tc-loader": "window loader"} ], output: { filename: isDevelopment ? "[name].[contenthash].js" : "[contenthash].js", chunkFilename: isDevelopment ? "[name].[contenthash].js" : "[contenthash].js", path: path.resolve(__dirname, 'dist'), publicPath: "js/" }, performance: { hints: false }, optimization: { splitChunks: { chunks: "all" }, minimize: !isDevelopment, minimizer: [new TerserPlugin()] } }};