Giter Club home page Giter Club logo

crank's People

Contributors

appear avatar bjohnso5 avatar brainkim avatar canadaduane avatar censiscalumjohnson avatar chiptus avatar dependabot[bot] avatar fritx avatar jake-low avatar mcjazzyfunky avatar monodop avatar neonfuz avatar nicosommi avatar pjdon avatar richiemccoll avatar ryhinchey avatar splintor avatar toddlucas avatar vvo avatar waynebaylor avatar will-stone avatar wuweiweiwu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

crank's Issues

Alternate implementation of events/handlers for codesandbox TodoMVC

I (and others) have not been super comfortable with the way crank.js encourages using raw strings in events and event handlers. I re-wrote your codesandbox example using something a little closer to a "action creator" and "reducer switch statement" style of coding, as well as binding directly to the on-attributes of elements instead of using addEventHandler everywhere. I thought it might be useful to show people an alternative without so many addEventHandlers and raw strings.

https://codesandbox.io/s/crank-todomvc-alternative-jluse
https://gist.github.com/geophree/a317c658fd6d7bc51e1e80aa5243be7d

Let me know what you think!

Virtualized-list components a possible great application?

Seems like the libraries like react-virtualized could be implemented fairly easily by Crank's async generator components, instead of the hairy machinery involved in those libraries.

Maybe I'm wrong (without implementation (and a deeper understanding of react-virtualized) this is just speculation), but my instinct is that if you had a list of 10K entries, that you could have a Crank component that returned essentially just an empty non-zero-height div, and an intersection observer on the parent list that would ask any sub-component element in view or about to come into view to instantiate its actual contents.

Am I off base here?

Github Actions, Travis, Circle ?

@brainkim do you have a preference for one of these services? With more PRs coming in, it'd be great to have a CI service run tests, the linter and a build.

Happy to set one of these up!

Documentation suggestion

In the JSX, Elements and Renderers documentation, section Element Diffing:

Crank uses the same “virtual DOM diffing” algorithm made popular by React, where elements of the tree are compared by tag and position between renders, and subtrees whose root tags don’t match are thrown away.

Maybe better stated as

... subtrees whose root tags don't match have the old version discarded and replaced by the new.

which is still clumsy, but you get the idea.

Passing context as a function argument

The current API uses this to provide a component context that has useful methods related to the component. Has it been considered to pass the context as an argument to the function. Maybe something like this?

function Counter(props, context) {
  let count = 0

  context.addEventListener('click', () => {
    count++

    context.refresh()
  })

  return <button>Count: {count}</button>
}

This might feel more familiars to users that use arrow functions (although you couldn't use arrow functions for generator functions anyway), and could allow easier static typing by not having to worry about typing this. It also goes nicely with the general direction the ecosystem has been leaning towards; being as functional as possible, by not accessing implicit values like this.

Teach people how to work with (async) generators

I really like the idea behind this framework. It's great that it just uses language built in concepts instead of reinventing things like React does with Suspense. When I first heard about Suspense, I was actually a big fan of it but after reading about Crank, I've changed my mind :D.

However, getting back to the topic :). When using Crank, you will have to use generators and async generators. There is no way around it and it's actually a good thing :). But for many people it will be a barrier to switch from React, where use of generators is limited. I've read about generators and async generators a few years ago but to be honest I've never had a need to use them until now :). And believe me, it's hard to get used to new "syntax" after so many years of using React and just to reason in those async concepts. Especially when you start seeing async generators and loops.

So my point is that early in the docs there should be some links to good resources about generators and async generators, tutorials, video tutorials etc. I think people are quite familiar with async/await already but generators/async generators is something that most people don't use and don't understand. Actually, I plan to refresh my knowledge about that beside having more than 10 years of work experience in JS. I just didn't have to use (async) generators so far :)

babel-preset-crank

I made a babel preset for crank so you don't have to type these two lines in every component you write:

