Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 11 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@ As of 8.x the plugin no longer bundles its third-party libraries in its own
resources. It declares them as transitive runtime dependencies, served through
Spring Boot's standard `/webjars/**` classpath mapping, in two groups:

- **Replaceable** — `org.webjars.npm:jquery`, `org.webjars.npm:jquery-ui`,
`org.webjars.npm:bootstrap` and `org.webjars.npm:bootstrap-icons`, linked with
ordinary `<link>` and `<script>` tags. An application that already ships these
can supply its own; see below.
- **Replaceable** — `org.webjars.npm:jquery`, `org.webjars.npm:bootstrap` and
`org.webjars.npm:bootstrap-icons`, linked with ordinary `<link>` and `<script>`
tags. An application that already ships these can supply its own; see below.
- **Not replaceable** — CodeMirror 6, which is ES-module-only. The plugin emits
an import map covering the whole module graph (`@codemirror/*`, `@lezer/*`,
`style-mod`, `crelt`, `w3c-keyname`, `@marijn/find-cluster-break`) and a module
Expand All @@ -37,15 +36,15 @@ Spring Boot's standard `/webjars/**` classpath mapping, in two groups:
Two things follow:

- If your security configuration restricts URLs, `/webjars/**` must remain
reachable. Bootstrap and the jQuery UI theme only affect styling, but the
console does not work at all without jQuery, jQuery UI or CodeMirror.
reachable. Bootstrap only affects styling, but the console does not work at
all without jQuery or CodeMirror.
- The console links whatever webjar version your application actually resolves
(your dependency management — typically the `grails-bom` platform — wins over
the plugin's requested version), so overriding any of these versions in your
app is safe. jQuery has no version pinned here at all; the BOM supplies it.
The BOM does not manage jQuery UI or CodeMirror, so those carry versions from
the plugin's `gradle.properties`, and the import map resolves each module's
version from the classpath at render time.
The BOM does not manage CodeMirror, so those carry versions from the plugin's
`gradle.properties`, and the import map resolves each module's version from
the classpath at render time.

#### Supplying your own copies

Expand All @@ -67,14 +66,13 @@ Drop the four runtime dependencies too, so those jars stop shipping:
```groovy
implementation('org.grails.plugins:grails-web-console:8.0.0') {
exclude group: 'org.webjars.npm', module: 'jquery'
exclude group: 'org.webjars.npm', module: 'jquery-ui'
exclude group: 'org.webjars.npm', module: 'bootstrap'
exclude group: 'org.webjars.npm', module: 'bootstrap-icons'
}
```

Do not exclude the `codemirror__*` artifacts; the editor will not load without
them. Supplying the other four then becomes your job — all of them, since the
them. Supplying the other three then becomes your job — all of them, since the
flag is all-or-nothing. Point `grails.plugin.console.layout` at a layout of your
own that emits the tags ahead of `<g:layoutHead/>`, so the console's own
stylesheets still win the cascade and jQuery is defined before its bundle runs:
Expand All @@ -83,16 +81,14 @@ stylesheets still win the cascade and jQuery is defined before its bundle runs:
<head>
<asset:stylesheet href="webjars/bootstrap/%/dist/css/bootstrap.css"/>
<asset:stylesheet href="webjars/bootstrap-icons/%/font/bootstrap-icons.css"/>
<asset:stylesheet href="webjars/jquery-ui/%/dist/themes/base/jquery-ui.css"/>
<asset:javascript src="webjars/jquery/%/dist/jquery.js"/>
<asset:javascript src="webjars/jquery-ui/%/dist/jquery-ui.js"/>
<asset:javascript src="webjars/bootstrap/%/dist/js/bootstrap.bundle.js"/>
<g:layoutHead/>
</head>
```

Order matters: jQuery UI extends jQuery. The bundled `app/` in this repository
runs exactly this arrangement, so the recipe is exercised on every build.
The bundled `app/` in this repository runs exactly this arrangement, so the
recipe is exercised on every build.

`%` (or `*`) stands in for the version, so the layout survives a Bootstrap
upgrade — asset-pipeline matches it against the compiled manifest in production
Expand Down
5 changes: 0 additions & 5 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ dependencies {
testAndDevelopmentOnly("org.webjars.npm:jquery")
testAndDevelopmentOnly("org.webjars.npm:bootstrap")
testAndDevelopmentOnly("org.webjars.npm:bootstrap-icons")
testAndDevelopmentOnly("org.webjars.npm:jquery-ui:1.14.2") // not managed by grails-bom
runtimeOnly("com.h2database:h2")
runtimeOnly("org.apache.tomcat:tomcat-jdbc")
runtimeOnly("org.fusesource.jansi:jansi")
Expand All @@ -56,7 +55,6 @@ dependencies {
// the plugin's import map.
implementation(project(':grails-web-console')) {
exclude group: 'org.webjars.npm', module: 'jquery'
exclude group: 'org.webjars.npm', module: 'jquery-ui'
exclude group: 'org.webjars.npm', module: 'bootstrap'
exclude group: 'org.webjars.npm', module: 'bootstrap-icons'
}
Expand All @@ -79,9 +77,6 @@ assets {
'webjars/bootstrap/*/dist/css/bootstrap.css',
'webjars/bootstrap-icons/*/font/bootstrap-icons.css',
'webjars/bootstrap-icons/*/font/fonts/*',
'webjars/jquery-ui/*/dist/jquery-ui.js',
'webjars/jquery-ui/*/dist/themes/base/jquery-ui.css',
'webjars/jquery-ui/*/dist/themes/base/images/*',
]
}

Expand Down
2 changes: 0 additions & 2 deletions app/grails-app/views/layouts/console.gsp
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
jQuery is defined before its bundle runs. --%>
<asset:stylesheet href="webjars/bootstrap/%/dist/css/bootstrap.css"/>
<asset:stylesheet href="webjars/bootstrap-icons/%/font/bootstrap-icons.css"/>
<asset:stylesheet href="webjars/jquery-ui/%/dist/themes/base/jquery-ui.css"/>
<asset:javascript src="webjars/jquery/%/dist/jquery.js"/>
<asset:javascript src="webjars/jquery-ui/%/dist/jquery-ui.js"/>
<asset:javascript src="webjars/bootstrap/%/dist/js/bootstrap.bundle.js"/>
<g:layoutHead/>
</head>
Expand Down
6 changes: 0 additions & 6 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,9 @@ testLoggerVersion=4.0.0

# Served as webjars at runtime; the gulp build reads these to generate the
# /webjars/... links in the GSP fragments (single source of truth for both)
bootstrapVersion=5.3.8
jqueryUiVersion=1.14.2
codeMirrorStateVersion=6.7.1
codeMirrorViewVersion=6.43.6
codeMirrorCommandsVersion=6.10.2
codeMirrorLanguageVersion=6.12.4
codeMirrorLegacyModesVersion=6.5.1
codeMirrorThemeOneDarkVersion=6.1.3
bootstrapIconsVersion=1.13.1

org.gradle.caching=true
org.gradle.daemon=true
Expand Down
52 changes: 7 additions & 45 deletions gulp-tasks/paths.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,18 @@
import fs from 'node:fs';

export const timestamp = new Date().getTime();

// Webjar versions are defined once in gradle.properties: the plugin declares the
// matching org.webjars.npm dependencies, and the build below writes /webjars/...
// links for them into the GSP fragments.
const gradleProperties = Object.fromEntries(
fs.readFileSync('./gradle.properties', 'utf8')
.split('\n')
.filter(line => line.includes('=') && !line.trim().startsWith('#'))
.map(line => line.split('=', 2).map(part => part.trim()))
);

const bootstrapVersion = gradleProperties.bootstrapVersion;
const bootstrapIconsVersion = gradleProperties.bootstrapIconsVersion;
const jqueryUiVersion = gradleProperties.jqueryUiVersion;

// Assets served from the consuming app's webjar classpath rather than copied
// into the plugin's public resources. The generated GSP resolves the version
// present on the runtime classpath (the app's dependency management may pick a
// different one than this plugin requested); defaultVersion is only a fallback.
// into the plugin's public resources. Every version is resolved from the runtime
// classpath at render time, since the consuming app's dependency management —
// the grails-bom platform manages all three — decides which one actually wins.
const webjars = {
css: [
{ name: 'bootstrap', file: 'dist/css/bootstrap.min.css', defaultVersion: bootstrapVersion },
{ name: 'bootstrap-icons', file: 'font/bootstrap-icons.min.css', defaultVersion: bootstrapIconsVersion },
// the theme's images/ sit beside this file inside the jar, so its relative
// url() references resolve without copying anything into the plugin
{ name: 'jquery-ui', file: 'dist/themes/base/jquery-ui.min.css', defaultVersion: jqueryUiVersion },
{ name: 'bootstrap', file: 'dist/css/bootstrap.min.css' },
{ name: 'bootstrap-icons', file: 'font/bootstrap-icons.min.css' },
],
js: [
// jQuery first: the console's bundle expects it as a global. No
// defaultVersion — the Grails BOM manages this one, so there is no
// build-time version here to fall back to.
// jQuery first: the console's bundle expects it as a global
{ name: 'jquery', file: 'dist/jquery.min.js' },
{ name: 'jquery-ui', file: 'dist/jquery-ui.min.js', defaultVersion: jqueryUiVersion },
{ name: 'bootstrap', file: 'dist/js/bootstrap.bundle.min.js', defaultVersion: bootstrapVersion },
{ name: 'bootstrap', file: 'dist/js/bootstrap.bundle.min.js' },
],
};

Expand Down Expand Up @@ -66,10 +44,6 @@ const moduleWebjars = [
];

const vendorCssAssets = [
{
src: './web/vendor/jquery-layout/css/jquery.layout.css',
publicPath: '/vendor/jquery-layout/css/jquery.layout.css',
},
];

const vendorJsAssets = [
Expand All @@ -93,18 +67,6 @@ const vendorJsAssets = [
src: './node_modules/handlebars/dist/handlebars.runtime.min.js',
publicPath: '/vendor/js/libs/handlebars.runtime.min.js',
},
{
src: './web/vendor/js/plugins/jquery.selector-polyfill.js',
publicPath: '/vendor/js/plugins/jquery.selector-polyfill.js',
},
{
src: './web/vendor/jquery-layout/js/jquery.layout-latest.min.js',
publicPath: '/vendor/jquery-layout/js/jquery.layout-latest.min.js',
},
{
src: './web/vendor/js/plugins/jquery.hotkeys.js',
publicPath: '/vendor/js/plugins/jquery.hotkeys.js',
},
];

export const paths = {
Expand Down
24 changes: 10 additions & 14 deletions plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,22 @@ dependencies {
console("org.apache.grails:grails-console")

// Served by Spring Boot's /webjars/** classpath mapping in the consuming app.
// The platform manages jQuery, so its version lives in the Grails BOM rather
// than here; the generated GSP resolves whatever the app's own dependency
// management settles on regardless (see WebjarVersions). Bootstrap, jQuery UI and CodeMirror
// are not in the BOM, so their versions stay in gradle.properties, read by both
// this build and gulp so the /webjars links it writes cannot drift.
// The platform manages jQuery, Bootstrap and Bootstrap Icons, so no version is
// stated for them; the generated GSP resolves whatever the consuming app's own
// dependency management settles on regardless (see WebjarVersions). CodeMirror
// is not in the BOM, so those versions live in gradle.properties.
runtimeOnly platform("org.apache.grails:grails-bom:$grailsVersion")
runtimeOnly "org.webjars.npm:jquery"
runtimeOnly "org.webjars.npm:jquery-ui:${project.jqueryUiVersion}"
// CodeMirror 6 is ESM-only, so index.gsp emits an import map over these and a
// module shim rather than plain script tags. Only what the editor imports is
// declared; the rest of the graph (@lezer/*, style-mod, crelt, w3c-keyname,
// @marijn/find-cluster-break) arrives transitively from these webjars' POMs.
runtimeOnly "org.webjars.npm:codemirror__state:${project.codeMirrorStateVersion}"
runtimeOnly "org.webjars.npm:codemirror__view:${project.codeMirrorViewVersion}"
// module shim rather than plain script tags. Only the three roots are declared
// — @codemirror/state, view and language are dependencies of all of them, and
// arrive transitively along with @lezer/*, style-mod, crelt, w3c-keyname and
// @marijn/find-cluster-break.
runtimeOnly "org.webjars.npm:codemirror__commands:${project.codeMirrorCommandsVersion}"
runtimeOnly "org.webjars.npm:codemirror__language:${project.codeMirrorLanguageVersion}"
runtimeOnly "org.webjars.npm:codemirror__legacy-modes:${project.codeMirrorLegacyModesVersion}"
runtimeOnly "org.webjars.npm:codemirror__theme-one-dark:${project.codeMirrorThemeOneDarkVersion}"
runtimeOnly "org.webjars.npm:bootstrap:${project.bootstrapVersion}"
runtimeOnly "org.webjars.npm:bootstrap-icons:${project.bootstrapIconsVersion}"
runtimeOnly "org.webjars.npm:bootstrap"
runtimeOnly "org.webjars.npm:bootstrap-icons"

testImplementation platform("org.apache.grails:grails-bom:$grailsVersion")
testImplementation("org.apache.grails:grails-testing-support-web")
Expand Down
1 change: 0 additions & 1 deletion run-jasmine-jsdom.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ const loadBrowserLibraries = context => {
'node_modules/backbone.radio/build/backbone.radio.min.js',
'node_modules/backbone.marionette/lib/backbone.marionette.min.js',
'node_modules/handlebars/dist/handlebars.runtime.min.js',
'web/vendor/js/plugins/jquery.hotkeys.js',
].forEach(file => loadScript(resolveFromRoot(file), context));
};

Expand Down
16 changes: 13 additions & 3 deletions web/app/app.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,22 @@ Application = Marionette.Application.extend
$('body').css 'visibility', 'visible'

_initKeybindings: ->
$(document).on 'keydown', null, 'Ctrl+return Meta+return', => @execute 'execute'
$(document).on 'keydown', null, 'Ctrl+s Meta+s', (event) =>
# Document-wide so the shortcuts work with focus anywhere — the scripts
# panel, nothing at all. App.Util.Keys applies the two guards: not while
# typing in a field, and not for keys the editor's own keymap already
# handled.
keys = App.Util.Keys

keys.global ((event) -> event.key is 'Enter' and keys.mod event), =>
@execute 'execute'

keys.global ((event) -> event.key?.toLowerCase() is 's' and keys.mod event), (event) =>
event.preventDefault()
event.stopPropagation()
@execute 'save'
$(document).on 'keydown', null, 'esc', => @execute 'clear'

keys.global ((event) -> event.key is 'Escape'), =>
@execute 'clear'

createLink: (action, params) ->
link = "#{@data.baseUrl}/#{action}"
Expand Down
Loading