diff --git a/README.md b/README.md index 0df869c..7b0ef5c 100644 --- a/README.md +++ b/README.md @@ -19,24 +19,23 @@ $ cd cspace-ui-plugin-profile-bonsai.js $ npm install ``` -To run the cspace-ui application configured with this plugin: +To run the cspace-ui application configured with this plugin in development, using a remote +back-end CollectionSpace server: ``` -$ npm run devserver +$ npm run devserver --back-end=https://bonsai.dev.collectionspace.org ``` Then open a browser to http://localhost:8080. -By default, the application served from the dev server will use the CollectionSpace services API -located at http://localhost:8180. - -To run the application against CollectionSpace services located on a different host, edit -index.html, and change the `serverUrl` configuration property. For example, to use a server running -on nightly.collectionspace.org, port 8180, use the settings: +Alternatively, to run the cspace-ui application configured with this plugin in development, using +the UI configuration in index.html: ``` -cspaceUI({ - serverUrl: 'http://nightly.collectionspace.org:8180', - // ... -}); +$ npm run devserver ``` + +By default, the configuration in index.html uses the CollectionSpace services API located at +http://localhost:8180. To run the application against CollectionSpace services located on a +different host, edit index.html, and change the `serverUrl` configuration property. Note that the +specified server must be configured to allow CORS requests from http://localhost:8080. diff --git a/index.html b/index.html index 93b7428..ead5de1 100644 --- a/index.html +++ b/index.html @@ -26,7 +26,7 @@ This bundle is served from the URL /cspaceUIPluginProfileBonsai.js, but it does not exist in the filesystem. --> - +
diff --git a/webpack.config.js b/webpack.config.js index 64ce73e..aa83116 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,14 @@ const { execSync } = require('child_process'); const path = require('path'); const webpack = require('webpack'); +const webpackDevServerConfig = require('./webpackDevServerConfig'); + +/** + * The public path to local webpack assets. This is chosen to have low chance of collision with any + * path on a proxied back-end (e.g., "/cspace/core" or "/cspace-services"). This should start and + * end with slashes. + */ +const publicPath = '/webpack-dev-assets/'; const library = 'cspaceUIPluginProfileBonsai'; const isProduction = process.env.NODE_ENV === 'production'; @@ -24,7 +32,7 @@ try { console.log('Failed to get repository url from npm: %s', err.stderr.toString()); } -const config = { +module.exports = async () => ({ mode: isProduction ? 'production' : 'development', entry: './src/index.js', output: { @@ -33,6 +41,7 @@ const config = { libraryTarget: 'umd', libraryExport: 'default', path: path.resolve(__dirname, 'dist'), + publicPath, }, module: { rules: [ @@ -87,12 +96,10 @@ const config = { resolve: { extensions: ['.js', '.jsx'], }, - devServer: { - historyApiFallback: true, - static: { - directory: __dirname, - }, - }, -}; - -module.exports = config; + devServer: await webpackDevServerConfig({ + library, + localIndex: process.env.npm_config_local_index, + proxyTarget: process.env.npm_config_back_end, + publicPath, + }), +}); diff --git a/webpackDevServerConfig.js b/webpackDevServerConfig.js new file mode 100644 index 0000000..9d43429 --- /dev/null +++ b/webpackDevServerConfig.js @@ -0,0 +1,321 @@ +/* global fetch */ +/* eslint import/no-extraneous-dependencies: "off" */ +/* eslint-disable no-console */ + +const fs = require('fs'); + +const { + createProxyMiddleware, + responseInterceptor, +} = require('http-proxy-middleware'); + +/** + * Generates a regular expression that matches a script URL for a given library. + * + * @param library The name of the library. + * @returns A regular expression that detects if the library is used on an HTML page. + */ +const scriptUrlPattern = (library) => new RegExp(`src=".*?/${library}(@.*?)?(\\.min)?\\.js"`, 'g'); + +/** + * Determines if an HTML page uses a given library. + * + * @param page The HTML content of the page. + * @param library The name of the library. + * @returns true if the page uses the library; false otherwise. + */ +const pageUsesLibrary = (page, library) => scriptUrlPattern(library).test(page); + +/** + * Determines if a given library is a CSpace UI plugin that can be injected into an HTML page. + * + * @param page The HTML content of the page. + * @param library The name of the library. + * @returns true if the library is a plugin that can be injected; false otherwise. + */ +const canInjectLibraryAsPlugin = (page, library) => ( + library.startsWith('cspaceUIPlugin') + && page.includes('cspaceUI({') +); + +/** + * Verifies that a given target URL can be used as a back-end for a given library under + * development. If not, print a message and exit. + * + * A URL can be used as a back end if: + * - It is a valid URL. + * - It is reachable. + * - It returns HTML content that we know how to inject the library into, i.e. it has a + * conventional CSpace UI index.html page. + * + * @param proxyTarget The URL to verify. + * @param library The name of the library. + */ +const verifyTarget = async (proxyTarget) => { + try { + // eslint-disable-next-line no-unused-vars + const verifiedUrl = new URL(proxyTarget); + } catch (error) { + console.error(`The back-end URL ${proxyTarget} is not a valid URL.`); + process.exit(1); + } + + let response; + + try { + response = await fetch(proxyTarget); + } catch (error) { + response = null; + } + + if (!(response && response.ok)) { + console.error(`The back-end URL ${proxyTarget} is not reachable.`); + process.exit(1); + } +}; + +/** + * Inject an element containing a status message into a CSpace HTML page. + * + * @param page The HTML content of the page. + * @param status The status message. + * @returns The HTML content of the page with the status message injected. + */ +const injectStatusElement = (page, status) => page.replace( + '', + ` + +