如何捆绑(缩小)Kotlin React 应用程序以进行部署?
How to bundle (minify) a Kotlin React app for deployment?
该应用是使用 Kotlin React 应用的默认模板创建的:
- 使用基于 KTS 的 Gradle 构建脚本;
- Kotlin JS 插件 1.6.10;
- React 17.0.2 的 Kotlin 包装器。
当使用 ./gradlew browserProductionWebpack
而不进行任何额外调整时,它会生成一个 build/distributions
目录,其中包含:
- 所有资源(无任何修改);
index.html
(没有任何修改);
- Kotlin 源代码编译成一个缩小的
.js
文件。
我想要的是:
- 向生成的
.js
文件添加一些哈希;
- 缩小
index.html
文件并在其中引用散列 .js
文件;
- 缩小所有资源(
.json
个本地化文件)。
请提示我一些可能的方向。通过将相应的脚本添加到 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,
},
}),
],
}
}
如有任何提示,我们将不胜感激。
首先要考虑几件事。
如果需要一些灵活的捆绑配置,很可能无法使用 Kotlin-wrapped (Gradle) 解决方案。检查 org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
后发现只能配置有限的一组东西。
JS-based webpack 配置需要一点逆向工程来找出从 Kotlin/Gradle 端生成的内容以及如何扩展它。
对于简单的配置(恕我直言),在使用 Kotlin JS 插件时几乎不需要调整捆绑选项。
让我们从最初的 webpack 配置开始。在我的例子中(参见上面问题中的简短环境描述)它们出现在 ./build/js/packages/<project-name>/webpack.config.js
中。这些原始配置还将包括我们在 ./webpack.config.d
文件夹中创建的 JS 文件的所有内容。
一些 webpack 配置需要外部 JS 依赖。我们需要在 build.gradle.kts
的 dependencies
块中声明它们。在我的例子中,它们表示为:
// 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 配置的差异很小。但作为优势,我们获得了更灵活的配置,可以更轻松地进一步调整。
该应用是使用 Kotlin React 应用的默认模板创建的:
- 使用基于 KTS 的 Gradle 构建脚本;
- Kotlin JS 插件 1.6.10;
- React 17.0.2 的 Kotlin 包装器。
当使用 ./gradlew browserProductionWebpack
而不进行任何额外调整时,它会生成一个 build/distributions
目录,其中包含:
- 所有资源(无任何修改);
index.html
(没有任何修改);- Kotlin 源代码编译成一个缩小的
.js
文件。
我想要的是:
- 向生成的
.js
文件添加一些哈希; - 缩小
index.html
文件并在其中引用散列.js
文件; - 缩小所有资源(
.json
个本地化文件)。
请提示我一些可能的方向。通过将相应的脚本添加到 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,
},
}),
],
}
}
如有任何提示,我们将不胜感激。
首先要考虑几件事。
如果需要一些灵活的捆绑配置,很可能无法使用 Kotlin-wrapped (Gradle) 解决方案。检查
org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
后发现只能配置有限的一组东西。JS-based webpack 配置需要一点逆向工程来找出从 Kotlin/Gradle 端生成的内容以及如何扩展它。
对于简单的配置(恕我直言),在使用 Kotlin JS 插件时几乎不需要调整捆绑选项。
让我们从最初的 webpack 配置开始。在我的例子中(参见上面问题中的简短环境描述)它们出现在 ./build/js/packages/<project-name>/webpack.config.js
中。这些原始配置还将包括我们在 ./webpack.config.d
文件夹中创建的 JS 文件的所有内容。
一些 webpack 配置需要外部 JS 依赖。我们需要在 build.gradle.kts
的 dependencies
块中声明它们。在我的例子中,它们表示为:
// 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 配置的差异很小。但作为优势,我们获得了更灵活的配置,可以更轻松地进一步调整。