sveltejs / svelte

Cybernetically enhanced web apps
https://svelte.dev
MIT License
78.38k stars 4.1k forks source link

Unable to get "slots" and "extend" working with Custom Element API #8997

Open phasetri opened 1 year ago

phasetri commented 1 year ago

Describe the bug

Hello,

I am having difficulties getting the slot feature working for Svelte Components that are compiled as Custom Elements. When I compile the component into a custom element and use it as follows

<head>
  <script src="./counter-component-wc.js"></script>
</head>

<body>
  <counter-component>
    <p slot="middle">blue world</p>
  </counter-component/>
</body>

I expected "blue world" to replace the default slot content of <counter-component>. However, the default slot content (i.e. <span>This is the default slot </span> still gets rendered:

image

In addition, I can't seem to get the extend keyword to work with the Custom Element API:

<svelte:options
  customElement={{
    tag: 'counter-component',
    extend: (customElementConstructor) => {
      return class extends customElementConstructor {
        constructor() {
          super();
          console.log('hello world')
        }
      };
    }
  }},
/>

Specifically, I extended the customElementConstructor with a class that simply prints "hello world" in its constructor body. However, "hello world" doesn't show up in the console whenever the custom element <counter-component> is mounted on the web page.

Check out the reproduction instructions below for details. The bottom of the instructions includes a .zip of the repository that you can use as well if it's more convenient.

Am I doing something wrong, or is this a bug?

Thanks for your time!

Reproduction

# Create a new project. Specify "SvelteKit demo app", "Typescript", and "Prettier" when prompted.
npm create svelte@latest my-app 
cd my-app
npm install

npm install \
  @rollup/plugin-commonjs \
  @rollup/plugin-node-resolve \
  @rollup/plugin-replace \
  @rollup/plugin-typescript \
  rollup-plugin-css-only \
  rollup-plugin-scss \
  rollup-plugin-svelte

In the included src/routes/Counter.svelte, insert a <svelte:options> element to mark the component as a custom element with a custom element constructor that simply prints "hello world". In addition, add an arbitrary <slot> element in the component's template:

<!-- src/routes/Counter.svelte -->

<svelte:options
  customElement={{
    tag: 'counter-component',
    extend: (customElementConstructor) => {
      return class extends customElementConstructor {
        constructor() {
          super();
          console.log('hello world')
        }
      };
    }
  }},
/>

...
...

<div class="counter">

  <slot name="header">
    <span>This is the default slot</span>
  </slot>

  ...
  ...
</div>

Create a file called counter-component-wc.ts in the src/ directory that imports the .svelte file that we want to compile into a custom element:

// src/counter-component-wc.ts

import Counter from './routes/Counter.svelte';

Create a rollup.config.js file in the root of the repository, which is used to compile counter-component-wc.ts into a standalone counter-component-wc.js file:

// rollup.config.js 

import resolve from '@rollup/plugin-node-resolve';
import svelte from 'rollup-plugin-svelte';
import scss from 'rollup-plugin-scss'
import typescript from '@rollup/plugin-typescript';
import commonjs from '@rollup/plugin-commonjs';
import sveltePreprocess from 'svelte-preprocess';

export default {
  input: 'src/counter-component-wc.ts',
  output: {
    file: 'public/counter-component-wc.js',
    format: 'iife',
    name: 'app',
  },
  plugins: [
    resolve({
      // Allow browser-specific functionality like onMount() to be 
      // included in bundle.
      browser: true
    }),
    commonjs(),
    scss(),
    typescript(),
    svelte({
      preprocess: sveltePreprocess({scss: true}),
      compilerOptions: {customElement: true}
    }),
  ]
};

Run npx rollup -c rollup.config.js. counter-component-wc.js should be generated in the public/ directory.

Afterwards, create a simple index.html file that uses the custom element that we just compiled. The <counter-component> has an arbitrary <p> nested with a slot="header" attribute.

<!--  public/index.html -->

<!doctype html>
<html>
<head>
  <title>Web Component Sandbox</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <script src="./counter-component-wc.js"></script>
</head>
<body>
  <h1>Hello World. This is a Web Component Demo.</h1>
  <br/>
  <counter-component>
    <p slot="header">blue world</p>
  </counter-component/>
</body>
</html>

Then open public/index.html in your browser.

I also have a .zip of the repository with the above edits. Extract the zip, run npm install, npx rollup -c rollup.config.js, and then open public/index.html in your browser.

my-app.zip

Logs

No response

System Info

System:
    OS: Windows 10 10.0.19045
    CPU: (8) x64 Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz
    Memory: 5.91 GB / 15.96 GB
  Binaries:
    Node: 16.17.1 - C:\Program Files\nodejs\node.EXE
    npm: 8.15.0 - C:\Program Files\nodejs\npm.CMD
    pnpm: 8.6.6 - ~\AppData\Local\pnpm\pnpm.EXE
  Browsers:
    Edge: Spartan (44.19041.1266.0), Chromium (114.0.1823.82)
    Internet Explorer: 11.0.19041.1566
  npmPackages:
    svelte: ^4.0.5 => 4.0.5

Severity

annoyance

dummdidumm commented 1 year ago

extend isn't available yet, apologies for the confusion. It will be available in the next release.

dummdidumm commented 1 year ago

The problem with the slot is that the DOM for the slot isn't available when Svelte loads the inner component and determines whether or not a slot is present. You can work around it by moving the script tag to the bottom of the body tag.