Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
phasetri opened this issue Jul 18, 2023 · 2 comments · May be fixed by #8999
Open

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

phasetri opened this issue Jul 18, 2023 · 2 comments · May be fixed by #8999

Comments

@phasetri
Copy link

phasetri commented Jul 18, 2023

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
Copy link
Member

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

dummdidumm added a commit that referenced this issue Jul 19, 2023
It seems that browser resolve "await Promise.resolve()" extra fast (close to synchronous) so the DOM isn't actually updated further. This fixes an issue where web component slots don't appear when the script registering the web components is invoked before them being added to the DOM
fixes #8997
@dummdidumm dummdidumm linked a pull request Jul 19, 2023 that will close this issue
5 tasks
@dummdidumm
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants