如何捆绑(缩小)Kotlin React 应用程序以进行部署?

How to bundle (minify) a Kotlin React app for deployment?

该应用是使用 Kotlin React 应用的默认模板创建的:

当使用 ./gradlew browserProductionWebpack 而不进行任何额外调整时,它会生成一个 build/distributions 目录,其中包含:

我想要的是:

请提示我一些可能的方向。通过将相应的脚本添加到 webpack.config.d 来查看 webpack 配置,但还没有成功:尝试将所需的依赖项添加到 build.gradle.kts,即:

implementation(devNpm("terser-webpack-plugin", "5.3.1"))
implementation(devNpm("html-webpack-plugin", "5.5.0"))

并描述 webpack 脚本:

const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  optimization: {
      minimizer: [
        new TerserPlugin(),
        new HtmlWebpackPlugin({
          minify: {
            removeAttributeQuotes: true,
            collapseWhitespace: true,
            removeComments: true,
          },
        }),
      ],
  }
}

如有任何提示,我们将不胜感激。

首先要考虑几件事。

  1. 如果需要一些灵活的捆绑配置,很可能无法使用 Kotlin-wrapped (Gradle) 解决方案。检查 org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig 后发现只能配置有限的一组东西。

  2. JS-based webpack 配置需要一点逆向工程来找出从 Kotlin/Gradle 端生成的内容以及如何扩展它。

  3. 对于简单的配置(恕我直言),在使用 Kotlin JS 插件时几乎不需要调整捆绑选项。

让我们从最初的 webpack 配置开始。在我的例子中(参见上面问题中的简短环境描述)它们出现在 ./build/js/packages/<project-name>/webpack.config.js 中。这些原始配置还将包括我们在 ./webpack.config.d 文件夹中创建的 JS 文件的所有内容。

一些 webpack 配置需要外部 JS 依赖。我们需要在 build.gradle.ktsdependencies 块中声明它们。在我的例子中,它们表示为:

// Bundling.
implementation(devNpm("html-webpack-plugin", "5.5.0"))
implementation(devNpm("uglifyjs-webpack-plugin", "2.2.0"))
implementation(devNpm("terser-webpack-plugin", "5.3.1"))
implementation(devNpm("copy-webpack-plugin", "9.1.0" )) // newer versions don't work correctly with npm and Yarn
implementation(devNpm("node-json-minify", "3.0.0"))

我还删除了 build.gradle.kts 中的所有 commonWebpackConfig,因为它们将在 JS 级别手动执行。

所有 webpack JS 配置(在 ./webpack.config.d 文件夹内)分为 3 个文件:

common.js(开发和生产构建的开发服务器配置):

// All route paths should fallback to the index page to make SPA's routes processed correctly.
const devServer = config.devServer = config.devServer || {};
devServer.historyApiFallback = true;

development.js:

// All configs inside of this file will be enabled only in the development mode.
// To check the outputs of this config, see ../build/processedResources/js/main
if (config.mode == "development") {

    const HtmlWebpackPlugin = require("html-webpack-plugin");

    // Pointing to the template to be used as a base and injecting the JS sources path into.
    config.plugins.push(new HtmlWebpackPlugin({ template: "./kotlin/index.html" }));

}

production.js:

// All configs inside of this file will be enabled only in the production mode.
// The result webpack configurations file will be generated inside ../build/js/packages/<project-name>
// To check the outputs of this config, see ../build/distributions
if (config.mode == "production") {

    const HtmlWebpackPlugin     = require("html-webpack-plugin"),
          UglifyJsWebpackPlugin = require("uglifyjs-webpack-plugin"),
          TerserWebpackPlugin   = require("terser-webpack-plugin"),
          CopyWebpackPlugin     = require("copy-webpack-plugin"),
          NodeJsonMinify        = require("node-json-minify");

    // Where to output and how to name JS sources.
    // Using hashes for correct caching.
    // The index.html will be updated correspondingly to refer the compiled JS sources.
    config.output.filename = "js/[name].[contenthash].js";

    // Making sure optimization and minimizer configs exist, or accessing its properties can crash otherwise.
    config.optimization = config.optimization || {};
    const minimizer = config.optimization.minimizer = config.optimization.minimizer || [];

    // Minifying HTML.
    minimizer.push(new HtmlWebpackPlugin({
        template: "./kotlin/index.html",
        minify: {
            removeAttributeQuotes: true,
            collapseWhitespace: true,
            removeComments: true,
        },
    }));

    // Minifying and obfuscating JS.
    minimizer.push(new UglifyJsWebpackPlugin({
        parallel: true,   // speeds up the compilation
        sourceMap: false, // help to match obfuscated functions with their origins, not needed for now
        uglifyOptions: {
            compress: {
                drop_console: true, // removing console calls
            }
        }
    }));

    // Additional JS minification.
    minimizer.push(new TerserWebpackPlugin({
        extractComments: true // excluding all comments (mostly licence-related ones) into a separate file
    }));

    // Minifying JSON locales.
    config.plugins.push(new CopyWebpackPlugin({
        patterns: [
            {
                context: "./kotlin",
                from: "./locales/**/*.json",
                to: "[path][name][ext]",
                transform: content => NodeJsonMinify(content.toString())
            }
        ]
    }));

}

我使用样式组件,因此没有提供 CSS 配置。在其他方面,这些配置所做的缩小几乎与 out-of-the-box 没有任何额外配置的情况相同。差异是:

  • JS 资源在他们的名字中使用哈希:它是从索引页面 HTML 模板中正确引用的;
  • HTML 模板已缩小;
  • 语言环境(只是简单的 JSON 文件)被缩小。

它看起来有点像开销,因为正如开头提到的,它几乎与 out-of-the-box 配置的差异很小。但作为优势,我们获得了更灵活的配置,可以更轻松地进一步调整。