build: migrate to RSPack

Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
This commit is contained in:
Grigorii K. Shartsev 2025-10-01 12:18:54 +02:00
parent 45b424b196
commit d845c0f848
No known key found for this signature in database
4 changed files with 217 additions and 167 deletions

View file

@ -9,16 +9,14 @@
"fs": false
},
"scripts": {
"analyze": "npm run analyze:stats && npm run analyze:serve",
"analyze:report": "webpack-bundle-analyzer js/stats.json js/ --mode static --report js/stats/report.html",
"analyze:serve": "webpack-bundle-analyzer js/stats.json js/",
"analyze:stats": "npm run build -- --json js/stats.json",
"build": "node --max-old-space-size=4096 ./node_modules/webpack/bin/webpack.js --node-env production --progress",
"dev": "node --max-old-space-size=4096 ./node_modules/webpack/bin/webpack.js --node-env development --progress",
"analyze": "rspack build --env production --analyze",
"analyze:stats": "rspack build --env production --json js/stats.json",
"build": "rspack build --env production",
"dev": "rspack build --env development",
"lint": "eslint",
"lint:fix": "eslint --fix",
"opengrep": "opengrep --opengrep-ignore-pattern=noopengrep --error --exclude '*.php' --exclude 'docs' --include '*.vue' --include '*.js' --include '*.ts' --exclude-rule 'javascript.vue.security.audit.xss.templates.avoid-v-html.avoid-v-html' --exclude-rule 'javascript.lang.security.detect-insecure-websocket.detect-insecure-websocket'",
"serve": "node --max-old-space-size=4096 ./node_modules/webpack/bin/webpack.js serve --node-env development --progress --allowed-hosts all",
"serve": "NODE_ENV=development rspack serve",
"stylelint": "stylelint \"src/**/*.scss\" \"src/**/*.vue\"",
"stylelint:fix": "stylelint \"src/**/*.scss\" \"src/**/*.vue\" --fix",
"test": "vitest",
@ -26,7 +24,7 @@
"typescript:check": "vue-tsc --noEmit",
"typescript:generate": "npx openapi-typescript -t",
"typescript:generate-core-types": "./src/types/generate-core-types.sh",
"watch": "node --max-old-space-size=4096 ./node_modules/webpack/bin/webpack.js --node-env development --progress --watch"
"watch": "rspack build --env development --watch"
},
"browserslist": [
"extends @nextcloud/browserslist-config"

211
rspack.config.js Normal file
View file

@ -0,0 +1,211 @@
/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
const browserslistConfig = require('@nextcloud/browserslist-config')
const { defineConfig } = require('@rspack/cli')
const { CssExtractRspackPlugin, LightningCssMinimizerRspackPlugin, DefinePlugin, IgnorePlugin, ProgressPlugin, SwcJsMinimizerRspackPlugin } = require('@rspack/core')
const NodePolyfillPlugin = require('@rspack/plugin-node-polyfill')
const path = require('node:path')
const { VueLoaderPlugin } = require('vue-loader')
module.exports = defineConfig((env) => {
const appName = process.env.npm_package_name
const appVersion = process.env.npm_package_version
const mode = (env.development && 'development') || (env.production && 'production') || process.env.NODE_ENV || 'production'
const isDev = mode === 'development'
process.env.NODE_ENV = mode
console.info('Building', appName, appVersion, '\n')
return {
target: 'web',
mode,
devtool: isDev ? 'cheap-source-map' : 'source-map',
entry: {
'admin-settings': path.join(__dirname, 'src', 'mainAdminSettings.js'),
collections: path.join(__dirname, 'src', 'collections.js'),
main: path.join(__dirname, 'src', 'main.js'),
recording: path.join(__dirname, 'src', 'mainRecording.js'),
'files-sidebar': [
path.join(__dirname, 'src', 'mainFilesSidebar.js'),
path.join(__dirname, 'src', 'mainFilesSidebarLoader.js'),
],
'public-share-auth-sidebar': path.join(__dirname, 'src', 'mainPublicShareAuthSidebar.js'),
'public-share-sidebar': path.join(__dirname, 'src', 'mainPublicShareSidebar.js'),
flow: path.join(__dirname, 'src', 'flow.js'),
deck: path.join(__dirname, 'src', 'deck.js'),
maps: path.join(__dirname, 'src', 'maps.js'),
search: path.join(__dirname, 'src', 'search.js'),
icons: path.join(__dirname, 'src', 'icons.css'),
},
output: {
path: path.resolve('./js'),
filename: `${appName}-[name].js?v=[contenthash]`,
chunkFilename: `${appName}-[name].js?v=[contenthash]`,
// Set publicPath via __webpack_public_path__
publicPath: 'auto',
// We are working with .wasm and .tflite files as resources on a public path: it must be with the original name in the output folder's root
assetModuleFilename: '[name][ext]?v=[contenthash]',
// We are working with .wasm files as resources on a public path: disabling default wasm loading as source
wasmLoading: false,
enabledWasmLoadingTypes: [],
clean: true,
devtoolNamespace: appName,
// Make sure sourcemaps have a proper path and do not leak local paths
// Source: @nextcloud/webpack-vue-config
devtoolModuleFilenameTemplate(info) {
const rootDir = process.cwd()
const rel = path.relative(rootDir, info.absoluteResourcePath)
return `webpack:///${appName}/${rel}`
},
},
devServer: {
hot: true,
host: '127.0.0.1',
port: 3000,
client: {
overlay: false,
},
devMiddleware: {
writeToDisk: true,
},
headers: {
'Access-Control-Allow-Origin': '*',
},
},
optimization: {
chunkIds: 'named',
splitChunks: {
automaticNameDelimiter: '-',
cacheGroups: {
defaultVendors: {
reuseExistingChunk: true,
},
},
},
minimize: !isDev,
minimizer: [
new SwcJsMinimizerRspackPlugin({
minimizerOptions: {
targets: browserslistConfig,
},
}),
new LightningCssMinimizerRspackPlugin({
minimizerOptions: {
targets: browserslistConfig,
},
}),
],
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
experimentalInlineMatchResource: true,
},
},
{
test: /\.css$/,
use: [
{
loader: CssExtractRspackPlugin.loader,
},
'css-loader',
],
},
{
test: /\.scss$/,
use: [
{
loader: CssExtractRspackPlugin.loader,
},
'css-loader',
'sass-loader',
],
},
{
test: /\.ts$/,
exclude: [/node_modules/],
loader: 'builtin:swc-loader',
options: {
jsc: {
parser: {
syntax: 'typescript',
},
},
env: {
targets: browserslistConfig,
},
},
type: 'javascript/auto',
},
{
test: /\.(png|jpe?g|gif|svg|webp)$/i,
type: 'asset',
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.wasm$/i,
type: 'asset/resource',
},
{
test: /\.tflite$/i,
type: 'asset/resource',
},
{
resourceQuery: /raw/,
type: 'asset/source',
},
],
},
plugins: [
new ProgressPlugin(),
new VueLoaderPlugin(),
new NodePolyfillPlugin(),
new DefinePlugin({
IS_DESKTOP: false,
__IS_DESKTOP__: false,
appName: JSON.stringify(appName),
appVersion: JSON.stringify(appVersion),
}),
new CssExtractRspackPlugin({
filename: '../css/talk-[name].css',
chunkFilename: '../css/chunks/[id].chunk.css',
ignoreOrder: true,
}),
new IgnorePlugin({
resourceRegExp: /^\.[/\\]locale$/,
contextRegExp: /moment[/\\]min$/,
}),
],
resolve: {
extensions: ['*', '.ts', '.js', '.vue'],
symlinks: false,
fallback: {
fs: false,
},
},
cache: true,
}
})

