adaptar-vite-plugin 1.0.2 → 1.0.4

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.
@@ -46,6 +46,18 @@ export function interceptHmrErrors(server) {
46
46
  suppressReloadUntil = 0; // reset — only suppress once
47
47
  return;
48
48
  }
49
+ // A Vite update has been compiled and sent to the preview client. The
50
+ // injected bridge associates this with the server-owned preview operation
51
+ // ID carried in the iframe URL.
52
+ if (payload.type === "update") {
53
+ originalSend(payload);
54
+ originalSend({
55
+ type: "custom",
56
+ event: "adaptar:compile-success",
57
+ data: { compiledAt: Date.now() },
58
+ });
59
+ return;
60
+ }
49
61
  }
50
62
  originalSend(payload);
51
63
  };
package/dist/html-tags.js CHANGED
@@ -4,14 +4,29 @@ import { SELECTION_BRIDGE_SCRIPT } from "./scripts/selection-bridge.js";
4
4
  * The inline module that subscribes to the `adaptar:error` custom HMR event
5
5
  * and forwards it to the parent window via postMessage.
6
6
  */
7
- const HMR_LISTENER_SCRIPT = /* js */ `
8
- if (import.meta.hot) {
9
- import.meta.hot.on('adaptar:error', (data) => {
10
- try {
11
- window.parent.postMessage({ source: 'adaptar-preview', type: 'error', error: data }, '*');
12
- } catch (_) {}
13
- });
14
- }
7
+ const HMR_LISTENER_SCRIPT = /* js */ `
8
+ const adaptarOperationId = new URL(window.location.href).searchParams.get('__adaptarOperation') || undefined;
9
+ const postToHost = (type, data) => {
10
+ try {
11
+ window.parent.postMessage({
12
+ source: 'adaptar-preview',
13
+ type,
14
+ operationId: adaptarOperationId,
15
+ ...data,
16
+ }, '*');
17
+ } catch (_) {}
18
+ };
19
+
20
+ if (import.meta.hot) {
21
+ import.meta.hot.on('adaptar:error', (data) => {
22
+ postToHost('error', { error: data });
23
+ });
24
+ import.meta.hot.on('adaptar:compile-success', (data) => {
25
+ postToHost('compile-success', data || {});
26
+ });
27
+ }
28
+
29
+ postToHost('compile-success', { compiledAt: Date.now(), initial: true });
15
30
  `.trim();
16
31
  export function buildHtmlTags() {
17
32
  return [
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { Plugin } from "vite";
2
1
  /**
3
2
  * Adaptar Vite Plugin
4
3
  *
@@ -11,4 +10,4 @@ import type { Plugin } from "vite";
11
10
  * 3. `hmr-interceptor` — intercepts Vite's internal WebSocket to surface
12
11
  * build/HMR errors as structured `adaptar:error` events.
13
12
  */
14
- export declare function adaptar(): Plugin;
13
+ export declare function adaptar(): any;
@@ -1 +1 @@
1
- export declare const ERROR_BRIDGE_SCRIPT = "(function(){\n if (window.__ADAPTAR_ERROR_BRIDGE__) return;\n window.__ADAPTAR_ERROR_BRIDGE__ = true;\n\n function send(message, stack, filename, lineno, colno, source, plugin) {\n try {\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'error',\n error: {\n message: message || 'Unknown error',\n stack: stack || '',\n filename: filename,\n lineno: lineno,\n colno: colno,\n source: source,\n plugin: plugin\n }\n }, '*');\n } catch (_) {}\n }\n\n // Resource load errors (scripts, stylesheets)\n window.addEventListener('error', function(e) {\n var target = e.target || e.srcElement;\n if (target && target !== window && target.tagName) {\n var tag = String(target.tagName).toLowerCase();\n var url = target.src || target.href;\n if (url && (tag === 'script' || tag === 'link')) {\n send(\n (tag === 'link' ? 'Failed to load stylesheet: ' : 'Failed to load module: ') + url,\n '', url, undefined, undefined, 'resource-load'\n );\n return;\n }\n }\n send(\n e.message || (e.error && e.error.message) || 'Runtime error occurred',\n e.error ? e.error.stack : '',\n e.filename, e.lineno, e.colno, 'runtime'\n );\n });\n\n // Unhandled promise rejections\n window.addEventListener('unhandledrejection', function(e) {\n var r = e.reason;\n send(\n r && r.message ? r.message : String(r || 'Unhandled Promise Rejection'),\n r && r.stack,\n r && r.fileName,\n r && r.lineNumber,\n r && r.columnNumber,\n 'unhandledrejection'\n );\n });\n\n // Console error interception for silent Vite/Syntax errors\n var nativeConsoleError = console.error;\n console.error = function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n return typeof a === 'string' ? a : (a && a.message ? a.message : String(a));\n }).join(' ');\n if (/Failed to load module|Internal Server Error|Syntax Error/i.test(msg)) {\n send(msg, '', undefined, undefined, undefined, 'console.error');\n }\n return nativeConsoleError.apply(console, args);\n };\n\n // Blank screen detector\n window.addEventListener('load', function() {\n setTimeout(function() {\n var root = document.getElementById('root');\n if (root && root.children.length === 0 && !(root.textContent || '').trim()) {\n send(\n 'Runtime error: Preview failed to render; a component or import may have failed silently.',\n '', location.href, undefined, undefined, 'blank-screen'\n );\n }\n }, 3000);\n });\n})();";
1
+ export declare const ERROR_BRIDGE_SCRIPT = "(function(){\n if (window.__ADAPTAR_ERROR_BRIDGE__) return;\n window.__ADAPTAR_ERROR_BRIDGE__ = true;\n\n var operationId = new URL(window.location.href).searchParams.get('__adaptarOperation') || undefined;\n\n function sendRendered(heartbeat) {\n try {\n var root = document.getElementById('root');\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: heartbeat ? 'render-heartbeat' : 'preview-rendered',\n operationId: operationId,\n renderedAt: Date.now(),\n hasRootContent: Boolean(root && (root.children.length || (root.textContent || '').trim()))\n }, '*');\n } catch (_) {}\n }\n\n function send(message, stack, filename, lineno, colno, source, plugin) {\n try {\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'error',\n operationId: operationId,\n error: {\n message: message || 'Unknown error',\n stack: stack || '',\n filename: filename,\n lineno: lineno,\n colno: colno,\n source: source,\n plugin: plugin\n }\n }, '*');\n } catch (_) {}\n }\n\n // Resource load errors (scripts, stylesheets)\n window.addEventListener('error', function(e) {\n var target = e.target || e.srcElement;\n if (target && target !== window && target.tagName) {\n var tag = String(target.tagName).toLowerCase();\n var url = target.src || target.href;\n if (url && (tag === 'script' || tag === 'link' || tag === 'img')) {\n send(\n (tag === 'link'\n ? 'Failed to load stylesheet: '\n : tag === 'img'\n ? 'Failed to load image: '\n : 'Failed to load module: ') + url,\n '', url, undefined, undefined, 'resource-load'\n );\n return;\n }\n }\n send(\n e.message || (e.error && e.error.message) || 'Runtime error occurred',\n e.error ? e.error.stack : '',\n e.filename, e.lineno, e.colno, 'runtime'\n );\n });\n\n // Unhandled promise rejections\n window.addEventListener('unhandledrejection', function(e) {\n var r = e.reason;\n send(\n r && r.message ? r.message : String(r || 'Unhandled Promise Rejection'),\n r && r.stack,\n r && r.fileName,\n r && r.lineNumber,\n r && r.columnNumber,\n 'unhandledrejection'\n );\n });\n\n // Console error interception for silent Vite/Syntax errors\n var nativeConsoleError = console.error;\n console.error = function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n return typeof a === 'string' ? a : (a && a.message ? a.message : String(a));\n }).join(' ');\n if (/Failed to load module|Internal Server Error|Syntax Error/i.test(msg)) {\n send(msg, '', undefined, undefined, undefined, 'console.error');\n }\n return nativeConsoleError.apply(console, args);\n };\n\n // Blank screen detector\n window.addEventListener('load', function() {\n requestAnimationFrame(function() {\n requestAnimationFrame(function() {\n sendRendered(false);\n });\n });\n\n setTimeout(function() {\n var root = document.getElementById('root');\n if (root && root.children.length === 0 && !(root.textContent || '').trim()) {\n send(\n 'Runtime error: Preview failed to render; a component or import may have failed silently.',\n '', location.href, undefined, undefined, 'blank-screen'\n );\n } else {\n var images = Array.prototype.slice.call(document.querySelectorAll('img'));\n var controls = Array.prototype.slice.call(\n document.querySelectorAll('button, a[href], input, select, textarea')\n );\n var checks = {\n viewportWidth: document.documentElement.clientWidth || window.innerWidth || 0,\n horizontalOverflow:\n document.documentElement.scrollWidth >\n (document.documentElement.clientWidth || window.innerWidth || 0) + 2,\n brokenImages: images.filter(function(img) {\n return img.complete && img.naturalWidth === 0;\n }).length,\n missingImageAlt: images.filter(function(img) {\n return !img.hasAttribute('alt');\n }).length,\n unlabeledControls: controls.filter(function(control) {\n var label = (\n control.getAttribute('aria-label') ||\n control.getAttribute('title') ||\n control.textContent ||\n control.value ||\n ''\n ).trim();\n return !label;\n }).length\n };\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'preview-stable',\n operationId: operationId,\n stableAt: Date.now(),\n hasRootContent: Boolean(root && (root.children.length || (root.textContent || '').trim())),\n checks: checks\n }, '*');\n }\n }, 3000);\n });\n\n window.setInterval(function() { sendRendered(true); }, 20000);\n})();";
@@ -1,13 +1,29 @@
1
1
  export const ERROR_BRIDGE_SCRIPT = /* js */ `(function(){
2
- if (window.__ADAPTAR_ERROR_BRIDGE__) return;
3
- window.__ADAPTAR_ERROR_BRIDGE__ = true;
2
+ if (window.__ADAPTAR_ERROR_BRIDGE__) return;
3
+ window.__ADAPTAR_ERROR_BRIDGE__ = true;
4
+
5
+ var operationId = new URL(window.location.href).searchParams.get('__adaptarOperation') || undefined;
6
+
7
+ function sendRendered(heartbeat) {
8
+ try {
9
+ var root = document.getElementById('root');
10
+ window.parent.postMessage({
11
+ source: 'adaptar-preview',
12
+ type: heartbeat ? 'render-heartbeat' : 'preview-rendered',
13
+ operationId: operationId,
14
+ renderedAt: Date.now(),
15
+ hasRootContent: Boolean(root && (root.children.length || (root.textContent || '').trim()))
16
+ }, '*');
17
+ } catch (_) {}
18
+ }
4
19
 
5
20
  function send(message, stack, filename, lineno, colno, source, plugin) {
6
21
  try {
7
- window.parent.postMessage({
8
- source: 'adaptar-preview',
9
- type: 'error',
10
- error: {
22
+ window.parent.postMessage({
23
+ source: 'adaptar-preview',
24
+ type: 'error',
25
+ operationId: operationId,
26
+ error: {
11
27
  message: message || 'Unknown error',
12
28
  stack: stack || '',
13
29
  filename: filename,
@@ -26,11 +42,15 @@ export const ERROR_BRIDGE_SCRIPT = /* js */ `(function(){
26
42
  if (target && target !== window && target.tagName) {
27
43
  var tag = String(target.tagName).toLowerCase();
28
44
  var url = target.src || target.href;
29
- if (url && (tag === 'script' || tag === 'link')) {
30
- send(
31
- (tag === 'link' ? 'Failed to load stylesheet: ' : 'Failed to load module: ') + url,
32
- '', url, undefined, undefined, 'resource-load'
33
- );
45
+ if (url && (tag === 'script' || tag === 'link' || tag === 'img')) {
46
+ send(
47
+ (tag === 'link'
48
+ ? 'Failed to load stylesheet: '
49
+ : tag === 'img'
50
+ ? 'Failed to load image: '
51
+ : 'Failed to load module: ') + url,
52
+ '', url, undefined, undefined, 'resource-load'
53
+ );
34
54
  return;
35
55
  }
36
56
  }
@@ -68,15 +88,58 @@ export const ERROR_BRIDGE_SCRIPT = /* js */ `(function(){
68
88
  };
69
89
 
70
90
  // Blank screen detector
71
- window.addEventListener('load', function() {
72
- setTimeout(function() {
91
+ window.addEventListener('load', function() {
92
+ requestAnimationFrame(function() {
93
+ requestAnimationFrame(function() {
94
+ sendRendered(false);
95
+ });
96
+ });
97
+
98
+ setTimeout(function() {
73
99
  var root = document.getElementById('root');
74
- if (root && root.children.length === 0 && !(root.textContent || '').trim()) {
75
- send(
76
- 'Runtime error: Preview failed to render; a component or import may have failed silently.',
77
- '', location.href, undefined, undefined, 'blank-screen'
78
- );
79
- }
80
- }, 3000);
81
- });
100
+ if (root && root.children.length === 0 && !(root.textContent || '').trim()) {
101
+ send(
102
+ 'Runtime error: Preview failed to render; a component or import may have failed silently.',
103
+ '', location.href, undefined, undefined, 'blank-screen'
104
+ );
105
+ } else {
106
+ var images = Array.prototype.slice.call(document.querySelectorAll('img'));
107
+ var controls = Array.prototype.slice.call(
108
+ document.querySelectorAll('button, a[href], input, select, textarea')
109
+ );
110
+ var checks = {
111
+ viewportWidth: document.documentElement.clientWidth || window.innerWidth || 0,
112
+ horizontalOverflow:
113
+ document.documentElement.scrollWidth >
114
+ (document.documentElement.clientWidth || window.innerWidth || 0) + 2,
115
+ brokenImages: images.filter(function(img) {
116
+ return img.complete && img.naturalWidth === 0;
117
+ }).length,
118
+ missingImageAlt: images.filter(function(img) {
119
+ return !img.hasAttribute('alt');
120
+ }).length,
121
+ unlabeledControls: controls.filter(function(control) {
122
+ var label = (
123
+ control.getAttribute('aria-label') ||
124
+ control.getAttribute('title') ||
125
+ control.textContent ||
126
+ control.value ||
127
+ ''
128
+ ).trim();
129
+ return !label;
130
+ }).length
131
+ };
132
+ window.parent.postMessage({
133
+ source: 'adaptar-preview',
134
+ type: 'preview-stable',
135
+ operationId: operationId,
136
+ stableAt: Date.now(),
137
+ hasRootContent: Boolean(root && (root.children.length || (root.textContent || '').trim())),
138
+ checks: checks
139
+ }, '*');
140
+ }
141
+ }, 3000);
142
+ });
143
+
144
+ window.setInterval(function() { sendRendered(true); }, 20000);
82
145
  })();`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adaptar-vite-plugin",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "description": "Vite plugin for Adaptar preview error and selection bridging",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -61,11 +61,24 @@ export function interceptHmrErrors(server: ViteDevServer): void {
61
61
  }
62
62
 
63
63
  // ── Suppress full-reload that follows a fatal error ──────────────────
64
- if (payload.type === "full-reload" && Date.now() < suppressReloadUntil) {
64
+ if (payload.type === "full-reload" && Date.now() < suppressReloadUntil) {
65
65
  suppressReloadUntil = 0; // reset — only suppress once
66
66
  return;
67
- }
68
- }
67
+ }
68
+
69
+ // A Vite update has been compiled and sent to the preview client. The
70
+ // injected bridge associates this with the server-owned preview operation
71
+ // ID carried in the iframe URL.
72
+ if (payload.type === "update") {
73
+ originalSend(payload);
74
+ originalSend({
75
+ type: "custom",
76
+ event: "adaptar:compile-success",
77
+ data: { compiledAt: Date.now() },
78
+ });
79
+ return;
80
+ }
81
+ }
69
82
 
70
83
  originalSend(payload);
71
84
  };
package/src/html-tags.ts CHANGED
@@ -6,15 +6,30 @@ import { SELECTION_BRIDGE_SCRIPT } from "./scripts/selection-bridge.js";
6
6
  * The inline module that subscribes to the `adaptar:error` custom HMR event
7
7
  * and forwards it to the parent window via postMessage.
8
8
  */
9
- const HMR_LISTENER_SCRIPT = /* js */ `
10
- if (import.meta.hot) {
11
- import.meta.hot.on('adaptar:error', (data) => {
12
- try {
13
- window.parent.postMessage({ source: 'adaptar-preview', type: 'error', error: data }, '*');
14
- } catch (_) {}
15
- });
16
- }
17
- `.trim();
9
+ const HMR_LISTENER_SCRIPT = /* js */ `
10
+ const adaptarOperationId = new URL(window.location.href).searchParams.get('__adaptarOperation') || undefined;
11
+ const postToHost = (type, data) => {
12
+ try {
13
+ window.parent.postMessage({
14
+ source: 'adaptar-preview',
15
+ type,
16
+ operationId: adaptarOperationId,
17
+ ...data,
18
+ }, '*');
19
+ } catch (_) {}
20
+ };
21
+
22
+ if (import.meta.hot) {
23
+ import.meta.hot.on('adaptar:error', (data) => {
24
+ postToHost('error', { error: data });
25
+ });
26
+ import.meta.hot.on('adaptar:compile-success', (data) => {
27
+ postToHost('compile-success', data || {});
28
+ });
29
+ }
30
+
31
+ postToHost('compile-success', { compiledAt: Date.now(), initial: true });
32
+ `.trim();
18
33
 
19
34
  export function buildHtmlTags(): HtmlTagDescriptor[] {
20
35
  return [
package/src/index.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { Plugin } from "vite";
2
1
  import { interceptHmrErrors } from "./hmr-interceptor.js";
3
2
  import { buildHtmlTags } from "./html-tags.js";
4
3
 
@@ -14,7 +13,7 @@ import { buildHtmlTags } from "./html-tags.js";
14
13
  * 3. `hmr-interceptor` — intercepts Vite's internal WebSocket to surface
15
14
  * build/HMR errors as structured `adaptar:error` events.
16
15
  */
17
- export function adaptar(): Plugin {
16
+ export function adaptar(): any {
18
17
  return {
19
18
  name: "adaptar-vite-bridge",
20
19
  enforce: "pre",
@@ -34,7 +33,7 @@ export function adaptar(): Plugin {
34
33
  return buildHtmlTags();
35
34
  },
36
35
 
37
- configureServer(server) {
36
+ configureServer(server: any) {
38
37
  interceptHmrErrors(server);
39
38
  },
40
39
  };
@@ -1,13 +1,29 @@
1
1
  export const ERROR_BRIDGE_SCRIPT = /* js */ `(function(){
2
- if (window.__ADAPTAR_ERROR_BRIDGE__) return;
3
- window.__ADAPTAR_ERROR_BRIDGE__ = true;
2
+ if (window.__ADAPTAR_ERROR_BRIDGE__) return;
3
+ window.__ADAPTAR_ERROR_BRIDGE__ = true;
4
+
5
+ var operationId = new URL(window.location.href).searchParams.get('__adaptarOperation') || undefined;
6
+
7
+ function sendRendered(heartbeat) {
8
+ try {
9
+ var root = document.getElementById('root');
10
+ window.parent.postMessage({
11
+ source: 'adaptar-preview',
12
+ type: heartbeat ? 'render-heartbeat' : 'preview-rendered',
13
+ operationId: operationId,
14
+ renderedAt: Date.now(),
15
+ hasRootContent: Boolean(root && (root.children.length || (root.textContent || '').trim()))
16
+ }, '*');
17
+ } catch (_) {}
18
+ }
4
19
 
5
20
  function send(message, stack, filename, lineno, colno, source, plugin) {
6
21
  try {
7
- window.parent.postMessage({
8
- source: 'adaptar-preview',
9
- type: 'error',
10
- error: {
22
+ window.parent.postMessage({
23
+ source: 'adaptar-preview',
24
+ type: 'error',
25
+ operationId: operationId,
26
+ error: {
11
27
  message: message || 'Unknown error',
12
28
  stack: stack || '',
13
29
  filename: filename,
@@ -26,11 +42,15 @@ export const ERROR_BRIDGE_SCRIPT = /* js */ `(function(){
26
42
  if (target && target !== window && target.tagName) {
27
43
  var tag = String(target.tagName).toLowerCase();
28
44
  var url = target.src || target.href;
29
- if (url && (tag === 'script' || tag === 'link')) {
30
- send(
31
- (tag === 'link' ? 'Failed to load stylesheet: ' : 'Failed to load module: ') + url,
32
- '', url, undefined, undefined, 'resource-load'
33
- );
45
+ if (url && (tag === 'script' || tag === 'link' || tag === 'img')) {
46
+ send(
47
+ (tag === 'link'
48
+ ? 'Failed to load stylesheet: '
49
+ : tag === 'img'
50
+ ? 'Failed to load image: '
51
+ : 'Failed to load module: ') + url,
52
+ '', url, undefined, undefined, 'resource-load'
53
+ );
34
54
  return;
35
55
  }
36
56
  }
@@ -68,15 +88,58 @@ export const ERROR_BRIDGE_SCRIPT = /* js */ `(function(){
68
88
  };
69
89
 
70
90
  // Blank screen detector
71
- window.addEventListener('load', function() {
72
- setTimeout(function() {
91
+ window.addEventListener('load', function() {
92
+ requestAnimationFrame(function() {
93
+ requestAnimationFrame(function() {
94
+ sendRendered(false);
95
+ });
96
+ });
97
+
98
+ setTimeout(function() {
73
99
  var root = document.getElementById('root');
74
- if (root && root.children.length === 0 && !(root.textContent || '').trim()) {
75
- send(
76
- 'Runtime error: Preview failed to render; a component or import may have failed silently.',
77
- '', location.href, undefined, undefined, 'blank-screen'
78
- );
79
- }
80
- }, 3000);
81
- });
82
- })();`;
100
+ if (root && root.children.length === 0 && !(root.textContent || '').trim()) {
101
+ send(
102
+ 'Runtime error: Preview failed to render; a component or import may have failed silently.',
103
+ '', location.href, undefined, undefined, 'blank-screen'
104
+ );
105
+ } else {
106
+ var images = Array.prototype.slice.call(document.querySelectorAll('img'));
107
+ var controls = Array.prototype.slice.call(
108
+ document.querySelectorAll('button, a[href], input, select, textarea')
109
+ );
110
+ var checks = {
111
+ viewportWidth: document.documentElement.clientWidth || window.innerWidth || 0,
112
+ horizontalOverflow:
113
+ document.documentElement.scrollWidth >
114
+ (document.documentElement.clientWidth || window.innerWidth || 0) + 2,
115
+ brokenImages: images.filter(function(img) {
116
+ return img.complete && img.naturalWidth === 0;
117
+ }).length,
118
+ missingImageAlt: images.filter(function(img) {
119
+ return !img.hasAttribute('alt');
120
+ }).length,
121
+ unlabeledControls: controls.filter(function(control) {
122
+ var label = (
123
+ control.getAttribute('aria-label') ||
124
+ control.getAttribute('title') ||
125
+ control.textContent ||
126
+ control.value ||
127
+ ''
128
+ ).trim();
129
+ return !label;
130
+ }).length
131
+ };
132
+ window.parent.postMessage({
133
+ source: 'adaptar-preview',
134
+ type: 'preview-stable',
135
+ operationId: operationId,
136
+ stableAt: Date.now(),
137
+ hasRootContent: Boolean(root && (root.children.length || (root.textContent || '').trim())),
138
+ checks: checks
139
+ }, '*');
140
+ }
141
+ }, 3000);
142
+ });
143
+
144
+ window.setInterval(function() { sendRendered(true); }, 20000);
145
+ })();`;