@dev-to/react-plugin 0.3.0 → 0.4.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.
@@ -1 +1 @@
1
- {"version":3,"file":"debugHtml.d.ts","sourceRoot":"","sources":["../src/debugHtml.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEhE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,iBAAiB,CAAA;IACxB,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,KAAK,EAAE,WAAW,CAAA;IAClB,gBAAgB,EAAE,oBAAoB,CAAA;IACtC,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,qBAAqB,UAyf5D"}
1
+ {"version":3,"file":"debugHtml.d.ts","sourceRoot":"","sources":["../src/debugHtml.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEhE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,iBAAiB,CAAA;IACxB,uBAAuB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACrC,KAAK,EAAE,WAAW,CAAA;IAClB,gBAAgB,EAAE,oBAAoB,CAAA;IACtC,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,qBAAqB,UAskB5D"}
package/dist/index.js CHANGED
@@ -238,17 +238,14 @@ function renderDebugHtml(params) {
238
238
 
239
239
  ${hasConfig ? `
240
240
  <table>
241
- <thead><tr><th>组件名称 <small class="muted">(Component Name)</small></th><th>映射入口 <small class="muted">(Short Path)</small></th></tr></thead>
241
+ <thead><tr><th>组件名称 <small class="muted">(Component Name)</small></th><th>映射入口 <small class="muted">(Short Path)</small></th><th>包装地址 <small class="muted">(UMD Wrapper)</small></th></tr></thead>
242
242
  <tbody>
243
243
  ${Object.entries(resolvedDevComponentMap).map(([name, entry])=>{
244
244
  const abs = entryPathMap[name];
245
245
  const displayPath = abs ? getShortPath(abs) : entry;
246
- return `<tr>
247
- <td><code class="code-name">${name}</code></td>
248
- <td>
249
- ${abs ? `<a href="${toVsCodeUrl(abs)}" class="link-code" title="点击在 IDE 中打开"><code>${escapeHtml(displayPath)}</code></a>` : `<code>${escapeHtml(entry)}</code>`}
250
- </td>
251
- </tr>`;
246
+ const wrapperUrl = (originCandidates[0] || 'http://localhost:5173') + '/__dev_to_react__/loader/' + name + '.js';
247
+ const entryHtml = abs ? '<a href="' + toVsCodeUrl(abs) + '" class="link-code" title="点击在 IDE 中打开"><code>' + escapeHtml(displayPath) + '</code></a>' : '<code>' + escapeHtml(entry) + '</code>';
248
+ return '<tr><td><code class="code-name">' + name + "</code></td><td>" + entryHtml + '</td><td><div style="display: flex; align-items: center; gap: 6px;"><code style="flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px;">' + escapeHtml(wrapperUrl) + '</code><button class="copy-wrapper-btn" data-url="' + wrapperUrl + '" style="padding: 2px 8px; font-size: 11px; border: 1px solid var(--b); background: #fff; border-radius: 4px; cursor: pointer; color: var(--t); transition: .2s;" title="复制包装地址">\uD83D\uDCCB</button></div></td></tr>';
252
249
  }).join('')}
253
250
  </tbody>
254
251
  </table>
@@ -330,6 +327,56 @@ reactHmrHostPlugin(<span class="str">'Demo'</span>, { open: <span class="kw">tru
330
327
  </details>
331
328
  </div>
332
329
 
330
+ <div class="card">
331
+ <h3>🎁 UMD 动态包装器 (Auto-Generated Wrapper)</h3>
332
+ <p class="muted">无需额外配置,每个组件都自动生成一个轻量级 UMD 包装器,可直接在无 React 框架支持的宿主环境中使用。</p>
333
+
334
+ <div class="info-grid">
335
+ <div class="info-label">端点:</div>
336
+ <div class="info-value"><code>/__dev_to_react__/loader/{ComponentName}.js</code></div>
337
+ <div class="info-label">作用:</div>
338
+ <div class="info-value">自动将组件导出为 React 组件实例,无需宿主集成 @dev-to/react-loader</div>
339
+ <div class="info-label">依赖:</div>
340
+ <div class="info-value"><code>react</code> &amp; <code>react-dom@18</code> (CDN 或本地)</div>
341
+ </div>
342
+
343
+ <details>
344
+ <summary>包装器工作原理与集成示例</summary>
345
+ <div style="margin-top: 12px;">
346
+ <h4 style="color: var(--t); font-size: 14px; margin-top: 0; margin-bottom: 8px;">🔧 什么是包装器?</h4>
347
+ <p class="muted" style="margin-bottom: 12px;">
348
+ 包装器是一个自动生成的 UMD 模块,它包装了原始的 render 函数并导出为 React 组件。
349
+ 这样,无论宿主是否集成了 ReactLoader,都能直接作为 React 组件使用。
350
+ </p>
351
+
352
+ <h4 style="color: var(--t); font-size: 14px; margin-top: 16px; margin-bottom: 8px;">📖 集成方式</h4>
353
+ <pre style="font-size: 12px; line-height: 1.7;">
354
+ <span class="cmt">// 1. 加载 React 和 ReactDOM</span>
355
+ <span class="kw">&lt;script&gt;</span> <span class="kw">src</span>=<span class="str">"https://unpkg.com/react@18/umd/react.production.min.js"</span> <span class="kw">&lt;/script&gt;</span>
356
+ <span class="kw">&lt;script&gt;</span> <span class="kw">src</span>=<span class="str">"https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"</span> <span class="kw">&lt;/script&gt;</span>
357
+
358
+ <span class="cmt">// 2. 加载包装器脚本</span>
359
+ <span class="kw">&lt;script&gt;</span> <span class="kw">src</span>=<span class="str">"\${originCandidates[0] || 'http://localhost:5173'}/__dev_to_react__/loader/{ComponentName}.js"</span> <span class="kw">&lt;/script&gt;</span>
360
+
361
+ <span class="cmt">// 3. 直接作为 React 组件使用</span>
362
+ <span class="kw">const</span> root = ReactDOM.createRoot(document.getElementById(<span class="str">'app'</span>));
363
+ root.render(React.createElement(window.ComponentName, { prop1: <span class="str">'value1'</span> }));
364
+
365
+ <span class="cmt">// 或在宿主 React 组件中使用</span>
366
+ <span class="kw">const</span> Component = window.ComponentName;
367
+ <span class="kw">&lt;&gt;</span>&lt;Component prop1=<span class="str">"value1"</span> /&gt;<span class="kw">&lt;/&gt;</span></pre>
368
+
369
+ <h4 style="color: var(--t); font-size: 14px; margin-top: 16px; margin-bottom: 8px;">⚡ 关键特性</h4>
370
+ <ul class="muted" style="margin: 8px 0; padding-left: 20px;">
371
+ <li><b>零配置</b>:自动为每个组件生成包装器,无需手动编写</li>
372
+ <li><b>兼容现有宿主</b>:支持 CommonJS、AMD、浏览器全局三种模式</li>
373
+ <li><b>自动依赖管理</b>:若未加载 React,包装器会自动从 CDN 加载(可配置)</li>
374
+ <li><b>轻量级</b>:仅包含加载逻辑,核心渲染由 ReactLoader 负责</li>
375
+ </ul>
376
+ </div>
377
+ </details>
378
+ </div>
379
+
333
380
  <div class="card">
334
381
  <h3>📦 构建与部署</h3>
335
382
  <p class="muted">执行 <code>vite build --mode lib</code> 将组件打包为 UMD 格式以供发布。</p>
@@ -468,6 +515,27 @@ CSS: <span class="str">dist/&lt;name&gt;/&lt;name&gt;.css</span></pre>
468
515
  setTimeout(() => { copyFullBtn.textContent = '复制原始命令'; }, 2000);
469
516
  });
470
517
 
518
+ // 绑定包装地址复制按钮事件
519
+ document.querySelectorAll('.copy-wrapper-btn').forEach(btn => {
520
+ btn.onclick = (e) => {
521
+ e.preventDefault();
522
+ const url = btn.getAttribute('data-url');
523
+ copy(url, () => {
524
+ const originalText = btn.textContent;
525
+ btn.textContent = '✓';
526
+ btn.style.borderColor = '#10b981';
527
+ btn.style.color = '#10b981';
528
+ setTimeout(() => {
529
+ btn.textContent = originalText;
530
+ btn.style.borderColor = '';
531
+ btn.style.color = '';
532
+ }, 1500);
533
+ });
534
+ };
535
+ btn.onmouseover = () => { btn.style.borderColor = 'var(--p)'; btn.style.color = 'var(--p)'; };
536
+ btn.onmouseout = () => { btn.style.borderColor = ''; btn.style.color = ''; };
537
+ });
538
+
471
539
  const serverActualPort = ${'number' == typeof actualPort ? actualPort : 'null'};
472
540
  document.getElementById('actualPortDisplay').textContent = serverActualPort || location.port || '-';
473
541
  })();
@@ -822,33 +890,42 @@ function createLoaderUmdWrapper(options) {
822
890
  * Global name: ${globalName}
823
891
  * Generated by ${constants_PLUGIN_LOG_PREFIX}
824
892
  *
825
- * This wrapper uses @dev-to/react-loader to dynamically load and render the component.
893
+ * This wrapper automatically exports a React component that can be used in any React environment.
894
+ * No need to manually integrate @dev-to/react-loader.
895
+ *
896
+ * ============= Quick Start =============
826
897
  *
827
- * Usage:
828
898
  * 1. Load React and ReactDOM:
829
899
  * <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
830
900
  * <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
831
901
  *
832
- * 2. Load ReactLoader:
833
- * <script src="https://cdn.jsdelivr.net/npm/@dev-to/react-loader@latest/dist/index.umd.js"></script>
834
- *
835
- * 3. Load this wrapper:
902
+ * 2. Load this wrapper:
836
903
  * <script src="${origin}/__dev_to_react__/loader/${componentName}.js"></script>
837
904
  *
838
- * 4. Render the component (returns a Promise):
839
- * <div id="app"></div>
840
- * <script>
841
- * window.${globalName}.render(
842
- * document.getElementById('app'),
843
- * { prop1: 'value1', prop2: 'value2' }
844
- * ).then(function(root) {
845
- * console.log('Component rendered successfully');
846
- * }).catch(function(error) {
847
- * console.error('Failed to render component:', error);
848
- * });
849
- * </script>
905
+ * 3. Use as a React component:
906
+ *
907
+ * // Option A: Direct React rendering
908
+ * const Component = window.${globalName};
909
+ * const root = ReactDOM.createRoot(document.getElementById('app'));
910
+ * root.render(React.createElement(Component, { prop1: 'value1' }));
850
911
  *
851
- * Note: ReactLoader will be automatically loaded from CDN if not already available.
912
+ * // Option B: Use in JSX (if using Babel)
913
+ * const Component = window.${globalName};
914
+ * root.render(<Component prop1="value1" />);
915
+ *
916
+ * // Option C: Direct function call (legacy compatibility)
917
+ * window.${globalName}(document.getElementById('app'), { prop1: 'value1' })
918
+ * .then(root => console.log('Rendered'))
919
+ * .catch(err => console.error('Error:', err));
920
+ *
921
+ * ============= Features =============
922
+ * ✓ Zero configuration required
923
+ * ✓ Automatic React/ReactDOM detection
924
+ * ✓ Supports CommonJS, AMD, and global scope
925
+ * ✓ Auto-loads ReactLoader from CDN if needed
926
+ * ✓ Works in any React environment
927
+ *
928
+ * Note: Make sure React v18+ and react-dom/client are available globally.
852
929
  */
853
930
  (function (root, factory) {
854
931
  if (typeof exports === 'object' && typeof module !== 'undefined') {
@@ -860,7 +937,9 @@ function createLoaderUmdWrapper(options) {
860
937
  } else {
861
938
  // Browser globals
862
939
  var globalObj = typeof globalThis !== 'undefined' ? globalThis : (typeof self !== 'undefined' ? self : root);
863
- factory((globalObj.${globalName} = {}), globalObj.React, globalObj.ReactDOM, globalObj.DevToReactLoader);
940
+ var tempExports = {};
941
+ factory(tempExports, globalObj.React, globalObj.ReactDOM, globalObj.DevToReactLoader);
942
+ globalObj.${globalName} = tempExports.default;
864
943
  }
865
944
  })(this, function (exports, React, ReactDOM, ReactLoaderModule) {
866
945
  'use strict';
@@ -964,13 +1043,37 @@ function createLoaderUmdWrapper(options) {
964
1043
  });
965
1044
  }
966
1045
 
967
- // Export the API
968
- exports.render = render;
969
- exports.config = config;
970
- exports.default = { render: render, config: config };
1046
+ // Create a React component wrapper for the render function
1047
+ function ComponentWrapper(props) {
1048
+ var containerRef = React.useRef(null);
1049
+ var isFirstRender = React.useRef(true);
971
1050
 
972
- // Mark as ES module
973
- Object.defineProperty(exports, '__esModule', { value: true });
1051
+ React.useEffect(function() {
1052
+ if (!containerRef.current) return;
1053
+
1054
+ render(containerRef.current, props).catch(function(err) {
1055
+ console.error('${constants_PLUGIN_LOG_PREFIX} Failed to render ${componentName}:', err);
1056
+ console.error('${constants_PLUGIN_LOG_PREFIX} Props:', props);
1057
+ });
1058
+
1059
+ if (isFirstRender.current) {
1060
+ isFirstRender.current = false;
1061
+ if (typeof console !== 'undefined' && console.info) {
1062
+ console.info(
1063
+ '%c${constants_PLUGIN_LOG_PREFIX}%c Successfully loaded and rendered component: %c${componentName}',
1064
+ 'color: #10b981; font-weight: bold;',
1065
+ 'color: #64748b;',
1066
+ 'color: #3b82f6; font-weight: bold;'
1067
+ );
1068
+ }
1069
+ }
1070
+ }, [props]);
1071
+
1072
+ return React.createElement('div', { ref: containerRef });
1073
+ }
1074
+
1075
+ // Export the API
1076
+ exports.default = ComponentWrapper;
974
1077
  });
975
1078
  `;
976
1079
  return code;
@@ -1 +1 @@
1
- {"version":3,"file":"loaderUmdWrapper.d.ts","sourceRoot":"","sources":["../src/loaderUmdWrapper.ts"],"names":[],"mappings":"AAGA,UAAU,6BAA6B;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG,MAAM,CAwKrF"}
1
+ {"version":3,"file":"loaderUmdWrapper.d.ts","sourceRoot":"","sources":["../src/loaderUmdWrapper.ts"],"names":[],"mappings":"AAGA,UAAU,6BAA6B;IACrC,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,6BAA6B,GAAG,MAAM,CA2MrF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-to/react-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",