So you’re tired of running small examples, and ready to write a real site? Then you’ve arrived at the right chapter. Even with the entire Yesod library at your fingertips, there are still a lot of steps you need to go through to get a production-quality site set up. Considerations include:
Config file parsing
Signal handling (*nix)
More efficient static file serving
A good file layout
The scaffolded site is a combination of many Yesoders’ best practices, rolled into a ready-to-use skeleton for your sites. It is highly recommended for all sites. This chapter will explain the overall structure of the scaffolding, how to use it, and some of its less-than-obvious features.
For the most part, this chapter will not contain code samples. It is recommended that you follow along with an actual scaffolded site.
Due to the nature of the scaffolded site, it is the most fluid component of Yesod, and can change from version to version. It is possible that the information in this chapter will be slightly outdated by the time you are reading it.
The yesod-bin
package installs an executable (conveniently named yesod as well). This executable provides a few commands (run yesod
by itself to get a
list). In order to generate a scaffolding, the command is yesod init
. This
will start a question-and-answer process where you get to provide basic
details. After answering the questions, you will have a site template in a
subfolder with the name of your project.
The most important of these questions concerns the database backend. You get a few choices here, including SQL and MongoDB backends, or you can select the simple option and skip database support. This last option also turns off a few extra dependencies, giving you a leaner overall site. The remainder of this chapter will focus on the scaffoldings for one of the database backends. There will be minor differences for the simple backend.
After creating your files, the scaffolder will print a message about getting
started. You should follow those instructions to ensure a reliable installation.
In particular, the commands provided will ensure that any missing dependencies are built and installed.
Even if you’ve installed the yesod
package, you most likely do not yet
have in place all the dependencies needed by your site. For example, none of
the database backends (or the JavaScript minifier, hjsmin
) are installed when
installing the yesod
package.
Finally, to launch your development site, you’ll use yesod devel
. This site will automatically be rebuilt and reloaded whenever you change your code.
The scaffolded site is built as a fully cabalized Haskell package. In addition to source files, config files, templates, and static files are produced.
Whether directly using cabal
or indirectly using yesod devel
, building
your code will always go through the cabal file. If you open the file, you’ll
see that there are both library and executable blocks. If the library-only
flag is turned on, then the executable block is not built. This is how yesod
devel
calls your app. Otherwise, the executable is built.
The library-only
flag should only be used by yesod devel
; you should never
be explicitly passing it into cabal
. There is an additional flag, dev
, that allows Cabal to build an executable but turns on some of the same features as the library-only
flag—i.e., no optimizations and reload versions of the Shakespearean template functions.
In general, you will build as follows:
When developing, use yesod devel
exclusively.
When building a production build, perform cabal clean && cabal
configure && cabal build
. This will produce an optimized executable
in your dist folder. (You can also use the yesod keter
command for this.)
You might be surprised to see the NoImplicitPrelude
extension. We turn this
on because the site includes its own module, Import
, with a few changes to Prelude
that make working with Yesod a little more convenient.
The last thing to note is the exported-modules
list. If you add any modules to
your application, you must update this list to get yesod devel
to work
correctly. Unfortunately, neither Cabal nor GHC will give you a warning if you
forget to make this update, and instead you’ll get a very scary-looking error
message from yesod devel
.
Multiple times in this book, you’ve seen comments stating that while we’re declaring our routes/entities with quasiquotes for convenience, “in a production site, you should use an external file.'' The scaffolding uses such an external file.
Routes are defined in config/routes, and entities in config/models. They have the exact same syntax as the quasiquoting you’ve seen throughout the book, and yesod devel
knows to automatically recompile the appropriate modules when these files change.
The models file is referenced by Model.hs. You are free to declare whatever you like in this file, but here are some guidelines:
The mkYesod
function that we have used throughout the book declares a few
things:
Route type
Route render function
Dispatch function
The dispatch function refers to all of the handler functions, and therefore all of those must either be defined in the same file as the dispatch function, or be imported into the module containing the dispatch function.
Meanwhile, the handler functions will almost certainly refer to the route type. Therefore, they either must be in the same file where the route type is defined, or must import that file. If you follow the logic here, your entire application must essentially live in a single file!
Clearly this isn’t what we want. So, instead of using mkYesod
, the scaffolded site uses a decomposed version of the function. Foundation
calls mkYesodData
, which declares the route type and render function. It does
not declare the dispatch function, so the handler functions need not be in scope.
The Import.hs file imports Foundation.hs, and all the handler modules import
Import.hs.
In Application.hs, we call mkYesodDispatch
, which creates our dispatch
function. For this to work, all handler functions must be in scope, so be sure
to add an import
statement for any new handler modules you create.
Other than that, Application.hs is pretty simple. It provides two primary
functions: getApplicationDev
is used by yesod devel
to launch your app, and
makeApplication
is used by the executable to launch.
Foundation.hs is much more exciting because it does the following:
It declares your foundation data type and a number of instances, such as Yesod
, YesodAuth
, and YesodPersist
.
It imports the message files. If you look for the line starting with
mkMessage
, you will see that it specifies the folder containing the
messages (messages/) and the default language (en
, for English).
This is the right file for adding extra instances for your foundation, such as
YesodAuthEmail
or YesodBreadcrumbs
.
We’ll be referring back to this file later, as we discuss some of the special
implementations of Yesod
typeclass methods.
The Import
module was born out of a few commonly recurring patterns:
I want to define some helper functions (maybe the <> = mappend
operator) to be used by all handlers.
I’m always adding the same five import
statements (e.g., Data.Text
,
Control.Applicative
, etc.) to every handler module.
I want to make sure I never use some evil function (head
, readFile
, etc.) from Prelude
.
Yes, “evil” is hyperbole. If you’re wondering why I listed those functions as bad, head
is partial and throws exceptions on an empty list, and readFile
uses lazy I/O, which doesn’t close file handles quickly enough. Also, readFile
uses String
instead of Text
.
The solution is to turn on the NoImplicitPrelude
language extension,
re-export the parts of Prelude
we want, add in all the other stuff we want,
define our own functions as well, and then import this file in all handlers.
It is likely that, at some point after publishing this chapter, the
scaffolded site will switch to an alternative prelude, such as
classy-prelude-yesod
. Don’t be surprised if Import
looks quite different than described here.
Handler modules should go inside the Handler/ folder. The site template includes one module: Handler/Home.hs. How you split up your handler functions into individual modules is your decision, but a good rule of thumb is:
Different methods for the same route should go in the same file (e.g., getBlogR
and postBlogR
).
Related routes should also go in the same file (e.g., getPeopleR
and getPersonR
).
Of course, it’s entirely up to you. When you add a new handler file, make sure you do the following:
Add it to version control (you are using version control, right?).
Add it to the cabal file.
Add it to the Application.hs file.
Put a module
statement at the top, and an import Import
line below it.
You can use the yesod add-handler
command to automate the last three steps.
It’s very common to want to include CSS and JavaScript specific to a page. You
don’t want to have to include those Lucius and Julius files
manually every time you refer to a Hamlet file. For this, the site template
provides the widgetFile
function.
If you have a handler function:
getHomeR
=
defaultLayout
$
(
widgetFile
"homepage"
)
Yesod will look for the following files:
templates/homepage.hamlet
templates/homepage.lucius
templates/homepage.cassius
templates/homepage.julius
If any of those files are present, they will be automatically included in the output.
Due to the nature of how this works, if you launch your app with yesod devel
and then create a new file (e.g., templates/homepage.julius), the contents will not be included until the file calling widgetFile
is recompiled. In such a case, you may need to force a save of that file to get yesod devel
to recompile.
One of the first things you’ll want to customize is the look of your site. The layout is actually broken up into two files:
This contains just the basic shell of a page. This file is interpreted as plain Hamlet, not as a Widget
, and therefore cannot refer to other widgets, embed i18n strings, or add extra CSS/JS.
This is where you would put the bulk of your page. You must remember to include the widget
value in the page, as that contains the per-page contents. This file is interpreted as a Widget
.
Also, because default-layout is included via the widgetFile
function, any Lucius, Cassius, or Julius files named default-layout.* will automatically be included as well.
The scaffolded site automatically includes the static file subsite, optimized for serving files that will not change over the lifetime of the current build. What this means is that:
When your static file identifiers are generated (e.g., static/mylogo.png becomes mylogo_png), a query string parameter is added to it with a hash of the contents of the file. All of this happens at compile time.
When yesod-static
serves your static files, it sets expiration headers far
in the future and includes an etag based on a hash of your content.
Whenever you embed a link to mylogo_png
, the rendering includes the
query string parameter. If you change the logo, recompile, and launch your
new app, the query string will have changed, causing users to ignore the
cached copy and download a new version.
Additionally, you can set a specific static root in your Settings.hs file to serve from a different domain name. This has the advantage of not requiring transmission of cookies for static file requests, and also lets you offload static file hosting to a CDN or a service like Amazon S3. See the comments in the file for more details.
Another optimization is that CSS and JavaScript included in your widgets will not be included inside your HTML. Instead, their contents will be written to an external file, and a link given. This file will be named based on a hash of the contents as well, meaning:
Caching works properly.
Yesod can avoid an expensive disk write of the CSS/JavaScript file contents if a file with the same hash already exists.
Finally, all of your JavaScript is automatically minified via hjsmin
.
The purpose of this chapter was not to explain every line that exists in the scaffolded site, but instead to give a general overview of how it works. The best way to become more familiar with it is to jump right in and start writing a Yesod site with it.