@soleil-se/app-util 4.0.0 → 4.1.0
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/CHANGELOG.md +9 -0
- package/MIGRATION.md +20 -0
- package/client/svelte/index.js +18 -19
- package/client/{attribute-util → utils}/index.js +5 -8
- package/client/vue/index.js +16 -26
- package/common/index.js +5 -5
- package/docs/1.render.md +2 -4
- package/docs/2.svelte.md +22 -5
- package/docs/3.vue.md +18 -3
- package/package.json +2 -2
- package/server/index.js +14 -12
- /package/client/{attribute-util → utils}/getCurrentScript.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [4.1.0] - 2021-02-17
|
|
8
|
+
### Changed
|
|
9
|
+
- Put app data and metadata in script elements instead of data attributes.
|
|
10
|
+
|
|
11
|
+
## [4.0.1] - 2021-01-28
|
|
12
|
+
### Fixed
|
|
13
|
+
- Crashes when no data is passed to app and then trying to call `getAppData`.
|
|
14
|
+
|
|
7
15
|
## [4.0.0] - 2021-01-27
|
|
8
16
|
A much needed major overhaul of the package.
|
|
9
17
|
See [MIGRATION](./MIGRATION.md).
|
|
@@ -12,6 +20,7 @@ See [MIGRATION](./MIGRATION.md).
|
|
|
12
20
|
- Major refactoring of package.
|
|
13
21
|
- Exports for render functions are moved.
|
|
14
22
|
- All documented constants and functions from base import (`@soleil-api/webapp-util`) now works both in a server and client context.
|
|
23
|
+
- Settings for selector is moved from server to client rendering functions.
|
|
15
24
|
|
|
16
25
|
### Removed
|
|
17
26
|
- `noScript` option in `render` (formerly `renderApp`) has been removed. If a no script message is needed use the `html` option wrapped in `<noscript>`.
|
package/MIGRATION.md
CHANGED
|
@@ -5,6 +5,8 @@ Migration from version 2 or 3 to 4.
|
|
|
5
5
|
<!-- TOC -->
|
|
6
6
|
|
|
7
7
|
- [renderApp](#renderapp)
|
|
8
|
+
- [Export](#export)
|
|
9
|
+
- [Settings](#settings)
|
|
8
10
|
- [renderTemplate](#rendertemplate)
|
|
9
11
|
- [getRouteUri](#getrouteuri)
|
|
10
12
|
- [AppData](#appdata)
|
|
@@ -18,6 +20,7 @@ Migration from version 2 or 3 to 4.
|
|
|
18
20
|
<!-- /TOC -->
|
|
19
21
|
|
|
20
22
|
## renderApp
|
|
23
|
+
### Export
|
|
21
24
|
Export moved and renamed.
|
|
22
25
|
|
|
23
26
|
Old:
|
|
@@ -28,6 +31,23 @@ New:
|
|
|
28
31
|
```js
|
|
29
32
|
import { render } from '@soleil-api/webapp-util/server';
|
|
30
33
|
```
|
|
34
|
+
|
|
35
|
+
### Settings
|
|
36
|
+
Selector is moved to client side rendering functions.
|
|
37
|
+
|
|
38
|
+
Old (index.js):
|
|
39
|
+
```js
|
|
40
|
+
res.send(renderApp(data, { selector: '#mount_app_here' }));
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
New (main.js):
|
|
44
|
+
```js
|
|
45
|
+
import { render } from '@soleil-api/webapp-util/client/vue';
|
|
46
|
+
import App from './App.vue';
|
|
47
|
+
|
|
48
|
+
render(App, { selector: '#mount_app_here' });
|
|
49
|
+
```
|
|
50
|
+
|
|
31
51
|
## renderTemplate
|
|
32
52
|
Export moved.
|
|
33
53
|
|
package/client/svelte/index.js
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
import {
|
|
3
|
-
import { getAppData } from '../../common';
|
|
2
|
+
import { appId, getAppData } from '../../common';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
|
-
* Renders a Svelte application
|
|
7
|
-
* @param {
|
|
5
|
+
* Renders a client side Svelte application.
|
|
6
|
+
* @param {*} App Svelte app root component.
|
|
7
|
+
* @param {Object} [settings={}] Settings object.
|
|
8
|
+
* @param {String} [settings.selector=`#app_mount_${appId}`] Query selector for mount element.
|
|
9
|
+
* @param {String} [settings.intro=false] If true, will play transitions on initial render,
|
|
10
|
+
* rather than waiting for subsequent state changes.
|
|
11
|
+
* @return {*} Initialized Svelte component.
|
|
8
12
|
*/
|
|
9
|
-
export function render(App
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
13
|
+
export function render(App, {
|
|
14
|
+
selector = `#app_mount_${appId}`,
|
|
15
|
+
intro = false,
|
|
16
|
+
} = {}) {
|
|
17
|
+
const target = document.querySelector(selector);
|
|
18
|
+
const hydrate = target.childElementCount > 0;
|
|
14
19
|
const props = getAppData();
|
|
15
|
-
|
|
16
|
-
return new App({
|
|
17
|
-
hydrate,
|
|
18
|
-
target: document.querySelector(selector),
|
|
19
|
-
props,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
return document.addEventListener('DOMContentLoaded', () => new App({
|
|
20
|
+
return new App({
|
|
23
21
|
hydrate,
|
|
24
|
-
target
|
|
22
|
+
target,
|
|
25
23
|
props,
|
|
26
|
-
|
|
24
|
+
intro,
|
|
25
|
+
});
|
|
27
26
|
}
|
|
@@ -6,7 +6,7 @@ import getCurrentScript from './getCurrentScript';
|
|
|
6
6
|
* @param {String} attribute Attribute to decode.
|
|
7
7
|
* @returns {Object} Decoded JSON object.
|
|
8
8
|
*/
|
|
9
|
-
export const
|
|
9
|
+
export const getAttribute = (attribute) => {
|
|
10
10
|
const currentScript = getCurrentScript();
|
|
11
11
|
const encoded = currentScript.getAttribute(attribute);
|
|
12
12
|
if (encoded) {
|
|
@@ -15,10 +15,7 @@ export const decodeAttribute = (attribute) => {
|
|
|
15
15
|
return undefined;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* @returns {String} Parsed attribute value.
|
|
23
|
-
*/
|
|
24
|
-
export const parseAttribute = (attribute) => JSON.parse(decodeAttribute(attribute));
|
|
18
|
+
export const parseJson = (type) => {
|
|
19
|
+
const id = getAttribute('data-app-id');
|
|
20
|
+
return JSON.parse(document.getElementById(`${type}_${id}`)?.textContent || '{}');
|
|
21
|
+
};
|
package/client/vue/index.js
CHANGED
|
@@ -1,42 +1,32 @@
|
|
|
1
1
|
/* eslint-disable import/prefer-default-export */
|
|
2
|
-
/* eslint-disable-next-line import/no-unresolved */
|
|
2
|
+
/* eslint-disable-next-line import/no-unresolved, import/no-extraneous-dependencies */
|
|
3
3
|
import Vue from 'vue';
|
|
4
4
|
|
|
5
|
-
import {
|
|
6
|
-
import { getAppData, isOffline } from '../../common';
|
|
5
|
+
import { appId, getAppData, isOffline } from '../../common';
|
|
7
6
|
|
|
8
7
|
const offlineModeMixin = {
|
|
9
8
|
mounted() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
$
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
});
|
|
18
|
-
}
|
|
9
|
+
const $anchors = this.$el.querySelectorAll('a');
|
|
10
|
+
$anchors.forEach(($anchor) => {
|
|
11
|
+
$anchor.addEventListener('click', (e) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
window.$svjq($anchor).trigger('click');
|
|
14
|
+
}, false);
|
|
15
|
+
});
|
|
19
16
|
},
|
|
20
17
|
};
|
|
21
18
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param {
|
|
19
|
+
* Renders a client side Vue application.
|
|
20
|
+
* @param {*} App Vue app root component.
|
|
21
|
+
* @param {Object} [settings={}] Settings object.
|
|
22
|
+
* @param {String} [settings.selector=`#app_mount_${appId}`] Query selector for mount element.
|
|
24
23
|
*/
|
|
25
|
-
export function render(App) {
|
|
26
|
-
const selector = decodeAttribute('data-selector');
|
|
24
|
+
export function render(App, { selector = `#app_mount_${appId}` } = {}) {
|
|
27
25
|
const options = getAppData();
|
|
28
26
|
|
|
29
27
|
Object.assign(App, options, {
|
|
30
|
-
mixins: [offlineModeMixin],
|
|
28
|
+
mixins: isOffline ? [offlineModeMixin] : [],
|
|
31
29
|
});
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (mountElementExists) {
|
|
36
|
-
new Vue(App).$mount(selector);
|
|
37
|
-
} else {
|
|
38
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
39
|
-
new Vue(App).$mount(selector);
|
|
40
|
-
});
|
|
41
|
-
}
|
|
31
|
+
new Vue(App).$mount(selector);
|
|
42
32
|
}
|
package/common/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* eslint-disable global-require */
|
|
2
|
-
import {
|
|
2
|
+
import { parseJson } from '../client/utils';
|
|
3
3
|
import getLegacyRouteUri from './legacy/getRouteUri';
|
|
4
4
|
import getLegacyViewUri from './legacy/getViewUri';
|
|
5
5
|
|
|
6
|
-
const appMetadata = process.browser ?
|
|
6
|
+
const appMetadata = process.browser ? parseJson('app_meta') : {};
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Get an ID for the app.
|
|
@@ -124,7 +124,7 @@ export function getAppMetadata() {
|
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
let appData = process.browser ?
|
|
127
|
+
let appData = process.browser ? parseJson('app_data') : undefined;
|
|
128
128
|
|
|
129
129
|
export function setAppData(data) {
|
|
130
130
|
appData = data;
|
|
@@ -137,8 +137,8 @@ export function setAppData(data) {
|
|
|
137
137
|
* @return {*|Object} - Value or object.
|
|
138
138
|
*/
|
|
139
139
|
export function getAppData(key) {
|
|
140
|
-
if (process.browser && !
|
|
141
|
-
setAppData(
|
|
140
|
+
if (process.browser && !appData) {
|
|
141
|
+
setAppData(parseJson('app_data'));
|
|
142
142
|
}
|
|
143
143
|
return key ? appData[key] : appData;
|
|
144
144
|
}
|
package/docs/1.render.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Render
|
|
2
2
|
Returns HTML for a script tag with server data avaliable as attributes on the tag.
|
|
3
|
-
Framework
|
|
3
|
+
Framework agnostic.
|
|
4
4
|
|
|
5
5
|
## index.js
|
|
6
6
|
### render([data], [settings]) ⇒ <code>String</code>
|
|
@@ -15,7 +15,6 @@ Get a HTML string for rendering an application.
|
|
|
15
15
|
| [data] | <code>Object</code> | <code>{}</code> | Server data that will be available in the attribute. `data-app-data` on the script tag. |
|
|
16
16
|
| [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
|
|
17
17
|
| [settings.html] | <code>String</code> | <code>''</code> | HTML that will be rendered inside mount element. |
|
|
18
|
-
| [settings.selector] | <code>String</code> | <code>`[data-portlet-id="${portletId}"]`</code> | Query selector for where the app should be mounted. |
|
|
19
18
|
| [settings.async] | <code>Boolean</code> | <code>false</code> | If the app script should be loaded asynchronously. |
|
|
20
19
|
| [settings.defer] | <code>Boolean</code> | <code>true</code> | If the app script should be loaded after DOM is ready. |
|
|
21
20
|
| [settings.req] | <code>Object</code> | | The req object from SiteVision, pass this to optimize browser specific script loading if you have multiple instances of the app. |
|
|
@@ -42,8 +41,7 @@ import { render } from '@soleil-api/webapp-util/server';
|
|
|
42
41
|
router.get('/', (req, res) => {
|
|
43
42
|
const data = { foo: 'bar' };
|
|
44
43
|
const settings = {
|
|
45
|
-
html: '<noscript>You can put a noscript message here for example!</noscript>',
|
|
46
|
-
selector: '#mount_me_here',
|
|
44
|
+
html: '<noscript>You can put a noscript message here for example!</noscript>',
|
|
47
45
|
async: false,
|
|
48
46
|
defer: true,
|
|
49
47
|
req,
|
package/docs/2.svelte.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
SiteVision supports both server and client side rendering with Svelte.
|
|
3
3
|
|
|
4
4
|
## index.js
|
|
5
|
-
For rendering a client side only app use the framework
|
|
5
|
+
For rendering a client side only app use the framework agnostic [client renderer](./1.render.md).
|
|
6
6
|
|
|
7
|
-
### `render(App, [props], [settings])`
|
|
7
|
+
### `render(App, [props], [settings])`
|
|
8
8
|
`@soleil-api/webapp-util/server/svelte`
|
|
9
9
|
|
|
10
10
|
Get a HTML string for rendering a universal Svelte application.
|
|
@@ -16,7 +16,6 @@ If a client bundle is available the server rendered HTML will be hydrated and no
|
|
|
16
16
|
| --- | --- | --- | --- |
|
|
17
17
|
| [props] | <code>Object</code> | <code>{}</code> | Server data that will be available as props and as app data. |
|
|
18
18
|
| [settings] | <code>Object</code> | <code>{}</code> | Settings object. Forwarded to |
|
|
19
|
-
| [settings.selector] | <code>String</code> | <code>`[data-portlet-id="${portletId}"]`</code> | Query selector for where the app should be mounted. |
|
|
20
19
|
| [settings.async] | <code>Boolean</code> | <code>false</code> | If the app script should be loaded asynchronously. |
|
|
21
20
|
| [settings.defer] | <code>Boolean</code> | <code>true</code> | If the app script should be loaded after DOM is ready. |
|
|
22
21
|
| [settings.req] | <code>Object</code> | | The req object from SiteVision, pass this to optimize browser specific script loading if you have multiple instances of the app. |
|
|
@@ -38,10 +37,20 @@ router.get('/', (req, res) => {
|
|
|
38
37
|
|
|
39
38
|
## main.js
|
|
40
39
|
|
|
41
|
-
### `render(App)`
|
|
40
|
+
### `render(App, [settings])`
|
|
42
41
|
`@soleil-api/webapp-util/client/svelte`
|
|
43
42
|
|
|
44
|
-
Renders
|
|
43
|
+
Renders a client side Svelte application.
|
|
44
|
+
|
|
45
|
+
**Returns**: <code>\*</code> - Initialized Svelte component.
|
|
46
|
+
|
|
47
|
+
| Param | Type | Default | Description |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| App | <code>\*</code> | | Svelte app root component. |
|
|
50
|
+
| [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
|
|
51
|
+
| [settings.selector] | <code>String</code> | <code>`#app_mount_${appId}`</code> | Query selector for mount element. |
|
|
52
|
+
| [settings.intro] | <code>String</code> | <code>false</code> | If true, will play transitions on initial render, rather than waiting for subsequent state changes. |
|
|
53
|
+
|
|
45
54
|
|
|
46
55
|
In `app_src/main.js` or `app_src/client/index.js`.
|
|
47
56
|
```javascript
|
|
@@ -50,3 +59,11 @@ import App from './App.svelte';
|
|
|
50
59
|
|
|
51
60
|
render(App);
|
|
52
61
|
```
|
|
62
|
+
|
|
63
|
+
Mount the app in another element:
|
|
64
|
+
```javascript
|
|
65
|
+
import { render } from '@soleil-api/webapp-util/client/svelte';
|
|
66
|
+
import App from './App.svelte';
|
|
67
|
+
|
|
68
|
+
render(App, { selector: '#mount_app_here' });
|
|
69
|
+
```
|
package/docs/3.vue.md
CHANGED
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
SiteVision supports client side rendering with Vue.
|
|
3
3
|
|
|
4
4
|
## index.js
|
|
5
|
-
For rendering a client side only app use the framework
|
|
5
|
+
For rendering a client side only app use the framework agnostic [client renderer](./1.render.md).
|
|
6
6
|
|
|
7
7
|
## main.js
|
|
8
|
-
|
|
8
|
+
|
|
9
|
+
### `render(App, [settings])`
|
|
9
10
|
`@soleil-api/webapp-util/client/vue`
|
|
11
|
+
Renders a client side Vue application.
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
| Param | Type | Default | Description |
|
|
14
|
+
| --- | --- | --- | --- |
|
|
15
|
+
| App | <code>\*</code> | | Vue app root component. |
|
|
16
|
+
| [settings] | <code>Object</code> | <code>{}</code> | Settings object. |
|
|
17
|
+
| [settings.selector] | <code>String</code> | <code>`#app_mount_${appId}`</code> | Query selector for mount element. |
|
|
12
18
|
|
|
13
19
|
In `app_src/main.js` or `app_src/client/index.js`.
|
|
14
20
|
```javascript
|
|
@@ -17,3 +23,12 @@ import App from './App.vue';
|
|
|
17
23
|
|
|
18
24
|
render(App);
|
|
19
25
|
```
|
|
26
|
+
|
|
27
|
+
Mount the app in another element:
|
|
28
|
+
```javascript
|
|
29
|
+
import { render } from '@soleil-api/webapp-util/client/vue';
|
|
30
|
+
import App from './App.vue';
|
|
31
|
+
|
|
32
|
+
render(App, { selector: '#mount_app_here' });
|
|
33
|
+
```
|
|
34
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soleil-se/app-util",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "Utility functions for Webapps.",
|
|
5
5
|
"main": "./common/index.js",
|
|
6
6
|
"author": "Soleil AB",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"optionalDependencies": {
|
|
15
15
|
"vue": "^2.6.11"
|
|
16
16
|
},
|
|
17
|
-
"gitHead": "
|
|
17
|
+
"gitHead": "5248563f9ecdec86866cb5cd361990c5ff8626a5",
|
|
18
18
|
"dependencies": {},
|
|
19
19
|
"devDependencies": {}
|
|
20
20
|
}
|
package/server/index.js
CHANGED
|
@@ -18,7 +18,6 @@ const isIE = (req) => {
|
|
|
18
18
|
* `data-app-data` on the script tag.
|
|
19
19
|
* @param {Object} [settings={}] Settings object.
|
|
20
20
|
* @param {String} [settings.html=''] HTML that will be rendered inside mount element.
|
|
21
|
-
* @param {String} [settings.selector=`[data-portlet-id="${portletId}"]`] Query selector for
|
|
22
21
|
* where the app should be mounted.
|
|
23
22
|
* @param {Boolean} [settings.async=false] If the app script should be loaded asynchronously.
|
|
24
23
|
* [Read more about async and defer.](https://flaviocopes.com/javascript-async-defer/)
|
|
@@ -30,35 +29,38 @@ const isIE = (req) => {
|
|
|
30
29
|
*/
|
|
31
30
|
export function render(data, {
|
|
32
31
|
html = '',
|
|
33
|
-
selector = `[data-portlet-id="${appId}"]`,
|
|
34
32
|
async = false,
|
|
35
33
|
defer = true,
|
|
36
34
|
req,
|
|
37
35
|
} = {}) {
|
|
38
36
|
const appMetadata = getAppMetadata();
|
|
39
37
|
|
|
38
|
+
const mountElement = `<div id="app_mount_${appId}">${html}</div>`;
|
|
39
|
+
const metaScriptTag = `<script id="app_meta_${appId}" type="application/json">${JSON.stringify(appMetadata)}</script>`;
|
|
40
|
+
const dataScriptTag = data ? `<script id="app_data_${appId}" type="application/json">${JSON.stringify(data)}</script>` : '';
|
|
41
|
+
|
|
40
42
|
if (isOffline) {
|
|
41
43
|
return `
|
|
42
|
-
|
|
44
|
+
${mountElement}
|
|
45
|
+
${metaScriptTag}
|
|
46
|
+
${dataScriptTag}
|
|
43
47
|
<script>
|
|
44
48
|
window.svDocReady(function() {
|
|
45
|
-
var targetElement = document.
|
|
49
|
+
var targetElement = document.getElementById('app_mount_${appId}');
|
|
46
50
|
var script = document.createElement("script");
|
|
47
51
|
script.src = "${getResourceUri('client/index.js')}?${appImportDate}${appId}";
|
|
48
|
-
script.setAttribute("data-
|
|
49
|
-
script.setAttribute("data-app-data", "${encodeURIComponent(JSON.stringify(data))}");
|
|
50
|
-
script.setAttribute("data-selector", "${encodeURIComponent(selector)}");
|
|
52
|
+
script.setAttribute("data-app-id", "${appId}");
|
|
51
53
|
targetElement.parentNode.insertBefore(script, targetElement.nextSibling);
|
|
52
54
|
});
|
|
53
55
|
</script>`;
|
|
54
56
|
}
|
|
55
57
|
return `
|
|
56
|
-
|
|
58
|
+
${mountElement}
|
|
59
|
+
${metaScriptTag}
|
|
60
|
+
${dataScriptTag}
|
|
57
61
|
<script
|
|
58
|
-
src="${getResourceUri('client/index.js')}?${appImportDate}${isIE(req) ? appId : ''}"
|
|
59
|
-
data-
|
|
60
|
-
data-app-data="${encodeURIComponent(JSON.stringify(data))}"
|
|
61
|
-
data-selector="${encodeURIComponent(selector)}"
|
|
62
|
+
src="${getResourceUri('client/index.js')}?${appImportDate}${isIE(req) ? appId : ''}"
|
|
63
|
+
data-app-id="${appId}"
|
|
62
64
|
${async && !defer ? 'async' : ''}
|
|
63
65
|
${defer ? 'defer' : ''}>
|
|
64
66
|
</script>`;
|
|
File without changes
|