@redhat-cloud-services/frontend-components-config-utilities 1.5.3 → 1.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -123,3 +123,71 @@ This packages exposes these federated shared dependencies
123
123
  * `@redhat-cloud-services/frontend-components` - version taken from your `package.json`
124
124
  * `@redhat-cloud-services/frontend-components-utilities` - version taken from your `package.json`
125
125
  * `@redhat-cloud-services/frontend-components-notifications` - version taken from your `package.json`
126
+
127
+ ## Extensions plugin
128
+
129
+ In order to share some code into extension points or to add new extension point we can use `ExtensionsPlugin`
130
+
131
+ Simply import it in your webpack config and add it to your plugins
132
+
133
+ ```JS
134
+ const { resolve } = require('path');
135
+ const config = require('@redhat-cloud-services/frontend-components-config');
136
+ const ExtensionsPlugin = require('@redhat-cloud-services/frontend-components-config/extensions-plugin');
137
+
138
+ const { config: webpackConfig, plugins } = config({
139
+ rootFolder: resolve(__dirname, '../'),
140
+ ...(process.env.BETA && { deployment: 'beta/apps' }),
141
+ });
142
+
143
+ plugins.push(
144
+ require('@redhat-cloud-services/frontend-components-config/federated-modules')({
145
+ root: resolve(__dirname, '../'),
146
+ }),
147
+ new ExtensionsPlugin({})
148
+ );
149
+
150
+ module.exports = {
151
+ ...webpackConfig,
152
+ plugins
153
+ }
154
+ ```
155
+
156
+ ### Arguments
157
+
158
+ There are three arguments `ExtensionsPlugin` constructor accepts:
159
+ * `pluginConfig`
160
+ * `fedModuleConfig`
161
+ * `options`
162
+
163
+ ### `pluginConfig`
164
+
165
+ This config contains information about extensions, plugin requirements, its name and description. Most of it (name, description and version) is calculated from your root `package.json`. But you can override these values:
166
+
167
+ * `name` - plugin name (pulled from `package.json`)
168
+ * `version` - version of the plugin
169
+ * `displayName` - display name of the plugin
170
+ * `description` - description of the plugin (pulled from `package.json`)
171
+ * `dependencies` - object of dependencies which will be passed down to module federation (no need to list general react dependencies)
172
+ * `disableStaticPlugins` - list of static plugins this plugin disables on load
173
+ * `extensions` - list of extension objects.
174
+
175
+ #### extension object
176
+
177
+ Each extension object requires a `type` and `properties`. The type can be either custom extension or one of predefined:
178
+
179
+ * `console.navigation/section` - a section in navigation (identifies secondary nav)
180
+ * `properties`
181
+ * `id` - id of the section
182
+ * `name` - name of the section, this will be shown in the UI
183
+ * `console.page/route` - route passed to react-router
184
+ * `properties` - in theory any react-router path prop can be used here
185
+ * `path` - (string, or array) path on which the component will be rendered
186
+ * `component`
187
+ * `$codeRef` - federated module used to render on the route
188
+ * `console.navigation/href` - navigation href, used to render leafs of navigation
189
+ * `properties`
190
+ * `id` - id of the href
191
+ * `section` - (optional) used to group nav items under section (omit for flat nav)
192
+ * `name` - name of the href, thiw will be shown in the UI
193
+ * `href` - used to mutate the URL
@@ -0,0 +1,77 @@
1
+ const webpack = require('webpack');
2
+
3
+ class ExtensionsMapper {
4
+ constructor(plugin, options) {
5
+ if (!plugin) {
6
+ throw new Error('Missing plugin config!');
7
+ }
8
+
9
+ this.plugin = plugin;
10
+ this.options = {
11
+ remoteEntryCallback: 'window.loadPluginEntry',
12
+ remoteEntryFile: 'plugin-entry.js',
13
+ pluginManifestFile: 'plugin-manifest.json',
14
+ ...options
15
+ };
16
+ }
17
+
18
+ apply(compiler) {
19
+ compiler.hooks.thisCompilation.tap(ExtensionsMapper.name, (compilation) => {
20
+ // Generate extensions manifest
21
+ compilation.hooks.processAssets.tap(
22
+ {
23
+ name: ExtensionsMapper.name,
24
+ stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL
25
+ },
26
+ () => {
27
+ compilation.emitAsset(
28
+ this.options.pluginManifestFile,
29
+ new webpack.sources.RawSource(
30
+ Buffer.from(JSON.stringify({
31
+ version: '0.0.0',
32
+ description: '',
33
+ displayName: '',
34
+ dependencies: {},
35
+ disableStaticPlugins: {},
36
+ ...this.plugin
37
+ }, null, 2))
38
+ )
39
+ );
40
+ }
41
+ );
42
+
43
+ // Update plugin-entry.js file
44
+ compilation.hooks.processAssets.tap(
45
+ {
46
+ name: ExtensionsMapper.name,
47
+ stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
48
+ },
49
+ () => {
50
+ compilation.updateAsset(this.options.remoteEntryFile, (source) => {
51
+ const newSource = new webpack.sources.ReplaceSource(source);
52
+
53
+ const fromIndex = source
54
+ .source()
55
+ .toString()
56
+ .indexOf(`${this.options.remoteEntryCallback}(`);
57
+
58
+ if (fromIndex < 0) {
59
+ const error = new webpack.WebpackError(`Missing call to ${this.options.remoteEntryCallback}`);
60
+ error.file = this.options.remoteEntryFile;
61
+ compilation.errors.push(error);
62
+ } else {
63
+ newSource.insert(
64
+ fromIndex + this.options.remoteEntryCallback.length + 1,
65
+ `'${this.plugin.name}@${this.plugin.version}', `
66
+ );
67
+ }
68
+
69
+ return newSource;
70
+ });
71
+ }
72
+ );
73
+ });
74
+ }
75
+ }
76
+
77
+ module.exports = ExtensionsMapper;
@@ -0,0 +1,54 @@
1
+ const { resolve } = require('path');
2
+ const fedModule = require('./federated-modules');
3
+ const ExtensionsMapper = require('./extension-mapper');
4
+
5
+ class ExtensionsPlugin {
6
+ constructor(plugin, fedMod, options) {
7
+ if (!plugin) {
8
+ throw new Error('Missing plugin config!');
9
+ }
10
+
11
+ this.plugin = plugin;
12
+ this.fedMod = {
13
+ moduleName: 'plugin-entry',
14
+ libName: 'window.loadPluginEntry',
15
+ useFileHash: false,
16
+ libType: 'jsonp',
17
+ ...fedMod
18
+ };
19
+ this.options = {
20
+ remoteEntryCallback: 'window.loadPluginEntry',
21
+ remoteEntryFile: `${this.fedMod.moduleName}.js`,
22
+ pluginManifestFile: 'plugin-manifest.json',
23
+ ...options
24
+ };
25
+ }
26
+
27
+ apply(compiler) {
28
+ const root = this.fedMod.root || compiler.context;
29
+ const { insights, version, description } = require(resolve(root, './package.json')) || {};
30
+
31
+ // create federation module
32
+ fedModule({
33
+ ...this.fedMod,
34
+ root
35
+ }).apply(compiler);
36
+
37
+ // generate plugin manifest and update plugin-entry file
38
+ new ExtensionsMapper(
39
+ {
40
+ ...this.plugin,
41
+ name: this.plugin.name || (insights && insights.appname),
42
+ version: this.plugin.version || version || '0.0.0',
43
+ displayName: this.plugin.displayName || '',
44
+ description: this.plugin.description || description || '',
45
+ dependencies: { ...this.plugin.dependencies || {} },
46
+ disableStaticPlugins: [ ...this.plugin.disableStaticPlugins || [] ],
47
+ extensions: [ ...this.plugin.extensions || [] ]
48
+ },
49
+ this.options
50
+ ).apply(compiler);
51
+ }
52
+ }
53
+
54
+ module.exports = ExtensionsPlugin;
@@ -27,6 +27,8 @@ module.exports = ({
27
27
  debug,
28
28
  moduleName,
29
29
  useFileHash = true,
30
+ libType = 'var',
31
+ libName,
30
32
  exclude = []
31
33
  }) => {
32
34
  const { dependencies, insights } = require(resolve(root, './package.json')) || {};
@@ -64,7 +66,7 @@ module.exports = ({
64
66
  return new ModuleFederationPlugin({
65
67
  name: appName,
66
68
  filename: `${appName}${useFileHash ? '.[chunkhash]' : ''}.js`,
67
- library: { type: 'var', name: appName },
69
+ library: { type: libType, name: libName || appName },
68
70
  exposes: {
69
71
  ...exposes || {
70
72
  './RootApp': resolve(root, './src/AppEntry')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redhat-cloud-services/frontend-components-config-utilities",
3
- "version": "1.5.3",
3
+ "version": "1.5.7",
4
4
  "description": "Utilities for shared config used in RedHat Cloud Services project.",
5
5
  "main": "index.js",
6
6
  "publishConfig": {
package/proxy.js CHANGED
@@ -31,7 +31,8 @@ module.exports = ({
31
31
  useCloud = false,
32
32
  target = '',
33
33
  keycloakUri = '',
34
- registry = []
34
+ registry = [],
35
+ isChrome = false
35
36
  }) => {
36
37
  const proxy = [];
37
38
  const majorEnv = env.split('-')[0];
@@ -50,12 +51,16 @@ module.exports = ({
50
51
  }
51
52
 
52
53
  let agent;
54
+
55
+ if (env.startsWith('prod') || env.startsWith('stage')) {
56
+ // PROD and stage are deployed with Akamai which requires a corporate proxy
57
+ agent = new HttpsProxyAgent(proxyURL);
58
+ }
59
+
53
60
  if (env.startsWith('stage')) {
54
61
  // stage-stable / stage-beta branches don't exist in build repos
55
62
  // Currently stage pulls from QA
56
63
  env = env.replace('stage', 'qa');
57
- // QA and stage are deployed with Akamai which requires a corporate proxy
58
- agent = new HttpsProxyAgent(proxyURL);
59
64
  }
60
65
 
61
66
  if (!Array.isArray(appUrl)) {
@@ -179,6 +184,19 @@ module.exports = ({
179
184
  return false;
180
185
  },
181
186
  target,
187
+ ...(isChrome && {
188
+ bypass: (req) => {
189
+ /**
190
+ * Bypass any HTML requests if using chrome
191
+ * Serves as a historyApiFallback when refreshing on any other URL than '/'
192
+ */
193
+ if (!req.url.match(/\/api\//) && !req.url.match(/\./) && req.headers.accept.includes('text/html')) {
194
+ return '/';
195
+ }
196
+
197
+ return null;
198
+ }
199
+ }),
182
200
  router: router(target, useCloud),
183
201
  ...(agent && {
184
202
  agent,
@@ -211,32 +229,38 @@ module.exports = ({
211
229
  },
212
230
  onBeforeSetupMiddleware({ app, compiler, options }) {
213
231
  app.enable('strict routing'); // trailing slashes are mean
214
- let chromePath = localChrome;
215
- if (standaloneConfig) {
216
- if (standaloneConfig.chrome) {
217
- chromePath = resolvePath(reposDir, standaloneConfig.chrome.path);
218
- keycloakUri = standaloneConfig.chrome.keycloakUri;
219
- }
220
- } else if (!localChrome && useProxy) {
221
- if (typeof defaultServices.chrome === 'function') {
222
- defaultServices.chrome = defaultServices.chrome({});
223
- }
232
+ /**
233
+ * Allow serving chrome assets
234
+ * This will allow running chrome as a host application
235
+ */
236
+ if (!isChrome) {
237
+ let chromePath = localChrome;
238
+ if (standaloneConfig) {
239
+ if (standaloneConfig.chrome) {
240
+ chromePath = resolvePath(reposDir, standaloneConfig.chrome.path);
241
+ keycloakUri = standaloneConfig.chrome.keycloakUri;
242
+ }
243
+ } else if (!localChrome && useProxy) {
244
+ if (typeof defaultServices.chrome === 'function') {
245
+ defaultServices.chrome = defaultServices.chrome({});
246
+ }
224
247
 
225
- chromePath = checkoutRepo({
226
- repo: `${defaultServices.chrome.path}#${env}`,
227
- reposDir,
228
- overwrite: true
229
- });
230
- }
248
+ chromePath = checkoutRepo({
249
+ repo: `${defaultServices.chrome.path}#${env}`,
250
+ reposDir,
251
+ overwrite: true
252
+ });
253
+ }
231
254
 
232
- if (chromePath) {
233
- registerChrome({
234
- app,
235
- chromePath,
236
- keycloakUri,
237
- https: Boolean(options.https),
238
- proxyVerbose
239
- });
255
+ if (chromePath) {
256
+ registerChrome({
257
+ app,
258
+ chromePath,
259
+ keycloakUri,
260
+ https: Boolean(options.https),
261
+ proxyVerbose
262
+ });
263
+ }
240
264
  }
241
265
 
242
266
  registry.forEach(cb => cb({
@@ -15,7 +15,11 @@ function federate(argv, cwd) {
15
15
 
16
16
  try {
17
17
  fs.statSync(configPath);
18
- const config = require(configPath);
18
+ let config = require(configPath);
19
+ if (typeof config === 'function') {
20
+ config = config(process.env);
21
+ }
22
+
19
23
  const outputPath = config.output.path;
20
24
 
21
25
  concurrently([