@hyperspan/plugin-preact 0.1.5 → 0.2.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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/index.ts +44 -37
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperspan/plugin-preact",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "description": "Hyperspan Plugin for Preact",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -28,8 +28,8 @@
28
28
  "typescript": "^5.0.0"
29
29
  },
30
30
  "peerDependencies": {
31
- "@hyperspan/html": "^0.1.7",
32
- "@hyperspan/framework": "^0.3.3"
31
+ "@hyperspan/html": "0.1.7",
32
+ "@hyperspan/framework": "0.5.3"
33
33
  },
34
34
  "dependencies": {
35
35
  "preact": "^10.26.5",
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { clientImportMap, assetHash, ISLAND_PUBLIC_PATH } from '@hyperspan/framework/assets';
2
+ import { IS_PROD } from '@hyperspan/framework/server';
2
3
  import { resolve } from 'node:path';
3
4
 
4
5
  // External ESM = https://esm.sh/preact@10.26.4/compat
5
- const PREACT_PUBLIC_FILE_PATH = ISLAND_PUBLIC_PATH + '/preact-client.js';
6
6
  const PREACT_ISLAND_CACHE = new Map<string, string>();
7
7
 
8
8
  /**
@@ -10,22 +10,26 @@ const PREACT_ISLAND_CACHE = new Map<string, string>();
10
10
  */
11
11
  async function copyPreactToPublicFolder() {
12
12
  const sourceFile = resolve(__dirname, './preact-client.ts');
13
- await Bun.build({
13
+ const result = await Bun.build({
14
14
  entrypoints: [sourceFile],
15
- outdir: './public/' + ISLAND_PUBLIC_PATH,
15
+ outdir: `./public/${ISLAND_PUBLIC_PATH}`,
16
+ naming: IS_PROD ? '[dir]/[name]-[hash].[ext]' : undefined,
16
17
  minify: true,
17
18
  format: 'esm',
18
19
  target: 'browser',
19
20
  });
20
21
 
21
- clientImportMap.set('preact', PREACT_PUBLIC_FILE_PATH);
22
- clientImportMap.set('preact/compat', PREACT_PUBLIC_FILE_PATH);
23
- clientImportMap.set('preact/hooks', PREACT_PUBLIC_FILE_PATH);
24
- clientImportMap.set('preact/jsx-runtime', PREACT_PUBLIC_FILE_PATH);
22
+ const builtFileName = String(result.outputs[0].path.split('/').reverse()[0]).replace('.js', '');
23
+ const builtFilePath = `${ISLAND_PUBLIC_PATH}/${builtFileName}.js`;
24
+
25
+ clientImportMap.set('preact', builtFilePath);
26
+ clientImportMap.set('preact/compat', builtFilePath);
27
+ clientImportMap.set('preact/hooks', builtFilePath);
28
+ clientImportMap.set('preact/jsx-runtime', builtFilePath);
25
29
 
26
30
  if (!clientImportMap.has('react')) {
27
- clientImportMap.set('react', PREACT_PUBLIC_FILE_PATH);
28
- clientImportMap.set('react-dom', PREACT_PUBLIC_FILE_PATH);
31
+ clientImportMap.set('react', builtFilePath);
32
+ clientImportMap.set('react-dom', builtFilePath);
29
33
  }
30
34
  }
31
35
 
@@ -33,6 +37,11 @@ async function copyPreactToPublicFolder() {
33
37
  * Hyperspan Preact Plugin
34
38
  */
35
39
  export async function preactPlugin() {
40
+ // Ensure Preact can be loaded on the client
41
+ if (!clientImportMap.has('preact')) {
42
+ await copyPreactToPublicFolder();
43
+ }
44
+
36
45
  // Define a Bun plugin to handle .tsx files
37
46
  await Bun.plugin({
38
47
  name: 'Hyperspan Preact Loader',
@@ -40,7 +49,6 @@ export async function preactPlugin() {
40
49
  // when a .tsx file is imported...
41
50
  build.onLoad({ filter: /\.tsx$/ }, async (args) => {
42
51
  const jsId = assetHash(args.path);
43
- let contents = await Bun.file(args.path).text();
44
52
 
45
53
  // Cache: Avoid re-processing the same file
46
54
  if (PREACT_ISLAND_CACHE.has(jsId)) {
@@ -50,32 +58,30 @@ export async function preactPlugin() {
50
58
  };
51
59
  }
52
60
 
53
- // Ensure Preact can be loaded on the client
54
- if (!clientImportMap.has('preact')) {
55
- await copyPreactToPublicFolder();
56
- }
57
-
58
- if (contents) {
59
- // We need to build the file to ensure we can ship it to the client with dependencies
60
- // Ironic, right? Calling Bun.build() inside of a plugin that runs on Bun.build()?
61
- const result = await Bun.build({
62
- entrypoints: [args.path],
63
- //outdir: './public/' + ISLAND_PUBLIC_PATH,
64
- //naming: '[name]-[hash].js',
65
- external: Array.from(clientImportMap.keys()),
66
- minify: true,
67
- format: 'esm',
68
- target: 'browser',
69
- });
70
-
71
- contents = await result.outputs[0].text();
72
- }
61
+ // We need to build the file to ensure we can ship it to the client with dependencies
62
+ // Ironic, right? Calling Bun.build() inside of a plugin that runs on Bun.build()?
63
+ const result = await Bun.build({
64
+ entrypoints: [args.path],
65
+ outdir: `./public/${ISLAND_PUBLIC_PATH}`,
66
+ naming: IS_PROD ? '[dir]/[name]-[hash].[ext]' : undefined,
67
+ external: Array.from(clientImportMap.keys()),
68
+ minify: true,
69
+ format: 'esm',
70
+ target: 'browser',
71
+ env: 'APP_PUBLIC_*',
72
+ });
73
+
74
+ // Add output file to import map
75
+ const esmName = String(result.outputs[0].path.split('/').reverse()[0]).replace('.js', '');
76
+ clientImportMap.set(esmName, `${ISLAND_PUBLIC_PATH}/${esmName}.js`);
77
+
78
+ let contents = await result.outputs[0].text();
73
79
 
74
80
  // Look for the default export
75
- const RE_EXPORT_DEFAULT = /export\{(\w+) as default\}/;
76
- const RE_EXPORT_DEFAULT_FN = /export default function\s+(\w+)/;
77
- const RE_EXPORT_DEFAULT_CONST = /export default const\s+(\w+)/;
78
- const RE_EXPORT_DEFAULT_ANY = /export default\s+(\w+)/;
81
+ const RE_EXPORT_DEFAULT = /export\{([^\s]+) as default\}/;
82
+ const RE_EXPORT_DEFAULT_FN = /export default function\s+([^\s]+)/;
83
+ const RE_EXPORT_DEFAULT_CONST = /export default const\s+([^\s]+)/;
84
+ const RE_EXPORT_DEFAULT_ANY = /export default\s+([^\s]+)/;
79
85
 
80
86
  const exportedDefault = contents.match(RE_EXPORT_DEFAULT);
81
87
  const exportedDefaultFn = contents.match(RE_EXPORT_DEFAULT_FN);
@@ -95,7 +101,7 @@ export async function preactPlugin() {
95
101
  }
96
102
 
97
103
  // Add to contents so this is in the client JS as well
98
- contents = `import { h as __hs_h, render as __hs_render, hydrate as __hs_hydrate } from 'preact';${contents}`;
104
+ contents = `import { h, h as __hs_h, render as __hs_render, hydrate as __hs_hydrate } from 'preact';${contents}`;
99
105
 
100
106
  // Some _interesting_ work at play here...
101
107
  // We have to modify the original file contents to add an __HS_PLUGIN export that the renderIsland() function can use to render the component.
@@ -111,11 +117,12 @@ ${contents}
111
117
 
112
118
  // hyperspan:preact-plugin
113
119
  function __hs_renderIsland(jsContent = '', ssrContent = '', options = {}) {
120
+ const scriptTag = \`<script type="module" id="${jsId}_script" data-source-id="${jsId}">import ${componentName} from "${esmName}";\${jsContent}</script>\`;
114
121
  if (options.loading === 'lazy') {
115
- return \`<div id="${jsId}">\${ssrContent}</div><div data-loading="lazy" style="height:1px;width:1px;overflow:hidden;"><template><script type="module" data-source-id="${jsId}">${contents}\${jsContent}</script></template></div>\`;
122
+ return \`<div id="${jsId}">\${ssrContent}</div><div data-loading="lazy" style="height:1px;width:1px;overflow:hidden;"><template>\n\${scriptTag}</template></div>\`;
116
123
  }
117
124
 
118
- return \`<div id="${jsId}">\${ssrContent}</div><script type="module" data-source-id="${jsId}">${contents}\${jsContent}</script>\`;
125
+ return \`<div id="${jsId}">\${ssrContent}</div>\n\${scriptTag}\`;
119
126
  }
120
127
  ${componentName}.__HS_ISLAND = {
121
128
  id: "${jsId}",