/** @jsx createElement */
import {createElement} from "@bikeshaving/crank";

It works with webpack right now but for Parcel you'll need to upgrade to Parcel 2. There's an existing bug with JSX pragmas that won't get fixed until the next major version of parcel.

I'll close this issue in a day or two :)

Async generator components and `Copy` elements throw an error

https://codesandbox.io/s/cool-jennings-if0m4?file=/src/index.js

/** @jsx createElement */
import { createElement, Copy } from "@bikeshaving/crank";
import { renderer } from "@bikeshaving/crank/dom";

async function* WTF() {
  for await (const _ of this) {
    yield <div>Hello</div>;
    yield <Copy />;
  }
}

Throws the error: Unknown tag: Symbol(crank.Copy).

Internal jargon: This means that Copy elements are being turned into HostNodes and the intrinsic function on the renderer is being called with the symbol. Need to figure out why this is happening.

If anyone wants to try and fix this I’m happy to pair via some medium to guide them along (requires some advanced promise lore). Might require some explanation of Crank internals I dunno. If not I’ll probably have a fix soon.

TypeScript: Strongly typed intrinsic elements

If people are going to use onevent props we should probably have them typed to prevent no implicit any errors. Maybe we can take the typings from Preact or something similar? My one concern is that intrinsic elements are already sorta loosely typed (data-attrs can be of type any), and I don’t really see the value of typing intrinsic props as this seems like a lot of work to maintain and it’s immediately obvious in the course of development if you’ve messed up. I dunno. We need DOM 0 event props to have their arguments inferred but more than that I think there isn’t much benefit.

Passing context as an argument instead of this

I wonder if you considered calling component functions with a "context" arg instead of applying the same as this. And for generator components, the props property would be an iterable object.

function *Component({props}) {
  let oldName;
  for (const {name} of props) {
    if (!oldName) {
      yield <div>Hello {name}</div>;
    } else if (oldName != name) {
      yield <div>Goodbye {oldName} and hello {name}</div>;
    } else {
      yield <div>Hello again {name}</div>;
    }
    oldName = name;
  }
}

I haven't dug enough to know if this is true, but I wonder if this would stop people from falling into the "stale props" trap described in the Prop updates section.

I like this in JS, but I think it is easier for people to reason about when it appears in class methods. Maybe there is a reason component functions need to be called with a props object. Or maybe it would be too confusing to have props be iterable.

Errors silenced when one asyncgenerator calls another

<html>
<body>
<script type=module>
import {createElement} from "https://unpkg.com/@bikeshaving/[email protected]?module";
import {renderer} from "https://unpkg.com/@bikeshaving/[email protected]/dom?module";

async function* Two() {
  console.log(undeclared);
  return "hi";
}

async function* One() {
  for await (let props of this) yield createElement(Two, props);
}

void async function() {
  await renderer.render(createElement(One), document.body);
}();
</script>
</body>
</html>

Expected: This code should log an error on the console, "ReferenceError: undeclared is not defined"

Actual: Nothing appears on the console. Pledge calls .catch on Two's Promise and ignores the error.

(This wasted a lot of my time this evening. 😖)

Render priority

How does Crank prioritizes renders across components?

Will it be possible to do something like this?

import {createElement} from "@bikeshaving/crank";
import {renderer} from "@bikeshaving/crank/dom";

function *Timer() {
  let seconds = 0;
  const interval = setInterval(() => {
    seconds++;
    this.refresh(5); // pass an integer from 0 to 5 to indicate the priority of this refresh
  }, 1000);
  try {
    while (true) {
      yield <div>Seconds: {seconds}</div>;
    }
  } finally {
    clearInterval(interval);
  }
}

renderer.render(<Timer />, document.body);

Shared functionality across event listeners

In the spirit of DRY, would this be an appropriate pattern for sharing functionality between event listeners (e.g. writing to local storage, triggering a component refresh)?

