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

Support for SSR Frameworks #7

Open
Gennnji opened this issue Jan 27, 2020 · 6 comments
Open

Support for SSR Frameworks #7

Gennnji opened this issue Jan 27, 2020 · 6 comments

Comments

@Gennnji
Copy link
Contributor

Gennnji commented Jan 27, 2020

It will be very useful to make Svelte-components for use in SSR Frameworks such as React-based Next.js and Vue-based Nuxt.js.
Maybe, after implementation an adapter for Angular, there will be a usefulness in adapter for Angular Universal.

@Pagan-Idel
Copy link

Does the svelte-adapter work in Next.js? My next.js app can't recognize my svelte component. Cannot find module error.

@pngwn
Copy link
Owner

pngwn commented Feb 4, 2020

I’m not 100% on SSR, svelte has separate SSR builds so that will need to be handled somewhere. I’ll take a closer look at this shortly.

@Gennnji
Copy link
Contributor Author

Gennnji commented Feb 5, 2020

Actually I realized adapter for Next.js for my work. If I'll have a time, I'll try to contribute.

@pngwn
Copy link
Owner

pngwn commented Feb 5, 2020

@Gennnji I'd love to take a look at your approach if possible. There are a few ways around this, that I can think of, but I have some reservations about which way makes the most sense.

@Gennnji
Copy link
Contributor Author

Gennnji commented Apr 5, 2020

@pngwn Sorry for such late answer. Here is how I adapted your code for Next.js:

const React = require('react');
const createReactClass = require('create-react-class');

// useRef somehow errors with Next.js (maybe I couldn't handle it rightfully)
// So functional component became class component and also a CommonJS module
module.exports = function(Component, ComponentSsr, style = {}, tag = 'span') {
  const ComponentAdapter = createReactClass({
    getInitialState: function() {
      this.container = React.createRef();
      this.component = React.createRef();

      const { head, html, css } = ComponentSsr && ComponentSsr.render
        ? ComponentSsr.render(this.props)
        : { head: '', html: '', css: '' };

      this.componentHtml = (css && css.code ? `<style>${css.code}</style>` : '') + html;

      return {};
    },

    componentDidMount: function() {
      if (!Component) {
        return;
      }

      const eventRe = /on([A-Z]{1,}[a-zA-Z]*)/;
      const watchRe = /watch([A-Z]{1,}[a-zA-Z]*)/;

      this.component.current = new Component({
        target: this.container.current,
        hydrate: true,
        props: this.props,
      });

      let watchers = [];
      for (const key in this.props) {
        const eventMatch = key.match(eventRe);
        const watchMatch = key.match(watchRe);

        if (eventMatch && typeof this.props[key] === 'function') {
          this.component.current.$on(
            `${eventMatch[1][0].toLowerCase()}${eventMatch[1].slice(1)}`,
            this.props[key]
          );
        }

        if (watchMatch && typeof this.props[key] === 'function') {
          watchers.push([
            // name of Svelte component prop being watched
            `${watchMatch[1][0].toLowerCase()}${watchMatch[1].slice(1)}`,
            // callback to run when Svelte component prop changes
            this.props[key]
          ]);
        }
      }

      if (watchers.length) {
        const update = this.component.current.$$.update;
        // Changed function to arrow function, so context wouldn't change
        this.component.current.$$.update = () => {
          watchers.forEach(([name, callback]) => {
            // Starting from some version of Svelte names and values of props
            // are in different places
            const index = this.component.current.$$.props[name];
            callback(this.component.current.$$.ctx[index]);
          });
          update.apply(null, arguments);
        };
      }
    },

    componentDidUpdate: function() {
      if (this.component.current) {
        this.component.current.$set(this.props);
      }
    },

    componentWillUnmount: function() {
      if (this.component.current) {
        this.component.current.$destroy();
      }
    },

    render: function() {
      return React.createElement(tag, {
        ref: this.container,
        style,
        dangerouslySetInnerHTML: {
          __html: this.componentHtml,
        },
      });
    }
  });

  return function(props) {
    return React.createElement(ComponentAdapter, props);
  }
}

For using in Next.js I generate two bundles for Svelte App: client-side and SSR. Also I use CommonJS module so Next.js could run SSR without errors (maybe I just haven't configure webpack rightfully).
Oh, and I use UMD bundles for Svelte App, both for client-side and SSR, so Next.js could get identical html renders for both sides.
One more, among other updates I made fix for searching watchers for last versions of Svelte. Here are lines of code:

            // Starting from some version of Svelte names and values of props
            // are in different places
            const index = this.component.current.$$.props[name];
            callback(this.component.current.$$.ctx[index]);

@Gennnji
Copy link
Contributor Author

Gennnji commented Apr 5, 2020

@pngwn Yeah, just found concrete version of Svelte where changes for $$.props and $$.ctx were made to use bitmask for change tracking - https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md#3160 - Svelte v3.16
Commit - sveltejs/svelte#3945

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

No branches or pull requests

3 participants