client.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. const noop = () => {};
  2. let originalConsoleWarning;
  3. let consoleFilterRefs = 0;
  4. export default (element) => {
  5. return (Component, props, slotted, { client }) => {
  6. if (!element.hasAttribute('ssr')) return;
  7. const slots = {};
  8. for (const [key, value] of Object.entries(slotted)) {
  9. slots[key] = createSlotDefinition(key, value);
  10. }
  11. try {
  12. if (import.meta.env.DEV) useConsoleFilter();
  13. const component = new Component({
  14. target: element,
  15. props: {
  16. ...props,
  17. $$slots: slots,
  18. $$scope: { ctx: [] },
  19. },
  20. hydrate: client !== 'only',
  21. $$inline: true,
  22. });
  23. element.addEventListener('astro:unmount', () => component.$destroy(), { once: true });
  24. } finally {
  25. if (import.meta.env.DEV) finishUsingConsoleFilter();
  26. }
  27. };
  28. };
  29. function createSlotDefinition(key, children) {
  30. let parent;
  31. return [
  32. () => ({
  33. // mount
  34. m(target) {
  35. parent = target;
  36. target.insertAdjacentHTML(
  37. 'beforeend',
  38. `<astro-slot${key === 'default' ? '' : ` name="${key}"`}>${children}</astro-slot>`
  39. );
  40. },
  41. // create
  42. c: noop,
  43. // hydrate
  44. l: noop,
  45. // destroy
  46. d() {
  47. if (!parent) return;
  48. const slot = parent.querySelector(
  49. `astro-slot${key === 'default' ? ':not([name])' : `[name="${key}"]`}`
  50. );
  51. if (slot) slot.remove();
  52. },
  53. }),
  54. noop,
  55. noop,
  56. ];
  57. }
  58. /**
  59. * Reduces console noise by filtering known non-problematic warnings.
  60. *
  61. * Performs reference counting to allow parallel usage from async code.
  62. *
  63. * To stop filtering, please ensure that there always is a matching call
  64. * to `finishUsingConsoleFilter` afterwards.
  65. */
  66. function useConsoleFilter() {
  67. consoleFilterRefs++;
  68. if (!originalConsoleWarning) {
  69. originalConsoleWarning = console.warn;
  70. try {
  71. console.warn = filteredConsoleWarning;
  72. } catch (error) {
  73. // If we're unable to hook `console.warn`, just accept it
  74. }
  75. }
  76. }
  77. /**
  78. * Indicates that the filter installed by `useConsoleFilter`
  79. * is no longer needed by the calling code.
  80. */
  81. function finishUsingConsoleFilter() {
  82. consoleFilterRefs--;
  83. // Note: Instead of reverting `console.warning` back to the original
  84. // when the reference counter reaches 0, we leave our hook installed
  85. // to prevent potential race conditions once `check` is made async
  86. }
  87. /**
  88. * Hook/wrapper function for the global `console.warning` function.
  89. *
  90. * Ignores known non-problematic errors while any code is using the console filter.
  91. * Otherwise, simply forwards all arguments to the original function.
  92. */
  93. function filteredConsoleWarning(msg, ...rest) {
  94. if (consoleFilterRefs > 0 && typeof msg === 'string') {
  95. // Astro passes `class` and `data-astro-cid` props to the Svelte component, which
  96. // outputs the following warning, which we can safely filter out.
  97. // NOTE: In practice data-astro-cid props have a hash suffix. Hence the use of a
  98. // quoted prop name string without a closing quote.
  99. const isKnownSvelteError =
  100. msg.endsWith("was created with unknown prop 'class'") ||
  101. msg.includes("was created with unknown prop 'data-astro-cid");
  102. if (isKnownSvelteError) return;
  103. }
  104. originalConsoleWarning(msg, ...rest);
  105. }