TeaWeb/webpack.config.ts

255 lines
9.0 KiB
TypeScript

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<Configuration> => { 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()]
}
}};