function* App() {
    // ...
	const onEvent = ev => {
	    switch (ev.type) {
	      case "todo.create":
	        todos.push({
	          id: nextTodoId++,
	          title: ev.detail.title,
	          completed: false
	        });
	        break;
	      case "todo.destroy":
	        todos = todos.filter(todo => todo.id !== ev.detail.id);
	        break;
	    }
	    this.refresh();
	    save(todos);
	};
	
	this.addEventListener("todo.create", onEvent);
	this.addEventListener("todo.destroy", onEvent);
    // ...
}

TODO MVC Sandbox Example:
https://codesandbox.io/s/epic-tu-pnckp?file=/src/index.js

I realize this may be a JS question, not a crank question...

Stateful Crank Component example not working when hosted locally

I was following the Getting-Started guide and was unable to get the A Stateful Crank Component example running on my machine. I recreated all of the contents and ran npm install, but upon running parcel index.html --opent the app <div class="app"> is empty.

I managed to recreate and run A Simple Crank Component, but when I tried to turn the component into a stateful generator, it fails and gives no error.

Is there possibly a guide that walks new users through how to set up the components from scratch on their local machine instead of the online sandbox?

documentation typo

Lifecycles, Cleaning up after your components are removed
When a generator component is removed from the tree, Crank calls the return method on the generator object. You can think of it as whatever yield expression your component was suspended on being replaced by a return statement. This means that any loops that you were in when the generator was suspended is broken out of and no code after the . You can take advantage of this behavior by wrapping your yield loops in a try/finally to release any resources that your component may have used.

of and no code after the -- needs completion.

Web Components: Discussion about how to use Crank.js for custom elements

Wouldn't it be great to implement custom elements in a "crankish" 😄 way?
This is a discussion thread to gather all ideas to be found about the question how to use Crank.js or Crank.js patterns to implement custom elements.

This is a brainstorming, nobody expects a fully sophisticated proposal. So please share every idea that comes to your mind: Requirements, API suggestions, best practices , dos and don'ts, known pitfalls etc.

Here's a list of some popular web component libraries for inspiration:

Chinese Documentation

Hello, I am a developer from China, may I ask when you launch the Chinese document

Event + CustomEvent in all environments

Currently new CustomEvent will only work in the browser. I see a need to have an implementation that works in node as well. The rest of the event system in crank works in node and reusing events between the browser and node could be really helpful down the road for server side rendering.

I'm happy to work on a PR for it!

documentation typo

In Components, Dispatching events, in the sample code

  while (true) {
    yield (
      <div>
        <Buttons />
        <div>Last pressed id: {lastId == null ? "N/A" : lastId}</div>
      </div>
    );
  }

<Buttons /> should be <MyButtons />, IIUC.

async rendering of children: should be properly coordinated

Dear Author of Crank,
as requested in a reddit thread (reddit ), here is a small scenario that I think is not handled properly by crank:

/** @jsx createElement */
import { createElement } from "@bikeshaving/crank";
import { renderer } from "@bikeshaving/crank/dom";

function wait(i) {
  return new Promise(resolve => {
    setTimeout(resolve, i);
  })
}
async function A({i}) {
  await wait(1000);
  return <div>A:{i}</div>;
}

async function B({i}) {
  await wait(200);
  return <div>B: {i}</div>;
}

function *Parent() {
  let i = 1;
  this.addEventListener('click', () => {
    i++;
    this.refresh();
  });
  while (true) {
    yield <div><A i={i}/><B i={i}/></div>;
  }
}
renderer.render(<Parent />, document.body);

We have here a parent with two sub components A and B. These sub components render themselves at a different speed. Whenever I click on the parent, some information is propagated to the full sub tree.
What happens is that we see that B is updated first, and then A. I think that it is wrong. Both should be updated at the same moment. Otherwise, the UI is not in a consistent state.

Question regarding components whose side effects depend on (possibly changing) prop values