View file

@ -1,89 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
const nextcloudWebpackRules = require('@nextcloud/webpack-vue-config/rules')
const BabelLoaderExcludeNodeModulesExcept = require('babel-loader-exclude-node-modules-except')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { mergeWithRules } = require('webpack-merge')
// Replace rules with the same modules
module.exports = mergeWithRules({
module: {
rules: {
test: 'match',
loader: 'replace',
options: 'replace',
use: 'replace',
},
},
})(
{
module: {
// Reuse @nextcloud/webpack-vue-config/rules
rules: Object.values(nextcloudWebpackRules),
},
},
{
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
{
test: /\.js$/,
loader: 'esbuild-loader',
options: {
// Implicitly set as JS loader for only JS parts of Vue SFCs will be transpiled
loader: 'js',
target: 'es2020',
},
exclude: BabelLoaderExcludeNodeModulesExcept([
'@nextcloud/event-bus',
'ansi-regex',
'fast-xml-parser',
'hot-patcher',
'nextcloud-vue-collections',
'semver',
'strip-ansi',
'tributejs',
'webdav',
]),
},
{
test: /\.tsx?$/,
use: [{
loader: 'esbuild-loader',
options: {
// Implicitly set as TS loader so only <script lang="ts"> Vue SFCs will be transpiled
loader: 'ts',
target: 'es2020',
},
}],
},
{
test: /\.wasm$/i,
type: 'asset/resource',
},
{
test: /\.tflite$/i,
type: 'asset/resource',
},
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' },
},
{
resourceQuery: /raw/,
type: 'asset/source',
},
],
},
},
)

