/* jshint node:true */
const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const packageJson = require('./package.json');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const TerserPlugin = require('terser-webpack-plugin');
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
// === Git Revision Plugin with Fallback & Logging ===
let gitRevisionPlugin;
try {
const GitRevisionPlugin = require('git-revision-webpack-plugin');
gitRevisionPlugin = new GitRevisionPlugin({
commithashCommand: 'git rev-parse HEAD',
versionCommand: 'git describe --always --dirty',
});
gitRevisionPlugin.commithash(); // 触发测试
} catch (e) {
console.warn('[Git] Not available:', e.message);
gitRevisionPlugin = {
version: () => 'unknown',
commithash: () => 'unknown',
branch: () => 'unknown'
};
}
// === 支持的语言列表 ===
let supportedLangs = [];
try {
supportedLangs = fs.readdirSync('./locales/')
.filter(filename => /\.json$/.test(filename))
.map(filename => {
try {
const file = require(`./locales/${filename}`);
return Object.values(file.translation).some(v => typeof v === 'string') ? filename : null;
} catch {
return null;
}
})
.filter(Boolean)
.map(filename => filename.replace(/\.json$/, ''));
} catch (e) {
console.warn('[Locales] Failed to read languages:', e.message);
}
const MOMENTJS_LOCALES = [...new Set(supportedLangs.map(lang => lang.split('-')[0]))];
const JS_BUNDLE_NAME = 'jsxc.bundle.js';
// === 依赖许可证收集 ===
const dependencies = Object.keys(packageJson.dependencies).map(name => {
try {
const pkg = require(`./node_modules/${name}/package.json`);
return `${pkg.name}@${pkg.version} (${pkg.license || 'unknown'})`;
} catch (e) {
return `${name}@unknown (read error: ${e.message})`;
}
}).join(', ');
// === File Loader:扁平化输出 ===
const fileLoader = {
loader: 'file-loader',
options: {
name: '[name]-[sha1:hash:hex:8].[ext]',
outputPath: 'assets/',
publicPath: 'dist/assets/'
}
};
// === 输出目录 ===
const OUTPUT_PATH = path.resolve(__dirname, './dist/');
// === 基础配置对象(不包含动态参数)===
let config = {
entry: ['./scss/main.scss', './src/index.ts'],
output: {
filename: JS_BUNDLE_NAME,
chunkFilename: "[name].chunk.js",
path: OUTPUT_PATH,
publicPath: 'dist/',
libraryTarget: 'var',
library: 'JSXC',
clean: true,
},
optimization: {
splitChunks: {
minSize: 10,
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
},
},
performance: {
maxEntrypointSize: 3 * 1024 * 1024,
maxAssetSize: 3 * 1024 * 1024,
},
module: {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
include: [path.resolve(__dirname, 'src'), path.resolve(__dirname, 'test')],
options: {
transpileOnly: true,
},
},
{
test: /\.hbs$/,
loader: 'handlebars-loader',
include: path.resolve(__dirname, 'template'),
options: {
helperDirs: [path.resolve(__dirname, 'template', 'helpers')],
partialDirs: [path.resolve(__dirname, 'template', 'partials')]
}
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.(sass|scss)$/,
include: path.resolve(__dirname, 'scss'),
use: [
MiniCssExtractPlugin.loader,
{ loader: 'css-loader', options: { url: false } },
'sass-loader'
]
},
{
test: /\.(png|jpg|gif|mp3|wav|svg)$/,
use: [fileLoader]
},
{
test: /\.js$/,
resourceQuery: /path/,
use: [fileLoader]
}
]
},
resolve: {
extensions: [".ts", ".js", ".hbs", ".json"],
alias: {
'@connection': path.resolve(__dirname, 'src/connection/'),
'@ui': path.resolve(__dirname, 'src/ui/'),
'@util': path.resolve(__dirname, 'src/util/'),
'@vendor': path.resolve(__dirname, 'src/vendor/'),
'@src': path.resolve(__dirname, 'src/'),
},
fallback: {
fs: false,
stream: false,
path: false,
crypto: false,
process: false,
}
},
externals: {
'child_process': 'child_process',
'webworker-threads': 'webworker-threads'
},
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
process: 'process/browser',
}),
new MiniCssExtractPlugin({
filename: 'styles/jsxc.bundle.css',
}),
new CopyWebpackPlugin({
patterns: [
{ from: 'images/', to: 'images/' },
{ from: 'fonts/', to: 'fonts/' },
{ from: 'sound/', to: 'sound/' },
{ from: 'node_modules/emojione/assets/svg/', to: 'images/emojione/' },
{ from: 'LICENSE', to: 'LICENSE' },
{ from: 'README.md', to: 'README.md' },
{ from: 'example/index.html', to: 'example/index.html' },
{ from: 'example/css/', to: 'example/css/' },
{ from: 'example/js/', to: 'example/js/' },
],
}),
new webpack.LoaderOptionsPlugin({
options: {
handlebarsLoader: {}
}
}),
new webpack.ContextReplacementPlugin(
/moment[/\\]locale$/,
new RegExp(`^\\./(${MOMENTJS_LOCALES.join('|')})$`)
),
new ForkTsCheckerWebpackPlugin(),
],
devServer: {
injectClient: false,
port: 8091,
inline: true,
publicPath: '/dist/',
open: true,
openPage: 'example/index.html',
proxy: {
"/http-bind": "http://localhost:5280",
"/libsignal": "http://localhost",
"/xmpp-websocket": "ws://localhost:5280"
},
watchOptions: {
aggregateTimeout: 1300,
ignored: [
path.resolve(__dirname, 'dist'),
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, '.git'),
path.resolve(__dirname, 'test'),
'**/*.swp',
]
}
},
};
// === 动态导出函数(根据 argv 修改 config)===
module.exports = (env, argv) => {
// --- 设置 mode ---
if (typeof argv.mode === 'string') {
config.mode = argv.mode;
}
// --- 动态设置 version ---
let version;
const buildDate = new Date().toISOString();
if (argv && argv.release) {
version = packageJson.version; // 正式发布
} else if (process.env.BUILD === 'release') {
version = packageJson.version;
} else {
version = packageJson.version + '-git.' + gitRevisionPlugin.version();
}
// --- 设置输出文件名 ---
if (config.mode === 'production') {
config.output.filename = 'jsxc.bundle.min.js';
config.devtool = 'source-map';
} else {
config.devtool = 'eval-source-map';
}
// --- 生产压缩 ---
if (config.mode === 'production') {
config.optimization.minimizer = [
new TerserPlugin({
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
keep_fnames: /Session$/
},
format: { comments: false }
},
extractComments: false
})
];
}
// --- 注入全局变量 ---
const definePluginConfig = {
__VERSION__: JSON.stringify(version),
__BUILD_DATE__: JSON.stringify(buildDate),
__BUNDLE_NAME__: JSON.stringify(config.output.filename),
__DEPENDENCIES__: JSON.stringify(dependencies),
__LANGS__: JSON.stringify(supportedLangs),
'process.env.NODE_DEBUG': JSON.stringify(false),
};
config.plugins.push(new webpack.DefinePlugin(definePluginConfig));
// --- 添加 Banner ---
config.plugins.push(new webpack.BannerPlugin({
banner: `JavaScript XMPP Client - the open chat
Version: ${version}
Commit: ${gitRevisionPlugin.commithash()}
Branch: ${gitRevisionPlugin.branch()}
Build date: ${buildDate}
Homepage: ${packageJson.homepage}
JSXC is released under the MIT license, but this file also contains
dependencies which are released under a different license.
Dependencies: ${dependencies}
Licensed under MIT.`,
raw: false,
entryOnly: true
}));
// --- Bundle Analyzer ---
if (argv && argv.bundleAnalyzer) {
config.plugins.push(new BundleAnalyzerPlugin());
}
return config;
};
全面修复生成完整版源文件
最新发布