Normally the Github issue tracker is not necessarily there to ask questions about certain implementation cases. I hope you do not mind if I do nonetheless :-)

Please have a look at the following React demo:
https://codesandbox.io/s/frosty-kowalevski-4qp51?file=/src/index.js

The implementation of component "Ticker" is quite trivial in React
(normally I would use a "useInterval" custom hook for this, but this is not important here...)

What is the best way to implemenent that "Ticker" component in Crank.js?

Please find here a NOT YET WORKING Crank.js version of the demo:
https://codesandbox.io/s/tender-greider-qto05?file=/src/index.js

Many many thanks in advance.

addEventListener and delegation with tagName is awkward

Not an issue per se (like a bug or anything), but having just read through all the blog post and docs, this was the one bit that I thought "modern React" definitely does better at.

No idea if this is even possible, but I'm sure I won't be the only one turned off by this.

JSX isn't Just JavaScript™

https://github.com/bikeshaving/crank#just-javascript

Just JavaScript™
All components in Crank are just functions or generator functions. No classes, hooks, proxies or template languages are needed.

JSX isn't just JavaScript; you have to transpile it. In fact, I'd call JSX a powerful, simple, declarative template language. (And "classes" are just JavaScript, too!)

I suggest rephrasing like this instead:

No Magic
All components in Crank are just functions or generator functions. No classes, hooks, or proxies are needed.

documentation typo

In Special props and tags, in the example

function *Shuffler() {
  let nextId = 0;
  const els = Array.from({length: 4}, (_, i) => <span crank-key={i}>{i}</span>);
  while (true) {
    yield <div>{els}</div>;
    els.reverse();
  }
}

renderer.render(<Shuffler />, document.body);
console.log(document.body.innerHTML);
// "<div><span>0</span><span>1</span><span>2</span><span>3</span></div>";

const span0 = document.body.firstChild.firstChild;

renderer.render(<Shuffler />, document.body);
console.log(document.body.innerHTML);
// "<div><span>3</span><span>2</span><span>1</span><span>0</span></div>";

console.log(document.firstChild.lastChild === el0); // true
renderer.render(<Shuffler />, document.body);
console.log(document.body.innerHTML);
// "<div><span>0</span><span>1</span><span>2</span><span>3</span></div>";

console.log(document.firstChild.firstChild === el0); // true

either span0 should be renamed to el0 or vice versa.

Component hydration

(First of all, thanks for the great work on making this!)

I'm fascinated by the approach crank takes of modeling component states as local variables. How does it handle when a component instance needs to be discarded and re-created with states intact?

In a hypothetical server-side rendering scenario, the server would create the initial render and send the rendered HTML and component states to the client. The client replicates the initial render by creating new component instances and injecting component states. In both class-style and hook-style React, this is easy because class properties can be re-assigned and useState() acts as a DI mechanism. However, in crank components are impure and may have local variables, so the component states are implicit and harder to track by the framework.

Have you thought about how crank would handle this?

Are async and non-async generator components both needed?

The current generator component API seems a bit complicated.

You have non-async components, where each yield pauses until the next render, and async components, where the pause mechanism is awaiting new props from this instead.

This difference in behaviour seems rather error prone. Are both mechanisms needed or would it be possible to use just one? For instance, could non-async generator components be eliminated?

Question around beta phase / roadmap

Hey 👋

Great job 🎉 This is a refreshing view on writing UI components.

I'm curious on what work is left for the beta phase and do you have an idea of the roadmap for the library? I'm interested in contributing and I'm sure others are too.

Reusable component logic with setup and teardown

One really common problem with React's imperative style of class components was that it's very difficult to write code that's reused between components and involves both setup and teardown steps without using higher order components.

I can't think of an easy way to achieve this in plain JavaScript generators. How would you go about solving this problem in Crank?

If there's no immediate solution, it might be a good goal to keep in mind as the component API is evolving.

Getting refs

