sapper
Combat-ready apps, engineered by Svelte.
This is not a thing yet
If you visit this README in a few weeks, hopefully it will have blossomed into the app development framework we deserve. Right now, it's just a set of ideas.
Next.js introduced a beautiful idea — that you should be able to build your app as universal React components in a special pages directory, and the framework should take care of routing and rendering on both client and server. What if we did the same thing for Svelte?
High-level goals:
- Extreme ease of development
- Code-splitting and HMR out of the box (probably via webpack)
- Best-in-class performance
- As little magic as possible. Anyone should be able to understand how everything fits together, and e.g. make changes to the webpack config
Design
A Sapper app is just an Express app (conventionally, server.js) that uses the sapper middleware:
const app = require('express')();
const sapper = require('sapper');
const app = express();
app.use(sapper());
const { PORT = 3000 } = process.env;
app.listen(PORT, () => {
console.log(`listening on port ${PORT}`);
});
The middleware serves pages that match files in the routes directory, and assets generated by webpack. In development mode, the middleware once activated watches routes to keep the app up-to-date.
Routing
Like Next, routes are defined by the project directory structure, but with some crucial differences:
- Files with an
.htmlextension are treated as Svelte components. Theroutes/about.html(orroutes/about/index.html) would create the/aboutroute. - Files with a
.jsor.mjsextension are more generic route handlers. These files should export functions corresponding to the HTTP methods they support (example below). - Instead of route masking, we embed parameters in the filename. For example
post/%id%.htmlmaps to/post/:id, and the component will be rendered with the appropriate parameter. - Nested routes (read this article) can be handled by creating a file that matches the subroute — for example,
routes/app/settings/%submenu%.htmlwould match/app/settings/profileandapp/settings, but in the latter case thesubmenuparameter would benull.
An example of a generic route:
// routes/api/post/%id%.js
export async function get(req, res) {
try {
const data = await getPostFromDatabase(req.params.id);
const json = JSON.stringify(data);
res.set({
'Content-Type': 'application/json',
'Content-Length': json.length
});
res.send(json);
} catch (err) {
res.status(500).send(err.message);
}
}
Or, if you omit the res argument, it can use the return value:
// routes/api/post/%id%.js
export async function get(req, res) {
return await getPostFromDatabase(req.params.id);
}
Things to figure out
- How to customise the overall page template
- An equivalent of
getInitialProps - Critical CSS
storeintegration- Route transitions
- Equivalent of
next export - ...and lots of other things that haven't occurred to me yet.