This is an automated email from the ASF dual-hosted git repository.
dsmiley pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 255e9e7b85e build: decouple JS API client bundling into an optional
module (#4603)
255e9e7b85e is described below
commit 255e9e7b85efb801f4dad1c557aaa4ff5d6c4e0e
Author: David Smiley <[email protected]>
AuthorDate: Fri Jul 10 23:21:17 2026 -0400
build: decouple JS API client bundling into an optional module (#4603)
The OpenAPI-generated JS client (npm install + babel build + browserify
bundle)
was embedded directly in :solr:webapp's build.gradle, making that module
quietly
depend on npm/node for something unrelated to compiling/packaging the webapp
itself. Pull it out into its own module, :solr:webapp:js-client, which
exposes
the built bundle via a `jsClientBundle` configuration that :solr:webapp now
consumes as a normal cross-project dependency (mirroring how it already
consumes
:solr:server's serverLib/solrCore and :solr:api's jsClient outputs),
instead of
:solr:webapp defining the npm tasks and reaching into its own local task
outputs.
Also make the JS client bundle optional, mirroring the existing
disableUiModule
pattern:
- settings.gradle: new -PdisableJsClient=true flag
(gradle.ext.withJsClient),
excluding :solr:webapp:js-client from the build entirely when set.
- solr/webapp/build.gradle: the generatedJSClientBundle dependency is only
added
when the flag is on; the war{} block's
from(configurations.generatedJSClientBundle)
is left unconditional since an empty configuration is already a safe
no-op.
Refactor gradle/node.gradle so it no longer hardcodes which projects need
node/npm.
---
build.gradle | 1 +
gradle/node.gradle | 67 ++++++++++---------
settings.gradle | 6 ++
solr/solr-ref-guide/build.gradle | 1 +
solr/webapp/build.gradle | 91 ++-----------------------
solr/webapp/gradle.lockfile | 2 +-
solr/webapp/js-client/build.gradle.kts | 119 +++++++++++++++++++++++++++++++++
solr/webapp/js-client/gradle.lockfile | 5 ++
8 files changed, 174 insertions(+), 118 deletions(-)
diff --git a/build.gradle b/build.gradle
index dd939aac66a..80c97fe1a0f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -132,6 +132,7 @@ ext {
// Use gradle flag in root project for further referencing
withUiModule = gradle.ext.withUiModule
+ withJsClient = gradle.ext.withJsClient
}
// Include smaller chunks configuring dedicated build areas.
diff --git a/gradle/node.gradle b/gradle/node.gradle
index 0cf7923bac0..5934db87fdd 100644
--- a/gradle/node.gradle
+++ b/gradle/node.gradle
@@ -15,47 +15,50 @@
* limitations under the License.
*/
-configure([project(":solr:solr-ref-guide"), project(":solr:webapp")]) {
- apply plugin: libs.plugins.nodegradle.node.get().pluginId
+// Projects that need node/npm apply the node-gradle plugin themselves; this
shared
+// config reacts to whichever projects do so, rather than naming them
explicitly.
+allprojects {
+ pluginManager.withPlugin(libs.plugins.nodegradle.node.get().pluginId) {
- def npmRegistry = "${-> propertyOrEnvOrDefault("solr.npm.registry",
"SOLR_NPM_REGISTRY", '')}"
- if (!npmRegistry.isEmpty()) {
- tasks.npmSetup {
- args.addAll(['--registry', npmRegistry])
- }
-
- afterEvaluate {
- tasks.withType(NpmTask).each {npmTask ->
- npmTask.environment.put('NPM_CONFIG_REGISTRY', npmRegistry)
+ def npmRegistry = "${-> propertyOrEnvOrDefault("solr.npm.registry",
"SOLR_NPM_REGISTRY", '')}"
+ if (!npmRegistry.isEmpty()) {
+ tasks.npmSetup {
+ args.addAll(['--registry', npmRegistry])
}
- tasks.withType(NpxTask).each {npxTask ->
- npxTask.environment.put('NPM_CONFIG_REGISTRY', npmRegistry)
+
+ afterEvaluate {
+ tasks.withType(NpmTask).each {npmTask ->
+ npmTask.environment.put('NPM_CONFIG_REGISTRY', npmRegistry)
+ }
+ tasks.withType(NpxTask).each {npxTask ->
+ npxTask.environment.put('NPM_CONFIG_REGISTRY', npmRegistry)
+ }
}
}
- }
- project.ext {
- nodeProjectDir = layout.projectDirectory.dir(".gradle/node")
- }
+ project.ext {
+ nodeProjectDir = layout.projectDirectory.dir(".gradle/node")
+ }
- node {
- download = true
- version = libs.versions.nodejs.get()
+ node {
+ download = true
+ version = libs.versions.nodejs.get()
- def nodeDistUrl = "${-> propertyOrEnvOrDefault("solr.node.distUrl",
"SOLR_NODE_DIST_URL", '')}"
- if (!nodeDistUrl.isEmpty()) {
- distBaseUrl = nodeDistUrl
- }
+ def nodeDistUrl = "${-> propertyOrEnvOrDefault("solr.node.distUrl",
"SOLR_NODE_DIST_URL", '')}"
+ if (!nodeDistUrl.isEmpty()) {
+ distBaseUrl = nodeDistUrl
+ }
- // The directory where Node.js is unpacked (when download is true)
- workDir = file("${project.ext.nodeProjectDir.getAsFile().path}/nodejs")
+ // The directory where Node.js is unpacked (when download is true)
+ workDir = file("${project.ext.nodeProjectDir.getAsFile().path}/nodejs")
- // The directory where npm is installed (when a specific version is
defined)
- npmWorkDir = file("${project.ext.nodeProjectDir.getAsFile().path}/npm")
+ // The directory where npm is installed (when a specific version is
defined)
+ npmWorkDir = file("${project.ext.nodeProjectDir.getAsFile().path}/npm")
- // The Node.js project directory location
- // This is where the package.json file and node_modules directory are
located
- // By default it is at the root of the current project
- nodeProjectDir = project.ext.nodeProjectDir
+ // The Node.js project directory location
+ // This is where the package.json file and node_modules directory are
located
+ // By default it is at the root of the current project
+ nodeProjectDir = project.ext.nodeProjectDir
+ }
}
}
diff --git a/settings.gradle b/settings.gradle
index 76938b2e2cd..be01caa7fd7 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -89,5 +89,11 @@ if (gradle.ext.withUiModule) {
include(":solr:ui")
}
+def disableJsClientValue = providers.gradleProperty('disableJsClient').orNull
+gradle.ext.withJsClient = disableJsClientValue == null || disableJsClientValue
!= 'true'
+if (gradle.ext.withJsClient) {
+ include(":solr:webapp:js-client")
+}
+
// Configures development for joint Lucene/ Solr composite build.
apply from: file('gradle/lucene-dev/lucene-dev-repo-composite.gradle')
diff --git a/solr/solr-ref-guide/build.gradle b/solr/solr-ref-guide/build.gradle
index 66f4437249b..cf95613fecb 100644
--- a/solr/solr-ref-guide/build.gradle
+++ b/solr/solr-ref-guide/build.gradle
@@ -19,6 +19,7 @@ import groovy.json.JsonOutput
import org.apache.tools.ant.util.TeeOutputStream
apply plugin: 'java'
+apply plugin: libs.plugins.nodegradle.node.get().pluginId
description = 'Solr Reference Guide'
diff --git a/solr/webapp/build.gradle b/solr/webapp/build.gradle
index 62d6a130562..6ca3fcc6175 100644
--- a/solr/webapp/build.gradle
+++ b/solr/webapp/build.gradle
@@ -26,14 +26,10 @@ configurations {
war {}
serverLib
solrCore
- generatedJSClient
generatedJSClientBundle
-}
-
-ext {
- jsClientWorkspace = layout.buildDirectory.dir("jsClientWorkspace").get()
- jsClientBuildDir = layout.buildDirectory.dir("jsClientBuild").get()
- jsClientBundleDir = layout.buildDirectory.dir("jsClientBundle").get()
+ // TODO: :solr:ui's dev/prod distributions are pulled in via raw
cross-project task
+ // references (see generateUiDevFiles/generateUiProdFiles below) instead of a
+ // configuration like this one; consider exposing them as a real
configuration too.
}
dependencies {
@@ -42,57 +38,11 @@ dependencies {
solrCore project(":solr:core")
implementation(configurations.solrCore - configurations.serverLib)
- generatedJSClient project(path: ":solr:api", configuration: "jsClient")
- generatedJSClientBundle files(jsClientBundleDir) {
- builtBy "finalizeJsBundleDir"
- }
-}
-
-task syncJSClientSourceCode(type: Sync) {
- group = 'Solr JS Client'
- from configurations.generatedJSClient
-
- into jsClientWorkspace
-
- // Keep the node modules, so that they don't need to be re-downloaded
- preserve {
- include "node_modules/**"
+ if (gradle.ext.withJsClient) {
+ generatedJSClientBundle project(path: ":solr:webapp:js-client",
configuration: "jsClientBundle")
}
}
-task jsClientDownloadDeps(type: NpmTask) {
- group = 'Solr JS Client'
- dependsOn tasks.syncJSClientSourceCode
-
- args = ["install"]
- workingDir = file(jsClientWorkspace)
-
- inputs.dir("${jsClientWorkspace}/src")
- inputs.file("${jsClientWorkspace}/package.json")
- outputs.dir("${jsClientWorkspace}/node_modules")
-}
-
-task jsClientBuild(type: NpmTask) {
- group = 'Solr JS Client'
- dependsOn tasks.jsClientDownloadDeps
-
- args = ["run", "build"]
- workingDir = file(jsClientWorkspace)
-
- inputs.dir("${jsClientWorkspace}/src")
- inputs.file("${jsClientWorkspace}/package.json")
- inputs.dir("${jsClientWorkspace}/node_modules")
- outputs.dir("${jsClientWorkspace}/dist")
-}
-
-task downloadBrowserify(type: NpmTask) {
- group = 'Build Dependency Download'
- args = ["install", "browserify@${libs.versions.browserify.get()}"]
-
- inputs.property("browserify version", libs.versions.browserify.get())
- outputs.dir("${nodeProjectDir}/node_modules/browserify")
-}
-
if (gradle.ext.withUiModule) {
tasks.register("generateUiDevFiles") {
description = "Generate new UI for development and add files to outputs
for later referencing."
@@ -113,39 +63,10 @@ if (gradle.ext.withUiModule) {
}
}
-task generateJsClientBundle(type: NpxTask) {
- group = 'Solr JS Client'
- dependsOn tasks.downloadBrowserify
- dependsOn tasks.jsClientBuild
-
- command = 'browserify'
- args = ['dist/index.js', '-s', 'solrApi', '-o',
"${jsClientBuildDir}/index.js"]
- workingDir = file(jsClientWorkspace)
-
- inputs.dir(jsClientWorkspace)
- inputs.property("browserify version", libs.versions.browserify.get())
-
- outputs.file("${jsClientBuildDir}/index.js")
-}
-
-task finalizeJsBundleDir(type: Sync) {
- group = 'Solr JS Client'
-
- from configurations.generatedJSClient {
- include "README.md"
- include "docs/**"
- }
-
- from tasks.generateJsClientBundle {
- include "index.js"
- }
-
- into jsClientBundleDir
-}
-
war {
from("web")
+ // note: nonetheless may be disabled, copying nothing
from(configurations.generatedJSClientBundle, {
into "libs/solr"
})
diff --git a/solr/webapp/gradle.lockfile b/solr/webapp/gradle.lockfile
index 5a024c3cdc6..50e9b9ad8ec 100644
--- a/solr/webapp/gradle.lockfile
+++ b/solr/webapp/gradle.lockfile
@@ -161,4 +161,4 @@ org.slf4j:jcl-over-slf4j:2.0.17=serverLib,solrCore
org.slf4j:jul-to-slf4j:2.0.17=serverLib
org.slf4j:slf4j-api:2.0.17=serverLib,solrCore
org.xerial.snappy:snappy-java:1.1.10.8=solrCore
-empty=compileClasspath,generatedJSClient,generatedJSClientBundle,jarValidation,missingdoclet,providedCompile,providedRuntime,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,war
+empty=compileClasspath,generatedJSClientBundle,jarValidation,missingdoclet,providedCompile,providedRuntime,runtimeClasspath,testCompileClasspath,testRuntimeClasspath,war
diff --git a/solr/webapp/js-client/build.gradle.kts
b/solr/webapp/js-client/build.gradle.kts
new file mode 100644
index 00000000000..1ce43c25978
--- /dev/null
+++ b/solr/webapp/js-client/build.gradle.kts
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import com.github.gradle.node.npm.task.NpmTask
+import com.github.gradle.node.npm.task.NpxTask
+
+// Builds the OpenAPI-generated JS client (from :solr:api) into a single
bundled
+// file, for :solr:webapp to include in the war. This is the only place in the
+// build that needs npm/node for this purpose; it can be disabled entirely via
+// -PdisableJsClient=true (see settings.gradle).
+
+plugins {
+ id("base")
+ alias(libs.plugins.nodegradle.node)
+}
+
+description = "Generates a JavaScript client for Solr OpenApi"
+
+val jsClientWorkspace =
layout.buildDirectory.dir("jsClientWorkspace").get().asFile
+val jsClientBuildDir = layout.buildDirectory.dir("jsClientBuild").get().asFile
+val jsClientBundleDir =
layout.buildDirectory.dir("jsClientBundle").get().asFile
+
+val generatedJSClient = configurations.create("generatedJSClient")
+val jsClientBundle = configurations.create("jsClientBundle") {
+ isCanBeConsumed = true
+ isCanBeResolved = false
+}
+
+dependencies {
+ generatedJSClient(project(path = ":solr:api", configuration = "jsClient"))
+}
+
+val syncJSClientSourceCode = tasks.register<Sync>("syncJSClientSourceCode") {
+ from(generatedJSClient)
+
+ into(jsClientWorkspace)
+
+ // Keep the node modules, so that they don't need to be re-downloaded
+ preserve {
+ include("node_modules/**")
+ }
+}
+
+val jsClientDownloadDeps = tasks.register<NpmTask>("jsClientDownloadDeps") {
+ dependsOn(syncJSClientSourceCode)
+
+ args.set(listOf("install"))
+ workingDir.set(jsClientWorkspace)
+
+ inputs.dir("$jsClientWorkspace/src")
+ inputs.file("$jsClientWorkspace/package.json")
+ outputs.dir("$jsClientWorkspace/node_modules")
+}
+
+val jsClientBuild = tasks.register<NpmTask>("jsClientBuild") {
+ dependsOn(jsClientDownloadDeps)
+
+ args.set(listOf("run", "build"))
+ workingDir.set(jsClientWorkspace)
+
+ inputs.dir("$jsClientWorkspace/src")
+ inputs.file("$jsClientWorkspace/package.json")
+ inputs.dir("$jsClientWorkspace/node_modules")
+ outputs.dir("$jsClientWorkspace/dist")
+}
+
+val downloadBrowserify = tasks.register<NpmTask>("downloadBrowserify") {
+ args.set(listOf("install", "browserify@${libs.versions.browserify.get()}"))
+
+ inputs.property("browserify version", libs.versions.browserify.get())
+ outputs.dir(project.extra["nodeProjectDir"].toString() +
"/node_modules/browserify")
+}
+
+val generateJsClientBundle = tasks.register<NpxTask>("generateJsClientBundle")
{
+ dependsOn(downloadBrowserify)
+ dependsOn(jsClientBuild)
+
+ command.set("browserify")
+ args.set(listOf("dist/index.js", "-s", "solrApi", "-o",
"$jsClientBuildDir/index.js"))
+ workingDir.set(jsClientWorkspace)
+
+ inputs.dir(jsClientWorkspace)
+ inputs.property("browserify version", libs.versions.browserify.get())
+
+ outputs.file("$jsClientBuildDir/index.js")
+}
+
+val finalizeJsBundleDir = tasks.register<Sync>("finalizeJsBundleDir") {
+ from(generatedJSClient) {
+ include("README.md")
+ include("docs/**")
+ }
+
+ from(generateJsClientBundle) {
+ include("index.js")
+ }
+
+ into(jsClientBundleDir)
+}
+
+artifacts {
+ add("jsClientBundle", jsClientBundleDir) {
+ builtBy(finalizeJsBundleDir)
+ }
+}
diff --git a/solr/webapp/js-client/gradle.lockfile
b/solr/webapp/js-client/gradle.lockfile
new file mode 100644
index 00000000000..3a4ae5a9eaa
--- /dev/null
+++ b/solr/webapp/js-client/gradle.lockfile
@@ -0,0 +1,5 @@
+# This is a Gradle generated file for dependency locking.
+# Manual edits can break the build and are not advised.
+# This file is expected to be part of source control.
+# To regenerate this file, run: ./gradlew :solr:webapp:js-client:dependencies
--write-locks
+empty=generatedJSClient,jarValidation