Seems like getting a ref as shown in the documentation doesn't work on the first render
Screen Shot 2020-04-18 at 10 02 38 AM

results in
Screen Shot 2020-04-18 at 10 03 16 AM
on the first render and
Screen Shot 2020-04-18 at 10 03 27 AM
after button click

Crank.js killer demo?

Does anybody know by any chance a Crank.js demo that really kicks React's butt?
All the examples and demos that I've seen in Crank.js so far look very nice, of course, but frankly I have not seen anything that could not more or less equally nicely be implemented in React (by using some smart custom hooks, of course).
Would be great if anybody could show an example where the implementation in Crank.js really, really shines compared to its React counterpart. Thanks :)


[EDIT] The term "killer demo" may be a bit strong. I did not mean that the demo had to be extremely flashy or of a miniumum extent in lines of code. It's not really important whether the "killer demo" has 20 lines of code or more than 1000. But the implementation with Crank.js should actually be much easier (which often also implies: more concise) than its React counterpart.

For example, here is a small but quite nice IMHO "killer demo" which shows pretty well the power of React hook functions (if you do not think that this is a "killer demo" then just think about how this would have been implemented 18 months ago with the UI library of your choice):

function Metronome({ bpm = 60 }) {
  const [tock, setTock] = useState(false)

  useInterval(() => setTock(it => !it), 60000 / bpm)

  return <div>Metronome: {bpm} bpm => {tock ? 'Tock' : 'Tick'}</div>
}

https://codesandbox.io/s/jolly-bash-t511f?file=/src/index.js

Crank Q&A

Hi @brainkim,

I am a news editor for InfoQ. Would you be interested in an interview on Crank, its motivation, design principles and value added?

You may not necessarily know InfoQ so let me tell a few quick things about it. InfoQ's mission is to help dev teams adopt new technologies and practices. InfoQ's particularity is that content is written by software engineers/professional developers over professional writers. Our primary audience are tech leads, senior developers and software architects.

We regularly do interviews (see here) on a variety of subjects. Let me know if that is of any interest to you. I hope this finds you well and safe in these troubled days.

Kind regards

Typescript support for props in Component type

This library does a decent job with typescript, but it'd be really nice to have some support for strongly typing props in the Component type, and it might also be nice to have the same in Context. One use-case for this is to be able to use a Component<MyExpectedProps> in a function signature or config option. Another use-case would be defining type-safe higher order components. I can't see a way to get the prop types to propagate down properly as things are, but I could just be missing something.

Here's a concrete example of something I might want to do. I know I could pass the thing down with this.set but then I still don't get type-safety. With this method I could also require every route to have the prop.

import {createElement, Context, Component} from '@bikeshaving/crank';

export type RouteProps = {
    currentRoute: Route;
}

export type Route = {
    name: string;
    index: Component<RouteProps>;
}

export type RouterProps = {
    routes: Route[];
};

function getCurrentPath(): string[] {
    return new URL(window.location.href)
        .hash
        .substring(1)
        .split('/')
        .map(p => p.trim())
        .filter(p => p);
}

function getMatchingRoute(routes: Route[], currentPath: string[]): Route | null {
    const p = currentPath[0];
    const matches = routes.filter(r => r.name == p);
    if (matches.length > 0)
        return matches[0];
    return null;
}

export function* Router(this: Context, {routes}: RouterProps) {
    let currentPath = getCurrentPath();
    const listener = () => {
        currentPath = getCurrentPath();
        this.refresh();
    }
    window.addEventListener('popstate', listener);
    try {
        while(true) {
            const route = getMatchingRoute(routes, currentPath);
            if (route) {
                const C = route.index;
                yield <C route={currentRoute} />;
            }
            else {
                yield <div>Not found</div>
            }
        }
    }
    finally {
        window.removeEventListener('popstate', listener);
    }
}

async generators without props

