Skip to content

Commit 5b1c8b1

Browse files
authored
Merge pull request #5 from masonitedoors/remote-cra-support
Remote cra support
2 parents 78fa9dd + 5b60e71 commit 5b1c8b1

File tree

5 files changed

+129
-26
lines changed

5 files changed

+129
-26
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.4.0] - 2021-01-13
11+
12+
### Added
13+
14+
- Added support for loading a remote react app.
15+
1016
## [1.3.0] - 2020-03-10
1117

1218
### Added

README.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ The `register` method has 4 required parameters and should be called within the
6464
| :---------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
6565
| \$slug | string | The page slug where the React app will live on the site. The loader will also reserve this slug with WordPress, preventing any new posts from being made at the same URL. If any existing posts share the defined slug, they will not be able to be accessed on the front-end of the site once rewrite rules are flushed. |
6666
| \$root_id | string | The id of the root element that the React app should mount to. By default, Create React App has this as `'root'`. |
67-
| \$plugin_dir_path | string | The absolute path to the plugin directory that contains the react app. In most situations, this should be `plugin_dir_path( __FILE__ )`. |
67+
| \$cra_directory | string | The absolute path to the plugin directory that contains the react app. In most situations, this should be `plugin_dir_path( __FILE__ )`. This can also be a URL to the base directory of a React app on a different website. |
6868
| \$role | string | The WordPress user role required to view the page. If a user tries to access the page without this role, they will be redirected to the site's [home_url()](https://developer.wordpress.org/reference/functions/home_url/). If no authentication is needed, this should be set as `'nopriv'`. |
6969
| \$callback | callable | Optional callback function. This is only fired on the registered page before the React app assets are enqueued. |
7070
| \$wp_permalinks | array | Optional array of subdirectories off of the defined slug that we DO WANT WordPress to handle. |
@@ -83,7 +83,6 @@ add_action( 'plugins_loaded', function() {
8383
);
8484
});
8585
```
86-
8786
### URL Structure
8887

8988
When a React plugin is registered with this loader plugin, a [virtual page](https://metabox.io/how-to-create-a-virtual-page-in-wordpress/) is created within WordPress. As a result, this new page will not show up within the regular pages/posts list in wp-admin. Because of the nature of creating a virtual page by adding new rewrite rules to WordPress, the rewrite rules will need to be flushed before the new page will be accessible.
@@ -106,6 +105,24 @@ wp rewrite flush
106105

107106
Trailing slash has been removed for registerd React app pages. This was done in an effort to create consistency in behavior with create-react-app's node-server structure (the environment that fires up when you run `npm start`).
108107

108+
#### Remote React App Support
109+
110+
Support for loading React apps hosted on other websites is available as of version __1.4.0__.
111+
112+
This can be achived by using a URL to the root of the React app For the 3rd argument in the register method.
113+
If your React app's asset-manifest.json is https://example.org/my-react-app/asset-manifest.json, use the first part of the URL (omit asset-manifest.json).
114+
115+
```php
116+
add_action( 'plugins_loaded', function() {
117+
\ReactAppLoader\API::register(
118+
'my-react-app-slug',
119+
'root',
120+
'https://example.org/my-react-app/',
121+
'nopriv'
122+
);
123+
});
124+
```
125+
109126
## Recommendations
110127

111128
### Using Images Within Your React App

lib/API.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,20 @@ class API {
1515
*
1616
* @param string $slug The slug to tell WordPress to stop handling so react can handle routing.
1717
* @param string $root_id The id of the root element we will be mounting our react app to.
18-
* @param string $plugin_dir_path The absolute path to the plugin directory that contains the react app.
18+
* @param string $cra_directory The absolute path to the plugin directory that has the CRA based react app. Can also be a URL to a remote CRA base react app.
1919
* @param string $role The role required to view the page.
2020
* @param callable $callback The callback function that fires before assets are enqueued to the page.
2121
* @param array $wp_permalinks An array of subdirectories off of the defined slug that we DO WANT WordPress to handle.
2222
*/
23-
public static function register( $slug, $root_id, $plugin_dir_path, $role, $callback = false, $wp_permalinks = [] ) : void {
23+
public static function register( $slug, $root_id, $cra_directory, $role, $callback = false, $wp_permalinks = [] ): void {
2424
add_action(
2525
'init',
26-
function() use ( $slug, $root_id, $plugin_dir_path, $role, $callback, $wp_permalinks ) {
26+
function() use ( $slug, $root_id, $cra_directory, $role, $callback, $wp_permalinks ) {
2727
$virtual_page = new Virtual_Page();
2828
$virtual_page->create(
2929
$slug,
3030
$root_id,
31-
$plugin_dir_path,
31+
$cra_directory,
3232
$role,
3333
$callback,
3434
$wp_permalinks

lib/Assets.php

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Assets {
2121
* @type array $styles Style dependencies.
2222
* }
2323
*/
24-
public function enqueue( $directory, $opts = [] ) : void {
24+
public function enqueue( $directory, $opts = [] ): void {
2525
$defaults = [
2626
'base_url' => '',
2727
'handle' => basename( $directory ),
@@ -92,11 +92,69 @@ public function enqueue( $directory, $opts = [] ) : void {
9292
}
9393
}
9494

95+
/**
96+
* Enqueue our remote react app assets.
97+
*
98+
* @param string $base_url Base URL to our remote react app.
99+
*/
100+
public function enqueue_remote( $base_url ): void {
101+
$assets = self::fetch_remote_assets( $base_url );
102+
103+
foreach ( $assets as $asset_url ) {
104+
$is_js = preg_match( '/\.js$/', $asset_url );
105+
$is_css = preg_match( '/\.css$/', $asset_url );
106+
$handle = sanitize_key( basename( $asset_url ) );
107+
108+
if ( $is_js ) {
109+
wp_enqueue_script( $handle, $asset_url, [], null, true );
110+
} elseif ( $is_css ) {
111+
wp_enqueue_style( $handle, $asset_url, [] );
112+
}
113+
}
114+
}
115+
116+
/**
117+
* Makes a GET request to the remote react app to fetch the asset-manifest.json.
118+
* Returns an array of entrypoints but with absolute URLs.
119+
*
120+
* @param string $base_url The base URL for the asset-manifest.json.
121+
* @return array
122+
*/
123+
private static function fetch_remote_assets( $base_url ) {
124+
$url = trailingslashit( $base_url ) . 'asset-manifest.json';
125+
$response = wp_remote_get( $url );
126+
127+
if ( is_wp_error( $response ) ) {
128+
return [];
129+
}
130+
131+
$body = wp_remote_retrieve_body( $response );
132+
$assets = json_decode( $body, true );
133+
134+
if ( empty( $assets ) ) {
135+
return [];
136+
}
137+
138+
if ( ! array_key_exists( 'entrypoints', $assets ) ) {
139+
trigger_error( 'React App Loader: Entrypoints key was not found within your react app\'s asset-manifest.json. This may indicate that you are using an unsupported version of react-scripts. Your react app should be using react-scripts@3.2.0 or later.', E_USER_WARNING ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error
140+
return [];
141+
}
142+
143+
$filtered_assets = array_map(
144+
function( $asset_path ) use ( $base_url ) {
145+
return "$base_url/$asset_path";
146+
},
147+
array_values( $assets['entrypoints'] )
148+
);
149+
150+
return $filtered_assets;
151+
}
152+
95153
/**
96154
* Attempt to load a file at the specified path and parse its contents as JSON.
97155
*
98156
* @param string $path The path to the JSON file to load.
99-
* @return array|null;
157+
* @return array|null
100158
*/
101159
public static function load_asset_file( $path ) {
102160
if ( ! file_exists( $path ) ) {
@@ -117,7 +175,7 @@ public static function load_asset_file( $path ) {
117175
* decode and return the asset list JSON if found.
118176
*
119177
* @param string $directory Root directory containing `src` and `build` directory.
120-
* @return array|null;
178+
* @return array|null
121179
*/
122180
public static function get_assets_list( string $directory ) {
123181
$directory = trailingslashit( $directory );

lib/Virtual_Page.php

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ class Virtual_Page {
2525
private $root_id;
2626

2727
/**
28-
* The absolute path to the plugin directory that contains the react app.
28+
* The absolute path to the plugin directory that has the CRA based react app. Can also be a URL to a remote CRA base react app.
2929
*
3030
* @var string
3131
*/
32-
private $plugin_dir_path;
32+
private $cra_directory;
3333

3434
/**
3535
* The user role required to view to view this page.
@@ -65,19 +65,19 @@ class Virtual_Page {
6565
*
6666
* @param string $slug The slug to tell WordPress to stop handling so react can handle routing.
6767
* @param string $root_id The id of the root element we will be mounting our react app to.
68-
* @param string $plugin_dir_path The absolute path to the plugin directory that contains the react app.
68+
* @param string $cra_directory The absolute path to the plugin directory that has the CRA based react app. Can also be a URL to a remote CRA base react app.
6969
* @param string $role The role required to view the page.
7070
* @param callable $callback The callback function that fires before assets are enqueued to the page.
7171
* @param array $wp_permalinks An array of subdirectories off of the defined slug that we DO WANT WordPress to handle.
7272
*/
73-
public function create( $slug, $root_id, $plugin_dir_path, $role, $callback, $wp_permalinks ) {
74-
$this->slug = $slug;
75-
$this->root_id = $root_id;
76-
$this->plugin_dir_path = $plugin_dir_path;
77-
$this->role = $role;
78-
$this->key = basename( $plugin_dir_path );
79-
$this->callback = $callback;
80-
$this->wp_permalinks = $wp_permalinks;
73+
public function create( $slug, $root_id, $cra_directory, $role, $callback, $wp_permalinks ) {
74+
$this->slug = $slug;
75+
$this->root_id = $root_id;
76+
$this->cra_directory = $cra_directory;
77+
$this->role = $role;
78+
$this->key = basename( $cra_directory );
79+
$this->callback = $callback;
80+
$this->wp_permalinks = $wp_permalinks;
8181

8282
$this->generate_page();
8383
$this->disable_wp_rewrite();
@@ -88,7 +88,7 @@ public function create( $slug, $root_id, $plugin_dir_path, $role, $callback, $wp
8888
/**
8989
* Create the virtual page the react app with live within.
9090
*/
91-
public function generate_page() : void {
91+
public function generate_page(): void {
9292
add_filter(
9393
'query_vars',
9494
function( $query_vars ) {
@@ -127,7 +127,7 @@ function() {
127127
/**
128128
* Prevent WordPress from thinking that react app routes are separate WordPress pages.
129129
*/
130-
public function disable_wp_rewrite() : void {
130+
public function disable_wp_rewrite(): void {
131131
$regex_pattern = '^' . $this->slug . '/(.*)$';
132132

133133
if ( ! empty( $this->wp_permalinks ) ) {
@@ -145,7 +145,7 @@ public function disable_wp_rewrite() : void {
145145
/**
146146
* Handles various aspects of updating requests to our virtual pages.
147147
*/
148-
public function handle_request() : void {
148+
public function handle_request(): void {
149149
add_filter(
150150
'request',
151151
function( $request ) {
@@ -189,7 +189,7 @@ function( $request ) {
189189
* In the case of a bad (i.e conflicting slug), WordPress appends a "-2" to
190190
* the permalink.
191191
*/
192-
public function reserve_slug() : void {
192+
public function reserve_slug(): void {
193193
add_filter( 'wp_unique_post_slug_is_bad_hierarchical_slug', [ $this, 'fe_prevent_slug_conflict' ], 10, 4 );
194194
add_filter( 'wp_unique_post_slug_is_bad_flat_slug', [ $this, 'fe_prevent_slug_conflict' ], 10, 3 );
195195
}
@@ -238,6 +238,22 @@ function( $classes ) {
238238
);
239239
}
240240

241+
/**
242+
* Check if the string is a valid URL.
243+
*
244+
* @param string $possible_url String to check if a URL.
245+
* @return boolean
246+
*/
247+
private static function is_url( string $possible_url ) {
248+
$parts = wp_parse_url( $possible_url );
249+
250+
if ( isset( $parts['host'] ) ) {
251+
return true;
252+
}
253+
254+
return false;
255+
}
256+
241257
/**
242258
* Removes the trailing slash from current request URL.
243259
*/
@@ -254,7 +270,7 @@ function( $string ) {
254270
/**
255271
* Handles the displaying of our virtual page content.
256272
*/
257-
public function display_page_content() : void {
273+
public function display_page_content(): void {
258274
// Redirect user to homepage if they do not have permissions.
259275
$user = wp_get_current_user();
260276

@@ -265,7 +281,13 @@ public function display_page_content() : void {
265281
}
266282

267283
$assets = new Assets();
268-
$assets->enqueue( $this->plugin_dir_path );
284+
285+
// Handle the loading of assets from a remote URL or a local plugin.
286+
if ( self::is_url( $this->cra_directory ) ) {
287+
$assets->enqueue_remote( $this->cra_directory );
288+
} else {
289+
$assets->enqueue( $this->cra_directory );
290+
}
269291

270292
// Fire our callback if one is defined.
271293
if ( false !== $this->callback ) {

0 commit comments

Comments
 (0)