@wrdagency/react-islands 1.0.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/README.md ADDED
@@ -0,0 +1,7 @@
1
+ ## React Islands
2
+
3
+ Created by Kyle Cooper at [WRD.agency](https://webresultsdirect.com)
4
+
5
+ React Islands is a way of introducing React into static pages. We developed React Islands as a way of introducing React-based interactivity sprinkled into places on WordPress sites (rendered via PHP).
6
+
7
+ The ReactIslandsWebpackPlugin automatically creates statically rendered mark-up for each Webpack entry point. This is rendered to a HTML file at build time which can then be hydrated using the `createIsland`.
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@wrdagency/react-islands",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "./dist/index.js",
6
+ "scripts": {
7
+ "build": "tsc"
8
+ },
9
+ "keywords": [],
10
+ "author": "Kyle Thomas Cooper @ WRD",
11
+ "license": "MIT",
12
+ "devDependencies": {
13
+ "@babel/core": "^7.24.3",
14
+ "@babel/preset-env": "^7.24.3",
15
+ "@babel/preset-react": "^7.24.1",
16
+ "@types/react-dom": "^18.2.23",
17
+ "@types/tmp": "^0.2.6",
18
+ "@types/webpack": "^5.28.5",
19
+ "babel-loader": "^9.1.3",
20
+ "typescript": "^5.4.3",
21
+ "tmp": "^0.2.3",
22
+ "webpack": "^5.91.0",
23
+ "react": "^18.2.0",
24
+ "react-dom": "^18.2.0"
25
+ }
26
+ }
package/src/index.ts ADDED
@@ -0,0 +1,113 @@
1
+ import {
2
+ Compilation,
3
+ Compiler,
4
+ Configuration,
5
+ WebpackOptionsNormalized,
6
+ webpack,
7
+ } from "webpack";
8
+ import tmp from "tmp";
9
+ import { renderToStaticMarkup } from "react-dom/server";
10
+ import { hydrateRoot } from "react-dom/client";
11
+
12
+ tmp.setGracefulCleanup();
13
+
14
+ export class ReactIslandsWebpackPlugin {
15
+ createSubCompiler(
16
+ dir: string,
17
+ entry: Record<string, any>,
18
+ options: WebpackOptionsNormalized
19
+ ): Compiler {
20
+ const plugins = options.plugins.filter(
21
+ (plugin) => !(plugin instanceof ReactIslandsWebpackPlugin)
22
+ );
23
+
24
+ const compilerOptions: Configuration = {
25
+ mode: "development",
26
+ target: "node",
27
+ context: process.cwd(),
28
+ entry,
29
+ output: {
30
+ libraryTarget: "commonjs",
31
+ path: dir,
32
+ filename: "[name].js",
33
+ },
34
+ module: options.module,
35
+ stats: false,
36
+ devtool: false,
37
+ optimization: {
38
+ minimize: false,
39
+ },
40
+ plugins,
41
+ };
42
+
43
+ return webpack(compilerOptions);
44
+ }
45
+
46
+ async apply(compiler: Compiler) {
47
+ let components: string[] = [];
48
+
49
+ const options = compiler.options;
50
+
51
+ const entry =
52
+ typeof options.entry === "function"
53
+ ? await options.entry()
54
+ : options.entry;
55
+
56
+ const { name: dir } = tmp.dirSync();
57
+
58
+ const { RawSource } = compiler.webpack.sources;
59
+
60
+ compiler.hooks.run.tapAsync(
61
+ "ReactIslandsWebpackPlugin",
62
+ (compiler: Compiler, callback) => {
63
+ components = Object.keys(entry);
64
+
65
+ const subCompiler = this.createSubCompiler(dir, entry, options);
66
+
67
+ subCompiler.run((err, stats) => {
68
+ if (err) {
69
+ console.error(err);
70
+ throw new Error("ReactIslandsWebpackPlugin: Error during compile.");
71
+ }
72
+
73
+ if (stats?.hasErrors()) {
74
+ console.log(stats?.toString());
75
+ throw new Error("ReactIslandsWebpackPlugin: Failed to compile.");
76
+ }
77
+
78
+ callback();
79
+ console.log("ReactIslandsWebpackPlugin: Compilation complete.");
80
+ });
81
+ }
82
+ );
83
+
84
+ compiler.hooks.thisCompilation.tap(
85
+ "ReactIslandsWebpackPlugin",
86
+ (compilation: Compilation) => {
87
+ for (const name of components) {
88
+ const file = `${dir}/${name}.js`;
89
+ const { default: component } = require(file);
90
+
91
+ const markup = renderToStaticMarkup(component());
92
+
93
+ compilation.emitAsset(`${name}.html`, new RawSource(markup));
94
+ }
95
+ }
96
+ );
97
+ }
98
+ }
99
+
100
+ export function createIsland(name: string, Component: React.FC) {
101
+ const isBrowser =
102
+ typeof window !== "undefined" && typeof window.document !== "undefined";
103
+
104
+ if (isBrowser) {
105
+ document
106
+ .querySelectorAll(`[data-react-component="${name}"]`)
107
+ .forEach((element) => {
108
+ const propsJSON = element.getAttribute("data-react-props") || "{}";
109
+ const props = JSON.parse(propsJSON);
110
+ hydrateRoot(element, Component(props));
111
+ });
112
+ }
113
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist/",
4
+ "target": "es2016",
5
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
6
+ "module": "Node16",
7
+ // "rootDir": "./", /* Specify the root folder within your source files. */
8
+ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
9
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
10
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
11
+ "esModuleInterop": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "strict": true,
14
+ "skipLibCheck": true
15
+ }
16
+ }