I think the case of async generators without props have strange ergonomics. Consider this hypothetical login form that checks for a user in cookies/localstorage/whatever

import { getUser, login } from './auth'
export default async function* Login() {
  for await (let _ of this) {
    yield <LoadingIndicator />
    let user = await getUser()
    if (user) login(user)
    yield <UserForm />
  }
}

The for await (let _ of this) is necessary (I think) because async generators are continuously yielded until their iterator finishes. Its awkward because there are no props to collect from this, and so an used variable is necessary.

The only alternative I can think of is even more awkward, calling the iterator directly:

while (true) {
    await this[Symbol.asyncIterator]().next()
    yield <LoadingIndicator />
    let user = await getUser()
    if (user) login(user)
    yield <UserForm />
  }

Are there better alternatives?

React hooks

Currently a lot of business logic, functionality and libraries being written as custom hooks using hooks api.
Theoretically, would there be a way to use them with this framework?
Of course, maybe this generator way of writing components will enable to rewrite same logic in a much simpler way.
But I am asking about using existing code as it is.
For example, react-query, which exposes { isFetching, data } = useQuery(query, fetch query).

Reactivity

Hey, any reason not to make the framework reactive? Having to .refresh after making an uncontrolled change rather than reactive programming like MobX, Svelte or Vue is rather frustrating :]

Ready for the real world? - Episode #1

[Edit] "Ready for the real world?" is just a catch phrase - don't take that too serious 😉 in particular I do not mean "production ready" or something


Franky, I'm a bit concerned that most of the Crank demos I've seen in so far, have only covered use cases that are obviously well suited for Crank.
As by the end of the day, Crank will have to prove its power in ALL use cases and not just the well suited ones, please allow me to start a little series of small React demos that I think are a bit more challenging for Crank.
Would be great if some of you guys like to meet new challenges and try to find a good way how to implement those short examples with Crank.

Important: It's really NOT necessary to provide an actual running Crank demo - just think of some useful helper functions (without implementing them) or desired API changes for Crank if you like and provide some short pseudocode - it's not important whether everything is 100% correct or not, it's just about the patterns.
Also, it would be nice if the presumable code size of the Crank version (without the presumable code size of your imaginary helper functions) is more or less comparable with the code size of the React version.

