@wrdagency/react-islands 2.0.11 → 2.0.13

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/bin/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import e,{readFileSync as n}from"fs";import t from"yocto-spinner";import r from"@rollup/plugin-commonjs";import o from"@rollup/plugin-node-resolve";import s from"@rollup/plugin-replace";import i from"@rollup/plugin-terser";import a from"@rollup/plugin-typescript";import c from"path";import{rollup as l}from"rollup";import{typescriptPaths as p}from"rollup-plugin-typescript-paths";import{exec as u}from"child_process";import{promisify as m}from"util";import d from"command-line-args";import f from"command-line-usage";function g(e,n){let t="";return t+=function(e){return`import * as ${y(e)} from "${e}";\n`}(e),t+=`${n} = ${y(e)};\n`,t}function y(e){return e.replace(/^@/,"").replace(/\//g,"_").replace(/[-_](.)/g,(e,n)=>n.toUpperCase()).replace(/^[a-z]/,e=>e.toUpperCase())}const h=e=>({name:e.name,input:e.input,output:e.output||"./dist/",minify:j(e.minify,!0),ssg:j(e.ssg,!0),jsx:e.jsx||"react-jsx",typescript:j(e.ssg,!1),common:v(e.common||["react","react-dom/client","@wrdagency/react-islands"])}),j=(e,n)=>!1!==e&&(!0===e||n),v=(e=[""])=>Array.isArray(e)?e.reduce((e,n)=>({...e,[n]:"Islands._Common."+y(n)}),{}):e,w=m(u),x=async e=>{try{const{stdout:n,stderr:t}=await w(e);return n&&console.log(n),t&&console.error(t),n}catch(e){throw console.error(e),e}};function b(n){const{deleteAfterRunning:t=!1}=n;return{name:"rollup-plugin-run-script-after-builder",writeBundle(n,r){const o=n.dir?n.dir:c.dirname(n.file||"");for(const[n,s]of Object.entries(r)){const r=c.resolve(o,n);if("chunk"!==s.type||!s.isEntry)return;if(!r&&!r.endsWith("js"))return;if(!e.existsSync(r))return;x(`node ${r}`).then(()=>{t&&e.unlinkSync(r)})}}}}function $(e){const{dependencies:n,namespace:t=""}=e,r="\0virtual-entry";return{name:"rollup-plugin-virtualize-dependency",resolveId:e=>"virtual-entry"===e?r:null,load(e){if(e!==r)return null;let o=function(e){return e.split(".").filter(Boolean).map((e,n,t)=>{const r="window."+t.slice(0,n+1).join(".");return`${r} = ${r} || {}`}).join(";\n")}(t)+";\n";for(const[e,t]of Object.entries(n))o+=g(e,t);return o}}}function O(e){const n=h(e),{name:t,input:c,output:l,minify:u,ssg:m,jsx:d,typescript:f,common:g}=n,y=[],j=e=>{const{active:n=!0,name:m=`Islands.${t}`,subName:g,format:y,globals:h={},prefix:j,suffix:v,plugins:w=[]}=e;return n?{input:c,external:Object.keys(h),jsx:d,output:{name:m,globals:h,format:y,entryFileNames:`[name]/${g}`,dir:l,banner:j&&(e=>j(e.name)),footer:v&&(e=>v(e.name))},plugins:[o({extensions:[".cjs",".mjs",".js",".json",".node",".jsx",".ts",".tsx"]}),r(),s({preventAssignment:!0,"process.env.NODE_ENV":JSON.stringify("production")}),u&&i(),f&&a({noForceEmit:!0,compilerOptions:{outDir:l,jsx:d}}),f&&p(),...w]}:null};return y.push(j({active:!0,subName:"client.js",format:"iife",globals:g,suffix:e=>`\nwindow.Islands['${e}']?.render('${e}')`})),y.push(j({active:m,subName:"server.cjs",format:"cjs",suffix:()=>`var server = require('react-dom/server');\n\tvar fs = require('node:fs/promises');\n\tvar path = require('node:path');\nconst html = server.renderToString( module.exports.component( {} ) );\nconst file = path.resolve(__dirname, '${"ssg.html"}');\nfs.writeFile(file, html, { flag: "w+" });`,plugins:[b({deleteAfterRunning:!0})]})),y.filter(k)}function k(e){return null!==e}async function N(e,n){let t,r=!1;try{t=await l(e),await t.write(n)}catch(e){r=!0,console.error(e)}return t&&await t.close(),!r}!async function(e){const{command:n,help:t=!1,_unknown:r=[]}=d([{name:"command",type:String,defaultOption:!0},{name:"help",alias:"h",type:Boolean,description:"Display this usage guide."}],{stopAtFirstUnknown:!0});if(n&&n in e)return e[n].run([...r,t&&"--help"].filter(Boolean));if(t){const n=Object.keys(e).map(n=>({name:n,summary:e[n].description})),t=f([{header:"Synopsis",content:"npx react-islands <command> <options>"},{header:"Command List",content:n}]);console.log(t)}else console.error(`Command '${n}' not recognized by React Islands.`)}({build:new class{args;callback;description;constructor(e){let{args:n,callback:t,description:r}=e;n.push({name:"help",alias:"h",type:Boolean,description:"Display this usage guide."}),this.args=n,this.callback=t,this.description=r}run(e){const{help:n,...t}=d(this.args,{argv:e});if(!n)return this.callback(t);console.log(f([{header:"Options",optionList:this.args}]))}}({description:"Build and statically render the islands.",args:[{name:"config",type:String,alias:"c",description:"The config file to use.",defaultValue:"islands.config.json"}],callback:async e=>{const{config:a}=e,l=JSON.parse(n(a,"utf8")),{islands:p,...u}=l;if(!1!==u.common){const e=t({text:"Creating common dependencies file..."}).start(),n=function(e){const n=h(e),{output:t,minify:a,jsx:l,common:p}=n;return{input:"virtual-entry",jsx:l,output:{name:"Islands._Common",file:c.resolve(t,"common.js"),format:"iife"},plugins:[s({preventAssignment:!0,"process.env.NODE_ENV":JSON.stringify("production")}),$({dependencies:p,namespace:"Islands._Common"}),o(),r(),a&&i()]}}(u),{output:a,...l}=n;await N(l,a)?e.success("Succeeded: common dependencies file"):e.warning("Failed: common dependencies file")}for(const[e,n]of Object.entries(p)){const r=t({text:`Creating island ${e}...`}).start(),o=O({input:n,name:e,...u});let s=!1;for(const e of o){const{output:n,...t}=e;await N(t,n)||(s=!0)}o.map(async e=>{}),s?r.warning(`Failed island: ${e}`):r.success(`Succeeded island: ${e}`)}}})});
2
+ import e from"@rollup/plugin-commonjs";import n from"@rollup/plugin-node-resolve";import t from"@rollup/plugin-replace";import o from"@rollup/plugin-terser";import r from"path";import{rollup as s,watch as i}from"rollup";import a from"@rollup/plugin-typescript";import{typescriptPaths as c}from"rollup-plugin-typescript-paths";import l,{readFileSync as u}from"fs";import{exec as p}from"child_process";import{promisify as m}from"util";import d from"yocto-spinner";import f from"command-line-args";import g from"command-line-usage";class h{lines=[];import(e,n){return n||(n=h.packageNameToProperty(e)),this.add(`import * as ${n} from "${e}"`)}createGlobalObject(e){return this.add(e.split(".").map(e=>e.trim()).filter(Boolean).map((e,n,t)=>{const o="window."+t.slice(0,n+1).join(".");return`${o} = ${o} || {}`}))}setGlobalObjectProperty(e,n,t){return this.add(`window.${e}['${n}'] = ${t}`)}static packageNameToProperty(e){return e.replace(/^@/,"").replace(/\//g,"_").replace(/[-_](.)/g,(e,n)=>n.toUpperCase()).replace(/^[a-z]/,e=>e.toUpperCase())}static renderReactComponentToFile(e,n){return`var server = require('react-dom/server');\nvar fs = require('node:fs/promises');\nvar path = require('node:path');\nconst html = server.renderToString( ${n}( {} ) );\nconst file = path.resolve(__dirname, '${e}');\nfs.writeFile(file, html, { flag: "w+" });`}add(e){return Array.isArray(e)?this.lines.push(...e):this.lines.push(e),this}out(){return this.lines.join(";\n")}}function y(e){const{dependencies:n,namespace:t=""}=e,o="\0virtual-entry";return{name:"rollup-plugin-virtualize-dependency",resolveId:e=>"virtual-entry"===e?o:null,load(e){if(e!==o)return null;const r=new h;r.createGlobalObject(t);for(const e of n){const n=h.packageNameToProperty(e);r.import(e,n),r.setGlobalObjectProperty(t,n,n)}return r.out()}}}async function w(e){const{output:n,...t}=e;let o,r=!1;try{o=await s(t),await o.write(n)}catch(e){r=!0,console.error(e)}return o&&await o.close(),!r}function j(e){const{output:n,...t}=e,o=i({...t,output:n,watch:{clearScreen:!1}});return o.on("event",e=>{"BUNDLE_END"===e.code&&e.result&&e.result.close()}),o}function b(s){const{output:i,minify:a,jsx:c,common:l}=s;return{input:"virtual-entry",jsx:c,output:{name:"Islands._Common",file:r.resolve(i,"common.js"),format:"iife"},plugins:[t({preventAssignment:!0,"process.env.NODE_ENV":JSON.stringify("production")}),y({dependencies:l,namespace:"Islands._Common"}),n(),e(),a&&o()]}}const v=m(p);class ${line(e){console.log(e)}async spinner(e,n){const t=d({text:`${e}...`}).start();await n()?t.success(`Succeeded: ${e}`):t.warning(`Failed: ${e}`)}watcher(e,n){const t=d({text:`Rebuilding ${e}...`});let o=0;n.on("event",n=>{if("START"===n.code)t.clear(),t.start(),o=Date.now();else if("END"===n.code){const n=Date.now()-o;t.success(`Rebuilt: ${e} in ${n}ms.`)}else"ERROR"===n.code&&(t.warning(`Failed: ${e}`),console.error(n.error))})}async command(e){try{const{stdout:n,stderr:t}=await v(e);return n&&console.log(n),t&&console.error(t),n}catch(e){throw console.error(e),e}}}function x(e){const{deleteAfterRunning:n=!1}=e;return{name:"rollup-plugin-run-script-after-builder",writeBundle(e,t){const o=e.dir?e.dir:r.dirname(e.file||"");for(const[e,s]of Object.entries(t)){const t=r.resolve(o,e);if("chunk"!==s.type||!s.isEntry)return;if(!t&&!t.endsWith("js"))return;if(!l.existsSync(t))return;(new $).command(`node ${t}`).then(()=>{n&&l.unlinkSync(t)})}}}}function N(r,s){const{name:i,input:l,output:u,minify:p,ssg:m,jsx:d,typescript:f,common:g}=r,{name:h=`Islands.${i}`,subName:y,format:w,globals:j={},prefix:b,suffix:v,plugins:$=[]}=s;return{input:l,external:Object.keys(j),jsx:d,output:{name:h,globals:j,format:w,entryFileNames:`[name]/${y}`,dir:u,banner:b&&(e=>b(e.name)),footer:v&&(e=>v(e.name))},plugins:[n({extensions:[".cjs",".mjs",".js",".json",".node",".jsx",".ts",".tsx"]}),e(),t({preventAssignment:!0,"process.env.NODE_ENV":JSON.stringify("production")}),p&&o(),f&&a({outputToFilesystem:!0,noForceEmit:!0,compilerOptions:{outDir:u,jsx:d}}),f&&c(),...$]}}function O(e){return N(e,{subName:"client.js",format:"iife",globals:e.common.reduce((e,n)=>({...e,[n]:`Islands._Common["${h.packageNameToProperty(n)}"]`}),{}),suffix:e=>`\nwindow.Islands['${e}']?.render('${e}')`})}function k(e){const n=[O(e)];return e.ssg&&n.push(function(e){return N(e,{subName:"server.cjs",format:"cjs",suffix:()=>h.renderReactComponentToFile("ssg.html","module.exports.component"),plugins:[x({deleteAfterRunning:!0})]})}(e)),n}function S(e){return async function(e){let n=!0;for(const t of e)await w(t)||(n=!1);return n}(k(e))}function C(e){return j(O(e))}class T{args;callback;description;constructor(e){let{args:n,callback:t,description:o}=e;n.push({name:"help",alias:"h",type:Boolean,description:"Display this usage guide."}),this.args=n,this.callback=t,this.description=o}run(e){const{help:n,...t}=f(this.args,{argv:e});if(!n)return this.callback(t);console.log(g([{header:"Options",optionList:this.args}]))}}function _(e){const n=u(e,"utf8");return function(e){const n=(e,n)=>!1!==e&&(!0===e||n);return{islands:e.islands,output:e.output||"./dist/",minify:n(e.minify,!0),ssg:n(e.ssg,!0),jsx:e.jsx||"react-jsx",typescript:n(e.ssg,!1),common:e.common||["react","react/jsx-runtime","react-dom/client","@wrdagency/react-islands"]}}(JSON.parse(n))}!async function(e){const{command:n,help:t=!1,_unknown:o=[]}=f([{name:"command",type:String,defaultOption:!0},{name:"help",alias:"h",type:Boolean,description:"Display this usage guide."}],{stopAtFirstUnknown:!0});if(n&&n in e)return e[n].run([...o,t&&"--help"].filter(Boolean));if(t){const n=Object.keys(e).map(n=>({name:n,summary:e[n].description})),t=g([{header:"Synopsis",content:"npx react-islands <command> <options>"},{header:"Command List",content:n}]);console.log(t)}else console.error(`Command '${n}' not recognized by React Islands.`)}({build:new T({description:"Build and statically render the islands.",args:[{name:"config",type:String,alias:"c",description:"The config file to use.",defaultValue:"islands.config.json"}],callback:async e=>{const{config:n}=e,t=new $,o=_(n);o.common&&await t.spinner("Creating common dependencies file",async()=>w(b(o)));for(const[e,n]of Object.entries(o.islands))await t.spinner(`Creating island ${e}`,()=>S({name:e,input:n,...o}))}}),watch:new T({description:"Watch & build the islands for development",args:[{name:"config",type:String,alias:"c",description:"The config file to use.",defaultValue:"islands.config.json"}],callback:async e=>{const{config:n}=e,t=new $,o=_(n);if(o.common){const e=j(b(o));t.watcher("Common Dependencies",e)}for(const[e,n]of Object.entries(o.islands)){const r=C({name:e,input:n,...o});t.watcher(e,r)}}})});
package/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { FC } from 'react';
2
- import { JsxPreset } from 'rollup';
3
2
 
4
3
  interface IslandRenderOptions {
5
4
  shouldHydrate: boolean;
@@ -34,16 +33,15 @@ declare function withProps<T>(component: FC<T>, setProps: Partial<T>): FC<T>;
34
33
  */
35
34
  declare function isServer(): boolean;
36
35
 
37
- type BuildOptions = {
38
- name: string;
39
- input: string;
36
+ type ConfigOptions = {
37
+ islands: Record<string, string>;
40
38
  output: string;
41
39
  minify?: boolean;
42
40
  ssg?: boolean;
43
- jsx?: JsxPreset;
41
+ jsx?: "react" | "react-jsx" | "preserve" | "preserve-react";
44
42
  typescript?: boolean;
45
- common?: string[] | Record<string, string>;
43
+ common?: string[];
46
44
  };
47
45
 
48
46
  export { Island, isServer, withProps };
49
- export type { BuildOptions };
47
+ export type { ConfigOptions };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@wrdagency/react-islands",
4
- "version": "2.0.11",
4
+ "version": "2.0.13",
5
5
  "description": "",
6
6
  "main": "./dist/index.js",
7
7
  "files": [
@@ -1,8 +1,7 @@
1
- import { readFileSync } from "fs";
2
- import { OutputOptions } from "rollup";
3
- import yoctoSpinner from "yocto-spinner";
4
- import { createCommonConfig, createRollupConfigs, runRollup } from "../rollup";
1
+ import { buildCommon, buildIsland } from "../rollup";
5
2
  import { Command } from "../util/command";
3
+ import { readConfig } from "../util/config";
4
+ import { Output } from "../util/output";
6
5
 
7
6
  export default new Command({
8
7
  description: "Build and statically render the islands.",
@@ -17,57 +16,27 @@ export default new Command({
17
16
  },
18
17
  ],
19
18
  callback: async (args) => {
20
- const { config } = args;
19
+ const { config: configPath } = args;
21
20
 
22
- const configJson = JSON.parse(readFileSync(config, "utf8"));
21
+ const output = new Output();
22
+ const config = readConfig(configPath);
23
23
 
24
- const { islands, ...restConfig } = configJson;
25
-
26
- if (restConfig.common !== false) {
27
- const spinner = yoctoSpinner({
28
- text: `Creating common dependencies file...`,
29
- }).start();
30
-
31
- const commonRollupConfig = createCommonConfig(restConfig);
32
- const { output, ...input } = commonRollupConfig;
33
- const success = await runRollup(input, output as OutputOptions);
34
-
35
- if (success) {
36
- spinner.success(`Succeeded: common dependencies file`);
37
- } else {
38
- spinner.warning(`Failed: common dependencies file`);
39
- }
24
+ // 1. Build common dependencies.
25
+ if (config.common) {
26
+ await output.spinner("Creating common dependencies file", async () => {
27
+ return buildCommon(config);
28
+ });
40
29
  }
41
30
 
42
- for (const [name, input] of Object.entries(islands)) {
43
- const spinner = yoctoSpinner({
44
- text: `Creating island ${name}...`,
45
- }).start();
46
-
47
- const rollupConfigs = createRollupConfigs({
48
- input,
49
- name,
50
- ...restConfig,
31
+ // 2. Setup watcher for each island.
32
+ for (const [name, input] of Object.entries(config.islands)) {
33
+ await output.spinner(`Creating island ${name}`, () => {
34
+ return buildIsland({
35
+ name,
36
+ input,
37
+ ...config,
38
+ });
51
39
  });
52
-
53
- let hadFailure = false;
54
-
55
- for (const rollupConfig of rollupConfigs) {
56
- const { output, ...input } = rollupConfig;
57
- const success = await runRollup(input, output as OutputOptions);
58
-
59
- if (!success) {
60
- hadFailure = true;
61
- }
62
- }
63
-
64
- rollupConfigs.map(async (options) => {});
65
-
66
- if (hadFailure) {
67
- spinner.warning(`Failed island: ${name}`);
68
- } else {
69
- spinner.success(`Succeeded island: ${name}`);
70
- }
71
40
  }
72
41
  },
73
42
  });
@@ -0,0 +1,41 @@
1
+ import { watchCommon, watchIsland } from "../rollup";
2
+ import { Command } from "../util/command";
3
+ import { readConfig } from "../util/config";
4
+ import { Output } from "../util/output";
5
+
6
+ export default new Command({
7
+ description: "Watch & build the islands for development",
8
+ args: [
9
+ {
10
+ name: "config",
11
+ type: String,
12
+ alias: "c",
13
+ // @ts-ignore
14
+ description: "The config file to use.",
15
+ defaultValue: "islands.config.json",
16
+ },
17
+ ],
18
+ callback: async (args) => {
19
+ const { config: configPath } = args;
20
+
21
+ const output = new Output();
22
+ const config = readConfig(configPath);
23
+
24
+ // 1. Build common dependencies.
25
+ if (config.common) {
26
+ const watcher = watchCommon(config);
27
+ output.watcher("Common Dependencies", watcher);
28
+ }
29
+
30
+ // 2. Setup watcher for each island.
31
+ for (const [name, input] of Object.entries(config.islands)) {
32
+ const watcher = watchIsland({
33
+ name,
34
+ input,
35
+ ...config,
36
+ });
37
+
38
+ output.watcher(name, watcher);
39
+ }
40
+ },
41
+ });
package/src/bin/index.ts CHANGED
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import build from "./commands/build";
3
+ import watch from "./commands/watch";
3
4
  import { commandset } from "./util/command";
4
5
 
5
6
  commandset({
6
7
  build,
8
+ watch,
7
9
  });
@@ -0,0 +1,48 @@
1
+ import commonjsPlugin from "@rollup/plugin-commonjs";
2
+ import resolvePlugin from "@rollup/plugin-node-resolve";
3
+ import replacePlugin from "@rollup/plugin-replace";
4
+ import terserPlugin from "@rollup/plugin-terser";
5
+ import path from "path";
6
+ import { RollupOptions } from "rollup";
7
+ import { NormalizedConfigOptions } from "../util/config";
8
+ import { virtualizeDependencyPlugin } from "./plugins/virtualizeDependencyPlugin";
9
+ import { runRollup, watchRollup } from "./run";
10
+
11
+ export function createCommonConfig(
12
+ options: NormalizedConfigOptions
13
+ ): RollupOptions {
14
+ const { output, minify, jsx, common } = options;
15
+
16
+ return {
17
+ input: "virtual-entry",
18
+ jsx,
19
+ output: {
20
+ name: "Islands._Common",
21
+ file: path.resolve(output, "common.js"),
22
+ format: "iife",
23
+ },
24
+ plugins: [
25
+ replacePlugin({
26
+ preventAssignment: true,
27
+ "process.env.NODE_ENV": JSON.stringify("production"),
28
+ }),
29
+ virtualizeDependencyPlugin({
30
+ dependencies: common,
31
+ namespace: "Islands._Common",
32
+ }),
33
+ resolvePlugin(),
34
+ commonjsPlugin(),
35
+ minify && terserPlugin(),
36
+ ],
37
+ };
38
+ }
39
+
40
+ export function buildCommon(
41
+ options: NormalizedConfigOptions
42
+ ): Promise<boolean> {
43
+ return runRollup(createCommonConfig(options));
44
+ }
45
+
46
+ export function watchCommon(options: NormalizedConfigOptions) {
47
+ return watchRollup(createCommonConfig(options));
48
+ }
@@ -1,182 +1,3 @@
1
- import commonjsPlugin from "@rollup/plugin-commonjs";
2
- import resolvePlugin from "@rollup/plugin-node-resolve";
3
- import replacePlugin from "@rollup/plugin-replace";
4
- import terserPlugin from "@rollup/plugin-terser";
5
- import typescriptPlugin from "@rollup/plugin-typescript";
6
- import path from "path";
7
- import {
8
- InputOptions,
9
- ModuleFormat,
10
- OutputOptions,
11
- Plugin,
12
- rollup,
13
- RollupBuild,
14
- RollupOptions,
15
- } from "rollup";
16
- import { typescriptPaths as typescriptPathsPlugin } from "rollup-plugin-typescript-paths";
17
- import * as codeGen from "./generation";
18
- import { BuildOptions, normalizeOptions } from "./options";
19
- import { runScriptAfterBuildPlugin } from "./plugins/runScriptAfterBuildPlugin";
20
- import { virtualizeDependencyPlugin } from "./plugins/virtualizeDependencyPlugin";
21
-
22
- export function createRollupConfigs(options: BuildOptions): RollupOptions[] {
23
- const normalized = normalizeOptions(options);
24
- const { name, input, output, minify, ssg, jsx, typescript, common } =
25
- normalized;
26
-
27
- const configs: (RollupOptions | null)[] = [];
28
-
29
- const createRollupConfig = (opts: {
30
- active?: boolean;
31
- name?: string;
32
- subName: string;
33
- format: ModuleFormat;
34
- globals?: Record<string, string>;
35
- prefix?: (name: string) => string;
36
- suffix?: (name: string) => string;
37
- plugins?: Plugin[];
38
- }): RollupOptions | null => {
39
- const {
40
- active = true,
41
- name: mainName = `Islands.${name}`,
42
- subName,
43
- format,
44
- globals = {},
45
- prefix,
46
- suffix,
47
- plugins = [],
48
- } = opts;
49
-
50
- if (!active) {
51
- return null;
52
- }
53
-
54
- return {
55
- input,
56
- external: Object.keys(globals),
57
- jsx,
58
- output: {
59
- name: mainName,
60
- globals,
61
- format,
62
- entryFileNames: `[name]/${subName}`,
63
- dir: output,
64
- banner: prefix && ((chunk) => prefix(chunk.name)),
65
- footer: suffix && ((chunk) => suffix(chunk.name)),
66
- },
67
- plugins: [
68
- resolvePlugin({
69
- extensions: [
70
- ".cjs",
71
- ".mjs",
72
- ".js",
73
- ".json",
74
- ".node",
75
- ".jsx",
76
- ".ts",
77
- ".tsx",
78
- ],
79
- }),
80
- commonjsPlugin(),
81
- replacePlugin({
82
- preventAssignment: true,
83
- "process.env.NODE_ENV": JSON.stringify("production"),
84
- }),
85
- minify && terserPlugin(),
86
- typescript &&
87
- typescriptPlugin({
88
- noForceEmit: true,
89
- compilerOptions: {
90
- outDir: output,
91
- jsx,
92
- },
93
- }),
94
- typescript && typescriptPathsPlugin(),
95
- ...plugins,
96
- ],
97
- };
98
- };
99
-
100
- configs.push(
101
- createRollupConfig({
102
- active: true,
103
- subName: "client.js",
104
- format: "iife",
105
- globals: common,
106
- suffix: (name) => `\nwindow.Islands['${name}']?.render('${name}')`,
107
- })
108
- );
109
-
110
- configs.push(
111
- createRollupConfig({
112
- active: ssg,
113
- subName: "server.cjs",
114
- format: "cjs",
115
- suffix: () =>
116
- codeGen.renderComponentToFile("ssg.html", "module.exports.component"),
117
- plugins: [
118
- runScriptAfterBuildPlugin({
119
- deleteAfterRunning: true,
120
- }),
121
- ],
122
- })
123
- );
124
-
125
- return configs.filter(isRollupOptions);
126
- }
127
-
128
- export function createCommonConfig(options: BuildOptions): RollupOptions {
129
- const normalized = normalizeOptions(options);
130
- const { output, minify, jsx, common } = normalized;
131
-
132
- return {
133
- input: "virtual-entry",
134
- jsx,
135
- output: {
136
- name: "Islands._Common",
137
- file: path.resolve(output, "common.js"),
138
- format: "iife",
139
- },
140
- plugins: [
141
- replacePlugin({
142
- preventAssignment: true,
143
- "process.env.NODE_ENV": JSON.stringify("production"),
144
- }),
145
- virtualizeDependencyPlugin({
146
- dependencies: common,
147
- namespace: "Islands._Common",
148
- }),
149
- resolvePlugin(),
150
- commonjsPlugin(),
151
- minify && terserPlugin(),
152
- ],
153
- };
154
- }
155
-
156
- function isRollupOptions(
157
- config: RollupOptions | null
158
- ): config is RollupOptions {
159
- return config !== null;
160
- }
161
-
162
- export async function runRollup(
163
- inputOptions: InputOptions,
164
- outputOptions: OutputOptions
165
- ) {
166
- let bundle: RollupBuild | undefined;
167
- let buildFailed = false;
168
-
169
- try {
170
- bundle = await rollup(inputOptions);
171
- await bundle.write(outputOptions);
172
- } catch (error) {
173
- buildFailed = true;
174
- console.error(error);
175
- }
176
-
177
- if (bundle) {
178
- await bundle.close();
179
- }
180
-
181
- return !buildFailed;
182
- }
1
+ export * from "./common";
2
+ export * from "./island";
3
+ export * from "./run";
@@ -0,0 +1,141 @@
1
+ import commonjsPlugin from "@rollup/plugin-commonjs";
2
+ import resolvePlugin from "@rollup/plugin-node-resolve";
3
+ import replacePlugin from "@rollup/plugin-replace";
4
+ import terserPlugin from "@rollup/plugin-terser";
5
+ import typescriptPlugin from "@rollup/plugin-typescript";
6
+ import { ModuleFormat, Plugin, RollupOptions } from "rollup";
7
+ import { typescriptPaths as typescriptPathsPlugin } from "rollup-plugin-typescript-paths";
8
+ import { CodeGen } from "../util/code-gen";
9
+ import { IndividualIslandConfigOptions } from "../util/config";
10
+ import { runScriptAfterBuildPlugin } from "./plugins/runScriptAfterBuildPlugin";
11
+ import { runRollups, watchRollup } from "./run";
12
+
13
+ function createIslandRollupConfig(
14
+ config: IndividualIslandConfigOptions,
15
+ opts: {
16
+ name?: string;
17
+ subName: string;
18
+ format: ModuleFormat;
19
+ globals?: Record<string, string>;
20
+ prefix?: (name: string) => string;
21
+ suffix?: (name: string) => string;
22
+ plugins?: Plugin[];
23
+ }
24
+ ): RollupOptions {
25
+ const { name, input, output, minify, ssg, jsx, typescript, common } = config;
26
+
27
+ const {
28
+ name: mainName = `Islands.${name}`,
29
+ subName,
30
+ format,
31
+ globals = {},
32
+ prefix,
33
+ suffix,
34
+ plugins = [],
35
+ } = opts;
36
+
37
+ return {
38
+ input,
39
+ external: Object.keys(globals),
40
+ jsx,
41
+ output: {
42
+ name: mainName,
43
+ globals,
44
+ format,
45
+ entryFileNames: `[name]/${subName}`,
46
+ dir: output,
47
+ banner: prefix && ((chunk) => prefix(chunk.name)),
48
+ footer: suffix && ((chunk) => suffix(chunk.name)),
49
+ },
50
+ plugins: [
51
+ resolvePlugin({
52
+ extensions: [
53
+ ".cjs",
54
+ ".mjs",
55
+ ".js",
56
+ ".json",
57
+ ".node",
58
+ ".jsx",
59
+ ".ts",
60
+ ".tsx",
61
+ ],
62
+ }),
63
+ commonjsPlugin(),
64
+ replacePlugin({
65
+ preventAssignment: true,
66
+ "process.env.NODE_ENV": JSON.stringify("production"),
67
+ }),
68
+ minify && terserPlugin(),
69
+ typescript &&
70
+ typescriptPlugin({
71
+ outputToFilesystem: true,
72
+ noForceEmit: true,
73
+ compilerOptions: {
74
+ outDir: output,
75
+ jsx,
76
+ },
77
+ }),
78
+ typescript && typescriptPathsPlugin(),
79
+ ...plugins,
80
+ ],
81
+ };
82
+ }
83
+
84
+ export function createIslandRollupConfigClient(
85
+ config: IndividualIslandConfigOptions
86
+ ): RollupOptions {
87
+ return createIslandRollupConfig(config, {
88
+ subName: "client.js",
89
+ format: "iife",
90
+ globals: config.common.reduce((globals, packageName) => {
91
+ return {
92
+ ...globals,
93
+ [packageName]: `Islands._Common["${CodeGen.packageNameToProperty(
94
+ packageName
95
+ )}"]`,
96
+ };
97
+ }, {}),
98
+ suffix: (name) => `\nwindow.Islands['${name}']?.render('${name}')`,
99
+ });
100
+ }
101
+
102
+ export function createIslandRollupConfigServer(
103
+ config: IndividualIslandConfigOptions
104
+ ): RollupOptions {
105
+ return createIslandRollupConfig(config, {
106
+ subName: "server.cjs",
107
+ format: "cjs",
108
+ suffix: () =>
109
+ CodeGen.renderReactComponentToFile(
110
+ "ssg.html",
111
+ "module.exports.component"
112
+ ),
113
+ plugins: [
114
+ runScriptAfterBuildPlugin({
115
+ deleteAfterRunning: true,
116
+ }),
117
+ ],
118
+ });
119
+ }
120
+
121
+ export function createIslandRollupConfigs(
122
+ config: IndividualIslandConfigOptions
123
+ ): RollupOptions[] {
124
+ const configs: RollupOptions[] = [createIslandRollupConfigClient(config)];
125
+
126
+ if (config.ssg) {
127
+ configs.push(createIslandRollupConfigServer(config));
128
+ }
129
+
130
+ return configs;
131
+ }
132
+
133
+ export function buildIsland(
134
+ options: IndividualIslandConfigOptions
135
+ ): Promise<boolean> {
136
+ return runRollups(createIslandRollupConfigs(options));
137
+ }
138
+
139
+ export function watchIsland(options: IndividualIslandConfigOptions) {
140
+ return watchRollup(createIslandRollupConfigClient(options));
141
+ }
@@ -1,7 +1,7 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { Plugin } from "rollup";
4
- import { consoleExecute } from "../console";
4
+ import { Output } from "../../util/output";
5
5
 
6
6
  type Options = {
7
7
  deleteAfterRunning?: boolean;
@@ -32,7 +32,8 @@ export function runScriptAfterBuildPlugin(opts: Options): Plugin {
32
32
  return;
33
33
  }
34
34
 
35
- consoleExecute(`node ${filePath}`).then(() => {
35
+ const out = new Output();
36
+ out.command(`node ${filePath}`).then(() => {
36
37
  if (deleteAfterRunning) {
37
38
  fs.unlinkSync(filePath);
38
39
  }
@@ -1,8 +1,8 @@
1
1
  import { Plugin } from "rollup";
2
- import * as codeGen from "../generation";
2
+ import { CodeGen } from "../../util/code-gen";
3
3
 
4
4
  type Options = {
5
- dependencies: Record<string, string>;
5
+ dependencies: string[];
6
6
  namespace?: string;
7
7
  };
8
8
 
@@ -22,13 +22,16 @@ export function virtualizeDependencyPlugin(opts: Options): Plugin {
22
22
  return null;
23
23
  }
24
24
 
25
- let str = codeGen.createGlobalObject(namespace) + ";\n";
25
+ const code = new CodeGen();
26
+ code.createGlobalObject(namespace);
26
27
 
27
- for (const [dependency, name] of Object.entries(dependencies)) {
28
- str += codeGen.importAndGlobalisePackage(dependency, name);
28
+ for (const packageName of dependencies) {
29
+ const name = CodeGen.packageNameToProperty(packageName);
30
+ code.import(packageName, name);
31
+ code.setGlobalObjectProperty(namespace, name, name);
29
32
  }
30
33
 
31
- return str;
34
+ return code.out();
32
35
  },
33
36
  };
34
37
  }
@@ -0,0 +1,65 @@
1
+ import {
2
+ OutputOptions,
3
+ rollup,
4
+ RollupBuild,
5
+ RollupOptions,
6
+ RollupWatcher,
7
+ watch,
8
+ } from "rollup";
9
+
10
+ export async function runRollup(options: RollupOptions): Promise<boolean> {
11
+ const { output, ...input } = options;
12
+
13
+ let bundle: RollupBuild | undefined;
14
+ let buildFailed = false;
15
+
16
+ try {
17
+ bundle = await rollup(input);
18
+ await bundle.write(output as OutputOptions);
19
+ } catch (error) {
20
+ buildFailed = true;
21
+ console.error(error);
22
+ }
23
+
24
+ if (bundle) {
25
+ await bundle.close();
26
+ }
27
+
28
+ return !buildFailed;
29
+ }
30
+
31
+ export async function runRollups(options: RollupOptions[]): Promise<boolean> {
32
+ let isSuccess = true;
33
+
34
+ for (const rollupConfig of options) {
35
+ if (!(await runRollup(rollupConfig))) {
36
+ isSuccess = false;
37
+ }
38
+ }
39
+
40
+ return isSuccess;
41
+ }
42
+
43
+ export function watchRollup(options: RollupOptions): RollupWatcher {
44
+ const { output, ...input } = options;
45
+
46
+ const watcher = watch({
47
+ ...input,
48
+ output,
49
+ watch: {
50
+ clearScreen: false,
51
+ },
52
+ });
53
+
54
+ watcher.on("event", (evt) => {
55
+ if (evt.code === "BUNDLE_END" && evt.result) {
56
+ evt.result.close();
57
+ }
58
+ });
59
+
60
+ return watcher;
61
+ }
62
+
63
+ export function watchRollups(options: RollupOptions[]): RollupWatcher[] {
64
+ return options.map((rollupConfig) => watchRollup(rollupConfig));
65
+ }
@@ -0,0 +1,66 @@
1
+ export class CodeGen {
2
+ private lines: string[] = [];
3
+
4
+ public import(packageName: string, as?: string) {
5
+ if (!as) {
6
+ as = CodeGen.packageNameToProperty(packageName);
7
+ }
8
+
9
+ return this.add(`import * as ${as} from "${packageName}"`);
10
+ }
11
+
12
+ public createGlobalObject(scope: string): this {
13
+ return this.add(
14
+ scope
15
+ .split(".")
16
+ .map((s) => s.trim())
17
+ .filter(Boolean)
18
+ .map((_, index, levels) => {
19
+ const path = "window." + levels.slice(0, index + 1).join(".");
20
+ return `${path} = ${path} || {}`;
21
+ })
22
+ );
23
+ }
24
+
25
+ public setGlobalObjectProperty(
26
+ scope: string,
27
+ key: string,
28
+ value: string
29
+ ): this {
30
+ return this.add(`window.${scope}['${key}'] = ${value}`);
31
+ }
32
+
33
+ public static packageNameToProperty(packageName: string): string {
34
+ return packageName
35
+ .replace(/^@/, "")
36
+ .replace(/\//g, "_")
37
+ .replace(/[-_](.)/g, (_, c) => c.toUpperCase())
38
+ .replace(/^[a-z]/, (c) => c.toUpperCase());
39
+ }
40
+
41
+ public static renderReactComponentToFile(
42
+ outputFilename: string,
43
+ component: string
44
+ ) {
45
+ return `var server = require('react-dom/server');
46
+ var fs = require('node:fs/promises');
47
+ var path = require('node:path');
48
+ const html = server.renderToString( ${component}( {} ) );
49
+ const file = path.resolve(__dirname, '${outputFilename}');
50
+ fs.writeFile(file, html, { flag: "w+" });`;
51
+ }
52
+
53
+ public add(line: string | string[]): this {
54
+ if (Array.isArray(line)) {
55
+ this.lines.push(...line);
56
+ } else {
57
+ this.lines.push(line);
58
+ }
59
+
60
+ return this;
61
+ }
62
+
63
+ public out(): string {
64
+ return this.lines.join(";\n");
65
+ }
66
+ }
@@ -0,0 +1,68 @@
1
+ import { readFileSync } from "fs";
2
+
3
+ export type ConfigOptions = {
4
+ islands: Record<string, string>;
5
+ output: string;
6
+ minify?: boolean;
7
+ ssg?: boolean;
8
+ jsx?: "react" | "react-jsx" | "preserve" | "preserve-react";
9
+ typescript?: boolean;
10
+ common?: string[];
11
+ };
12
+
13
+ export type NormalizedConfigOptions = {
14
+ islands: Record<string, string>;
15
+ output: string;
16
+ minify: boolean;
17
+ ssg: boolean;
18
+ jsx: "react" | "react-jsx" | "preserve" | "preserve-react";
19
+ typescript: boolean;
20
+ common: string[];
21
+ };
22
+
23
+ export type IndividualIslandConfigOptions = Omit<
24
+ NormalizedConfigOptions,
25
+ "islands"
26
+ > & {
27
+ name: string;
28
+ input: string;
29
+ };
30
+
31
+ export function readConfig(path: string): NormalizedConfigOptions {
32
+ const contents = readFileSync(path, "utf8");
33
+ const json = JSON.parse(contents) as ConfigOptions;
34
+
35
+ return normalizeBuildOptions(json);
36
+ }
37
+
38
+ export function normalizeBuildOptions(
39
+ options: ConfigOptions
40
+ ): NormalizedConfigOptions {
41
+ const normalizeBoolean = (
42
+ value: boolean | undefined | null | void,
43
+ fallback: boolean
44
+ ): boolean => {
45
+ if (value === false) {
46
+ return false;
47
+ } else if (value === true) {
48
+ return true;
49
+ } else {
50
+ return fallback;
51
+ }
52
+ };
53
+
54
+ return {
55
+ islands: options.islands,
56
+ output: options.output || "./dist/",
57
+ minify: normalizeBoolean(options.minify, true),
58
+ ssg: normalizeBoolean(options.ssg, true),
59
+ jsx: options.jsx || "react-jsx",
60
+ typescript: normalizeBoolean(options.ssg, false),
61
+ common: options.common || [
62
+ "react",
63
+ "react/jsx-runtime",
64
+ "react-dom/client",
65
+ "@wrdagency/react-islands",
66
+ ],
67
+ };
68
+ }
@@ -0,0 +1,67 @@
1
+ import { exec } from "child_process";
2
+ import { RollupWatcher } from "rollup";
3
+ import { promisify } from "util";
4
+ import yoctoSpinner from "yocto-spinner";
5
+
6
+ const execPromise = promisify(exec);
7
+
8
+ export class Output {
9
+ public line(line: string) {
10
+ console.log(line);
11
+ }
12
+
13
+ public async spinner(message: string, callback: () => Promise<boolean>) {
14
+ const spinner = yoctoSpinner({
15
+ text: `${message}...`,
16
+ }).start();
17
+
18
+ const success = await callback();
19
+
20
+ if (success) {
21
+ spinner.success(`Succeeded: ${message}`);
22
+ } else {
23
+ spinner.warning(`Failed: ${message}`);
24
+ }
25
+ }
26
+
27
+ public watcher(name: string, watcher: RollupWatcher) {
28
+ const spinner = yoctoSpinner({
29
+ text: `Rebuilding ${name}...`,
30
+ });
31
+
32
+ let timeAtStart = 0;
33
+
34
+ watcher.on("event", (evt) => {
35
+ if (evt.code === "START") {
36
+ spinner.clear();
37
+ spinner.start();
38
+ timeAtStart = Date.now();
39
+ } else if (evt.code === "END") {
40
+ const timeElapsed = Date.now() - timeAtStart;
41
+ spinner.success(`Rebuilt: ${name} in ${timeElapsed}ms.`);
42
+ } else if (evt.code === "ERROR") {
43
+ spinner.warning(`Failed: ${name}`);
44
+ console.error(evt.error);
45
+ }
46
+ });
47
+ }
48
+
49
+ public async command(command: string) {
50
+ try {
51
+ const { stdout, stderr } = await execPromise(command);
52
+
53
+ if (stdout) {
54
+ console.log(stdout);
55
+ }
56
+
57
+ if (stderr) {
58
+ console.error(stderr);
59
+ }
60
+
61
+ return stdout;
62
+ } catch (e) {
63
+ console.error(e);
64
+ throw e;
65
+ }
66
+ }
67
+ }
package/src/index.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Island } from "./island";
2
2
  import { isServer, withProps } from "./util";
3
3
 
4
- export type { BuildOptions } from "./bin/rollup/options";
4
+ export type { ConfigOptions } from "./bin/util/config";
5
5
  export { Island, isServer, withProps };
@@ -1,23 +0,0 @@
1
- import { exec } from "child_process";
2
- import { promisify } from "util";
3
-
4
- const execPromise = promisify(exec);
5
-
6
- export const consoleExecute = async (command: string) => {
7
- try {
8
- const { stdout, stderr } = await execPromise(command);
9
-
10
- if (stdout) {
11
- console.log(stdout);
12
- }
13
-
14
- if (stderr) {
15
- console.error(stderr);
16
- }
17
-
18
- return stdout;
19
- } catch (e) {
20
- console.error(e);
21
- throw e;
22
- }
23
- };
@@ -1,57 +0,0 @@
1
- export function createGlobalObject(namespace: string): string {
2
- return namespace
3
- .split(".")
4
- .filter(Boolean)
5
- .map((_, index, levels) => {
6
- const path = "window." + levels.slice(0, index + 1).join(".");
7
- return `${path} = ${path} || {}`;
8
- })
9
- .join(";\n");
10
- }
11
-
12
- export function assignGlobalProperty(
13
- namespace: string,
14
- property: string,
15
- value: string
16
- ): string {
17
- const namespacing = createGlobalObject(namespace);
18
- return `${namespacing};\nwindow.${namespace}['${property}'] = ${value};\n`;
19
- }
20
-
21
- export function importPackage(packageName: string): string {
22
- return `import * as ${packageNameToProperty(
23
- packageName
24
- )} from "${packageName}";\n`;
25
- }
26
-
27
- export function importAndGlobalisePackage(
28
- packageName: string,
29
- globalName: string
30
- ): string {
31
- let str = "";
32
-
33
- str += importPackage(packageName);
34
- str += `${globalName} = ${packageNameToProperty(packageName)};\n`;
35
-
36
- return str;
37
- }
38
-
39
- export function packageNameToProperty(packageName: string): string {
40
- return packageName
41
- .replace(/^@/, "")
42
- .replace(/\//g, "_")
43
- .replace(/[-_](.)/g, (_, c) => c.toUpperCase())
44
- .replace(/^[a-z]/, (c) => c.toUpperCase());
45
- }
46
-
47
- export function renderComponentToFile(
48
- filename: string,
49
- component: string
50
- ): string {
51
- return `var server = require('react-dom/server');
52
- var fs = require('node:fs/promises');
53
- var path = require('node:path');
54
- const html = server.renderToString( module.exports.component( {} ) );
55
- const file = path.resolve(__dirname, '${filename}');
56
- fs.writeFile(file, html, { flag: "w+" });`;
57
- }
@@ -1,74 +0,0 @@
1
- import { JsxPreset } from "rollup";
2
- import { packageNameToProperty } from "./generation";
3
-
4
- export type BuildOptions = {
5
- name: string;
6
- input: string;
7
- output: string;
8
- minify?: boolean;
9
- ssg?: boolean;
10
- jsx?: JsxPreset;
11
- typescript?: boolean;
12
- common?: string[] | Record<string, string>;
13
- };
14
-
15
- export type NormalizedBuildOptions = {
16
- name: string;
17
- input: string;
18
- output: string;
19
- minify: boolean;
20
- ssg: boolean;
21
- jsx: JsxPreset;
22
- typescript: boolean;
23
- common: Record<string, string>;
24
- };
25
-
26
- export const normalizeOptions = (
27
- options: BuildOptions
28
- ): NormalizedBuildOptions => {
29
- return {
30
- name: options.name,
31
- input: options.input,
32
- output: options.output || "./dist/",
33
- minify: normalizeBoolean(options.minify, true),
34
- ssg: normalizeBoolean(options.ssg, true),
35
- jsx: options.jsx || "react-jsx",
36
- typescript: normalizeBoolean(options.ssg, false),
37
- common: normalizeCommonOption(
38
- options.common || [
39
- "react",
40
- "react-dom/client",
41
- "@wrdagency/react-islands",
42
- ]
43
- ),
44
- };
45
- };
46
-
47
- export const normalizeBoolean = (
48
- value: boolean | undefined | null | void,
49
- fallback: boolean
50
- ): boolean => {
51
- if (value === false) {
52
- return false;
53
- } else if (value === true) {
54
- return true;
55
- } else {
56
- return fallback;
57
- }
58
- };
59
-
60
- export const normalizeCommonOption = (
61
- share: BuildOptions["common"] = [""]
62
- ): NormalizedBuildOptions["common"] => {
63
- if (Array.isArray(share)) {
64
- return share.reduce(
65
- (record, packageName) => ({
66
- ...record,
67
- [packageName]: "Islands._Common." + packageNameToProperty(packageName),
68
- }),
69
- {}
70
- );
71
- } else {
72
- return share;
73
- }
74
- };