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

RFC: Preact CLI #496

Closed
addyosmani opened this issue Jan 10, 2017 · 28 comments
Closed

RFC: Preact CLI #496

addyosmani opened this issue Jan 10, 2017 · 28 comments

Comments

@addyosmani
Copy link

addyosmani commented Jan 10, 2017

Preact currently offers great compatibility with the rest of the CLI tools offered by the React ecosystem. These include nwb, which exposes Preact scaffolding and flags for build-time switching to Preact and create-react-app which you can mostly drop Preact into.

Increasingly, with folks seeing preact-compat as a stepping-stone into using Preact and more than just a drop-in for better loading perf than React in production, there are opportunities for Preact to define it's own CLI with Preact ecosystem best-practices. I see great opportunities for providing a "fast-by-default", zero-config experience out of the box ⚡️

Something that encodes:

  • Minimal scaffolding with a verifiable performance baseline 🏠
  • Minimal unit testing setup (c-r-a uses Jest) ⚒
  • Minimal prescribed application/directory structure 📁
  • Upgrade/Migration-friendly (similar to how c-r-a delegates this out to react-scripts) 🏃
  • Optionally prescribed minimal add-ons like preact-router
  • Pre-configured Webpack 2.x build. VueJS does an excellent job of this for both dev and prod.
  • Perf best practices, defaulting to things like code-splitting ✂️ (we're pushing for this in create-react-app too), link rel="preload" and maybe even Service Worker support (e.g support via sw-precache-webpack-plugin or offline-plugin) for Progressive Web Apps (as we documented here).
  • Depending on how brave we are, we could even aim for every app scaffolded and prod-built by preact-cli to be a PWA by default 🥇 (the Service Worker caching would help with instant repeat visit perf on both desktop and mobile. Also opts you into V8's code-caching earlier).
  • Given Preact's focus on size and performance, it could even use the Webpack performance budgeting feature to help folks keep their app size under check.
  • Something that supports create-react-app's awesome eject feature

A common question we might get is "Why does Preact need it's own CLI? Why not just continue using create-react-app?". My view is that as much as possible, c-r-a are open to baking in ideas like performance best practices out of the box, but are going to need to balance that with DX and beginner-friendliness. I feel like Preact is targeting a more performance savy audience and this gives us opportunities to explore this space a little more ambitiously.

Like, what if Preact even baked in ideas like PRPL for you? or could intelligently help you figure out what needs to be prefetched. This might be out of scope, but just throwing ideas out there to see if there would be interest in something like this 😄

PRPL support becomes a lot easier to automate once you know the directory structure. We could potentially take inspiration from Next.js's pages/ and introduce a routes/ directory that gets paired with bundle-loader?async in there.

Thoughts welcome! I'm approaching thinking of this through a browser vendor's perspective (perf) but there are probably other takes and ideas folks will have on what this might look like.

@lukeed
Copy link
Member

lukeed commented Jan 10, 2017

@developit I was talking to you about exactly this yesterday, all features mentioned 😄

@addyosmani
Copy link
Author

addyosmani commented Jan 11, 2017

@lukeed if you've got time, would love if we could summarize some of the notes shared about your WIP from slack earlier 😸

In terms of overall implementation and design for a Preact CLI, it would be great if we could focus on solving a few specific pain points:

  • Code-splitting is hard and a lot of folks aren't aware that this can be accomplished using dymamic import(). Preact CLI could offer an abstraction for routes where we automatically determine and code-split for you out of the box. There was a great post on how to accomplish this using react router that I was inspired by.

  • Service-workers are probably harder. It would be great if we'd consider auto caching static resources using existing tooling (e.g sw-precache).

  • Although Preact itself is small, it's hard for folks to stay on top of their bundle size over time. It's easy for dependencies to creep in and grow large over time. Bundle tracking utilities should be baked in to avoid folks falling off the fast path.

@lukeed
Copy link
Member

lukeed commented Jan 11, 2017

@addyosmani Would be happy to but unfortunately will have to wait until tomorrow. Not enough sleep and too much time in front of my screen today means that I'm toying with a potential migraine right now.

Full force tomorrow 👌 Will have time to work on my pwa-cli too!

@lukeed
Copy link
Member

lukeed commented Jan 11, 2017

@addyosmani I'm building a PWA generator for different libraries. So far, I'm for sure implementing for Preact, Inferno, and React. At some point I plan to extend the templating to include Angular2, Polymer2, etc.

Like most of the recent build tools, the CLI can run through a series of prompts (like Yeoman) or by passing in flags.

pwa new preact --sass --no-offline
# or
pwa new preact # run thru prompts

By default, all builds will be with webpack2 (but rollup is also an option for the smaller pwa), center around AppShell, and be capable of offline use. One may also be able to specify a preference for SASS, LESS, Stylus, or CSS-modules. There will probably be a Babel vs Buble choice too, with default on Babel. I'm not sure what other flags to include at this time, but we'll get there.

Once an app is created, it will create a simple directory structure:

|-- config
    |-- webpack.js
    |-- uglify.js
    |-- babel.js
    |-- sw.js
|-- src
    |-- index.html
    |-- index.js
    |-- index.sass
    |-- styles
        |-- ...
    |-- static
        |-- img
        |-- icon
    |-- views
        |-- index.js (see routes)
        |-- tags (aka components)
        |-- pages
            |-- home.js
            |-- ...
            |-- errors
                |-- 404.js

You can explore explore this instead. Its structure is similar enough.

The important part here is that all configurations are easily accessible via config. The app is built with great defaults, but most people don't like to be constrained by magic or by a predefined, untouchable set of defaults (like CRA).

For example, one may want to add or remove webpack chunks, be more aggressive with their code splitting, or add service worker behaviors beyond an offline precache.

Now, let's assume a dev created a PWA with an older version of the CLI. After upgrading the tool, the project config is scanned & diff'd for upgradeable features (as long as the structure remains intact).

pwa upgrade

Then another CLI prompt list appears with the installable features (a la Yeoman generators upgrade), which are really just installable configs. This is because the CLI/local install will already ship with all capabilities, but only an file entry in config/ will flag any given feature for use.

Similarly, individual PWA & build features can be installable by name... or you may want to just add everything new:

# caved & now need babel
pwa add babel

# add multiple
pwa add babel offline

# add all!
pwa upgrade --all

This adds capabilities that "upgrades" the app itself, not its dependencies. Build patterns & dependency versions are handled internally.

By "upgrading" to use Babel, a new files is placed at config/babel.js. Having this here now enables Babel incorporation in subsequent builds. It also marks Babel as "installed" in future upgrade scans.

Regarding routes (comment at src/views/index.js):
I've been toying with the idea of auto-generating routes based on the file names & structure inside src/views/pages. This is very much inspired by Next.js.

The problem here is that, so far, Preact & Inferno have each ported a different version of react-router as their official routers, and so the usage / structure is different. (compare here and here)

One way around this is to just create a new router for one of the libraries. But then I think that the CLI should not interfere with App code itself, that magic can be bad, and that there are too many options in this space & I shouldn't be forcing only one.

So then the easy route (heh) would be just to stub a router template for each library. They're all fairly easy to work with & I personally find it reassuring to actually see the structure of my application. Plus, devs can pick 'n choose routers, or drop 'em entirely if they so please.

This would also be the largest point of complexity/hindrance when trying to introduce NG2, Polymer2, and others to the CLI! 😨

The application code doesn't affect the build operations, which is my only priority after leaving them with a customized (and further customizable) PWA in their preferred flavor of library.

(Sorta came to this conclusion while writing 😄)


I haven't published any of the code yet because it lives as a handful of scattered, working pieces. It definitely needs to be unified under one codebase before being looked at. And some of the behaviors described are still TBD, but within reach.

This was probably a bit longer than I (or you) wanted...

@developit
Copy link
Member

developit commented Jan 11, 2017

@lukeed would you be open to having an option to use an internal (or via npm) directory for config/? That would get us a zeroconfig option, which I'm keen on.

Regarding router, I'd argue an async-aware router probably warrants a new (or extended) router entirely, similar to how Next.js ships with a next-aware Router.

@lukeed
Copy link
Member

lukeed commented Jan 11, 2017

@developit Sure, I just don't want to lose the ability to persist & save a feature list across machines or installs. It should be a part of version control, if using non-defaults.

A .config directory would work for me, but probably not what you're looking for.

I'm down for hiding all default config, then exposing it when asked. Then anything inside config/*.js would override the feature's settings. But then the same question remains: How do you save a project's custom mix of choices (SASS, Bublé, Rollup; non-default choices) running on default settings?

Would a root file (pwa.json or .pwa) suffice? It'd likely be a direct representation of the CLI prompts' responses. Then any customization is still maintained separately in config/.

@developit
Copy link
Member

Last paragraph makes sense to me - basically a CLI accepts arguments, which are config, so those could be saved somewhere (perhaps in the package.json even?).

@lukeed
Copy link
Member

lukeed commented Jan 11, 2017

Yup, I'll work towards that @developit.

@addyosmani
Copy link
Author

I'm building a PWA generator for different libraries. So far, I'm for sure implementing for Preact, Inferno, and React. At some point I plan to extend the templating to include Angular2, Polymer2, etc.

This sounds interesting! With at least libraries with a heavy Webpack stack, the tooling & plugins needed to wire up a PWA using a PRPL like pattern are pretty similar and I could see having a deterministic directory structure making this more feasible. Fascinated to see where this work goes.

For example, one may want to add or remove webpack chunks, be more aggressive with their code splitting, or add service worker behaviors beyond an offline precache.

To what extent were you thinking of defaulting to particular performance patterns for mobile web? For example, one could imagine service-worker precaching (for prod) being something you default to.

By "upgrading" to use Babel, a new files is placed at config/babel.js. Having this here now enables Babel incorporation in subsequent builds. It also marks Babel as "installed" in future upgrade scans.

Ah! I see. We'd considered going down this route for Web Starter Kit at one point. I think the biggest design challenge we ran into was how much (if at all) a config/babel.js type thing would require configuration beyond what a developer is used to using. How well the config for one type of pipeline works with other pipelines (like offline) when the config is separate. Any thoughts on that?

Other quick thoughts:

I think the awesome CLI you're describing here Luke is an evolution of something like nwb that goes in a specific direction (PWAs). I'm wondering however if Preact itself needs a preact-cli that's a little slimmer, requires (like CRA) zero-configuration and can be more focused on a specific stack of pieces @developit can guarantee will work well together consistently (preact-router etc).

Speaking from our experience creating Yeoman, it's really hard keeping workflows (and tooling + boilerplate) for multiple frameworks up to date over time. We ultimately found you optimize some more than others & will get community buy-in for a smaller subset vs. what most framework authors ended up doing - creating their own specific CLI.

Thoughts? I think it's great you're exploring the idea. Just thinking out loud if there's value in following the model other libraries & frameworks have taken lately on this one with a view to focus.

@lukeed
Copy link
Member

lukeed commented Jan 21, 2017

Thanks so much @addyosmani for your feedback!

To what extent were you thinking of defaulting to particular performance patterns for mobile web? For example, one could imagine service-worker precaching (for prod) being something you default to.

I'm starting off each install with the basic Lighthouse requirements, so that the user can see firsthand what a 100/100 score looks like (I still have to teach others how this can be done). This also includes a precaching service worker (for prod). And I'll start them off with an example of an App Shell, too. Not sure if you got a chance to look at my preact-starter, but that is essentially what I'll be generating (currently missing App Shell).

...a config/babel.js type thing would require configuration beyond what a developer is used to using. How well the config for one type of pipeline works with other pipelines (like offline) when the config is separate. Any thoughts on that?

So far it's actually been very painless! By now, Webpack has enough plugins & integrations with tools that it's extremely easy to separate a plugin from its options (babel config example). So long as there's organization to it (aka, not a mix & match of root files and config directory), the developer and the CLI system has a clear expectation & destination to find these.

After discussing with @developit, I can even hide this config directory entirely & "reveal" configurations when requested, which is to say that the internal defaults will be used unless a matching config/_name_.js is found. Organization is key!

pwa config babel
# => printed: config/babel.js 

We ultimately found you optimize some more than others & will get community buy-in for a smaller subset

Definitely my only concern. My response to this is to only worry about the React family, for now. Polymer, Vue, and (I think) Angular are pretty solid at this point, so I don't need to try & "compete" with their workflows. Unless enough developers request for a copy in one of those, I consider them solved.

And by keeping it to one family, it's actually been pretty easy so far to update all members. I don't need to illustrate every nuance of each library; just produce a working default & maintain a highly useful done-for-you-but-also-highly-configurable build system. 😆

creating their own specific CLI.

This is actually one of my motivations for making this. There are so many boilerplates out there already, and in the case of React specifically, there are hundreds! Then if you filter down by star counts alone, there's probably half a dozen truly useful kits...but you have to "buy in" to the whole setup.

I often found myself digging through config files to figure out what was going on, then reworking them, or gutting them entirely just so that I could get going. Essentially, I was just keeping the app code, which eventually was replaced anyway lol. And that was all after an hour of weeding through a bunch of setups to find one I could start from.

So even though I'm adding one more boilerplate to the mix, at least for me, it's really important that a starter kit is illustrative in its application code & not overwhelm or force its users into an untouchable build system.

@Download
Copy link
Contributor

Download commented Feb 6, 2017

Wow great work being done here!

I cannot help myself but mention a few projects I have been / am working on that might be interisting if you want to create a full-blown app scaffolding for Preact.

  • uroute An async router, ported from Kriasoft, that does not depend on generators.
  • uhistory A tiny facade for the browser History API with a memory backed version for server side / tests.
  • uexpress A tiny facade to Express that allows you to write your Preact 'pages' (routing endpoints) as if they were Express apps, complete with the ability to mount it in the real Express:

my-server.js

import express from 'express'
import uexpress from 'uexpress'

import myapp from './myapp'  // myapp is a uexpress app

express()                    // the 'normal' express app
  // .use(cookieParser)      // .. express middleware
  .use(myapp)                // myapp is mounted in express
  .listen(8080)              // start the server

my-client.js

import uexpress from 'uexpress'

import myapp from './myapp'  // myapp is a uexpress app

uexpress()                   // uexpress works in the browser
  .use(myapp)                // myapp is mounted in uexpress
  .listen()                  // start the client

Admittedly, uexpress is still very experimental but I have been writing some code with it and it works pretty well. I got the idea from Feathers JS but took it a bit further than they did.

Any thoughts?

@developit
Copy link
Member

@Download All three look pretty slick, especially uexpress (and this, which might be good for mitt haha). I think Addy's original push was for more of a command-line wrapper around any & all boilerplate/setup, as a way to help those new to Preact or PWA's go from 0-100 (lighthouse + metric system joke here) without needing to know the underlying tooling.

I've got a couple spare cycles in the next 2 weeks that I'll be spending iterating on some ideas I've taken from Addy's wishlist. I don't think it will look much like the things that have been shared here, which is intentional since I don't want to reinvent things that are already great. My goal is to produce a prototype CLI that accomplishes what Vue's CLI does, but for Preact. It'll be leaning heavily on things that already exist, I'm just packaging up a good set of defaults that can help people get started quickly.

@Download
Copy link
Contributor

Download commented Feb 6, 2017

and this, which might be good for mitt haha

Ha ha yes. To be fair I just made a leaner version of events, this code was already in there. But yes that's a very sane default.

I am infected a bit with this sick need to have the smallest possible web bundle and events was annoying because it pulled in too many dependencies for my taste :)

@developit
Copy link
Member

@Download if you're just using it as an emitter and don't explicitly need all the Node-isms like setMaxListeners(), you might find mitt compelling at 199 bytes.

@Download
Copy link
Contributor

Download commented Feb 6, 2017

@developit Nice! 199b!
I tried to make uevents compatible, but this is very tempting!

@addyosmani
Copy link
Author

addyosmani commented Apr 4, 2017

Quick update: we've started work on the CLI and @developit is going to noodle on some prototypes. I'm sponsoring via OpenCollective as we ❤️ Preact and would love to see it lower the friction for folks building PWAs.

The current plan is to try building Preact CLI on top of the work @ev1stensberg is doing for Webpack CLI. See how far we can get.

@Download
Copy link
Contributor

Download commented Apr 4, 2017

Just one remark. I love most of the ideas here, but not the directory structure. The way it is organized currently in the example at the top of this thread, is by file type. That's not a modular approach imho. Adding a module / app / component / whatever it's called should not mean adding files to 5 different folders. Instead it should mean adding a folder with 5 different files in it.

In other words, the directory structure should be ordered based on functionality, not file types.
So instead of:

|-- src
    |-- styles
        |-- component-A.css
        |-- component-B.css
    |-- static
        |-- img
            |-- component-A.jpg
            |-- component-B.jpg
        |-- icon
            |-- component-A.png
            |-- component-B.png
    |-- views
        |-- index.js (see routes)
        |-- tags (aka components)
            |-- component-A.jsx
            |-- component-B.jsx

I'd like to see:

|-- src
    |-- component-A
        |-- style.css
        |-- routes.js
        |-- image.jpg
        |-- icon.png
        |-- view.jsx
    |-- component-B
        |-- style.css
        |-- routes.js
        |-- image.jpg
        |-- icon.png
        |-- view.jsx

Ideally, 'pages' as a concept would not be needed... The topmost component in the tree would be the 'page'.

@MartinMalinda
Copy link

@Download this grouping is in Ember known as pods and it is quite popular. It's been recently refactored into a little bit different, more specific structure - https://github.com/dgeb/rfcs/blob/module-unification/text/0000-module-unification.md

@Download
Copy link
Contributor

Download commented Apr 4, 2017

@MartinMalinda I don't have any experience with Ember, but in Django they do something similar. There they call them 'apps'.

This was in fact one of the driving forces for my uexpress experiment; Express has the concept of mountable 'apps' that can have their own settings, routes, handlers, middleware etc. It works very well.

@lukeed
Copy link
Member

lukeed commented Apr 4, 2017

I personally have a strong aversion to that structure, and I really haven't seen that practice since Angular 1.x days. There's nothing wrong with it, per se, but for my project(s), I went with the other structure because 1) it's more common now and 2) I prefer it.

@Download
Copy link
Contributor

Download commented Apr 4, 2017

@lukeed I have trouble determining which of the two approaches (by file type vs by functionality) you dislike and which one you like.

@lukeed
Copy link
Member

lukeed commented Apr 4, 2017

I like the one you proposed to replace 😜

@developit
Copy link
Member

@Download - I don't want to prescribe a folder structure, maybe we can have a config/cli option to specify a routable component filename pattern.

@evenstensberg
Copy link

👆 Agreed, 100% doable and allows people to be flexible about their scaffold.

@zouhir
Copy link
Member

zouhir commented Apr 9, 2017

@developit agreed. having a .preactrc so we can specify how the structure is going to be would be LIT 🔥

@developit
Copy link
Member

Just important to get that format right. For now it'll be a CLI parameter (--async-pattern=components/*), and you can always use async! to code-split any import 🌈

@lukeed
Copy link
Member

lukeed commented Apr 10, 2017

💗 I was hoping you'd go the auto-split route.

@developit
Copy link
Member

https://github.com/developit/preact-cli

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

No branches or pull requests

7 participants