The first example is a little "Metronome" (tick ... tock ...tick ... tock...) demo that I have already mentioned in some other issue (and due to issue #43 I assume that this might be a tough nut):

https://codesandbox.io/s/jolly-bash-t511f?file=/src/index.js

Thanks a lot in advance and good luck.

Small bug (and fix) in codesandbox TodoMVC

TL;DR: The crank.js example at https://codesandbox.io/s/crank-todomvc-k6s0xIt sometimes causes an exception around the re-rendering and dispatch of two "todo.destroy" events from the TodoItem, breaking the app. The immediate fix I found is instead of dispatching the "todo.destroy" event from the keydown handler, just call ev.target.blur().


While using the crank.js example at https://codesandbox.io/s/crank-todomvc-k6s0x when I edit a todo, delete the all the characters from the title, and the press the enter or escape key, the following exception is thrown in the console:

index.js:27 TypeError: Generator is already executing.
    at step (index-4b79835a.js? [sm]:62)
    at Object.eval [as next] (index-4b79835a.js? [sm]:58)
    at HostNode.commit (index-4b79835a.js? [sm]:1374)
    at ComponentNode.commit (index-4b79835a.js? [sm]:1644)
    at ComponentNode.ParentNode.updateChildren (index-4b79835a.js? [sm]:1271)
    at ComponentNode.updateChildren (index-4b79835a.js? [sm]:1458)
    at ComponentNode.step (index-4b79835a.js? [sm]:1523)
    at ComponentNode.run (index-4b79835a.js? [sm]:1614)
    at ComponentNode.refresh (index-4b79835a.js? [sm]:1546)
    at Context.refresh (index-4b79835a.js? [sm]:1747)
    at Context.eval (index.js? [sm]:269)
    at Context.dispatchEvent (index-4b79835a.js? [sm]:604)
    at Context.CrankEventTarget.dispatchEvent (index-4b79835a.js? [sm]:833)
    at Context.CrankEventTarget.dispatchEvent (index-4b79835a.js? [sm]:835)
    at Context.CrankEventTarget.dispatchEvent (index-4b79835a.js? [sm]:835)
    at HTMLLIElement.TodoItem.addEventListener.capture (index.js? [sm]:118)
    at updateChildren (dom.js? [sm]:125)
    at HostContext.eval (dom.js? [sm]:228)
    at step (index-4b79835a.js? [sm]:109)
    at Object.eval [as next] (index-4b79835a.js? [sm]:58)
    at HostNode.commit (index-4b79835a.js? [sm]:1374)
    at ComponentNode.commit (index-4b79835a.js? [sm]:1644)
    at ComponentNode.ParentNode.updateChildren (index-4b79835a.js? [sm]:1271)
    at ComponentNode.updateChildren (index-4b79835a.js? [sm]:1458)
    at ComponentNode.step (index-4b79835a.js? [sm]:1523)
    at ComponentNode.run (index-4b79835a.js? [sm]:1614)
    at ComponentNode.refresh (index-4b79835a.js? [sm]:1546)
    at Context.refresh (index-4b79835a.js? [sm]:1747)
    at Context.eval (index.js? [sm]:269)
    at Context.dispatchEvent (index-4b79835a.js? [sm]:604)
    at Context.CrankEventTarget.dispatchEvent (index-4b79835a.js? [sm]:833)
    at Context.CrankEventTarget.dispatchEvent (index-4b79835a.js? [sm]:835)
    at Context.CrankEventTarget.dispatchEvent (index-4b79835a.js? [sm]:835)
    at HTMLLIElement.eval (index.js? [sm]:95)

Browser info:

Google Chrome 83.0.4103.14 (Official Build) beta (64-bit)
Revision be04594a2b8411758b860104bc0a1033417178be-refs/branch-heads/4103@{#119}
JavaScript: V8 8.3.110.4
OS: Linux

It looks like the code is dispatching two "todo.destroy" events, one when enter/escape is pressed (through the keydown handler) and also, when active is set to false and the TodoItem is re-rended (or something else) causes the blur event to be triggered on the input, thus dispatching a second "todo.destroy" event. Not sure why that blows up so spectacularly though.

The fix I found is instead of dispatching the "todo.destroy" event from the keydown handler, just call ev.target.blur(). There may be a better error to be showing or some way to catch it.

re: "Introducting Crank"

https://crank.js.org/blog/introducing-crank Typo in page header, should be "Introducing".

A common remark I am seeing on Twitter is about this blog post's incendiary tone. The frustration is warranted and people are sympathetic to it:

  • "I appreciate a well-reasoned critique of React"

...but when it bubbles forward as external criticisms, it starts to alienate developers who are not sure about the project:

  • "Some of these sentences about other frameworks feel out of place in the docs."
  • "I don’t want to try the framework of someone who sees an approach they wouldn’t take and assumes the people behind it don’t have good reasons."

People who are sharing it are also having to apologize for the tone on your behalf:

  • "I was not sure about sharing for that reason."
  • "I hope it doesn't become the norm."

Now, let us propose I do not care what you do, because I don't. I am not personally invested very much and I am not interested in judging, and I certainly do not think you should defang every sentence you make. But I am aware you have goals, and you may decide that your previous tone has not had the effect you desire, and that it might be worth changing.

So if you are interested in altering your text in order to improve your conveyance of excitement over the thing you made instead of conveying frustration with others, then walking more of this back to "I-statements" will help. And based on my observation of your position, in general "building pop" for Crank instead of trying to "build heat" for your rivals might be the better focus, as your rivals are (friendly, but) already large heels and you do not need to do much but remind people they exist.

I would have sent this privately over e-mail if I had discovered Crank 3 days ago, but you already have quite a bit of attention so people already have their eye on you, here, and you cannot possibly change this post quietly anymore. So by posting here I am not trying to "call you out", nor shame you into a specific course of action, as I am sure there are other routes to take, merely provide a mirror in which to reflect, or, ah, a View to your Model.

Hyperscript!

Someone asked on reddit if we’re going to support hyperscript. I think we could definitely do this and promote it as an alternative to JSX/whatever. The only concern I have is that hyperscript typically has special stuff where they use the CSS syntax to specify id/classes in the tag (h("a#my-link", props, children))? Do we need to implement this to be hyperscript compatible?

Event handling

First, congrats on the framework, I love the ideas, this is terrific! This is the future for sure, should be react 2.0 really.

This is actually pretty close to what I designed during my PhD. (Basically views = generators), so as you say this is really something that needed to be “discovered”.

Now there is only One thing that I am not totally a fond of in the framework, and it is the event listener syntax/pattern. This might be due to a technical impossibility, but I think an API closer to what react does with function handlers passed in onXXX attributes on html elements could potentially be better than calling this.addListener and adding a big switch on event attributes in there.... However I love how your approach kind of unifies regular state management and reducer-based state management in the same concept.

I wonder if there could be away to get the best of both worlds!

Esnext esm/* target, rather than es5?

Package of @bikeshaving/crank 0.1.1 at npm has a 142k esm/ dir. Odd because github files are much smaller, 42k only (10k gzip) for all direct src/ children. From what I see in esm/index-4b79835a.js, the build system adds huge polyfills for very old browser engines, and replaces all usage of generators and async with es5 state machines. Since these files are es modules which export stuff, one can't just require them in old node or run in browser as script without an additional transpilation step on behalf of the app developer. My question is, how does applying such radical transforms to the esm dir help users of old engines in practice then? Doesn't it negate the top benefit of crank, excellent runtime performance in modern browsers due to using their native features? Would reconfiguring the build system to keep esnext generators and async in the esm dir make sense?

Typo in website/guides/04-async-components.md

Was casually reading the documentation and guides and found this little guy:

  await p2;
  console.log(document.body.innerHTML); // "Run 2"
  await p3;
  console.log(document.body.innerHTML); // "Run 4"
  await p4;
  console.log(document.body.innerHTML); // "Run 4"

Awesome work by the way!.. seeing the JavaScript you have throughout the site is a breath of fresh air!

Why not use the return value of `yield` for prop updates?

I was wondering if you've considered using the return value of yield for prop updates instead of using for props of this.

I think it would make more sense because passing new arguments to a generator using the next() function is meant for the cooperative multitasking you're trying to achieve.

Additionally, for props of this makes the example in your documentation is a little confusing because the message function parameter is unused and is immediately shadowed by the loop variable:

function *Counter({message}) {
  let count = 0;
  for ({message} of this) {
    count++;
    yield (
      <div>{message} {count}</div>
    );
  }
}

Using the return value of yield for prop updates might be less confusing:

function* Counter({ message }) {
  let count = 0;
  while (true) {
    count++;
    ({ message } = yield (<div>{message} {count}</div>));
  }
}

It would also allow easily implementing different behavior based on prop updates:

function* Counter({ message }) {
  const { message: updatedMessage } = yield (<div>first {message}</div>);

  if (message === updatedMessage) {
    return (<div>Uncreative!</div>);
  }

  message = updatedMessage;

  let count = 1;
  while (true) {
    count++;
    ({ message } = yield (<div>{message} {count}</div>));
  }
}

It also feels more "Just JavaScript" to use the existing generator functionality rather than invent some new concept for cooperative multitasking using for props of this.

I will admit that it's a bit cumbersome to have to parenthesize every object destructuring assignment for each return from yield.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.