@wordpress/server-side-render 6.3.0 → 6.4.1-next.46f643fa0.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 +6 -0
- package/README.md +73 -85
- package/build/hook.js +171 -0
- package/build/hook.js.map +1 -0
- package/build/index.js +28 -42
- package/build/index.js.map +1 -1
- package/build/server-side-render.js +95 -133
- package/build/server-side-render.js.map +1 -1
- package/build-module/hook.js +161 -0
- package/build-module/hook.js.map +1 -0
- package/build-module/index.js +16 -40
- package/build-module/index.js.map +1 -1
- package/build-module/server-side-render.js +97 -133
- package/build-module/server-side-render.js.map +1 -1
- package/package.json +11 -12
- package/src/hook.js +177 -0
- package/src/index.js +16 -38
- package/src/server-side-render.js +94 -157
- package/src/test/index.js +2 -4
|
@@ -1,56 +1,23 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* External dependencies
|
|
3
|
-
*/
|
|
4
|
-
import fastDeepEqual from 'fast-deep-equal/es6';
|
|
5
|
-
|
|
6
1
|
/**
|
|
7
2
|
* WordPress dependencies
|
|
8
3
|
*/
|
|
9
|
-
import { useDebounce, usePrevious } from '@wordpress/compose';
|
|
10
4
|
import {
|
|
11
5
|
RawHTML,
|
|
12
|
-
useCallback,
|
|
13
6
|
useEffect,
|
|
14
|
-
useLayoutEffect,
|
|
15
|
-
useRef,
|
|
16
7
|
useState,
|
|
8
|
+
useRef,
|
|
9
|
+
useMemo,
|
|
17
10
|
} from '@wordpress/element';
|
|
18
11
|
import { __, sprintf } from '@wordpress/i18n';
|
|
19
|
-
import apiFetch from '@wordpress/api-fetch';
|
|
20
|
-
import { addQueryArgs } from '@wordpress/url';
|
|
21
12
|
import { Placeholder, Spinner } from '@wordpress/components';
|
|
22
|
-
import {
|
|
13
|
+
import { useSelect } from '@wordpress/data';
|
|
23
14
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
context: 'edit',
|
|
29
|
-
...( null !== attributes ? { attributes } : {} ),
|
|
30
|
-
...urlQueryArgs,
|
|
31
|
-
} );
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function removeBlockSupportAttributes( attributes ) {
|
|
35
|
-
const {
|
|
36
|
-
backgroundColor,
|
|
37
|
-
borderColor,
|
|
38
|
-
fontFamily,
|
|
39
|
-
fontSize,
|
|
40
|
-
gradient,
|
|
41
|
-
textColor,
|
|
42
|
-
className,
|
|
43
|
-
...restAttributes
|
|
44
|
-
} = attributes;
|
|
45
|
-
|
|
46
|
-
const { border, color, elements, spacing, typography, ...restStyles } =
|
|
47
|
-
attributes?.style || EMPTY_OBJECT;
|
|
15
|
+
/**
|
|
16
|
+
* Internal dependencies
|
|
17
|
+
*/
|
|
18
|
+
import { useServerSideRender } from './hook';
|
|
48
19
|
|
|
49
|
-
|
|
50
|
-
...restAttributes,
|
|
51
|
-
style: restStyles,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
20
|
+
const EMPTY_OBJECT = {};
|
|
54
21
|
|
|
55
22
|
function DefaultEmptyResponsePlaceholder( { className } ) {
|
|
56
23
|
return (
|
|
@@ -60,11 +27,11 @@ function DefaultEmptyResponsePlaceholder( { className } ) {
|
|
|
60
27
|
);
|
|
61
28
|
}
|
|
62
29
|
|
|
63
|
-
function DefaultErrorResponsePlaceholder( {
|
|
30
|
+
function DefaultErrorResponsePlaceholder( { message, className } ) {
|
|
64
31
|
const errorMessage = sprintf(
|
|
65
32
|
// translators: %s: error message describing the problem
|
|
66
33
|
__( 'Error loading block: %s' ),
|
|
67
|
-
|
|
34
|
+
message
|
|
68
35
|
);
|
|
69
36
|
return <Placeholder className={ className }>{ errorMessage }</Placeholder>;
|
|
70
37
|
}
|
|
@@ -102,141 +69,111 @@ function DefaultLoadingResponsePlaceholder( { children } ) {
|
|
|
102
69
|
);
|
|
103
70
|
}
|
|
104
71
|
|
|
105
|
-
export
|
|
72
|
+
export function ServerSideRender( props ) {
|
|
73
|
+
const prevContentRef = useRef( '' );
|
|
106
74
|
const {
|
|
107
75
|
className,
|
|
108
76
|
EmptyResponsePlaceholder = DefaultEmptyResponsePlaceholder,
|
|
109
77
|
ErrorResponsePlaceholder = DefaultErrorResponsePlaceholder,
|
|
110
78
|
LoadingResponsePlaceholder = DefaultLoadingResponsePlaceholder,
|
|
79
|
+
...restProps
|
|
111
80
|
} = props;
|
|
112
81
|
|
|
113
|
-
const
|
|
114
|
-
const fetchRequestRef = useRef();
|
|
115
|
-
const [ response, setResponse ] = useState( null );
|
|
116
|
-
const prevProps = usePrevious( props );
|
|
117
|
-
const [ isLoading, setIsLoading ] = useState( false );
|
|
118
|
-
const latestPropsRef = useRef( props );
|
|
119
|
-
|
|
120
|
-
useLayoutEffect( () => {
|
|
121
|
-
latestPropsRef.current = props;
|
|
122
|
-
}, [ props ] );
|
|
123
|
-
|
|
124
|
-
const fetchData = useCallback( () => {
|
|
125
|
-
if ( ! isMountedRef.current ) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const {
|
|
130
|
-
attributes,
|
|
131
|
-
block,
|
|
132
|
-
skipBlockSupportAttributes = false,
|
|
133
|
-
httpMethod = 'GET',
|
|
134
|
-
urlQueryArgs,
|
|
135
|
-
} = latestPropsRef.current;
|
|
136
|
-
|
|
137
|
-
setIsLoading( true );
|
|
138
|
-
|
|
139
|
-
let sanitizedAttributes =
|
|
140
|
-
attributes &&
|
|
141
|
-
__experimentalSanitizeBlockAttributes( block, attributes );
|
|
142
|
-
|
|
143
|
-
if ( skipBlockSupportAttributes ) {
|
|
144
|
-
sanitizedAttributes =
|
|
145
|
-
removeBlockSupportAttributes( sanitizedAttributes );
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// If httpMethod is 'POST', send the attributes in the request body instead of the URL.
|
|
149
|
-
// This allows sending a larger attributes object than in a GET request, where the attributes are in the URL.
|
|
150
|
-
const isPostRequest = 'POST' === httpMethod;
|
|
151
|
-
const urlAttributes = isPostRequest
|
|
152
|
-
? null
|
|
153
|
-
: sanitizedAttributes ?? null;
|
|
154
|
-
const path = rendererPath( block, urlAttributes, urlQueryArgs );
|
|
155
|
-
const data = isPostRequest
|
|
156
|
-
? { attributes: sanitizedAttributes ?? null }
|
|
157
|
-
: null;
|
|
158
|
-
|
|
159
|
-
// Store the latest fetch request so that when we process it, we can
|
|
160
|
-
// check if it is the current request, to avoid race conditions on slow networks.
|
|
161
|
-
const fetchRequest = ( fetchRequestRef.current = apiFetch( {
|
|
162
|
-
path,
|
|
163
|
-
data,
|
|
164
|
-
method: isPostRequest ? 'POST' : 'GET',
|
|
165
|
-
} )
|
|
166
|
-
.then( ( fetchResponse ) => {
|
|
167
|
-
if (
|
|
168
|
-
isMountedRef.current &&
|
|
169
|
-
fetchRequest === fetchRequestRef.current &&
|
|
170
|
-
fetchResponse
|
|
171
|
-
) {
|
|
172
|
-
setResponse( fetchResponse.rendered );
|
|
173
|
-
}
|
|
174
|
-
} )
|
|
175
|
-
.catch( ( error ) => {
|
|
176
|
-
if (
|
|
177
|
-
isMountedRef.current &&
|
|
178
|
-
fetchRequest === fetchRequestRef.current
|
|
179
|
-
) {
|
|
180
|
-
setResponse( {
|
|
181
|
-
error: true,
|
|
182
|
-
errorMsg: error.message,
|
|
183
|
-
} );
|
|
184
|
-
}
|
|
185
|
-
} )
|
|
186
|
-
.finally( () => {
|
|
187
|
-
if (
|
|
188
|
-
isMountedRef.current &&
|
|
189
|
-
fetchRequest === fetchRequestRef.current
|
|
190
|
-
) {
|
|
191
|
-
setIsLoading( false );
|
|
192
|
-
}
|
|
193
|
-
} ) );
|
|
194
|
-
|
|
195
|
-
return fetchRequest;
|
|
196
|
-
}, [] );
|
|
197
|
-
|
|
198
|
-
const debouncedFetchData = useDebounce( fetchData, 500 );
|
|
199
|
-
|
|
200
|
-
// When the component unmounts, set isMountedRef to false. This will
|
|
201
|
-
// let the async fetch callbacks know when to stop.
|
|
202
|
-
useEffect( () => {
|
|
203
|
-
isMountedRef.current = true;
|
|
204
|
-
return () => {
|
|
205
|
-
isMountedRef.current = false;
|
|
206
|
-
};
|
|
207
|
-
}, [] );
|
|
82
|
+
const { content, status, error } = useServerSideRender( restProps );
|
|
208
83
|
|
|
84
|
+
// Store the previous successful HTML response to show while loading.
|
|
209
85
|
useEffect( () => {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if ( prevProps === undefined ) {
|
|
213
|
-
fetchData();
|
|
214
|
-
} else if ( ! fastDeepEqual( prevProps, props ) ) {
|
|
215
|
-
debouncedFetchData();
|
|
86
|
+
if ( content ) {
|
|
87
|
+
prevContentRef.current = content;
|
|
216
88
|
}
|
|
217
|
-
} );
|
|
89
|
+
}, [ content ] );
|
|
218
90
|
|
|
219
|
-
|
|
220
|
-
const hasEmptyResponse = response === '';
|
|
221
|
-
const hasError = !! response?.error;
|
|
222
|
-
|
|
223
|
-
if ( isLoading ) {
|
|
91
|
+
if ( status === 'loading' ) {
|
|
224
92
|
return (
|
|
225
93
|
<LoadingResponsePlaceholder { ...props }>
|
|
226
|
-
{
|
|
227
|
-
<RawHTML className={ className }>
|
|
94
|
+
{ !! prevContentRef.current && (
|
|
95
|
+
<RawHTML className={ className }>
|
|
96
|
+
{ prevContentRef.current }
|
|
97
|
+
</RawHTML>
|
|
228
98
|
) }
|
|
229
99
|
</LoadingResponsePlaceholder>
|
|
230
100
|
);
|
|
231
101
|
}
|
|
232
102
|
|
|
233
|
-
if (
|
|
103
|
+
if ( status === 'success' && ! content ) {
|
|
234
104
|
return <EmptyResponsePlaceholder { ...props } />;
|
|
235
105
|
}
|
|
236
106
|
|
|
237
|
-
if (
|
|
238
|
-
return <ErrorResponsePlaceholder
|
|
107
|
+
if ( status === 'error' ) {
|
|
108
|
+
return <ErrorResponsePlaceholder message={ error } { ...props } />;
|
|
239
109
|
}
|
|
240
110
|
|
|
241
|
-
return <RawHTML className={ className }>{
|
|
111
|
+
return <RawHTML className={ className }>{ content }</RawHTML>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* A component that renders server-side content for blocks.
|
|
116
|
+
*
|
|
117
|
+
* Note: URL query will include the current post ID when applicable.
|
|
118
|
+
* This is useful for blocks that depend on the context of the current post for rendering.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```jsx
|
|
122
|
+
* import { ServerSideRender } from '@wordpress/server-side-render';
|
|
123
|
+
* // Legacy import for WordPress 6.8 and earlier
|
|
124
|
+
* // import { default as ServerSideRender } from '@wordpress/server-side-render';
|
|
125
|
+
*
|
|
126
|
+
* function Example() {
|
|
127
|
+
* return (
|
|
128
|
+
* <ServerSideRender
|
|
129
|
+
* block="core/archives"
|
|
130
|
+
* attributes={ { showPostCounts: true } }
|
|
131
|
+
* urlQueryArgs={ { customArg: 'value' } }
|
|
132
|
+
* className="custom-class"
|
|
133
|
+
* />
|
|
134
|
+
* );
|
|
135
|
+
* }
|
|
136
|
+
* ```
|
|
137
|
+
*
|
|
138
|
+
* @param {Object} props Component props.
|
|
139
|
+
* @param {string} props.block The identifier of the block to be serverside rendered.
|
|
140
|
+
* @param {Object} props.attributes The block attributes to be sent to the server for rendering.
|
|
141
|
+
* @param {string} [props.className] Additional classes to apply to the wrapper element.
|
|
142
|
+
* @param {string} [props.httpMethod='GET'] The HTTP method to use ('GET' or 'POST'). Default is 'GET'
|
|
143
|
+
* @param {Object} [props.urlQueryArgs] Additional query arguments to append to the request URL.
|
|
144
|
+
* @param {boolean} [props.skipBlockSupportAttributes=false] Whether to remove block support attributes before sending.
|
|
145
|
+
* @param {Function} [props.EmptyResponsePlaceholder] Component rendered when the API response is empty.
|
|
146
|
+
* @param {Function} [props.ErrorResponsePlaceholder] Component rendered when the API response is an error.
|
|
147
|
+
* @param {Function} [props.LoadingResponsePlaceholder] Component rendered while the API request is loading.
|
|
148
|
+
*
|
|
149
|
+
* @return {JSX.Element} The rendered server-side content.
|
|
150
|
+
*/
|
|
151
|
+
export function ServerSideRenderWithPostId( {
|
|
152
|
+
urlQueryArgs = EMPTY_OBJECT,
|
|
153
|
+
...props
|
|
154
|
+
} ) {
|
|
155
|
+
const currentPostId = useSelect( ( select ) => {
|
|
156
|
+
// FIXME: @wordpress/server-side-render should not depend on @wordpress/editor.
|
|
157
|
+
// It is used by blocks that can be loaded into a *non-post* block editor.
|
|
158
|
+
// eslint-disable-next-line @wordpress/data-no-store-string-literals
|
|
159
|
+
const postId = select( 'core/editor' )?.getCurrentPostId();
|
|
160
|
+
|
|
161
|
+
// For templates and template parts we use a custom ID format.
|
|
162
|
+
// Since they aren't real posts, we don't want to use their ID
|
|
163
|
+
// for server-side rendering. Since they use a string based ID,
|
|
164
|
+
// we can assume real post IDs are numbers.
|
|
165
|
+
return postId && typeof postId === 'number' ? postId : null;
|
|
166
|
+
}, [] );
|
|
167
|
+
|
|
168
|
+
const newUrlQueryArgs = useMemo( () => {
|
|
169
|
+
if ( ! currentPostId ) {
|
|
170
|
+
return urlQueryArgs;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
post_id: currentPostId,
|
|
174
|
+
...urlQueryArgs,
|
|
175
|
+
};
|
|
176
|
+
}, [ currentPostId, urlQueryArgs ] );
|
|
177
|
+
|
|
178
|
+
return <ServerSideRender urlQueryArgs={ newUrlQueryArgs } { ...props } />;
|
|
242
179
|
}
|
package/src/test/index.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internal dependencies
|
|
3
3
|
*/
|
|
4
|
-
import {
|
|
5
|
-
rendererPath,
|
|
6
|
-
removeBlockSupportAttributes,
|
|
7
|
-
} from '../server-side-render';
|
|
4
|
+
import { rendererPath, removeBlockSupportAttributes } from '../hook';
|
|
8
5
|
|
|
9
6
|
describe( 'rendererPath', () => {
|
|
10
7
|
test( 'should return an base path for empty input', () => {
|
|
@@ -107,6 +104,7 @@ describe( 'rendererPath', () => {
|
|
|
107
104
|
},
|
|
108
105
|
},
|
|
109
106
|
},
|
|
107
|
+
shadow: 'var:preset|shadow|crisp',
|
|
110
108
|
spacing: {
|
|
111
109
|
margin: {
|
|
112
110
|
top: '10px',
|