View file

@ -1,70 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
const nextcloudWebpackConfig = require('@nextcloud/webpack-vue-config')
const { EsbuildPlugin } = require('esbuild-loader')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const path = require('node:path')
const webpack = require('webpack')
const { mergeWithRules } = require('webpack-merge')
const commonWebpackConfig = require('./webpack.common.config.js')
module.exports = mergeWithRules({
module: {
// Rules from @nextcloud/webpack-vue-config/rules already added by commonWebpackConfig
rules: 'replace',
},
optimization: {
minimizer: 'replace',
},
})(nextcloudWebpackConfig, commonWebpackConfig, {
entry: {
'admin-settings': path.join(__dirname, 'src', 'mainAdminSettings.js'),
collections: path.join(__dirname, 'src', 'collections.js'),
main: path.join(__dirname, 'src', 'main.js'),
recording: path.join(__dirname, 'src', 'mainRecording.js'),
'files-sidebar': [
path.join(__dirname, 'src', 'mainFilesSidebar.js'),
path.join(__dirname, 'src', 'mainFilesSidebarLoader.js'),
],
'public-share-auth-sidebar': path.join(__dirname, 'src', 'mainPublicShareAuthSidebar.js'),
'public-share-sidebar': path.join(__dirname, 'src', 'mainPublicShareSidebar.js'),
flow: path.join(__dirname, 'src', 'flow.js'),
deck: path.join(__dirname, 'src', 'deck.js'),
maps: path.join(__dirname, 'src', 'maps.js'),
search: path.join(__dirname, 'src', 'search.js'),
icons: path.join(__dirname, 'src', 'icons.css'),
},
output: {
assetModuleFilename: '[name][ext]?v=[contenthash]',
},
optimization: {
splitChunks: {
cacheGroups: {
defaultVendors: {
reuseExistingChunk: true,
},
},
},
minimizer: [
new EsbuildPlugin({
target: 'es2020',
}),
],
},
plugins: [
new webpack.DefinePlugin({ IS_DESKTOP: false }),
new MiniCssExtractPlugin({
filename: '../css/talk-[name].css',
chunkFilename: '../css/chunks/[id].chunk.css',
ignoreOrder: true,
}),
],
cache: true,
})