staltz / cycle-custom-elementify

Converts a Cycle.js app to a custom element (Web Component)
26 stars 2 forks source link

Upgrade for custom elements v1 #1

Open kristianmandrup opened 7 years ago

kristianmandrup commented 7 years ago

Time to upgrade to custom elements v1

Started upgrade effort here

First major problem (TypeScript type issue)

import runCycle from '@cycle/run';

type Component = (sources: RequiredSources) => RequiredSinks;
// ...

    const { sources, sinks, run } = runCycle(component, {
      DOM: makeDOMDriver(self),
      props: () => self._cyclejsProps$
    }) as CycleExec;

Which conflicts with run function interface

export function run<So extends Sources, Si extends Sinks>(
                   main: (sources: So) => Si,
                   drivers: Drivers<So, Si>): DisposeFunction {
  const {run, sinks} = setup(main, drivers);
  if (typeof window !== 'undefined' && window['CyclejsDevTool_startGraphSerializer']) {
    window['CyclejsDevTool_startGraphSerializer'](sinks);
  }
  return run();
}

export default run;

I guess the problem is <So extends Sources, Si extends Sinks> which conflicts with Component = (sources: RequiredSources) => RequiredSinks; so the Required types likely have to extend Sources and Sinks respectively?

So this fixed the compiler issue:

export interface RequiredSources extends Sources {
  DOM: DOMSource;
  props: Stream<Object>;
}

export interface RequiredSinks extends Sinks {
  DOM: Stream<VNode>;
}

Now is complains that DisposeFunction can't be converted to CycleExec since it is missing sources...

// at /src/index.ts in cycle-custom-elementify

type CycleExec = {
  sources: RequiredSources;
  sinks: RequiredSinks;
  run: () => () => {};
}

export declare type DisposeFunction = () => void; from @cycle/run/lib/index.d.ts

So I would think I need to "wrap" the run function to instead return a CycleExec compatible interface.

// normal run
export function run<So extends Sources, Si extends Sinks>(
                   main: (sources: So) => Si,
                   drivers: Drivers<So, Si>): DisposeFunction {

Return CycleExec

// Cycle run wrapper??
export type CycleExec = {
  sources: RequiredSources;
  sinks: RequiredSinks;
  run: () => () => {};
}

export function cycleExecRun<So extends Sources, Si extends Sinks>(
  main: (sources: So) => Si,
  drivers: Drivers<So, Si>): CycleExec {
  const { run, sinks } = setup(main, drivers);
  if (typeof window !== 'undefined' && window['CyclejsDevTool_startGraphSerializer']) {
    window['CyclejsDevTool_startGraphSerializer'](sinks);
  }
  // hmm!?
  return {
    sources,
    sinks,
    run
  };
}

But now the sinks returned are incompatible. Getting tricky! The setup(...) function returns a run in the form function run(): DisposeFunction { which still conflicts with CycleExec so maybe I should change the CycleExec type instead! I think that's the key

Finally got it down to:

export type CycleExec = {
  sources: Sources,
  sinks: Sinks;
  run: DisposeFunction
}

export function cycleExecRun<So extends Sources, Si extends Sinks>(
  main: (sources: So) => Si,
  drivers: Drivers<So, Si>): CycleExec {
  const { run, sinks, sources } = setup(main, drivers);
  if (typeof window !== 'undefined' && window['CyclejsDevTool_startGraphSerializer']) {
    window['CyclejsDevTool_startGraphSerializer'](sinks);
  }
  // hmm!?
  return {
    sources,
    sinks,
    run
  };
}

// ...

    const { sources, sinks, run } = cycleExecRun(component, {
      DOM: makeDOMDriver(self),
      props: () => self._cyclejsProps$
    }) as CycleExec;
    sources.DOM.elements().addListener(createDispatcherForAllSinks(sinks));
    self._cyclejsDispose = run(); // <= last remaining issue!!

    // changed to:
    self._cyclejsDispose = run;

My only type problem now is in the example app, where it is missing an Index signature in Sinks to be compatible?

Otherwise, looks it like a pretty smooth upgrade :)

kristianmandrup commented 7 years ago

Perhaps better to use this custom elements polyfill which is v0 and v1 compatible and super light weight!

kristianmandrup commented 7 years ago

I just found a similar adapter for Vue vue-custom-element which uses document-register-element that looks perfect for this objective!

import 'document-register-element/build/document-register-element';

If we could just get the typings right (as described above), we would finally be able to mix and match components across most modern front-end frameworks!