diff --git a/psc-bundle/Main.hs b/psc-bundle/Main.hs
--- a/psc-bundle/Main.hs
+++ b/psc-bundle/Main.hs
@@ -20,15 +20,9 @@
 
 module Main (main) where
 
-import Data.List (nub)
-import Data.Maybe (mapMaybe, catMaybes)
 import Data.Traversable (for)
-import Data.Generics (everything, everywhere, mkQ, mkT)
-import Data.Graph
 import Data.Version (showVersion)
 
-import qualified Data.Set as S
-
 import Control.Applicative
 import Control.Monad
 import Control.Monad.Error.Class
@@ -41,66 +35,12 @@
 import System.IO (stderr, hPutStrLn)
 import System.Directory (createDirectoryIfMissing)
 
-import Language.JavaScript.Parser
+import Language.PureScript.Bundle
 
 import Options.Applicative as Opts
 
 import qualified Paths_purescript as Paths
 
--- | The type of error messages. We separate generation and rendering of errors using a data
--- type, in case we need to match on error types later.
-data ErrorMessage
-  = UnsupportedModulePath String
-  | InvalidTopLevel
-  | UnableToParseModule
-  | UnsupportedExport
-  | ErrorInFile FilePath ErrorMessage
-  deriving Show
-
--- | Modules are either "regular modules" (i.e. those generated by psc-make) or foreign modules.
-data ModuleType 
-  = Regular 
-  | Foreign 
-  deriving (Show, Eq, Ord)
-  
--- | A module is identified by its module name and its type.
-data ModuleIdentifier = ModuleIdentifier String ModuleType deriving (Show, Eq, Ord)
-
-moduleName :: ModuleIdentifier -> String
-moduleName (ModuleIdentifier name _) = name
-
--- | A piece of code is identified by its module and its name. These keys are used to label vertices
--- in the dependency graph.
-type Key = (ModuleIdentifier, String)
-
--- | An export is either a "regular export", which exports a name from the regular module we are in,
--- or a reexport of a declaration in the corresponding foreign module.
--- 
--- Regular exports are labelled, since they might re-export an operator with another name. 
-data ExportType 
-  = RegularExport String 
-  | ForeignReexport 
-  deriving (Show, Eq, Ord)
-
--- | There are four types of module element we are interested in:
---
--- 1) Require statements
--- 2) Member declarations
--- 3) Export lists
--- 4) Everything else
---
--- Each is labelled with the original AST node which generated it, so that we can dump it back
--- into the output during codegen.
-data ModuleElement 
-  = Require JSNode String ModuleIdentifier
-  | Member JSNode Bool String [JSNode] [Key]
-  | ExportsList [(ExportType, String, JSNode, [Key])]
-  | Other JSNode
-  deriving Show
-
--- | A module is just a list of elements of the types listed above. 
-data Module = Module ModuleIdentifier [ModuleElement] deriving Show
-  
 -- | Command line options.
 data Options = Options
   { optionsInputFiles  :: [FilePath]
@@ -110,33 +50,6 @@
   , optionsNamespace   :: String
   } deriving Show
 
--- | Prepare an error message for consumption by humans.
-printErrorMessage :: ErrorMessage -> [String]
-printErrorMessage (UnsupportedModulePath s) =
-  [ "A CommonJS module has an unsupported name (" ++ show s ++ ")."
-  , "The following file names are supported:"
-  , "  1) index.js (psc-make native modules)"
-  , "  2) foreign.js (psc-make foreign modules)"
-  ]
-printErrorMessage InvalidTopLevel =
-  [ "Expected a list of source elements at the top level." ] 
-printErrorMessage UnableToParseModule =
-  [ "The module could not be parsed." ]
-printErrorMessage UnsupportedExport =
-  [ "An export was unsupported. Exports can be defined in one of two ways: "
-  , "  1) exports.name = ..."
-  , "  2) exports = { ... }"
-  ]
-printErrorMessage (ErrorInFile filename e) =
-  ("Error in file " ++ show filename ++ ":")
-  : ""
-  : map ("  " ++) (printErrorMessage e)
-
--- | Unpack the node inside a JSNode. This is useful when pattern matching.
-node :: JSNode -> Node
-node (NN n) = n
-node (NT n _ _) = n
-
 -- | Given a filename, assuming it is in the correct place on disk, infer a ModuleIdentifier.
 guessModuleIdentifier :: (Applicative m, MonadError ErrorMessage m) => FilePath -> m ModuleIdentifier
 guessModuleIdentifier filename = ModuleIdentifier (takeFileName (takeDirectory filename)) <$> (guessModuleType (takeFileName filename))
@@ -145,419 +58,23 @@
   guessModuleType "foreign.js" = pure Foreign 
   guessModuleType name = throwError $ UnsupportedModulePath name
 
--- | Calculate the ModuleIdentifier which a require(...) statement imports.
-checkImportPath :: String -> ModuleIdentifier -> S.Set String -> Maybe ModuleIdentifier
-checkImportPath "./foreign" m _     = 
-  Just (ModuleIdentifier (moduleName m) Foreign)
-checkImportPath name        _ names 
-  | name `S.member` names = Just (ModuleIdentifier name Regular)
-checkImportPath _           _ _     = Nothing
-
--- | Compute the dependencies of all elements in a module, and add them to the tree.
--- 
--- Members and exports can have dependencies. A dependency is of one of the following forms:
---
--- 1) module.name or member["name"]
---    
---    where module was imported using
---
---    var module = require("Module.Name");
---
--- 2) name
---
---    where name is the name of a member defined in the current module.
-withDeps :: Module -> Module
-withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
-  where    
-  -- | Collects all modules which are imported, so that we can identify dependencies of the first type.
-  imports :: [(String, ModuleIdentifier)]
-  imports = mapMaybe toImport es
-    where
-    toImport :: ModuleElement -> Maybe (String, ModuleIdentifier)
-    toImport (Require _ nm mid) = Just (nm, mid)
-    toImport _ = Nothing
-    
-  -- | Collects all member names in scope, so that we can identify dependencies of the second type.
-  boundNames :: [String]
-  boundNames = mapMaybe toBoundName es
-    where
-    toBoundName :: ModuleElement -> Maybe String
-    toBoundName (Member _ _ nm _ _) = Just nm
-    toBoundName _ = Nothing
-
-  -- | Calculate dependencies and add them to the current element.
-  expandDeps :: ModuleElement -> ModuleElement
-  expandDeps (Member n f nm decl _) = Member n f nm decl (nub (concatMap (dependencies modulePath) decl))
-  expandDeps (ExportsList exps) = ExportsList (map expand exps)
-      where
-      expand (ty, nm, n1, _) = (ty, nm, n1, nub (dependencies modulePath n1))
-  expandDeps other = other
-  
-  dependencies :: ModuleIdentifier -> JSNode -> [(ModuleIdentifier, String)]
-  dependencies m = everything (++) (mkQ [] toReference)
-    where
-    toReference :: Node -> [(ModuleIdentifier, String)]
-    toReference (JSMemberDot [ mn ] _ nm)
-      | JSIdentifier mn' <- node mn
-      , JSIdentifier nm' <- node nm
-      , Just mid <- lookup mn' imports
-      = [(mid, nm')]
-    toReference (JSMemberSquare [ mn ] _ nm _)
-      | JSIdentifier mn' <- node mn
-      , JSExpression [ s ] <- node nm
-      , JSStringLiteral _ nm' <- node s
-      , Just mid <- lookup mn' imports
-      = [(mid, nm')]
-    toReference (JSIdentifier nm)
-      | nm `elem` boundNames
-      = [(m, nm)]
-    toReference _ = []
-
--- | Attempt to create a Module from a Javascript AST.
--- 
--- Each type of module element is matched using pattern guards, and everything else is bundled into the 
--- Other constructor.
-toModule :: forall m. (Applicative m, MonadError ErrorMessage m) => S.Set String -> ModuleIdentifier -> JSNode -> m Module
-toModule mids mid top 
-  | JSSourceElementsTop ns <- node top = Module mid <$> mapM toModuleElement ns
-  | otherwise = throwError InvalidTopLevel
-  where      
-  toModuleElement :: JSNode -> m ModuleElement
-  toModuleElement n
-    | JSVariables var [ varIntro ] _ <- node n
-    , JSLiteral "var" <- node var 
-    , JSVarDecl impN [ eq, req, impP ] <- node varIntro
-    , JSIdentifier importName <- node impN
-    , JSLiteral "=" <- node eq
-    , JSIdentifier "require" <- node req
-    , JSArguments _ [ impS ] _ <- node impP 
-    , JSStringLiteral _ importPath <- node impS
-    , Just importPath' <- checkImportPath importPath mid mids
-    = pure (Require n importName importPath')
-  toModuleElement n
-    | JSVariables var [ varIntro ] _ <- node n
-    , JSLiteral "var" <- node var 
-    , JSVarDecl declN (eq : decl) <- node varIntro
-    , JSIdentifier name <- node declN
-    , JSLiteral "=" <- node eq
-    = pure (Member n False name decl [])
-  toModuleElement n
-    | JSExpression (e : op : decl) <- node n
-    , Just name <- accessor (node e)
-    , JSOperator eq <- node op
-    , JSLiteral "=" <- node eq
-    = pure (Member n True name decl [])
-    where
-    accessor :: Node -> Maybe String
-    accessor (JSMemberDot [ exports ] _ nm)
-      | JSIdentifier "exports" <- node exports
-      , JSIdentifier name <- node nm
-      = Just name
-    accessor (JSMemberSquare [ exports ] _ nm _)
-      | JSIdentifier "exports" <- node exports
-      , JSExpression [e] <- node nm
-      , JSStringLiteral _ name <- node e
-      = Just name
-    accessor _ = Nothing
-  toModuleElement n
-    | JSExpression (mnExp : op : obj: _) <- node n
-    , JSMemberDot [ mn ] _ e <- node mnExp
-    , JSIdentifier "module" <- node mn
-    , JSIdentifier "exports" <- node e
-    , JSOperator eq <- node op
-    , JSLiteral "=" <- node eq
-    , JSObjectLiteral _ props _ <- node obj
-    = ExportsList <$> mapM toExport (filter (not . isSeparator) (map node props))
-    where
-    toExport :: Node -> m (ExportType, String, JSNode, [Key])
-    toExport (JSPropertyNameandValue name _ [val] ) = 
-      (,,val,[]) <$> exportType (node val) 
-                 <*> extractLabel (node name)
-    toExport _ = throwError UnsupportedExport
-  
-    exportType :: Node -> m ExportType
-    exportType (JSMemberDot [f] _ _)
-      | JSIdentifier "$foreign" <- node f
-      = pure ForeignReexport
-    exportType (JSMemberSquare [f] _ _ _)
-      | JSIdentifier "$foreign" <- node f
-      = pure ForeignReexport
-    exportType (JSIdentifier s) = pure (RegularExport s)
-    exportType _ = throwError UnsupportedExport
-  
-    extractLabel :: Node -> m String
-    extractLabel (JSStringLiteral _ nm) = pure nm
-    extractLabel (JSIdentifier nm) = pure nm
-    extractLabel _ = throwError UnsupportedExport
-    
-    isSeparator :: Node -> Bool
-    isSeparator (JSLiteral ",") = True
-    isSeparator _ = False
-  toModuleElement other = pure (Other other)
-
--- | Eliminate unused code based on the specified entry point set.
-compile :: [Module] -> [ModuleIdentifier] -> [Module]
-compile modules [] = modules
-compile modules entryPoints = filteredModules
-  where    
-  (graph, _, vertexFor) = graphFromEdges verts
-  
-  -- | The vertex set
-  verts :: [(ModuleElement, Key, [Key])]
-  verts = do
-    Module mid els <- modules
-    concatMap (toVertices mid) els
-    where
-    -- | Create a set of vertices for a module element.
-    -- 
-    -- Some special cases worth commenting on:
-    --
-    -- 1) Regular exports which simply export their own name do not count as dependencies.
-    --    Regular exports which rename and reexport an operator do count, however.
-    -- 
-    -- 2) Require statements don't contribute towards dependencies, since they effectively get
-    --    inlined wherever they are used inside other module elements.
-    toVertices :: ModuleIdentifier -> ModuleElement -> [(ModuleElement, Key, [Key])]
-    toVertices p m@(Member _ _ nm _ deps) = [(m, (p, nm), deps)]
-    toVertices p m@(ExportsList exps) = mapMaybe toVertex exps
-      where
-      toVertex (ForeignReexport, nm, _, ks) = Just (m, (p, nm), ks)
-      toVertex (RegularExport nm, nm1, _, ks) | nm /= nm1 = Just (m, (p, nm1), ks)
-      toVertex _ = Nothing
-    toVertices _ _ = []
-  
-  -- | The set of vertices whose connected components we are interested in keeping.
-  entryPointVertices :: [Vertex]
-  entryPointVertices = catMaybes $ do
-    (_, k@(mid, _), _) <- verts
-    guard $ mid `elem` entryPoints
-    return (vertexFor k)
-  
-  -- | The set of vertices reachable from an entry point
-  reachableSet :: S.Set Vertex
-  reachableSet = S.fromList (concatMap (reachable graph) entryPointVertices)
-  
-  filteredModules :: [Module]
-  filteredModules = map filterUsed modules
-    where
-    filterUsed :: Module -> Module
-    filterUsed (Module mid ds) = Module mid (map filterExports (go ds))
-      where
-      go :: [ModuleElement] -> [ModuleElement]
-      go [] = []
-      go (d : Other semi : rest) 
-        | JSLiteral ";" <- node semi
-        , not (isDeclUsed d)
-        = go rest
-      go (d : rest) 
-        | not (isDeclUsed d) = go rest
-        | otherwise = d : go rest
-        
-      -- | Filter out the exports for members which aren't used.
-      filterExports :: ModuleElement -> ModuleElement
-      filterExports (ExportsList exps) = ExportsList (filter (\(_, nm, _, _) -> isKeyUsed (mid, nm)) exps)
-      filterExports me = me
-    
-      isDeclUsed :: ModuleElement -> Bool
-      isDeclUsed (Member _ _ nm _ _) = isKeyUsed (mid, nm)
-      isDeclUsed _ = True
-      
-      isKeyUsed :: Key -> Bool
-      isKeyUsed k 
-        | Just me <- vertexFor k = me `S.member` reachableSet
-        | otherwise = False
-  
--- | Topologically sort the module dependency graph, so that when we generate code, modules can be 
--- defined in the right order.
-sortModules :: [Module] -> [Module]
-sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (topSort graph))
-  where
-  (graph, nodeFor, _) = graphFromEdges $ do
-    m@(Module mid els) <- modules
-    return (m, mid, mapMaybe getKey els)
-    
-  getKey :: ModuleElement -> Maybe ModuleIdentifier
-  getKey (Require _ _ mi) = Just mi 
-  getKey _ = Nothing
-  
--- | A module is empty if it contains no exported members (in other words,
--- if the only things left after dead code elimination are module imports and
--- "other" foreign code).
---
--- If a module is empty, we don't want to generate code for it.
-isModuleEmpty :: Module -> Bool
-isModuleEmpty (Module _ els) = all isElementEmpty els
-  where
-  isElementEmpty :: ModuleElement -> Bool
-  isElementEmpty (ExportsList exps) = null exps
-  isElementEmpty (Require _ _ _) = True
-  isElementEmpty (Other _) = True
-  isElementEmpty _ = False
-  
--- | Generate code for a set of modules, including a call to main().
---
--- Modules get defined on the global PS object, as follows:
---
---     var PS = { };
---     (function(exports) {
---       ...
---     })(PS["Module.Name"] = PS["Module.Name"] || {});
---
--- In particular, a module and its foreign imports share the same namespace inside PS. 
--- This saves us from having to generate unique names for a module and its foreign imports,
--- and is safe since a module shares a namespace with its foreign imports in PureScript as well
--- (so there is no way to have overlaps in code generated by psc-make).
-codeGen :: Options -> [Module] -> String
-codeGen Options{..} ms = renderToString (NN (JSSourceElementsTop (prelude ++ concatMap moduleToJS ms ++ maybe [] runMain optionsMainModule)))
-  where
-  moduleToJS :: Module -> [JSNode]
-  moduleToJS (Module mn ds) = wrap (moduleName mn) (indent (concatMap declToJS ds))
-    where
-    declToJS :: ModuleElement -> [JSNode]
-    declToJS (Member n _ _ _ _) = [n]
-    declToJS (Other n) = [n]       
-    declToJS (Require _ nm req) = 
-      [ NN (JSVariables (NT (JSLiteral "var") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n  " ]) 
-                        [ NN (JSVarDecl (sp (JSIdentifier nm)) 
-                                        [ sp (JSLiteral "=")
-                                        , moduleReference sp (moduleName req)
-                                        ])
-                        ]
-                        (nt (JSLiteral ";"))) ]
-    declToJS (ExportsList exps) = map toExport exps
-      
-      where 
-      toExport :: (ExportType, String, JSNode, [Key]) -> JSNode
-      toExport (_, nm, val, _) =
-        NN (JSExpression [ NN (JSMemberSquare [ NT (JSIdentifier "exports") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n  " ] ] 
-                                              (nt (JSLiteral "[")) 
-                                              (NN (JSExpression [ nt (JSStringLiteral '"' nm) ]))
-                                              (nt (JSLiteral "]")))
-                         , NN (JSOperator (sp (JSLiteral "=")))
-                         , reindent val
-                         , nt (JSLiteral ";")
-                         ])
-      
-      reindent :: JSNode -> JSNode
-      reindent (NT n _ _) = sp n
-      reindent nn = nn
-  
-  indent :: [JSNode] -> [JSNode]
-  indent = everywhere (mkT squash)
-    where
-    squash (NT n pos ann) = NT n (keepCol pos) (map splat ann)
-    squash nn = nn
-    
-    splat (CommentA pos s) = CommentA (keepCol pos) s
-    splat (WhiteSpace pos w) = WhiteSpace (keepCol pos) w
-    splat ann = ann
-    
-    keepCol (TokenPn _ _ c) = TokenPn 0 0 (c + 2)
-    
-  prelude :: [JSNode]
-  prelude = 
-    [ NN (JSVariables (NT (JSLiteral "var") tokenPosnEmpty [ CommentA tokenPosnEmpty ("// Generated by psc-bundle " ++ showVersion Paths.version)
-                                                           , WhiteSpace tokenPosnEmpty "\n"
-                                                           ]) 
-                                                           [ NN (JSVarDecl (sp (JSIdentifier optionsNamespace)) 
-                                                                           [ sp (JSLiteral "=")
-                                                                           , NN (JSObjectLiteral (sp (JSLiteral "{")) 
-                                                                                                 []
-                                                                                                 (sp (JSLiteral "}")))
-                                                                           ])
-                                                           ] 
-                                                           (nt (JSLiteral ";")))
-    , lf
-    ]
-    
-  moduleReference :: (Node -> JSNode) -> String -> JSNode
-  moduleReference f mn = 
-    NN (JSMemberSquare [ f (JSIdentifier optionsNamespace) ] 
-                       (nt (JSLiteral "[")) 
-                       (NN (JSExpression [ nt (JSStringLiteral '"' mn) ]))
-                       (nt (JSLiteral "]")))
-    
-  wrap :: String -> [JSNode] -> [JSNode]
-  wrap mn ds = 
-    [ NN (JSExpression [ NN (JSExpressionParen (nt (JSLiteral "(")) 
-                                               (NN (JSExpression [ NN (JSFunctionExpression (nt (JSLiteral "function")) 
-                                                                                            [] 
-                                                                                            (nt (JSLiteral "(") ) [nt (JSIdentifier "exports")] (nt (JSLiteral ")")) 
-                                                                                            (NN (JSBlock [sp (JSLiteral "{")] 
-                                                                                                         (lf : ds)
-                                                                                                         [nl (JSLiteral "}")])))]))
-                                               (nt (JSLiteral ")")))
-                       , NN (JSArguments (nt (JSLiteral "(")) 
-                                         [ NN (JSExpression [ moduleReference nt mn
-                                                            , NN (JSOperator (sp (JSLiteral "=")))
-                                                            , NN (JSExpressionBinary "||"
-                                                                                     [ moduleReference sp mn ] 
-                                                                                     (sp (JSLiteral "||")) 
-                                                                                     [ emptyObj ]) 
-                                                            ])
-                                         ]
-                                         (nt (JSLiteral ")")))
-                       ])
-    , nt (JSLiteral ";")
-    , lf
-    ]
-    where
-    emptyObj = NN (JSObjectLiteral (sp (JSLiteral "{")) [] (nt (JSLiteral "}")))
-    
-  runMain :: String -> [JSNode]
-  runMain mn = 
-    [ NN (JSExpression [ NN (JSMemberDot [ NN (JSMemberSquare [ nl (JSIdentifier optionsNamespace) ] 
-                                                              (nt (JSLiteral "[")) 
-                                                              (NN (JSExpression [ nt (JSStringLiteral '"' mn) ])) 
-                                                              (nt (JSLiteral "]")))
-                                         ] 
-                                         (nt (JSLiteral ".")) 
-                                         (nt (JSIdentifier "main")))
-                       , NN (JSArguments (nt (JSLiteral "(")) [] (nt (JSLiteral ")")))
-                       ])
-    , nt (JSLiteral ";")
-    ]
-  
-  nt :: Node -> JSNode
-  nt n = NT n tokenPosnEmpty []
-  
-  lf :: JSNode
-  lf = NT (JSLiteral "") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n" ]
-  
-  sp :: Node -> JSNode
-  sp n = NT n tokenPosnEmpty [ WhiteSpace tokenPosnEmpty " " ]
-  
-  nl :: Node -> JSNode
-  nl n = NT n tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n" ]
-
 -- | The main application function.
 -- This function parses the input files, performs dead code elimination, filters empty modules
 -- and generates and prints the final Javascript bundle.
 app :: forall m. (Applicative m, MonadError ErrorMessage m, MonadIO m) => Options -> m String
-app opts@Options{..} = do
+app Options{..} = do
   inputFiles <- concat <$> mapM (liftIO . glob) optionsInputFiles
   when (null inputFiles) . liftIO $ do
     hPutStrLn stderr "psc-bundle: No input files."
     exitFailure
   input <- for inputFiles $ \filename -> do
     js <- liftIO (readFile filename)
-    ast <- fromRight (parse js filename)
     mid <- guessModuleIdentifier filename
-    return (mid, ast)
-    
-  let mids = S.fromList (map (moduleName . fst) input)
+    return (mid, js)
     
-  modules <- mapM (fmap withDeps . uncurry (toModule mids)) input
-  
-  let compiled = compile modules (map (`ModuleIdentifier` Regular) optionsEntryPoints)
-      sorted   = sortModules (filter (not . isModuleEmpty) compiled)
-  
-  return (codeGen opts sorted)
-  
-  where
-  fromRight :: Either a b -> m b
-  fromRight (Right b) = pure b
-  fromRight (Left _) = throwError UnableToParseModule
+  let entryIds = (map (`ModuleIdentifier` Regular) optionsEntryPoints)
+
+  bundle input entryIds optionsMainModule optionsNamespace
     
 -- | Command line options parser.
 options :: Parser Options
@@ -572,10 +89,10 @@
        metavar "FILE"
     <> help "The input .js file(s)"
     
-  outputFile :: Parser FilePath	
-  outputFile = strOption $		
-       short 'o'		
-    <> long "output"		
+  outputFile :: Parser FilePath
+  outputFile = strOption $
+       short 'o'
+    <> long "output"
     <> help "The output .js file"
     
   entryPoint :: Parser String
diff --git a/psc-publish/ErrorsWarnings.hs b/psc-publish/ErrorsWarnings.hs
--- a/psc-publish/ErrorsWarnings.hs
+++ b/psc-publish/ErrorsWarnings.hs
@@ -33,7 +33,7 @@
   deriving (Show)
 
 data PackageWarning
-  = ResolutionNotVersion PackageName
+  = NoResolvedVersion PackageName
   | UndeclaredDependency PackageName
   | UnacceptableVersion (PackageName, String)
   deriving (Show)
@@ -253,7 +253,7 @@
       [ "An IO exception occurred:", show exc ]
 
 data CollectedWarnings = CollectedWarnings
-  { resolutionNotVersions  :: [PackageName]
+  { noResolvedVersions     :: [PackageName]
   , undeclaredDependencies :: [PackageName]
   , unacceptableVersions   :: [(PackageName, String)]
   }
@@ -268,7 +268,7 @@
 collectWarnings = foldMap singular
   where
   singular w = case w of
-    ResolutionNotVersion pn -> CollectedWarnings [pn] [] []
+    NoResolvedVersion    pn -> CollectedWarnings [pn] [] []
     UndeclaredDependency pn -> CollectedWarnings [] [pn] []
     UnacceptableVersion t   -> CollectedWarnings [] [] [t]
 
@@ -276,7 +276,7 @@
 renderWarnings warns =
   let CollectedWarnings{..} = collectWarnings warns
       go toBox warns' = toBox <$> NonEmpty.nonEmpty warns'
-      mboxes = [ go warnResolutionNotVersions  resolutionNotVersions
+      mboxes = [ go warnNoResolvedVersions     noResolvedVersions
                , go warnUndeclaredDependencies undeclaredDependencies
                , go warnUnacceptableVersions   unacceptableVersions
                ]
@@ -286,18 +286,18 @@
                      , indented (vcat (intersperse spacer boxes))
                      ]
 
-warnResolutionNotVersions :: NonEmpty PackageName -> Box
-warnResolutionNotVersions pkgNames =
+warnNoResolvedVersions :: NonEmpty PackageName -> Box
+warnNoResolvedVersions pkgNames =
   let singular = NonEmpty.length pkgNames == 1
       pl a b = if singular then b else a
 
       packages   = pl "packages" "package"
-      were       = pl "were" "was"
       anyOfThese = pl "any of these" "this"
       these      = pl "these" "this"
   in vcat $
     [ para (concat
-      ["The following ", packages, " ", were, " not resolved to a version:"])
+      ["The following ", packages, " did not appear to have a resolved "
+      , "version:"])
     ] ++
       bulletedList runPackageName (NonEmpty.toList pkgNames)
       ++
diff --git a/psc-publish/Main.hs b/psc-publish/Main.hs
--- a/psc-publish/Main.hs
+++ b/psc-publish/Main.hs
@@ -3,15 +3,18 @@
 {-# LANGUAGE TupleSections #-}
 {-# LANGUAGE GeneralizedNewtypeDeriving #-}
 
+module Main where
+
 import Prelude hiding (userError)
 
 import Data.Maybe
 import Data.Char (isSpace)
 import Data.String (fromString)
-import Data.List (stripPrefix, isSuffixOf, (\\))
+import Data.List (stripPrefix, isSuffixOf, (\\), nubBy)
 import Data.List.Split (splitOn)
 import Data.List.NonEmpty (NonEmpty(..))
 import Data.Version
+import Data.Function (on)
 import Safe (headMay)
 
 import qualified Data.ByteString.Lazy.Char8 as BL
@@ -37,6 +40,7 @@
                               runPackageName, parsePackageName, Repository(..))
 import qualified Web.Bower.PackageMeta as Bower
 
+import qualified Language.PureScript as P (version)
 import qualified Language.PureScript.Docs as D
 import Utils
 import ErrorsWarnings
@@ -106,6 +110,7 @@
   pkgResolvedDependencies     <- getResolvedDependencies declaredDeps
 
   let pkgUploader = D.NotYetKnown
+  let pkgCompilerVersion = P.version
 
   return D.Package{..}
 
@@ -170,6 +175,12 @@
 
 data DependencyStatus
   = Missing
+    -- ^ Listed in bower.json, but not installed.
+  | NoResolution
+    -- ^ In the output of `bower list --json --offline`, there was no
+    -- _resolution key. This can be caused by adding the dependency using
+    -- `bower link`, or simply copying it into bower_components instead of
+    -- installing it normally.
   | ResolvedOther String
     -- ^ Resolved, but to something other than a version. The String argument
     -- is the resolution type. The values it can take that I'm aware of are
@@ -196,19 +207,36 @@
 getResolvedDependencies declaredDeps = do
   depsBS <- fromString <$> readProcess' "bower" ["list", "--json", "--offline"] ""
 
-  deps <- catchLeft (parse asBowerResolvedDependencies depsBS)
-                    (internalError . JSONError FromBowerList)
+  -- Check for undeclared dependencies
+  toplevels <- catchJSON (parse asToplevelDependencies depsBS)
+  warnUndeclared declaredDeps toplevels
 
-  warnUndeclared declaredDeps (map fst deps)
+  deps <- catchJSON (parse asResolvedDependencies depsBS)
   handleDeps deps
 
   where
-  asBowerResolvedDependencies ::
-    Parse BowerError [(PackageName, DependencyStatus)]
-  asBowerResolvedDependencies =
-    key "dependencies"
-      (eachInObjectWithKey (parsePackageName . T.unpack) asDependencyStatus)
+  catchJSON = flip catchLeft (internalError . JSONError FromBowerList)
 
+-- | Extracts all dependencies and their versions from
+--   `bower list --json --offline`
+asResolvedDependencies :: Parse BowerError [(PackageName, DependencyStatus)]
+asResolvedDependencies = nubBy ((==) `on` fst) <$> go
+  where
+  go =
+    fmap (fromMaybe []) $
+      keyMay "dependencies" $
+        (++) <$> eachInObjectWithKey (parsePackageName . T.unpack)
+                                     asDependencyStatus
+             <*> (concatMap snd <$> eachInObject asResolvedDependencies)
+
+-- | Extracts only the top level dependency names from the output of
+--   `bower list --json --offline`
+asToplevelDependencies :: Parse BowerError [PackageName]
+asToplevelDependencies =
+  fmap (map fst) $
+    key "dependencies" $
+      eachInObjectWithKey (parsePackageName . T.unpack) (return ())
+
 asDependencyStatus :: Parse e DependencyStatus
 asDependencyStatus = do
   isMissing <- keyOrDefault "missing" False asBool
@@ -217,7 +245,7 @@
       return Missing
     else
       key "pkgMeta" $
-        key "_resolution" $ do
+        keyOrDefault "_resolution" NoResolution $ do
           type_ <- key "type" asString
           case type_ of
             "version" -> ResolvedVersion <$> key "tag" asString
@@ -230,12 +258,12 @@
 handleDeps ::
   [(PackageName, DependencyStatus)] -> PrepareM [(PackageName, Version)]
 handleDeps deps = do
-  let (missing, notVersion, installed) = partitionDeps deps
+  let (missing, noVersion, installed) = partitionDeps deps
   case missing of
     (x:xs) ->
       userError (MissingDependencies (x :| xs))
     [] -> do
-      mapM_ (warn . ResolutionNotVersion . fst) notVersion
+      mapM_ (warn . NoResolvedVersion) noVersion
       withVersions <- catMaybes <$> mapM tryExtractVersion' installed
       filterM (liftIO . isPureScript . bowerDir . fst) withVersions
 
@@ -244,7 +272,8 @@
   go (pkgName, d) (ms, os, is) =
     case d of
       Missing           -> (pkgName : ms, os, is)
-      ResolvedOther o   -> (ms, (pkgName, o) : os, is)
+      NoResolution      -> (ms, pkgName : os, is)
+      ResolvedOther _   -> (ms, pkgName : os, is)
       ResolvedVersion v -> (ms, os, (pkgName, v) : is)
 
   bowerDir pkgName = "bower_components/" ++ runPackageName pkgName
diff --git a/psc/Main.hs b/psc/Main.hs
--- a/psc/Main.hs
+++ b/psc/Main.hs
@@ -36,7 +36,7 @@
 import qualified Language.PureScript as P
 import qualified Paths_purescript as Paths
 
-import Make
+import Language.PureScript.Make
 
 data PSCMakeOptions = PSCMakeOptions
   { pscmInput        :: [FilePath]
diff --git a/psc/Make.hs b/psc/Make.hs
deleted file mode 100644
--- a/psc/Make.hs
+++ /dev/null
@@ -1,140 +0,0 @@
------------------------------------------------------------------------------
---
--- Module      :  Make
--- Copyright   :  (c) 2013-14 Phil Freeman, (c) 2014 Gary Burgess, and other contributors
--- License     :  MIT
---
--- Maintainer  :  Phil Freeman <paf31@cantab.net>
--- Stability   :  experimental
--- Portability :
---
--- |
---
------------------------------------------------------------------------------
-
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE TupleSections #-}
-
-module Make
-  ( Make(..)
-  , runMake
-  , buildMakeActions
-  ) where
-
-import Control.Applicative
-import Control.Monad
-import Control.Monad.Error.Class (MonadError(..))
-import Control.Monad.Trans.Except
-import Control.Monad.Reader
-import Control.Monad.Writer
-
-import Data.Maybe (fromMaybe)
-import Data.Time.Clock
-import Data.Traversable (traverse)
-import Data.Version (showVersion)
-import qualified Data.Map as M
-
-import System.Directory
-       (doesFileExist, getModificationTime, createDirectoryIfMissing)
-import System.FilePath ((</>), takeDirectory)
-import System.IO.Error (tryIOError)
-
-import qualified Language.PureScript as P
-import qualified Language.PureScript.CodeGen.JS as J
-import qualified Language.PureScript.CoreFn as CF
-import qualified Paths_purescript as Paths
-
-newtype Make a = Make { unMake :: ReaderT P.Options (WriterT P.MultipleErrors (ExceptT P.MultipleErrors IO)) a }
-  deriving (Functor, Applicative, Monad, MonadIO, MonadError P.MultipleErrors, MonadWriter P.MultipleErrors, MonadReader P.Options)
-
-runMake :: P.Options -> Make a -> IO (Either P.MultipleErrors (a, P.MultipleErrors))
-runMake opts = runExceptT . runWriterT . flip runReaderT opts . unMake
-
-makeIO :: (IOError -> P.ErrorMessage) -> IO a -> Make a
-makeIO f io = do
-  e <- liftIO $ tryIOError io
-  either (throwError . P.singleError . f) return e
-
--- Traverse (Either e) instance (base 4.7)
-traverseEither :: Applicative f => (a -> f b) -> Either e a -> f (Either e b)
-traverseEither _ (Left x) = pure (Left x)
-traverseEither f (Right y) = Right <$> f y
-
-buildMakeActions :: FilePath
-                 -> M.Map P.ModuleName (Either P.RebuildPolicy String)
-                 -> M.Map P.ModuleName (FilePath, P.ForeignJS)
-                 -> Bool
-                 -> P.MakeActions Make
-buildMakeActions outputDir filePathMap foreigns usePrefix =
-  P.MakeActions getInputTimestamp getOutputTimestamp readExterns codegen progress
-  where
-
-  getInputTimestamp :: P.ModuleName -> Make (Either P.RebuildPolicy (Maybe UTCTime))
-  getInputTimestamp mn = do
-    let path = fromMaybe (error "Module has no filename in 'make'") $ M.lookup mn filePathMap
-    e1 <- traverseEither getTimestamp path
-    fPath <- maybe (return Nothing) (getTimestamp . fst) $ M.lookup mn foreigns
-    return $ fmap (max fPath) e1
-
-  getOutputTimestamp :: P.ModuleName -> Make (Maybe UTCTime)
-  getOutputTimestamp mn = do
-    let filePath = P.runModuleName mn
-        jsFile = outputDir </> filePath </> "index.js"
-        externsFile = outputDir </> filePath </> "externs.purs"
-    min <$> getTimestamp jsFile <*> getTimestamp externsFile
-
-  readExterns :: P.ModuleName -> Make (FilePath, String)
-  readExterns mn = do
-    let path = outputDir </> P.runModuleName mn </> "externs.purs"
-    (path, ) <$> readTextFile path
-
-  codegen :: CF.Module CF.Ann -> P.Environment -> P.SupplyVar -> P.Externs -> Make ()
-  codegen m _ nextVar exts = do
-    let mn = CF.moduleName m
-    foreignInclude <- case mn `M.lookup` foreigns of
-      Just (path, _)
-        | not $ requiresForeign m -> do
-            tell $ P.errorMessage $ P.UnnecessaryFFIModule mn path
-            return Nothing
-        | otherwise -> return $ Just $ J.JSApp (J.JSVar "require") [J.JSStringLiteral "./foreign"]
-      Nothing | requiresForeign m -> throwError . P.errorMessage $ P.MissingFFIModule mn
-              | otherwise -> return Nothing
-    pjs <- P.evalSupplyT nextVar $ P.prettyPrintJS <$> J.moduleToJs m foreignInclude
-    let filePath = P.runModuleName mn
-        jsFile = outputDir </> filePath </> "index.js"
-        externsFile = outputDir </> filePath </> "externs.purs"
-        foreignFile = outputDir </> filePath </> "foreign.js"
-        prefix = ["Generated by psc version " ++ showVersion Paths.version | usePrefix]
-        js = unlines $ map ("// " ++) prefix ++ [pjs]
-    writeTextFile jsFile js
-    maybe (return ()) (writeTextFile foreignFile . snd) $ mn `M.lookup` foreigns
-    writeTextFile externsFile exts
-
-  requiresForeign :: CF.Module a -> Bool
-  requiresForeign = not . null . CF.moduleForeign
-
-  getTimestamp :: FilePath -> Make (Maybe UTCTime)
-  getTimestamp path = makeIO (const (P.SimpleErrorWrapper $ P.CannotGetFileInfo path)) $ do
-    exists <- doesFileExist path
-    traverse (const $ getModificationTime path) $ guard exists
-
-  readTextFile :: FilePath -> Make String
-  readTextFile path = do
-    verboseErrorsEnabled <- asks P.optionsVerboseErrors
-    makeIO (const (P.SimpleErrorWrapper $ P.CannotReadFile path)) $ do
-      when verboseErrorsEnabled $ putStrLn $ "Reading " ++ path
-      readFile path
-
-  writeTextFile :: FilePath -> String -> Make ()
-  writeTextFile path text = makeIO (const (P.SimpleErrorWrapper $ P.CannotWriteFile path)) $ do
-    mkdirp path
-    putStrLn $ "Writing " ++ path
-    writeFile path text
-    where
-    mkdirp :: FilePath -> IO ()
-    mkdirp = createDirectoryIfMissing True . takeDirectory
-
-  progress :: String -> Make ()
-  progress = liftIO . putStrLn
diff --git a/psci/Make.hs b/psci/Make.hs
deleted file mode 100644
--- a/psci/Make.hs
+++ /dev/null
@@ -1,127 +0,0 @@
------------------------------------------------------------------------------
---
--- Module      :  Make
--- Copyright   :  (c) 2013-14 Phil Freeman, (c) 2014 Gary Burgess, and other contributors
--- License     :  MIT
---
--- Maintainer  :  Phil Freeman <paf31@cantab.net>
--- Stability   :  experimental
--- Portability :
---
--- |
---
------------------------------------------------------------------------------
-
-{-# LANGUAGE DataKinds #-}
-{-# LANGUAGE GeneralizedNewtypeDeriving #-}
-{-# LANGUAGE TupleSections #-}
-
-module Make where
-
-import Data.List (isPrefixOf)
-import Data.Maybe (fromMaybe)
-import Data.Time.Clock
-import Data.Traversable (traverse)
-import qualified Data.Map as M
-
-import Control.Applicative
-import Control.Monad
-import Control.Monad.Error.Class (MonadError(..))
-import Control.Monad.IO.Class (MonadIO, liftIO)
-import Control.Monad.Reader (MonadReader, ReaderT, runReaderT)
-import Control.Monad.Trans.Except (ExceptT(..), runExceptT)
-import Control.Monad.Writer (MonadWriter, WriterT, runWriterT, tell)
-
-import System.Directory (getModificationTime, doesFileExist)
-import System.FilePath ((</>), pathSeparator)
-import System.IO.Error (tryIOError)
-
-import qualified Language.PureScript as P
-import qualified Language.PureScript.CodeGen.JS as J
-import qualified Language.PureScript.CoreFn as CF
-
-import IO (mkdirp)
-
-options :: P.Options
-options = P.Options False False Nothing False False False Nothing
-
-modulesDir :: FilePath
-modulesDir = ".psci_modules" ++ pathSeparator : "node_modules"
-
-newtype Make a = Make { unMake :: ReaderT P.Options (WriterT P.MultipleErrors (ExceptT P.MultipleErrors IO)) a }
-  deriving (Functor, Applicative, Monad, MonadIO, MonadError P.MultipleErrors, MonadWriter P.MultipleErrors, MonadReader P.Options)
-
-runMake :: Make a -> IO (Either P.MultipleErrors a)
-runMake = runExceptT . fmap fst . runWriterT . flip runReaderT options . unMake
-
-makeIO :: (IOError -> P.ErrorMessage) -> IO a -> Make a
-makeIO f io = do
-  e <- liftIO $ tryIOError io
-  either (throwError . P.singleError . f) return e
-
--- Traverse (Either e) instance (base 4.7)
-traverseEither :: Applicative f => (a -> f b) -> Either e a -> f (Either e b)
-traverseEither _ (Left x) = pure (Left x)
-traverseEither f (Right y) = Right <$> f y
-
-buildMakeActions :: M.Map P.ModuleName (Either P.RebuildPolicy String)
-                 -> M.Map P.ModuleName P.ForeignJS
-                 -> P.MakeActions Make
-buildMakeActions filePathMap foreigns =
-  P.MakeActions getInputTimestamp getOutputTimestamp readExterns codegen progress
-  where
-
-  getInputTimestamp :: P.ModuleName -> Make (Either P.RebuildPolicy (Maybe UTCTime))
-  getInputTimestamp mn = do
-    let path = fromMaybe (error "Module has no filename in 'make'") $ M.lookup mn filePathMap
-    traverseEither getTimestamp path
-
-  getOutputTimestamp :: P.ModuleName -> Make (Maybe UTCTime)
-  getOutputTimestamp mn = do
-    let filePath = P.runModuleName mn
-        jsFile = modulesDir </> filePath </> "index.js"
-        externsFile = modulesDir </> filePath </> "externs.purs"
-    min <$> getTimestamp jsFile <*> getTimestamp externsFile
-
-  readExterns :: P.ModuleName -> Make (FilePath, String)
-  readExterns mn = do
-    let path = modulesDir </> P.runModuleName mn </> "externs.purs"
-    (path, ) <$> readTextFile path
-
-  codegen :: CF.Module CF.Ann -> P.Environment -> P.SupplyVar -> P.Externs -> Make ()
-  codegen m _ nextVar exts = do
-    let mn = CF.moduleName m
-    foreignInclude <- case CF.moduleName m `M.lookup` foreigns of
-      Just path
-        | not $ requiresForeign m -> do tell $ P.errorMessage $ P.UnnecessaryFFIModule mn path
-                                        return Nothing
-        | otherwise -> return $ Just $ J.JSApp (J.JSVar "require") [J.JSStringLiteral "./foreign"]
-      Nothing | requiresForeign m -> throwError . P.errorMessage $ P.MissingFFIModule mn
-              | otherwise -> return Nothing
-    pjs <- P.evalSupplyT nextVar $ P.prettyPrintJS <$> J.moduleToJs m foreignInclude
-    let filePath = P.runModuleName $ CF.moduleName m
-        jsFile = modulesDir </> filePath </> "index.js"
-        externsFile = modulesDir </> filePath </> "externs.purs"
-        foreignFile = modulesDir </> filePath </> "foreign.js"
-    writeTextFile jsFile pjs
-    maybe (return ()) (writeTextFile foreignFile) $ CF.moduleName m `M.lookup` foreigns
-    writeTextFile externsFile exts
-
-  requiresForeign :: CF.Module a -> Bool
-  requiresForeign = not . null . CF.moduleForeign
-
-  getTimestamp :: FilePath -> Make (Maybe UTCTime)
-  getTimestamp path = makeIO (const (P.SimpleErrorWrapper $ P.CannotGetFileInfo path)) $ do
-    exists <- doesFileExist path
-    traverse (const $ getModificationTime path) $ guard exists
-
-  readTextFile :: FilePath -> Make String
-  readTextFile path = makeIO (const (P.SimpleErrorWrapper $ P.CannotReadFile path)) $ readFile path
-
-  writeTextFile :: FilePath -> String -> Make ()
-  writeTextFile path text = makeIO (const (P.SimpleErrorWrapper $ P.CannotWriteFile path)) $ do
-    mkdirp path
-    writeFile path text
-
-  progress :: String -> Make ()
-  progress s = unless ("Compiling $PSCI" `isPrefixOf` s) $ liftIO . putStrLn $ s
diff --git a/psci/PSCi.hs b/psci/PSCi.hs
--- a/psci/PSCi.hs
+++ b/psci/PSCi.hs
@@ -21,7 +21,7 @@
 module PSCi where
 
 import Data.Foldable (traverse_)
-import Data.List (intercalate, nub, sort)
+import Data.List (intercalate, nub, sort, isPrefixOf)
 import Data.Traversable (traverse)
 import Data.Tuple (swap)
 import Data.Version (showVersion)
@@ -30,10 +30,12 @@
 import Control.Applicative
 import Control.Arrow (first)
 import Control.Monad
+import Control.Monad.Error.Class (MonadError(..))
 import Control.Monad.Trans.Class
 import Control.Monad.Trans.Except (runExceptT)
 import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
 import Control.Monad.Trans.State.Strict
+import Control.Monad.IO.Class (liftIO)
 import Control.Monad.Writer (runWriter)
 import qualified Control.Monad.Trans.State.Lazy as L
 
@@ -45,6 +47,7 @@
 import System.FilePath (pathSeparator, (</>), isPathSeparator)
 import System.FilePath.Glob (glob)
 import System.Process (readProcessWithExitCode)
+import System.IO.Error (tryIOError)
 
 import qualified Language.PureScript as P
 import qualified Language.PureScript.Names as N
@@ -53,7 +56,6 @@
 import qualified Directive as D
 import Completion (completion)
 import IO (mkdirp)
-import Make
 import Parser (parseCommand)
 import Types
 
@@ -240,10 +242,25 @@
 indexFile :: FilePath
 indexFile = ".psci_modules" ++ pathSeparator : "index.js"
 
-make :: PSCiState -> [(Either P.RebuildPolicy FilePath, P.Module)] -> Make P.Environment
-make PSCiState{..} ms =
-  let filePathMap = M.fromList $ (first P.getModuleName . swap) `map` (psciLoadedModules ++ ms)
-  in P.make (buildMakeActions filePathMap (M.map snd psciForeignFiles)) (psciLoadedModules ++ ms)
+modulesDir :: FilePath
+modulesDir = ".psci_modules" ++ pathSeparator : "node_modules"
+
+-- | This is different than the runMake in 'Language.PureScript.Make' in that it specifies the
+-- options and ignores the warning messages.
+runMake :: P.Make a -> IO (Either P.MultipleErrors a)
+runMake mk = fmap (fmap fst) $ P.runMake (P.Options False False Nothing False False False Nothing) mk
+
+makeIO :: (IOError -> P.ErrorMessage) -> IO a -> P.Make a
+makeIO f io = do
+  e <- liftIO $ tryIOError io
+  either (throwError . P.singleError . f) return e
+
+make :: PSCiState -> [(Either P.RebuildPolicy FilePath, P.Module)] -> P.Make P.Environment
+make PSCiState{..} ms = P.make actions' (psciLoadedModules ++ ms)
+    where
+    filePathMap = M.fromList $ (first P.getModuleName . swap) `map` (psciLoadedModules ++ ms)
+    actions = P.buildMakeActions modulesDir filePathMap psciForeignFiles False
+    actions' = actions { P.progress = \s -> unless ("Compiling $PSCI" `isPrefixOf` s) $ liftIO . putStrLn $ s }
 
 -- |
 -- Takes a value declaration and evaluates it with the current state.
diff --git a/purescript.cabal b/purescript.cabal
--- a/purescript.cabal
+++ b/purescript.cabal
@@ -1,5 +1,5 @@
 name: purescript
-version: 0.7.0.0
+version: 0.7.1.0
 cabal-version: >=1.8
 build-type: Simple
 license: MIT
@@ -46,7 +46,9 @@
                    aeson-better-errors >= 0.8,
                    bytestring -any,
                    text -any,
-                   split -any
+                   split -any,
+                   language-javascript == 0.5.*,
+                   syb -any
 
     exposed-modules: Language.PureScript
                      Language.PureScript.AST
@@ -56,6 +58,7 @@
                      Language.PureScript.AST.SourcePos
                      Language.PureScript.AST.Traversals
                      Language.PureScript.AST.Exported
+                     Language.PureScript.Bundle
                      Language.PureScript.CodeGen
                      Language.PureScript.CodeGen.Externs
                      Language.PureScript.CodeGen.JS
@@ -83,6 +86,8 @@
                      Language.PureScript.Errors
                      Language.PureScript.Kinds
                      Language.PureScript.Linter
+                     Language.PureScript.Linter.Exhaustive
+                     Language.PureScript.Make
                      Language.PureScript.ModuleDependencies
                      Language.PureScript.Names
                      Language.PureScript.Options
@@ -152,7 +157,6 @@
     main-is: Main.hs
     buildable: True
     hs-source-dirs: psc
-    other-modules: Make
     ghc-options: -Wall -O2 -fno-warn-unused-do-bind
 
 executable psci
@@ -169,7 +173,6 @@
                    Directive
                    Completion
                    PSCi
-                   Make
                    IO
     ghc-options: -Wall -O2
 
@@ -217,9 +220,7 @@
   other-modules:
   other-extensions:
   build-depends:       base >=4 && <5,
-                       language-javascript == 0.5.*,
-                       syb -any,
-                       containers -any,
+                       purescript -any,
                        filepath -any,
                        directory -any,
                        mtl -any,
diff --git a/src/Language/PureScript.hs b/src/Language/PureScript.hs
--- a/src/Language/PureScript.hs
+++ b/src/Language/PureScript.hs
@@ -20,37 +20,18 @@
 
 module Language.PureScript
   ( module P
-  , RebuildPolicy(..)
-  , MakeActions(..)
-  , SupplyVar()
-  , Externs()
-  , make
   , version
   ) where
 
-import Data.Function (on)
-import Data.List (sortBy, groupBy)
-import Data.Maybe (fromMaybe)
-import Data.Time.Clock
 import Data.Version (Version)
-import qualified Data.Map as M
-import qualified Data.Set as S
 
-import Control.Applicative
-import Control.Arrow ((&&&))
-import Control.Monad
-import Control.Monad.Error.Class (MonadError(..))
-import Control.Monad.Reader
-import Control.Monad.Writer
-import Control.Monad.Supply.Class (fresh)
-
 import Language.PureScript.AST as P
 import Language.PureScript.Comments as P
-import Language.PureScript.CodeGen.Externs (moduleToPs)
 import Language.PureScript.Environment as P
 import Language.PureScript.Errors as P
 import Language.PureScript.Kinds as P
 import Language.PureScript.Linter as P
+import Language.PureScript.Make as P
 import Language.PureScript.ModuleDependencies as P
 import Language.PureScript.Names as P
 import Language.PureScript.Options as P
@@ -61,141 +42,8 @@
 import Control.Monad.Supply as P
 import Language.PureScript.TypeChecker as P
 import Language.PureScript.Types as P
-import qualified Language.PureScript.CoreFn as CoreFn
-import qualified Language.PureScript.Constants as C
 
 import qualified Paths_purescript as Paths
-
--- |
--- Actions that require implementations when running in "make" mode.
---
-data MakeActions m = MakeActions {
-  -- |
-  -- Get the timestamp for the input file(s) for a module. If there are multiple
-  -- files (.purs and foreign files, for example) the timestamp should be for
-  -- the most recently modified file.
-  --
-    getInputTimestamp :: ModuleName -> m (Either RebuildPolicy (Maybe UTCTime))
-  -- |
-  -- Get the timestamp for the output files for a module. This should be the
-  -- timestamp for the oldest modified file, or Nothing if any of the required
-  -- output files are missing.
-  --
-  , getOutputTimestamp :: ModuleName -> m (Maybe UTCTime)
-  -- |
-  -- Read the externs file for a module as a string and also return the actual
-  -- path for the file.
-  , readExterns :: ModuleName -> m (FilePath, String)
-  -- |
-  -- Run the code generator for the module and write any required output files.
-  --
-  , codegen :: CoreFn.Module CoreFn.Ann -> Environment -> SupplyVar -> Externs -> m ()
-  -- |
-  -- Respond to a progress update.
-  --
-  , progress :: String -> m ()
-  }
-
--- |
--- Generated code for an externs file.
---
-type Externs = String
-
--- |
--- A value to be used in the Supply monad.
---
-type SupplyVar = Integer
-
--- |
--- Determines when to rebuild a module
---
-data RebuildPolicy
-  -- | Never rebuild this module
-  = RebuildNever
-  -- | Always rebuild this module
-  | RebuildAlways deriving (Show, Eq, Ord)
-
--- |
--- Compiles in "make" mode, compiling each module separately to a js files and an externs file
---
--- If timestamps have not changed, the externs file can be used to provide the module's types without
--- having to typecheck the module again.
---
-make :: forall m. (Functor m, Applicative m, Monad m, MonadReader P.Options m, MonadError MultipleErrors m, MonadWriter MultipleErrors m)
-     => MakeActions m
-     -> [(Either RebuildPolicy FilePath, Module)]
-     -> m Environment
-make MakeActions{..} ms = do
-  (sorted, graph) <- sortModules $ map (importPrim . snd) ms
-  mapM_ lint sorted
-  toRebuild <- foldM (\s (Module _ moduleName' _ _) -> do
-    inputTimestamp <- getInputTimestamp moduleName'
-    outputTimestamp <- getOutputTimestamp moduleName'
-    return $ case (inputTimestamp, outputTimestamp) of
-      (Right (Just t1), Just t2) | t1 < t2 -> s
-      (Left RebuildNever, Just _) -> s
-      _ -> S.insert moduleName' s) S.empty sorted
-
-  marked <- rebuildIfNecessary (reverseDependencies graph) toRebuild sorted
-  (desugared, nextVar) <- runSupplyT 0 $ zip (map fst marked) <$> desugar (map snd marked)
-  evalSupplyT nextVar $ go initEnvironment desugared
-  where
-
-  go :: Environment -> [(Bool, Module)] -> SupplyT m Environment
-  go env [] = return env
-  go env ((False, m) : ms') = do
-    (_, env') <- lift . runCheck' env $ typeCheckModule Nothing m
-    go env' ms'
-  go env ((True, m@(Module coms moduleName' _ exps)) : ms') = do
-    lift $ progress $ "Compiling " ++ runModuleName moduleName'
-    (Module _ _ elaborated _, env') <- lift . runCheck' env $ typeCheckModule Nothing m
-    regrouped <- createBindingGroups moduleName' . collapseBindingGroups $ elaborated
-    let mod' = Module coms moduleName' regrouped exps
-        corefn = CoreFn.moduleToCoreFn env' mod'
-        [renamed] = renameInModules [corefn]
-        exts = moduleToPs mod' env'
-    nextVar <- fresh
-    lift $ codegen renamed env' nextVar exts
-    go env' ms'
-
-  rebuildIfNecessary :: M.Map ModuleName [ModuleName] -> S.Set ModuleName -> [Module] -> m [(Bool, Module)]
-  rebuildIfNecessary _ _ [] = return []
-  rebuildIfNecessary graph toRebuild (m@(Module _ moduleName' _ _) : ms') | moduleName' `S.member` toRebuild = do
-    let deps = fromMaybe [] $ moduleName' `M.lookup` graph
-        toRebuild' = toRebuild `S.union` S.fromList deps
-    (:) (True, m) <$> rebuildIfNecessary graph toRebuild' ms'
-  rebuildIfNecessary graph toRebuild (Module _ moduleName' _ _ : ms') = do
-    (path, externs) <- readExterns moduleName'
-    externsModules <- fmap (map snd) . alterErrors $ P.parseModulesFromFiles id [(path, externs)]
-    case externsModules of
-      [m'@(Module _ moduleName'' _ _)] | moduleName'' == moduleName' -> (:) (False, m') <$> rebuildIfNecessary graph toRebuild ms'
-      _ -> throwError . errorMessage . InvalidExternsFile $ path
-    where
-    alterErrors = flip catchError $ \(MultipleErrors errs) ->
-      throwError . MultipleErrors $ flip map errs $ \e -> case e of
-        SimpleErrorWrapper (ErrorParsingModule err) -> SimpleErrorWrapper (ErrorParsingExterns err)
-        _ -> e
-
-reverseDependencies :: ModuleGraph -> M.Map ModuleName [ModuleName]
-reverseDependencies g = combine [ (dep, mn) | (mn, deps) <- g, dep <- deps ]
-  where
-  combine :: (Ord a) => [(a, b)] -> M.Map a [b]
-  combine = M.fromList . map ((fst . head) &&& map snd) . groupBy ((==) `on` fst) . sortBy (compare `on` fst)
-
--- |
--- Add an import declaration for a module if it does not already explicitly import it.
---
-addDefaultImport :: ModuleName -> Module -> Module
-addDefaultImport toImport m@(Module coms mn decls exps)  =
-  if isExistingImport `any` decls || mn == toImport then m
-  else Module coms mn (ImportDeclaration toImport Implicit Nothing : decls) exps
-  where
-  isExistingImport (ImportDeclaration mn' _ _) | mn' == toImport = True
-  isExistingImport (PositionedDeclaration _ _ d) = isExistingImport d
-  isExistingImport _ = False
-
-importPrim :: Module -> Module
-importPrim = addDefaultImport (ModuleName [ProperName C.prim])
 
 version :: Version
 version = Paths.version
diff --git a/src/Language/PureScript/AST/Binders.hs b/src/Language/PureScript/AST/Binders.hs
--- a/src/Language/PureScript/AST/Binders.hs
+++ b/src/Language/PureScript/AST/Binders.hs
@@ -69,7 +69,7 @@
   -- |
   -- A binder with source position information
   --
-  | PositionedBinder SourceSpan [Comment] Binder deriving (Show, D.Data, D.Typeable)
+  | PositionedBinder SourceSpan [Comment] Binder deriving (Show, Eq, D.Data, D.Typeable)
 
 -- |
 -- Collect all names introduced in binders in an expression
diff --git a/src/Language/PureScript/Bundle.hs b/src/Language/PureScript/Bundle.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/PureScript/Bundle.hs
@@ -0,0 +1,540 @@
+-----------------------------------------------------------------------------
+--
+-- Module      :  psc-bundle
+-- Copyright   :  (c) Phil Freeman 2015
+-- License     :  MIT
+--
+-- Maintainer  :  Phil Freeman <paf31@cantab.net>
+-- Stability   :  experimental
+-- Portability :
+--
+-- | Bundles compiled PureScript modules for the browser.
+--
+-----------------------------------------------------------------------------
+
+{-# LANGUAGE PatternGuards #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | Bundle javascript for use in the browser.
+--
+-- This module takes as input the individual generated modules from 'Language.PureScript.Make' and
+-- performs dead code elimination, filters empty modules,
+-- and generates the final Javascript bundle.
+module Language.PureScript.Bundle (
+     bundle
+   , ModuleIdentifier(..)
+   , moduleName
+   , ModuleType(..)
+   , ErrorMessage(..)
+   , printErrorMessage
+) where
+
+import Data.List (nub)
+import Data.Maybe (mapMaybe, catMaybes)
+import Data.Generics (everything, everywhere, mkQ, mkT)
+import Data.Graph
+import Data.Version (showVersion)
+
+import qualified Data.Set as S
+
+import Control.Applicative
+import Control.Monad
+import Control.Monad.Error.Class
+import Language.JavaScript.Parser
+
+import qualified Paths_purescript as Paths
+
+-- | The type of error messages. We separate generation and rendering of errors using a data
+-- type, in case we need to match on error types later.
+data ErrorMessage
+  = UnsupportedModulePath String
+  | InvalidTopLevel
+  | UnableToParseModule
+  | UnsupportedExport
+  | ErrorInFile FilePath ErrorMessage
+  deriving Show
+
+-- | Modules are either "regular modules" (i.e. those generated by psc-make) or foreign modules.
+data ModuleType 
+  = Regular 
+  | Foreign 
+  deriving (Show, Eq, Ord)
+  
+-- | A module is identified by its module name and its type.
+data ModuleIdentifier = ModuleIdentifier String ModuleType deriving (Show, Eq, Ord)
+
+moduleName :: ModuleIdentifier -> String
+moduleName (ModuleIdentifier name _) = name
+
+-- | A piece of code is identified by its module and its name. These keys are used to label vertices
+-- in the dependency graph.
+type Key = (ModuleIdentifier, String)
+
+-- | An export is either a "regular export", which exports a name from the regular module we are in,
+-- or a reexport of a declaration in the corresponding foreign module.
+-- 
+-- Regular exports are labelled, since they might re-export an operator with another name. 
+data ExportType 
+  = RegularExport String 
+  | ForeignReexport 
+  deriving (Show, Eq, Ord)
+
+-- | There are four types of module element we are interested in:
+--
+-- 1) Require statements
+-- 2) Member declarations
+-- 3) Export lists
+-- 4) Everything else
+--
+-- Each is labelled with the original AST node which generated it, so that we can dump it back
+-- into the output during codegen.
+data ModuleElement 
+  = Require JSNode String ModuleIdentifier
+  | Member JSNode Bool String [JSNode] [Key]
+  | ExportsList [(ExportType, String, JSNode, [Key])]
+  | Other JSNode
+  deriving Show
+
+-- | A module is just a list of elements of the types listed above. 
+data Module = Module ModuleIdentifier [ModuleElement] deriving Show
+  
+-- | Prepare an error message for consumption by humans.
+printErrorMessage :: ErrorMessage -> [String]
+printErrorMessage (UnsupportedModulePath s) =
+  [ "A CommonJS module has an unsupported name (" ++ show s ++ ")."
+  , "The following file names are supported:"
+  , "  1) index.js (psc-make native modules)"
+  , "  2) foreign.js (psc-make foreign modules)"
+  ]
+printErrorMessage InvalidTopLevel =
+  [ "Expected a list of source elements at the top level." ] 
+printErrorMessage UnableToParseModule =
+  [ "The module could not be parsed." ]
+printErrorMessage UnsupportedExport =
+  [ "An export was unsupported. Exports can be defined in one of two ways: "
+  , "  1) exports.name = ..."
+  , "  2) exports = { ... }"
+  ]
+printErrorMessage (ErrorInFile filename e) =
+  ("Error in file " ++ show filename ++ ":")
+  : ""
+  : map ("  " ++) (printErrorMessage e)
+
+-- | Unpack the node inside a JSNode. This is useful when pattern matching.
+node :: JSNode -> Node
+node (NN n) = n
+node (NT n _ _) = n
+
+-- | Calculate the ModuleIdentifier which a require(...) statement imports.
+checkImportPath :: String -> ModuleIdentifier -> S.Set String -> Maybe ModuleIdentifier
+checkImportPath "./foreign" m _     = 
+  Just (ModuleIdentifier (moduleName m) Foreign)
+checkImportPath name        _ names 
+  | name `S.member` names = Just (ModuleIdentifier name Regular)
+checkImportPath _           _ _     = Nothing
+
+-- | Compute the dependencies of all elements in a module, and add them to the tree.
+-- 
+-- Members and exports can have dependencies. A dependency is of one of the following forms:
+--
+-- 1) module.name or member["name"]
+--    
+--    where module was imported using
+--
+--    var module = require("Module.Name");
+--
+-- 2) name
+--
+--    where name is the name of a member defined in the current module.
+withDeps :: Module -> Module
+withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
+  where    
+  -- | Collects all modules which are imported, so that we can identify dependencies of the first type.
+  imports :: [(String, ModuleIdentifier)]
+  imports = mapMaybe toImport es
+    where
+    toImport :: ModuleElement -> Maybe (String, ModuleIdentifier)
+    toImport (Require _ nm mid) = Just (nm, mid)
+    toImport _ = Nothing
+    
+  -- | Collects all member names in scope, so that we can identify dependencies of the second type.
+  boundNames :: [String]
+  boundNames = mapMaybe toBoundName es
+    where
+    toBoundName :: ModuleElement -> Maybe String
+    toBoundName (Member _ _ nm _ _) = Just nm
+    toBoundName _ = Nothing
+
+  -- | Calculate dependencies and add them to the current element.
+  expandDeps :: ModuleElement -> ModuleElement
+  expandDeps (Member n f nm decl _) = Member n f nm decl (nub (concatMap (dependencies modulePath) decl))
+  expandDeps (ExportsList exps) = ExportsList (map expand exps)
+      where
+      expand (ty, nm, n1, _) = (ty, nm, n1, nub (dependencies modulePath n1))
+  expandDeps other = other
+  
+  dependencies :: ModuleIdentifier -> JSNode -> [(ModuleIdentifier, String)]
+  dependencies m = everything (++) (mkQ [] toReference)
+    where
+    toReference :: Node -> [(ModuleIdentifier, String)]
+    toReference (JSMemberDot [ mn ] _ nm)
+      | JSIdentifier mn' <- node mn
+      , JSIdentifier nm' <- node nm
+      , Just mid <- lookup mn' imports
+      = [(mid, nm')]
+    toReference (JSMemberSquare [ mn ] _ nm _)
+      | JSIdentifier mn' <- node mn
+      , JSExpression [ s ] <- node nm
+      , JSStringLiteral _ nm' <- node s
+      , Just mid <- lookup mn' imports
+      = [(mid, nm')]
+    toReference (JSIdentifier nm)
+      | nm `elem` boundNames
+      = [(m, nm)]
+    toReference _ = []
+
+-- | Attempt to create a Module from a Javascript AST.
+-- 
+-- Each type of module element is matched using pattern guards, and everything else is bundled into the 
+-- Other constructor.
+toModule :: forall m. (Applicative m, MonadError ErrorMessage m) => S.Set String -> ModuleIdentifier -> JSNode -> m Module
+toModule mids mid top 
+  | JSSourceElementsTop ns <- node top = Module mid <$> mapM toModuleElement ns
+  | otherwise = throwError InvalidTopLevel
+  where      
+  toModuleElement :: JSNode -> m ModuleElement
+  toModuleElement n
+    | JSVariables var [ varIntro ] _ <- node n
+    , JSLiteral "var" <- node var 
+    , JSVarDecl impN [ eq, req, impP ] <- node varIntro
+    , JSIdentifier importName <- node impN
+    , JSLiteral "=" <- node eq
+    , JSIdentifier "require" <- node req
+    , JSArguments _ [ impS ] _ <- node impP 
+    , JSStringLiteral _ importPath <- node impS
+    , Just importPath' <- checkImportPath importPath mid mids
+    = pure (Require n importName importPath')
+  toModuleElement n
+    | JSVariables var [ varIntro ] _ <- node n
+    , JSLiteral "var" <- node var 
+    , JSVarDecl declN (eq : decl) <- node varIntro
+    , JSIdentifier name <- node declN
+    , JSLiteral "=" <- node eq
+    = pure (Member n False name decl [])
+  toModuleElement n
+    | JSExpression (e : op : decl) <- node n
+    , Just name <- accessor (node e)
+    , JSOperator eq <- node op
+    , JSLiteral "=" <- node eq
+    = pure (Member n True name decl [])
+    where
+    accessor :: Node -> Maybe String
+    accessor (JSMemberDot [ exports ] _ nm)
+      | JSIdentifier "exports" <- node exports
+      , JSIdentifier name <- node nm
+      = Just name
+    accessor (JSMemberSquare [ exports ] _ nm _)
+      | JSIdentifier "exports" <- node exports
+      , JSExpression [e] <- node nm
+      , JSStringLiteral _ name <- node e
+      = Just name
+    accessor _ = Nothing
+  toModuleElement n
+    | JSExpression (mnExp : op : obj: _) <- node n
+    , JSMemberDot [ mn ] _ e <- node mnExp
+    , JSIdentifier "module" <- node mn
+    , JSIdentifier "exports" <- node e
+    , JSOperator eq <- node op
+    , JSLiteral "=" <- node eq
+    , JSObjectLiteral _ props _ <- node obj
+    = ExportsList <$> mapM toExport (filter (not . isSeparator) (map node props))
+    where
+    toExport :: Node -> m (ExportType, String, JSNode, [Key])
+    toExport (JSPropertyNameandValue name _ [val] ) = 
+      (,,val,[]) <$> exportType (node val) 
+                 <*> extractLabel (node name)
+    toExport _ = throwError UnsupportedExport
+  
+    exportType :: Node -> m ExportType
+    exportType (JSMemberDot [f] _ _)
+      | JSIdentifier "$foreign" <- node f
+      = pure ForeignReexport
+    exportType (JSMemberSquare [f] _ _ _)
+      | JSIdentifier "$foreign" <- node f
+      = pure ForeignReexport
+    exportType (JSIdentifier s) = pure (RegularExport s)
+    exportType _ = throwError UnsupportedExport
+  
+    extractLabel :: Node -> m String
+    extractLabel (JSStringLiteral _ nm) = pure nm
+    extractLabel (JSIdentifier nm) = pure nm
+    extractLabel _ = throwError UnsupportedExport
+    
+    isSeparator :: Node -> Bool
+    isSeparator (JSLiteral ",") = True
+    isSeparator _ = False
+  toModuleElement other = pure (Other other)
+
+-- | Eliminate unused code based on the specified entry point set.
+compile :: [Module] -> [ModuleIdentifier] -> [Module]
+compile modules [] = modules
+compile modules entryPoints = filteredModules
+  where    
+  (graph, _, vertexFor) = graphFromEdges verts
+  
+  -- | The vertex set
+  verts :: [(ModuleElement, Key, [Key])]
+  verts = do
+    Module mid els <- modules
+    concatMap (toVertices mid) els
+    where
+    -- | Create a set of vertices for a module element.
+    -- 
+    -- Some special cases worth commenting on:
+    --
+    -- 1) Regular exports which simply export their own name do not count as dependencies.
+    --    Regular exports which rename and reexport an operator do count, however.
+    -- 
+    -- 2) Require statements don't contribute towards dependencies, since they effectively get
+    --    inlined wherever they are used inside other module elements.
+    toVertices :: ModuleIdentifier -> ModuleElement -> [(ModuleElement, Key, [Key])]
+    toVertices p m@(Member _ _ nm _ deps) = [(m, (p, nm), deps)]
+    toVertices p m@(ExportsList exps) = mapMaybe toVertex exps
+      where
+      toVertex (ForeignReexport, nm, _, ks) = Just (m, (p, nm), ks)
+      toVertex (RegularExport nm, nm1, _, ks) | nm /= nm1 = Just (m, (p, nm1), ks)
+      toVertex _ = Nothing
+    toVertices _ _ = []
+  
+  -- | The set of vertices whose connected components we are interested in keeping.
+  entryPointVertices :: [Vertex]
+  entryPointVertices = catMaybes $ do
+    (_, k@(mid, _), _) <- verts
+    guard $ mid `elem` entryPoints
+    return (vertexFor k)
+  
+  -- | The set of vertices reachable from an entry point
+  reachableSet :: S.Set Vertex
+  reachableSet = S.fromList (concatMap (reachable graph) entryPointVertices)
+  
+  filteredModules :: [Module]
+  filteredModules = map filterUsed modules
+    where
+    filterUsed :: Module -> Module
+    filterUsed (Module mid ds) = Module mid (map filterExports (go ds))
+      where
+      go :: [ModuleElement] -> [ModuleElement]
+      go [] = []
+      go (d : Other semi : rest) 
+        | JSLiteral ";" <- node semi
+        , not (isDeclUsed d)
+        = go rest
+      go (d : rest) 
+        | not (isDeclUsed d) = go rest
+        | otherwise = d : go rest
+        
+      -- | Filter out the exports for members which aren't used.
+      filterExports :: ModuleElement -> ModuleElement
+      filterExports (ExportsList exps) = ExportsList (filter (\(_, nm, _, _) -> isKeyUsed (mid, nm)) exps)
+      filterExports me = me
+    
+      isDeclUsed :: ModuleElement -> Bool
+      isDeclUsed (Member _ _ nm _ _) = isKeyUsed (mid, nm)
+      isDeclUsed _ = True
+      
+      isKeyUsed :: Key -> Bool
+      isKeyUsed k 
+        | Just me <- vertexFor k = me `S.member` reachableSet
+        | otherwise = False
+  
+-- | Topologically sort the module dependency graph, so that when we generate code, modules can be 
+-- defined in the right order.
+sortModules :: [Module] -> [Module]
+sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (topSort graph))
+  where
+  (graph, nodeFor, _) = graphFromEdges $ do
+    m@(Module mid els) <- modules
+    return (m, mid, mapMaybe getKey els)
+    
+  getKey :: ModuleElement -> Maybe ModuleIdentifier
+  getKey (Require _ _ mi) = Just mi 
+  getKey _ = Nothing
+  
+-- | A module is empty if it contains no exported members (in other words,
+-- if the only things left after dead code elimination are module imports and
+-- "other" foreign code).
+--
+-- If a module is empty, we don't want to generate code for it.
+isModuleEmpty :: Module -> Bool
+isModuleEmpty (Module _ els) = all isElementEmpty els
+  where
+  isElementEmpty :: ModuleElement -> Bool
+  isElementEmpty (ExportsList exps) = null exps
+  isElementEmpty (Require _ _ _) = True
+  isElementEmpty (Other _) = True
+  isElementEmpty _ = False
+  
+-- | Generate code for a set of modules, including a call to main().
+--
+-- Modules get defined on the global PS object, as follows:
+--
+--     var PS = { };
+--     (function(exports) {
+--       ...
+--     })(PS["Module.Name"] = PS["Module.Name"] || {});
+--
+-- In particular, a module and its foreign imports share the same namespace inside PS. 
+-- This saves us from having to generate unique names for a module and its foreign imports,
+-- and is safe since a module shares a namespace with its foreign imports in PureScript as well
+-- (so there is no way to have overlaps in code generated by psc-make).
+codeGen :: Maybe String -- ^ main module
+        -> String -- ^ namespace
+        -> [Module] -- ^ input modules
+        -> String
+codeGen optionsMainModule optionsNamespace ms = renderToString (NN (JSSourceElementsTop (prelude ++ concatMap moduleToJS ms ++ maybe [] runMain optionsMainModule)))
+  where
+  moduleToJS :: Module -> [JSNode]
+  moduleToJS (Module mn ds) = wrap (moduleName mn) (indent (concatMap declToJS ds))
+    where
+    declToJS :: ModuleElement -> [JSNode]
+    declToJS (Member n _ _ _ _) = [n]
+    declToJS (Other n) = [n]       
+    declToJS (Require _ nm req) = 
+      [ NN (JSVariables (NT (JSLiteral "var") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n  " ]) 
+                        [ NN (JSVarDecl (sp (JSIdentifier nm)) 
+                                        [ sp (JSLiteral "=")
+                                        , moduleReference sp (moduleName req)
+                                        ])
+                        ]
+                        (nt (JSLiteral ";"))) ]
+    declToJS (ExportsList exps) = map toExport exps
+      
+      where 
+      toExport :: (ExportType, String, JSNode, [Key]) -> JSNode
+      toExport (_, nm, val, _) =
+        NN (JSExpression [ NN (JSMemberSquare [ NT (JSIdentifier "exports") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n  " ] ] 
+                                              (nt (JSLiteral "[")) 
+                                              (NN (JSExpression [ nt (JSStringLiteral '"' nm) ]))
+                                              (nt (JSLiteral "]")))
+                         , NN (JSOperator (sp (JSLiteral "=")))
+                         , reindent val
+                         , nt (JSLiteral ";")
+                         ])
+      
+      reindent :: JSNode -> JSNode
+      reindent (NT n _ _) = sp n
+      reindent nn = nn
+  
+  indent :: [JSNode] -> [JSNode]
+  indent = everywhere (mkT squash)
+    where
+    squash (NT n pos ann) = NT n (keepCol pos) (map splat ann)
+    squash nn = nn
+    
+    splat (CommentA pos s) = CommentA (keepCol pos) s
+    splat (WhiteSpace pos w) = WhiteSpace (keepCol pos) w
+    splat ann = ann
+    
+    keepCol (TokenPn _ _ c) = TokenPn 0 0 (c + 2)
+    
+  prelude :: [JSNode]
+  prelude = 
+    [ NN (JSVariables (NT (JSLiteral "var") tokenPosnEmpty [ CommentA tokenPosnEmpty ("// Generated by psc-bundle " ++ showVersion Paths.version)
+                                                           , WhiteSpace tokenPosnEmpty "\n"
+                                                           ]) 
+                                                           [ NN (JSVarDecl (sp (JSIdentifier optionsNamespace)) 
+                                                                           [ sp (JSLiteral "=")
+                                                                           , NN (JSObjectLiteral (sp (JSLiteral "{")) 
+                                                                                                 []
+                                                                                                 (sp (JSLiteral "}")))
+                                                                           ])
+                                                           ] 
+                                                           (nt (JSLiteral ";")))
+    , lf
+    ]
+    
+  moduleReference :: (Node -> JSNode) -> String -> JSNode
+  moduleReference f mn = 
+    NN (JSMemberSquare [ f (JSIdentifier optionsNamespace) ] 
+                       (nt (JSLiteral "[")) 
+                       (NN (JSExpression [ nt (JSStringLiteral '"' mn) ]))
+                       (nt (JSLiteral "]")))
+    
+  wrap :: String -> [JSNode] -> [JSNode]
+  wrap mn ds = 
+    [ NN (JSExpression [ NN (JSExpressionParen (nt (JSLiteral "(")) 
+                                               (NN (JSExpression [ NN (JSFunctionExpression (nt (JSLiteral "function")) 
+                                                                                            [] 
+                                                                                            (nt (JSLiteral "(") ) [nt (JSIdentifier "exports")] (nt (JSLiteral ")")) 
+                                                                                            (NN (JSBlock [sp (JSLiteral "{")] 
+                                                                                                         (lf : ds)
+                                                                                                         [nl (JSLiteral "}")])))]))
+                                               (nt (JSLiteral ")")))
+                       , NN (JSArguments (nt (JSLiteral "(")) 
+                                         [ NN (JSExpression [ moduleReference nt mn
+                                                            , NN (JSOperator (sp (JSLiteral "=")))
+                                                            , NN (JSExpressionBinary "||"
+                                                                                     [ moduleReference sp mn ] 
+                                                                                     (sp (JSLiteral "||")) 
+                                                                                     [ emptyObj ]) 
+                                                            ])
+                                         ]
+                                         (nt (JSLiteral ")")))
+                       ])
+    , nt (JSLiteral ";")
+    , lf
+    ]
+    where
+    emptyObj = NN (JSObjectLiteral (sp (JSLiteral "{")) [] (nt (JSLiteral "}")))
+    
+  runMain :: String -> [JSNode]
+  runMain mn = 
+    [ NN (JSExpression [ NN (JSMemberDot [ NN (JSMemberSquare [ nl (JSIdentifier optionsNamespace) ] 
+                                                              (nt (JSLiteral "[")) 
+                                                              (NN (JSExpression [ nt (JSStringLiteral '"' mn) ])) 
+                                                              (nt (JSLiteral "]")))
+                                         ] 
+                                         (nt (JSLiteral ".")) 
+                                         (nt (JSIdentifier "main")))
+                       , NN (JSArguments (nt (JSLiteral "(")) [] (nt (JSLiteral ")")))
+                       ])
+    , nt (JSLiteral ";")
+    ]
+  
+  nt :: Node -> JSNode
+  nt n = NT n tokenPosnEmpty []
+  
+  lf :: JSNode
+  lf = NT (JSLiteral "") tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n" ]
+  
+  sp :: Node -> JSNode
+  sp n = NT n tokenPosnEmpty [ WhiteSpace tokenPosnEmpty " " ]
+  
+  nl :: Node -> JSNode
+  nl n = NT n tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n" ]
+
+-- | The bundling function.
+-- This function performs dead code elimination, filters empty modules
+-- and generates and prints the final Javascript bundle.
+bundle :: forall m. (Applicative m, MonadError ErrorMessage m)
+       => [(ModuleIdentifier, String)] -- ^ The input modules.  Each module should be javascript rendered from 'Language.PureScript.Make' or @psc-make@.
+       -> [ModuleIdentifier] -- ^ Entry points.  These module identifiers are used as the roots for dead-code elimination
+       -> Maybe String -- ^ An optional main module.
+       -> String -- ^ The namespace (e.g. PS).
+       -> m String
+bundle inputStrs entryPoints mainModule namespace = do
+  input <- forM inputStrs $ \(ident, js) -> do
+                ast <- either (const $ throwError UnableToParseModule) pure $ parse js (moduleName ident)
+                return (ident, ast)
+
+  let mids = S.fromList (map (moduleName . fst) input)
+    
+  modules <- mapM (fmap withDeps . uncurry (toModule mids)) input
+  
+  let compiled = compile modules entryPoints
+      sorted   = sortModules (filter (not . isModuleEmpty) compiled)
+  
+  return (codeGen mainModule namespace sorted)
diff --git a/src/Language/PureScript/CodeGen/JS/AST.hs b/src/Language/PureScript/CodeGen/JS/AST.hs
--- a/src/Language/PureScript/CodeGen/JS/AST.hs
+++ b/src/Language/PureScript/CodeGen/JS/AST.hs
@@ -23,6 +23,7 @@
 import Data.Traversable (traverse)
 
 import Language.PureScript.Comments
+import Language.PureScript.Traversals
 
 -- |
 -- Built-in unary operators
@@ -294,7 +295,7 @@
   go (JSBinary op j1 j2) = JSBinary op <$> f' j1 <*> f' j2
   go (JSArrayLiteral js) = JSArrayLiteral <$> traverse f' js
   go (JSIndexer j1 j2) = JSIndexer <$> f' j1 <*> f' j2
-  go (JSObjectLiteral js) = JSObjectLiteral <$> traverse (traverse f') js
+  go (JSObjectLiteral js) = JSObjectLiteral <$> traverse (sndM f') js
   go (JSAccessor prop j) = JSAccessor prop <$> f' j
   go (JSFunction name args j) = JSFunction name args <$> f' j
   go (JSApp j js) = JSApp <$> f' j <*> traverse f' js
diff --git a/src/Language/PureScript/Docs/ParseAndDesugar.hs b/src/Language/PureScript/Docs/ParseAndDesugar.hs
--- a/src/Language/PureScript/Docs/ParseAndDesugar.hs
+++ b/src/Language/PureScript/Docs/ParseAndDesugar.hs
@@ -53,20 +53,41 @@
   depsFiles'  <- mapM (\(pkgName, f) -> parseAs (FromDep pkgName) f) depsFiles
 
   runExceptT $ do
-    let eParsed = P.parseModulesFromFiles fileInfoToString (inputFiles' ++ depsFiles')
-    ms <- throwLeft ParseError eParsed
-
-    let depsModules = getDepsModuleNames (map (\(fp, m) -> (,m) <$> fp) ms)
-    let eSorted = P.sortModules . map (importPrim . snd) $ ms
-    (ms', _) <- throwLeft SortModulesError eSorted
+    ms         <- parseFiles (inputFiles' ++ depsFiles')
+    ms'        <- sortModules (map snd ms)
+    (bs, ms'') <- desugarWithBookmarks ms ms'
+    liftIO $ callback bs ms''
 
-    modules <- throwLeft DesugarError (desugar ms')
-    let modules' = map (addPackage depsModules) modules
-        bookmarks = concatMap collectBookmarks modules'
-    liftIO (callback bookmarks (takeLocals modules'))
+parseFiles ::
+  [(FileInfo, FilePath)]
+  -> ExceptT ParseDesugarError IO [(FileInfo, P.Module)]
+parseFiles =
+  throwLeft ParseError . P.parseModulesFromFiles fileInfoToString
 
+sortModules ::
+  [P.Module]
+  -> ExceptT ParseDesugarError IO [P.Module]
+sortModules =
+  fmap fst . throwLeft SortModulesError . sortModules' . map importPrim
   where
-  throwLeft f = either (throwError . f) return
+  sortModules' :: [P.Module] -> Either P.MultipleErrors ([P.Module], P.ModuleGraph)
+  sortModules' = P.sortModules
+
+desugarWithBookmarks ::
+  [(FileInfo, P.Module)]
+  -> [P.Module]
+  -> ExceptT ParseDesugarError IO ([Bookmark], [P.Module])
+desugarWithBookmarks msInfo msSorted =  do
+  msDesugared <- throwLeft DesugarError (desugar msSorted)
+
+  let msDeps = getDepsModuleNames (map (\(fp, m) -> (,m) <$> fp) msInfo)
+      msPackages = map (addPackage msDeps) msDesugared
+      bookmarks = concatMap collectBookmarks msPackages
+
+  return (bookmarks, takeLocals msPackages)
+
+throwLeft :: (MonadError e m) => (l -> e) -> Either l r -> m r
+throwLeft f = either (throwError . f) return
 
 -- | Specifies whether a PureScript source file is considered as:
 --
diff --git a/src/Language/PureScript/Docs/Types.hs b/src/Language/PureScript/Docs/Types.hs
--- a/src/Language/PureScript/Docs/Types.hs
+++ b/src/Language/PureScript/Docs/Types.hs
@@ -10,7 +10,8 @@
   where
 
 import Control.Arrow (first, (***))
-import Control.Applicative ((<$>), (<*>))
+import Control.Applicative ((<$>), (<*>), pure)
+import Control.Monad (when)
 import Data.Functor ((<$))
 import Data.Maybe (mapMaybe)
 import Data.Version
@@ -22,7 +23,7 @@
 import Data.ByteString.Lazy (ByteString)
 import qualified Data.Text as T
 
-import Web.Bower.PackageMeta hiding (Version)
+import Web.Bower.PackageMeta hiding (Version, displayError)
 
 import qualified Language.PureScript as P
 
@@ -43,6 +44,9 @@
   , pkgResolvedDependencies :: [(PackageName, Version)]
   , pkgGithub               :: (GithubUser, GithubRepo)
   , pkgUploader             :: a
+  , pkgCompilerVersion      :: Version
+    -- ^ The version of the PureScript compiler which was used to generate
+    -- this data. We store this in order to reject packages which are too old.
   }
   deriving (Show, Eq, Ord)
 
@@ -62,6 +66,7 @@
           pkgResolvedDependencies
           pkgGithub
           verifiedUser
+          pkgCompilerVersion
 
 packageName :: Package a -> PackageName
 packageName = bowerName . pkgMeta
@@ -170,7 +175,10 @@
   deriving (Show, Eq, Ord)
 
 data PackageError
-  = ErrorInPackageMeta BowerError
+  = CompilerTooOld Version Version
+      -- ^ Minimum allowable version for generating data with the current
+      -- parser, and actual version used.
+  | ErrorInPackageMeta BowerError
   | InvalidVersion
   | InvalidDeclarationType String
   | InvalidChildDeclarationType String
@@ -204,14 +212,21 @@
 ----------------------
 -- Parsing
 
-parseUploadedPackage :: ByteString -> Either (ParseError PackageError) UploadedPackage
-parseUploadedPackage = parse asUploadedPackage
+parseUploadedPackage :: Version -> ByteString -> Either (ParseError PackageError) UploadedPackage
+parseUploadedPackage minVersion = parse $ asUploadedPackage minVersion
 
-parseVerifiedPackage :: ByteString -> Either (ParseError PackageError) VerifiedPackage
-parseVerifiedPackage = parse asVerifiedPackage
+parseVerifiedPackage :: Version -> ByteString -> Either (ParseError PackageError) VerifiedPackage
+parseVerifiedPackage minVersion = parse $ asVerifiedPackage minVersion
 
-asPackage :: (forall e. Parse e a) -> Parse PackageError (Package a)
-asPackage uploader =
+asPackage :: Version -> (forall e. Parse e a) -> Parse PackageError (Package a)
+asPackage minimumVersion uploader = do
+  -- If the compilerVersion key is missing, we can be sure that it was produced
+  -- with 0.7.0.0, since that is the only released version that included the
+  -- psc-publish tool before this key was added.
+  compilerVersion <- keyOrDefault "compilerVersion" (Version [0,7,0,0] []) asVersion
+  when (compilerVersion < minimumVersion)
+    (throwCustomError $ CompilerTooOld minimumVersion compilerVersion)
+
   Package <$> key "packageMeta" asPackageMeta .! ErrorInPackageMeta
           <*> key "version" asVersion
           <*> key "versionTag" asString
@@ -220,9 +235,10 @@
           <*> key "resolvedDependencies" asResolvedDependencies
           <*> key "github" asGithub
           <*> key "uploader" uploader
+          <*> pure compilerVersion
 
-asUploadedPackage :: Parse PackageError UploadedPackage
-asUploadedPackage = asPackage asNotYetKnown
+asUploadedPackage :: Version -> Parse PackageError UploadedPackage
+asUploadedPackage minVersion = asPackage minVersion asNotYetKnown
 
 asNotYetKnown :: Parse e NotYetKnown
 asNotYetKnown = NotYetKnown <$ asNull
@@ -230,20 +246,42 @@
 instance A.FromJSON NotYetKnown where
   parseJSON = toAesonParser' asNotYetKnown
 
-asVerifiedPackage :: Parse PackageError VerifiedPackage
-asVerifiedPackage = asPackage asGithubUser
+asVerifiedPackage :: Version -> Parse PackageError VerifiedPackage
+asVerifiedPackage minVersion = asPackage minVersion asGithubUser
 
+displayPackageError :: PackageError -> Text
+displayPackageError e = case e of
+  CompilerTooOld minV usedV ->
+    "Expecting data produced by at least version " <> T.pack (showVersion minV)
+    <> " of the compiler, but it appears that " <> T.pack (showVersion usedV)
+    <> " was used."
+  ErrorInPackageMeta err ->
+    "Error in package metadata: " <> showBowerError err
+  InvalidVersion ->
+    "Invalid version"
+  InvalidDeclarationType str ->
+    "Invalid declaration type: \"" <> T.pack str <> "\""
+  InvalidChildDeclarationType str ->
+    "Invalid child declaration type: \"" <> T.pack str <> "\""
+  InvalidFixity ->
+    "Invalid fixity"
+  InvalidKind str ->
+    "Invalid kind: \"" <> T.pack str <> "\""
+  InvalidDataDeclType str ->
+    "Invalid data declaration type: \"" <> T.pack str <> "\""
+  where
+  (<>) = T.append
+
+instance A.FromJSON a => A.FromJSON (Package a) where
+  parseJSON = toAesonParser displayPackageError
+                            (asPackage (Version [0,0,0,0] []) fromAesonParser)
+
 asGithubUser :: Parse e GithubUser
 asGithubUser = GithubUser <$> asString
 
 instance A.FromJSON GithubUser where
   parseJSON = toAesonParser' asGithubUser
 
-instance A.FromJSON a => A.FromJSON (Package a) where
-  -- TODO: actual error display
-  parseJSON = toAesonParser (T.pack . show)
-                            (asPackage fromAesonParser)
-
 asVersion :: Parse PackageError Version
 asVersion = withString (maybe (Left InvalidVersion) Right . parseVersion')
 
@@ -396,6 +434,7 @@
                                                   pkgResolvedDependencies
       , "github"               .= pkgGithub
       , "uploader"             .= pkgUploader
+      , "compilerVersion"      .= showVersion P.version
       ]
 
 instance A.ToJSON NotYetKnown where
diff --git a/src/Language/PureScript/Errors.hs b/src/Language/PureScript/Errors.hs
--- a/src/Language/PureScript/Errors.hs
+++ b/src/Language/PureScript/Errors.hs
@@ -19,7 +19,7 @@
 module Language.PureScript.Errors where
 
 import Data.Either (lefts, rights)
-import Data.List (intercalate)
+import Data.List (intercalate, transpose)
 import Data.Function (on)
 import Data.Monoid
 import Data.Foldable (fold, foldMap)
@@ -120,6 +120,7 @@
   | TransitiveExportError DeclarationRef [DeclarationRef]
   | ShadowedName Ident
   | WildcardInferredType Type
+  | NotExhaustivePattern [[Binder]]
   | ClassOperator ProperName Ident
   deriving (Show)
 
@@ -229,6 +230,7 @@
   (TransitiveExportError _ _)   -> "TransitiveExportError"
   (ShadowedName _)              -> "ShadowedName"
   (WildcardInferredType _)      -> "WildcardInferredType"
+  (NotExhaustivePattern _)      -> "NotExhaustivePattern"
   (ClassOperator _ _)           -> "ClassOperator"
 
 -- |
@@ -272,6 +274,9 @@
 -- | A map from rigid type variable name/unknown variable pairs to new variables.
 type UnknownMap = M.Map (LabelType, Unknown) Unknown
 
+-- | How critical the issue is
+data Level = Error | Warning deriving Show
+
 -- |
 -- Extract nested error messages from wrapper errors
 --
@@ -344,8 +349,8 @@
 -- |
 -- Pretty print a single error, simplifying if necessary
 --
-prettyPrintSingleError :: Bool -> ErrorMessage -> State UnknownMap Box.Box
-prettyPrintSingleError full e = prettyPrintErrorMessage <$> onTypesInErrorMessageM replaceUnknowns (if full then e else simplifyErrorMessage e)
+prettyPrintSingleError :: Bool -> Level -> ErrorMessage -> State UnknownMap Box.Box
+prettyPrintSingleError full level e = prettyPrintErrorMessage <$> onTypesInErrorMessageM replaceUnknowns (if full then e else simplifyErrorMessage e)
  where
   -- |
   -- Pretty print an ErrorMessage
@@ -373,7 +378,7 @@
             , indent . line $ path
             ]
     goSimple (ErrorParsingExterns err) =
-      paras [ line "Error parsing externs files: "
+      paras [ lineWithLevel "parsing externs files: "
             , indent . line . show $ err
             ]
     goSimple (ErrorParsingFFIModule path) =
@@ -556,56 +561,61 @@
             ]
     goSimple (WildcardInferredType ty) =
       line $ "The wildcard type definition has the inferred type " ++ prettyPrintType ty
+    goSimple (NotExhaustivePattern bs) =
+      paras $ [ line "Pattern could not be determined to cover all cases."
+              , line $ "The definition has the following uncovered cases:\n"
+              , indent $ Box.hsep 1 Box.left (map (paras . map (line . prettyPrintBinderAtom)) (transpose bs))
+              ]
     go (NotYetDefined names err) =
       paras [ line $ "The following are not yet defined here: " ++ intercalate ", " (map show names) ++ ":"
             , indent $ go err
             ]
     go (ErrorUnifyingTypes t1 t2 err) =
-      paras [ line "Error unifying type "
+      paras [ lineWithLevel "unifying type "
             , indent $ line $ prettyPrintType t1
             , line "with type"
             , indent $ line $ prettyPrintType t2
             , go err
             ]
     go (ErrorInExpression expr err) =
-      paras [ line "Error in expression:"
+      paras [ lineWithLevel "in expression:"
             , indent $ line $ prettyPrintValue expr
             , go err
             ]
     go (ErrorInModule mn err) =
-      paras [ line $ "Error in module " ++ show mn ++ ":"
+      paras [ lineWithLevel $ "in module " ++ show mn ++ ":"
             , go err
             ]
     go (ErrorInSubsumption t1 t2 err) =
-      paras [ line "Error checking that type "
+      paras [ lineWithLevel "checking that type "
             , indent $ line $ prettyPrintType t1
             , line "subsumes type"
             , indent $ line $ prettyPrintType t2
             , go err
             ]
     go (ErrorInInstance name ts err) =
-      paras [ line $ "Error in type class instance " ++ show name ++ " " ++ unwords (map prettyPrintTypeAtom ts) ++ ":"
+      paras [ lineWithLevel $ "in type class instance " ++ show name ++ " " ++ unwords (map prettyPrintTypeAtom ts) ++ ":"
             , go err
             ]
     go (ErrorCheckingKind ty err) =
-      paras [ line "Error checking kind of type "
+      paras [ lineWithLevel "checking kind of type "
             , indent $ line $ prettyPrintType ty
             , go err
             ]
     go (ErrorInferringType expr err) =
-      paras [ line "Error inferring type of value "
+      paras [ lineWithLevel "inferring type of value "
             , indent $ line $ prettyPrintValue expr
             , go err
             ]
     go (ErrorCheckingType expr ty err) =
-      paras [ line "Error checking that value "
+      paras [ lineWithLevel "checking that value "
             , indent $ line $ prettyPrintValue expr
             , line "has type"
             , indent $ line $ prettyPrintType ty
             , go err
             ]
     go (ErrorInApplication f t a err) =
-      paras [ line "Error applying function"
+      paras [ lineWithLevel "applying function"
             , indent $ line $ prettyPrintValue f
             , line "of type"
             , indent $ line $ prettyPrintType t
@@ -614,35 +624,35 @@
             , go err
             ]
     go (ErrorInDataConstructor nm err) =
-      paras [ line $ "Error in data constructor " ++ show nm ++ ":"
+      paras [ lineWithLevel $ "in data constructor " ++ show nm ++ ":"
             , go err
             ]
     go (ErrorInTypeConstructor nm err) =
-      paras [ line $ "Error in type constructor " ++ show nm ++ ":"
+      paras [ lineWithLevel $ "in type constructor " ++ show nm ++ ":"
             , go err
             ]
     go (ErrorInBindingGroup nms err) =
-      paras [ line $ "Error in binding group " ++ intercalate ", " (map show nms) ++ ":"
+      paras [ lineWithLevel $ "in binding group " ++ intercalate ", " (map show nms) ++ ":"
             , go err
             ]
     go (ErrorInDataBindingGroup err) =
-      paras [ line $ "Error in data binding group:"
+      paras [ lineWithLevel $ "in data binding group:"
             , go err
             ]
     go (ErrorInTypeSynonym name err) =
-      paras [ line $ "Error in type synonym " ++ show name ++ ":"
+      paras [ lineWithLevel $ "in type synonym " ++ show name ++ ":"
             , go err
             ]
     go (ErrorInValueDeclaration n err) =
-      paras [ line $ "Error in value declaration " ++ show n ++ ":"
+      paras [ lineWithLevel $ "in value declaration " ++ show n ++ ":"
             , go err
             ]
     go (ErrorInForeignImport nm err) =
-      paras [ line $ "Error in foreign import " ++ show nm ++ ":"
+      paras [ lineWithLevel $ "in foreign import " ++ show nm ++ ":"
             , go err
             ]
     go (PositionedError srcSpan err) =
-      paras [ line $ "Error at " ++ displaySourceSpan srcSpan ++ ":"
+      paras [ lineWithLevel $ "at " ++ displaySourceSpan srcSpan ++ ":"
             , indent $ go err
             ]
     go (SimpleErrorWrapper sem) = goSimple sem
@@ -650,6 +660,9 @@
   line :: String -> Box.Box
   line = Box.text
 
+  lineWithLevel :: String -> Box.Box
+  lineWithLevel text = line $ show level ++ " " ++ text
+
   suggestions :: ErrorMessage -> [Box.Box]
   suggestions = suggestions' . unwrapErrorMessage
     where
@@ -727,23 +740,23 @@
 -- Pretty print multiple errors
 --
 prettyPrintMultipleErrors :: Bool -> MultipleErrors -> String
-prettyPrintMultipleErrors full = flip evalState M.empty . prettyPrintMultipleErrorsWith "Error:" "Multiple errors:" full
+prettyPrintMultipleErrors full = flip evalState M.empty . prettyPrintMultipleErrorsWith Error "Error:" "Multiple errors:" full
 
 -- |
 -- Pretty print multiple warnings
 --
 prettyPrintMultipleWarnings :: Bool -> MultipleErrors ->  String
-prettyPrintMultipleWarnings full = flip evalState M.empty . prettyPrintMultipleErrorsWith "Warning:" "Multiple warnings:" full
+prettyPrintMultipleWarnings full = flip evalState M.empty . prettyPrintMultipleErrorsWith Warning "Warning:" "Multiple warnings:" full
 
-prettyPrintMultipleErrorsWith :: String -> String -> Bool -> MultipleErrors -> State UnknownMap String
-prettyPrintMultipleErrorsWith intro _ full  (MultipleErrors [e]) = do
-  result <- prettyPrintSingleError full e
+prettyPrintMultipleErrorsWith :: Level -> String -> String -> Bool -> MultipleErrors -> State UnknownMap String
+prettyPrintMultipleErrorsWith level intro _ full  (MultipleErrors [e]) = do
+  result <- prettyPrintSingleError full level e
   return $ renderBox $
     Box.vcat Box.left [ Box.text intro
                       , result
                       ]
-prettyPrintMultipleErrorsWith _ intro full  (MultipleErrors es) = do
-  result <- forM es $ (liftM $ Box.moveRight 2) . prettyPrintSingleError full
+prettyPrintMultipleErrorsWith level _ intro full  (MultipleErrors es) = do
+  result <- forM es $ (liftM $ Box.moveRight 2) . prettyPrintSingleError full level
   return $ renderBox $
     Box.vcat Box.left [ Box.text intro
                       , Box.vsep 1 Box.left result
diff --git a/src/Language/PureScript/Linter.hs b/src/Language/PureScript/Linter.hs
--- a/src/Language/PureScript/Linter.hs
+++ b/src/Language/PureScript/Linter.hs
@@ -16,7 +16,7 @@
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 
-module Language.PureScript.Linter (lint) where
+module Language.PureScript.Linter (lint, module L) where
 
 import Data.List (mapAccumL, nub)
 import Data.Maybe (mapMaybe)
@@ -30,6 +30,7 @@
 import Language.PureScript.AST
 import Language.PureScript.Names
 import Language.PureScript.Errors
+import Language.PureScript.Linter.Exhaustive as L
 
 -- | Lint the PureScript AST.
 -- |
diff --git a/src/Language/PureScript/Linter/Exhaustive.hs b/src/Language/PureScript/Linter/Exhaustive.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/PureScript/Linter/Exhaustive.hs
@@ -0,0 +1,275 @@
+-----------------------------------------------------------------------------
+--
+-- Module      :  Language.PureScript.Exhaustive
+-- Copyright   :  (c) 2013-14 Phil Freeman, (c) 2014 Gary Burgess, and other contributors
+-- License     :  MIT
+--
+-- Maintainer  :  Phil Freeman <paf31@cantab.net>
+-- Stability   :  experimental
+-- Portability :
+--
+-- |
+-- | Module for exhaustivity checking over pattern matching definitions
+-- | The algorithm analyses the clauses of a definition one by one from top
+-- | to bottom, where in each step it has the cases already missing (uncovered),
+-- | and it generates the new set of missing cases.
+-- 
+-----------------------------------------------------------------------------
+
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Language.PureScript.Linter.Exhaustive 
+  ( checkExhaustive
+  , checkExhaustiveModule
+  ) where
+
+import qualified Data.Map as M
+import Data.Maybe (fromMaybe)
+import Data.List (foldl', sortBy, nub)
+import Data.Function (on)
+
+import Control.Monad (unless)
+import Control.Applicative
+import Control.Monad.Writer.Class
+
+import Language.PureScript.AST.Binders
+import Language.PureScript.AST.Declarations
+import Language.PureScript.Environment
+import Language.PureScript.Names as P
+import Language.PureScript.Kinds
+import Language.PureScript.Types as P
+import Language.PureScript.Errors
+
+import Language.PureScript.AST.Traversals (everywhereOnValuesTopDownM)
+
+-- |
+-- Qualifies a propername from a given qualified propername and a default module name
+--
+qualifyName :: a -> ModuleName -> Qualified a -> Qualified a
+qualifyName n defmn qn = Qualified (Just mn) n
+  where
+  (mn, _) = qualify defmn qn
+
+-- |
+-- Given an environment and a datatype or newtype name,
+-- this function returns the associated data constructors if it is the case of a datatype
+-- where: - ProperName is the name of the constructor (for example, "Nothing" in Maybe)
+--        - [Type] is the list of arguments, if it has (for example, "Just" has [TypeVar "a"])
+--
+getConstructors :: Environment -> ModuleName -> (Qualified ProperName) -> [(ProperName, [Type])]
+getConstructors env defmn n = extractConstructors lnte
+  where
+  qpn :: Qualified ProperName
+  qpn = getConsDataName n
+
+  getConsDataName :: (Qualified ProperName) -> (Qualified ProperName)
+  getConsDataName con = qualifyName nm defmn con
+    where
+    nm = case getConsInfo con of
+           Nothing -> error $ "ProperName " ++ show con ++ " not in the scope of the current environment in getConsDataName."
+           Just (_, pm, _, _) -> pm
+
+  getConsInfo :: (Qualified ProperName) -> Maybe (DataDeclType, ProperName, Type, [Ident])
+  getConsInfo con = M.lookup con dce
+    where
+    dce :: M.Map (Qualified ProperName) (DataDeclType, ProperName, Type, [Ident])
+    dce = dataConstructors env
+
+  lnte :: Maybe (Kind, TypeKind)
+  lnte = M.lookup qpn (types env)
+
+  extractConstructors :: Maybe (Kind, TypeKind) -> [(ProperName, [Type])]
+  extractConstructors (Just (_, DataType _ pt)) = pt
+  extractConstructors _ = error "Data name not in the scope of the current environment in extractConstructors"
+
+-- |
+-- Replicates a wildcard binder
+--
+initialize :: Int -> [Binder]
+initialize l = replicate l NullBinder
+
+-- |
+-- Applies a function over two lists of tuples that may lack elements
+--
+genericMerge :: Ord a =>
+  (a -> Maybe b -> Maybe c -> d) ->
+  [(a, b)] ->
+  [(a, c)] ->
+  [d]
+genericMerge _ [] [] = []
+genericMerge f bs [] = map (\(s, b) -> f s (Just b) Nothing) bs
+genericMerge f [] bs = map (\(s, b) -> f s Nothing (Just b)) bs
+genericMerge f bsl@((s, b):bs) bsr@((s', b'):bs')
+  | s < s' = (f s (Just b) Nothing) : genericMerge f bs bsr
+  | s > s' = (f s' Nothing (Just b')) : genericMerge f bsl bs'
+  | otherwise = (f s (Just b) (Just b')) : genericMerge f bs bs'
+
+-- |
+-- Find the uncovered set between two binders:
+-- the first binder is the case we are trying to cover the second one is the matching binder
+--
+missingCasesSingle :: Environment -> ModuleName -> Binder -> Binder -> [Binder]
+missingCasesSingle _ _ _ NullBinder = []
+missingCasesSingle _ _ _ (VarBinder _) = []
+missingCasesSingle env mn (VarBinder _) b = missingCasesSingle env mn NullBinder b
+missingCasesSingle env mn br (NamedBinder _ bl) = missingCasesSingle env mn br bl
+missingCasesSingle env mn NullBinder cb@(ConstructorBinder con _) =
+  concatMap (\cp -> missingCasesSingle env mn cp cb) allPatterns
+  where
+  allPatterns = map (\(p, t) -> ConstructorBinder (qualifyName p mn con) (initialize $ length t))
+                  $ getConstructors env mn con
+missingCasesSingle env mn cb@(ConstructorBinder con bs) (ConstructorBinder con' bs')
+  | con == con' = map (ConstructorBinder con) (missingCasesMultiple env mn bs bs')
+  | otherwise = [cb]
+missingCasesSingle _ _ NullBinder (ArrayBinder bs)
+  | null bs = [] 
+  | otherwise = []
+missingCasesSingle env mn NullBinder (ObjectBinder bs) =
+  map (ObjectBinder . zip (map fst bs)) allMisses
+  where
+  allMisses = missingCasesMultiple env mn (initialize $ length bs) (map snd bs)
+missingCasesSingle env mn (ObjectBinder bs) (ObjectBinder bs') =
+  map (ObjectBinder . zip sortedNames) $ uncurry (missingCasesMultiple env mn) (unzip binders)
+  where
+  sortNames = sortBy (compare `on` fst)
+
+  (sbs, sbs') = (sortNames bs, sortNames bs')
+
+  compB :: a -> Maybe a -> Maybe a -> (a, a)
+  compB e b b' = (fm b, fm b')
+    where
+    fm = fromMaybe e
+
+  compBS :: Eq a => b -> a -> Maybe b -> Maybe b -> (a, (b, b))
+  compBS e s b b' = (s, compB e b b')
+
+  (sortedNames, binders) = unzip $ genericMerge (compBS NullBinder) sbs sbs'
+missingCasesSingle _ _ NullBinder (BooleanBinder b) = [BooleanBinder $ not b]
+missingCasesSingle _ _ (BooleanBinder bl) (BooleanBinder br)
+  | bl == br = []
+  | otherwise = [BooleanBinder bl]
+missingCasesSingle env mn b (PositionedBinder _ _ cb) = missingCasesSingle env mn b cb
+missingCasesSingle _ _ b _ = [b]
+
+-- |
+-- Returns the uncovered set of binders
+-- the first argument is the list of uncovered binders at step i
+-- the second argument is the (i+1)th clause of a pattern matching definition
+--
+-- The idea of the algorithm is as follows:
+-- it processes each binder of the two lists (say, `x` and `y`) one by one
+-- at each step two cases arises:
+--   - there are no missing cases between `x` and `y`: this is very straightforward, it continues with the remaining cases
+--       but keeps the uncovered binder in its position.
+--   - there are missing cases, let us call it the set `U`: on the one hand, we mix each new uncovered case in `U`
+--       with the current tail of uncovered set. On the other hand, it continues with the remaining cases: here we
+--       can use `x` (but it will generate overlapping cases), or `y`, which will generate non-overlapping cases.
+--
+--     As an example, consider:
+--
+--       data N = Z | S N
+--       f Z Z = Z   --> {[S _, _], [Z, S _]} which are the right non-overlapping cases (GHC uses this).
+--
+--       if we use `x` instead of `y` (in this case, `y` stands for `Z` and `x` for `_`) we obtain:
+--       f Z Z = Z   --> {[S _, _], [_, S _]} you can see that both cases overlaps each other.
+--
+--       Up to now, we've decided to use `x` just because we expect to generate uncovered cases which might be
+--       redundant or not, but uncovered at least. If we use `y` instead, we'll need to have a redundancy checker
+--       (which ought to be available soon), or increase the complexity of the algorithm.
+--
+missingCasesMultiple :: Environment -> ModuleName -> [Binder] -> [Binder] -> [[Binder]]
+missingCasesMultiple env mn = go
+  where
+  go [] _ = []
+  go (x:xs) (y:ys)
+    | null miss = map (x :) (go xs ys)
+    | otherwise = map (: xs) miss ++ map (x :) (go xs ys)
+    where
+    miss = missingCasesSingle env mn x y
+  go _ _ = error "Argument lengths did not match in missingCasesMultiple."
+
+-- |
+-- Guard handling
+--
+-- We say a guard is exhaustive iff it has an `otherwise` (or `true`) expression.
+-- Example:
+--   f x | x < 0 = 0
+--       | otherwise = 1
+--   is exhaustive, whereas `f x | x < 0` is not
+--
+-- The function below say whether or not a guard has an `otherwise` expression
+-- It is considered that `otherwise` is defined in Prelude
+--
+isExhaustiveGuard :: Either [(Guard, Expr)] Expr -> Bool
+isExhaustiveGuard (Left gs) = not . null $ filter (\(g, _) -> isOtherwise g) gs
+  where
+  isOtherwise :: Expr -> Bool
+  isOtherwise (TypedValue _ (BooleanLiteral True) _) = True
+  isOtherwise (TypedValue _ (Var (Qualified (Just (ModuleName [ProperName "Prelude"])) (Ident "otherwise"))) _) = True
+  isOtherwise _ = False
+isExhaustiveGuard (Right _) = True 
+
+-- |
+-- Returns the uncovered set of case alternatives
+--
+missingCases :: Environment -> ModuleName -> [Binder] -> CaseAlternative -> [[Binder]]
+missingCases env mn uncovered ca = missingCasesMultiple env mn uncovered (caseAlternativeBinders ca)
+
+missingAlternative :: Environment -> ModuleName -> CaseAlternative -> [Binder] -> [[Binder]]
+missingAlternative env mn ca uncovered
+  | isExhaustiveGuard (caseAlternativeResult ca) = missingCases env mn uncovered ca
+  | otherwise = [uncovered]
+
+-- |
+-- Main exhaustivity checking function
+-- Starting with the set `uncovered = { _ }` (nothing covered, one `_` for each function argument),
+-- it partitions that set with the new uncovered cases, until it consumes the whole set of clauses.
+-- Then, returns the uncovered set of case alternatives.
+-- 
+checkExhaustive :: forall m. (MonadWriter MultipleErrors m) => Environment -> ModuleName -> [CaseAlternative] -> m ()
+checkExhaustive env mn cas = makeResult . nub $ foldl' step [initial] cas
+  where
+  step :: [[Binder]] -> CaseAlternative -> [[Binder]]
+  step uncovered ca = concatMap (missingAlternative env mn ca) uncovered
+
+  initial :: [Binder]
+  initial = initialize numArgs
+    where
+    numArgs = length . caseAlternativeBinders . head $ cas 
+
+  makeResult :: [[Binder]] -> m ()
+  makeResult bss = unless (null bss) tellWarning 
+    where
+    tellWarning = tell . errorMessage $ NotExhaustivePattern bss
+
+-- |
+-- Exhaustivity checking over a list of declarations
+-- 
+checkExhaustiveDecls :: forall m. (Applicative m, MonadWriter MultipleErrors m) => Environment -> ModuleName -> [Declaration] -> m ()
+checkExhaustiveDecls env mn ds =
+  let (f, _, _) = everywhereOnValuesTopDownM return checkExpr return
+
+      f' :: Declaration -> m Declaration
+      f' d@(BindingGroupDeclaration bs) = mapM_ (f' . convert) bs >> return d
+        where
+        convert :: (Ident, NameKind, Expr) -> Declaration
+        convert (name, nk, e) = ValueDeclaration name nk [] (Right e)
+      f' d@(ValueDeclaration name _ _ _) = censor (onErrorMessages (ErrorInValueDeclaration name)) $ f d
+      f' (PositionedDeclaration pos com dec) = PositionedDeclaration pos com <$> censor (onErrorMessages (PositionedError pos)) (f' dec)
+      -- Don't generate two warnings for desugared dictionaries.
+      f' d@TypeInstanceDeclaration{} = return d
+      f' d = f d
+
+  in mapM_ f' ds
+  where
+  checkExpr :: Expr -> m Expr
+  checkExpr c@(Case _ cas)  = checkExhaustive env mn cas >> return c
+  checkExpr other = return other
+
+-- |
+-- Exhaustivity checking over a single module
+-- 
+checkExhaustiveModule :: forall m. (Applicative m, MonadWriter MultipleErrors m) => Environment -> Module -> m ()
+checkExhaustiveModule env (Module _ mn ds _) = censor (onErrorMessages (ErrorInModule mn)) $ checkExhaustiveDecls env mn ds
+
diff --git a/src/Language/PureScript/Make.hs b/src/Language/PureScript/Make.hs
new file mode 100644
--- /dev/null
+++ b/src/Language/PureScript/Make.hs
@@ -0,0 +1,314 @@
+-----------------------------------------------------------------------------
+--
+-- Module      :  Make
+-- Copyright   :  (c) 2013-14 Phil Freeman, (c) 2014 Gary Burgess, and other contributors
+-- License     :  MIT
+--
+-- Maintainer  :  Phil Freeman <paf31@cantab.net>
+-- Stability   :  experimental
+-- Portability :
+--
+-- |
+--
+-----------------------------------------------------------------------------
+
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE TupleSections #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Language.PureScript.Make
+  (
+  -- * Make API
+    RebuildPolicy(..)
+  , MakeActions(..)
+  , SupplyVar()
+  , Externs()
+  , make
+  
+  -- * Implementation of Make API using files on disk
+  , Make(..)
+  , runMake
+  , buildMakeActions
+  ) where
+
+import Control.Applicative
+import Control.Arrow ((&&&))
+import Control.Monad
+import Control.Monad.Error.Class (MonadError(..))
+import Control.Monad.Trans.Except
+import Control.Monad.Reader
+import Control.Monad.Writer
+import Control.Monad.Supply
+import Control.Monad.Supply.Class (fresh)
+
+import Data.Function (on)
+import Data.List (sortBy, groupBy)
+import Data.Maybe (fromMaybe)
+import Data.Time.Clock
+import Data.Traversable (traverse)
+import Data.Version (showVersion)
+import qualified Data.Map as M
+import qualified Data.Set as S
+
+import System.Directory
+       (doesFileExist, getModificationTime, createDirectoryIfMissing)
+import System.FilePath ((</>), takeDirectory)
+import System.IO.Error (tryIOError)
+
+
+import Language.PureScript.AST
+import Language.PureScript.CodeGen.Externs (moduleToPs)
+import Language.PureScript.Environment
+import Language.PureScript.Errors
+import Language.PureScript.Linter
+import Language.PureScript.ModuleDependencies
+import Language.PureScript.Names
+import Language.PureScript.Options
+import Language.PureScript.Parser
+import Language.PureScript.Pretty
+import Language.PureScript.Renamer
+import Language.PureScript.Sugar
+import Language.PureScript.TypeChecker
+import qualified Language.PureScript.Constants as C
+
+import qualified Language.PureScript.CodeGen.JS as J
+import qualified Language.PureScript.CoreFn as CF
+import qualified Paths_purescript as Paths
+
+-- |
+-- Actions that require implementations when running in "make" mode.
+--
+data MakeActions m = MakeActions {
+  -- |
+  -- Get the timestamp for the input file(s) for a module. If there are multiple
+  -- files (.purs and foreign files, for example) the timestamp should be for
+  -- the most recently modified file.
+  --
+    getInputTimestamp :: ModuleName -> m (Either RebuildPolicy (Maybe UTCTime))
+  -- |
+  -- Get the timestamp for the output files for a module. This should be the
+  -- timestamp for the oldest modified file, or Nothing if any of the required
+  -- output files are missing.
+  --
+  , getOutputTimestamp :: ModuleName -> m (Maybe UTCTime)
+  -- |
+  -- Read the externs file for a module as a string and also return the actual
+  -- path for the file.
+  , readExterns :: ModuleName -> m (FilePath, String)
+  -- |
+  -- Run the code generator for the module and write any required output files.
+  --
+  , codegen :: CF.Module CF.Ann -> Environment -> SupplyVar -> Externs -> m ()
+  -- |
+  -- Respond to a progress update.
+  --
+  , progress :: String -> m ()
+  }
+
+-- |
+-- Generated code for an externs file.
+--
+type Externs = String
+
+-- |
+-- A value to be used in the Supply monad.
+--
+type SupplyVar = Integer
+
+-- |
+-- Determines when to rebuild a module
+--
+data RebuildPolicy
+  -- | Never rebuild this module
+  = RebuildNever
+  -- | Always rebuild this module
+  | RebuildAlways deriving (Show, Eq, Ord)
+
+-- |
+-- Compiles in "make" mode, compiling each module separately to a js files and an externs file
+--
+-- If timestamps have not changed, the externs file can be used to provide the module's types without
+-- having to typecheck the module again.
+--
+make :: forall m. (Functor m, Applicative m, Monad m, MonadReader Options m, MonadError MultipleErrors m, MonadWriter MultipleErrors m)
+     => MakeActions m
+     -> [(Either RebuildPolicy FilePath, Module)]
+     -> m Environment
+make MakeActions{..} ms = do
+  (sorted, graph) <- sortModules $ map (importPrim . snd) ms
+  mapM_ lint sorted
+  toRebuild <- foldM (\s (Module _ moduleName' _ _) -> do
+    inputTimestamp <- getInputTimestamp moduleName'
+    outputTimestamp <- getOutputTimestamp moduleName'
+    return $ case (inputTimestamp, outputTimestamp) of
+      (Right (Just t1), Just t2) | t1 < t2 -> s
+      (Left RebuildNever, Just _) -> s
+      _ -> S.insert moduleName' s) S.empty sorted
+
+  marked <- rebuildIfNecessary (reverseDependencies graph) toRebuild sorted
+  (desugared, nextVar) <- runSupplyT 0 $ zip (map fst marked) <$> desugar (map snd marked)
+  evalSupplyT nextVar $ go initEnvironment desugared
+  where
+
+  go :: Environment -> [(Bool, Module)] -> SupplyT m Environment
+  go env [] = return env
+  go env ((False, m) : ms') = do
+    (_, env') <- lift . runCheck' env $ typeCheckModule Nothing m
+    go env' ms'
+  go env ((True, m@(Module coms moduleName' _ exps)) : ms') = do
+    lift $ progress $ "Compiling " ++ runModuleName moduleName'
+    (checked@(Module _ _ elaborated _), env') <- lift . runCheck' env $ typeCheckModule Nothing m
+    checkExhaustiveModule env' checked
+    regrouped <- createBindingGroups moduleName' . collapseBindingGroups $ elaborated
+    let mod' = Module coms moduleName' regrouped exps
+        corefn = CF.moduleToCoreFn env' mod'
+        [renamed] = renameInModules [corefn]
+        exts = moduleToPs mod' env'
+    nextVar <- fresh
+    lift $ codegen renamed env' nextVar exts
+    go env' ms'
+
+  rebuildIfNecessary :: M.Map ModuleName [ModuleName] -> S.Set ModuleName -> [Module] -> m [(Bool, Module)]
+  rebuildIfNecessary _ _ [] = return []
+  rebuildIfNecessary graph toRebuild (m@(Module _ moduleName' _ _) : ms') | moduleName' `S.member` toRebuild = do
+    let deps = fromMaybe [] $ moduleName' `M.lookup` graph
+        toRebuild' = toRebuild `S.union` S.fromList deps
+    (:) (True, m) <$> rebuildIfNecessary graph toRebuild' ms'
+  rebuildIfNecessary graph toRebuild (Module _ moduleName' _ _ : ms') = do
+    (path, externs) <- readExterns moduleName'
+    externsModules <- fmap (map snd) . alterErrors $ parseModulesFromFiles id [(path, externs)]
+    case externsModules of
+      [m'@(Module _ moduleName'' _ _)] | moduleName'' == moduleName' -> (:) (False, m') <$> rebuildIfNecessary graph toRebuild ms'
+      _ -> throwError . errorMessage . InvalidExternsFile $ path
+    where
+    alterErrors = flip catchError $ \(MultipleErrors errs) ->
+      throwError . MultipleErrors $ flip map errs $ \e -> case e of
+        SimpleErrorWrapper (ErrorParsingModule err) -> SimpleErrorWrapper (ErrorParsingExterns err)
+        _ -> e
+
+reverseDependencies :: ModuleGraph -> M.Map ModuleName [ModuleName]
+reverseDependencies g = combine [ (dep, mn) | (mn, deps) <- g, dep <- deps ]
+  where
+  combine :: (Ord a) => [(a, b)] -> M.Map a [b]
+  combine = M.fromList . map ((fst . head) &&& map snd) . groupBy ((==) `on` fst) . sortBy (compare `on` fst)
+
+-- |
+-- Add an import declaration for a module if it does not already explicitly import it.
+--
+addDefaultImport :: ModuleName -> Module -> Module
+addDefaultImport toImport m@(Module coms mn decls exps)  =
+  if isExistingImport `any` decls || mn == toImport then m
+  else Module coms mn (ImportDeclaration toImport Implicit Nothing : decls) exps
+  where
+  isExistingImport (ImportDeclaration mn' _ _) | mn' == toImport = True
+  isExistingImport (PositionedDeclaration _ _ d) = isExistingImport d
+  isExistingImport _ = False
+
+importPrim :: Module -> Module
+importPrim = addDefaultImport (ModuleName [ProperName C.prim])
+
+-- |
+-- A monad for running make actions
+--
+newtype Make a = Make { unMake :: ReaderT Options (WriterT MultipleErrors (ExceptT MultipleErrors IO)) a }
+  deriving (Functor, Applicative, Monad, MonadIO, MonadError MultipleErrors, MonadWriter MultipleErrors, MonadReader Options)
+
+-- |
+-- Execute a 'Make' monad, returning either errors, or the result of the compile plus any warnings.
+--
+runMake :: Options -> Make a -> IO (Either MultipleErrors (a, MultipleErrors))
+runMake opts = runExceptT . runWriterT . flip runReaderT opts . unMake
+
+makeIO :: (IOError -> ErrorMessage) -> IO a -> Make a
+makeIO f io = do
+  e <- liftIO $ tryIOError io
+  either (throwError . singleError . f) return e
+
+-- Traverse (Either e) instance (base 4.7)
+traverseEither :: Applicative f => (a -> f b) -> Either e a -> f (Either e b)
+traverseEither _ (Left x) = pure (Left x)
+traverseEither f (Right y) = Right <$> f y
+
+-- |
+-- A set of make actions that read and write modules from the given directory.
+--
+buildMakeActions :: FilePath -- ^ the output directory
+                 -> M.Map ModuleName (Either RebuildPolicy String) -- ^ a map between module names and paths to the file containing the PureScript module
+                 -> M.Map ModuleName (FilePath, ForeignJS) -- ^ a map between module name and the file containing the foreign javascript for the module
+                 -> Bool -- ^ Generate a prefix comment?
+                 -> MakeActions Make
+buildMakeActions outputDir filePathMap foreigns usePrefix =
+  MakeActions getInputTimestamp getOutputTimestamp readExterns codegen progress
+  where
+
+  getInputTimestamp :: ModuleName -> Make (Either RebuildPolicy (Maybe UTCTime))
+  getInputTimestamp mn = do
+    let path = fromMaybe (error "Module has no filename in 'make'") $ M.lookup mn filePathMap
+    e1 <- traverseEither getTimestamp path
+    fPath <- maybe (return Nothing) (getTimestamp . fst) $ M.lookup mn foreigns
+    return $ fmap (max fPath) e1
+
+  getOutputTimestamp :: ModuleName -> Make (Maybe UTCTime)
+  getOutputTimestamp mn = do
+    let filePath = runModuleName mn
+        jsFile = outputDir </> filePath </> "index.js"
+        externsFile = outputDir </> filePath </> "externs.purs"
+    min <$> getTimestamp jsFile <*> getTimestamp externsFile
+
+  readExterns :: ModuleName -> Make (FilePath, String)
+  readExterns mn = do
+    let path = outputDir </> runModuleName mn </> "externs.purs"
+    (path, ) <$> readTextFile path
+
+  codegen :: CF.Module CF.Ann -> Environment -> SupplyVar -> Externs -> Make ()
+  codegen m _ nextVar exts = do
+    let mn = CF.moduleName m
+    foreignInclude <- case mn `M.lookup` foreigns of
+      Just (path, _)
+        | not $ requiresForeign m -> do
+            tell $ errorMessage $ UnnecessaryFFIModule mn path
+            return Nothing
+        | otherwise -> return $ Just $ J.JSApp (J.JSVar "require") [J.JSStringLiteral "./foreign"]
+      Nothing | requiresForeign m -> throwError . errorMessage $ MissingFFIModule mn
+              | otherwise -> return Nothing
+    pjs <- evalSupplyT nextVar $ prettyPrintJS <$> J.moduleToJs m foreignInclude
+    let filePath = runModuleName mn
+        jsFile = outputDir </> filePath </> "index.js"
+        externsFile = outputDir </> filePath </> "externs.purs"
+        foreignFile = outputDir </> filePath </> "foreign.js"
+        prefix = ["Generated by psc version " ++ showVersion Paths.version | usePrefix]
+        js = unlines $ map ("// " ++) prefix ++ [pjs]
+    writeTextFile jsFile js
+    maybe (return ()) (writeTextFile foreignFile . snd) $ mn `M.lookup` foreigns
+    writeTextFile externsFile exts
+
+  requiresForeign :: CF.Module a -> Bool
+  requiresForeign = not . null . CF.moduleForeign
+
+  getTimestamp :: FilePath -> Make (Maybe UTCTime)
+  getTimestamp path = makeIO (const (SimpleErrorWrapper $ CannotGetFileInfo path)) $ do
+    exists <- doesFileExist path
+    traverse (const $ getModificationTime path) $ guard exists
+
+  readTextFile :: FilePath -> Make String
+  readTextFile path = do
+    verboseErrorsEnabled <- asks optionsVerboseErrors
+    makeIO (const (SimpleErrorWrapper $ CannotReadFile path)) $ do
+      when verboseErrorsEnabled $ putStrLn $ "Reading " ++ path
+      readFile path
+
+  writeTextFile :: FilePath -> String -> Make ()
+  writeTextFile path text = makeIO (const (SimpleErrorWrapper $ CannotWriteFile path)) $ do
+    mkdirp path
+    putStrLn $ "Writing " ++ path
+    writeFile path text
+    where
+    mkdirp :: FilePath -> IO ()
+    mkdirp = createDirectoryIfMissing True . takeDirectory
+
+  progress :: String -> Make ()
+  progress = liftIO . putStrLn
diff --git a/src/Language/PureScript/Pretty/Values.hs b/src/Language/PureScript/Pretty/Values.hs
--- a/src/Language/PureScript/Pretty/Values.hs
+++ b/src/Language/PureScript/Pretty/Values.hs
@@ -15,7 +15,8 @@
 
 module Language.PureScript.Pretty.Values (
     prettyPrintValue,
-    prettyPrintBinder
+    prettyPrintBinder,
+    prettyPrintBinderAtom
 ) where
 
 import Data.Maybe (fromMaybe)
@@ -40,11 +41,7 @@
   match (CharLiteral c) = return $ show c
   match (BooleanLiteral True) = return "true"
   match (BooleanLiteral False) = return "false"
-  match (ArrayLiteral xs) = concat <$> sequence
-    [ return "[ "
-    , withIndent $ prettyPrintMany prettyPrintValue' xs
-    , return " ]"
-    ]
+  match (ArrayLiteral xs) = return $ "[" ++ intercalate ", " (map prettyPrintValue xs) ++ "]"
   match (ObjectLiteral ps) = prettyPrintObject' $ second Just `map` ps
   match (ObjectConstructor ps) = prettyPrintObject' ps
   match (ObjectGetter prop) = return $ "(." ++ prop ++ ")"
@@ -95,7 +92,7 @@
 prettyPrintCaseAlternative :: CaseAlternative -> StateT PrinterState Maybe String
 prettyPrintCaseAlternative (CaseAlternative binders result) =
   concat <$> sequence
-    [ intercalate ", " <$> forM binders prettyPrintBinder'
+    [ return (unwords (map prettyPrintBinderAtom binders))
     , prettyPrintResult result
     ]
   where
@@ -118,7 +115,7 @@
   prettyPrintValue' val
 prettyPrintDoNotationElement (DoNotationBind binder val) =
   concat <$> sequence
-    [ prettyPrintBinder' binder
+    [ return (prettyPrintBinder binder)
     , return " <- "
     , prettyPrintValue' val
     ]
@@ -131,13 +128,10 @@
 
 prettyPrintObject' :: [(String, Maybe Expr)] -> StateT PrinterState Maybe String
 prettyPrintObject' [] = return "{}"
-prettyPrintObject' ps = concat <$> sequence
-  [ return "{\n"
-  , withIndent $ prettyPrintMany prettyPrintObjectProperty ps
-  , return "\n"
-  , currentIndent
-  , return "}"
-  ]
+prettyPrintObject' ps = return $ "{ " ++ intercalate ", " (map prettyPrintObjectProperty ps) ++ "}"
+  where
+  prettyPrintObjectProperty :: (String, Maybe Expr) -> String
+  prettyPrintObjectProperty (key, value) = prettyPrintObjectKey key ++ ": " ++ maybe "_" prettyPrintValue value
 
 ifThenElse :: Pattern PrinterState Expr ((Expr, Expr), Expr)
 ifThenElse = mkPattern match
@@ -190,55 +184,35 @@
                   , [ Wrap ifThenElse $ \(th, el) cond -> "if " ++ cond ++ " then " ++ prettyPrintValue th ++ " else " ++ prettyPrintValue el ]
                   ]
 
-prettyPrintBinderAtom :: Pattern PrinterState Binder String
-prettyPrintBinderAtom = mkPattern' match
+prettyPrintBinderAtom :: Binder -> String
+prettyPrintBinderAtom NullBinder = "_"
+prettyPrintBinderAtom (StringBinder str) = show str
+prettyPrintBinderAtom (CharBinder c) = show c
+prettyPrintBinderAtom (NumberBinder num) = either show show num
+prettyPrintBinderAtom (BooleanBinder True) = "true"
+prettyPrintBinderAtom (BooleanBinder False) = "false"
+prettyPrintBinderAtom (VarBinder ident) = show ident
+prettyPrintBinderAtom (ConstructorBinder ctor []) = show ctor
+prettyPrintBinderAtom (ObjectBinder bs) = 
+  "{ " 
+  ++ intercalate ", " (map prettyPrintObjectPropertyBinder bs)
+  ++ " }"
   where
-  match :: Binder -> StateT PrinterState Maybe String
-  match NullBinder = return "_"
-  match (StringBinder str) = return $ show str
-  match (CharBinder c) = return $ show c
-  match (NumberBinder num) = return $ either show show num
-  match (BooleanBinder True) = return "true"
-  match (BooleanBinder False) = return "false"
-  match (VarBinder ident) = return $ show ident
-  match (ConstructorBinder ctor args) = concat <$> sequence
-    [ return $ show ctor ++ " "
-    , unwords <$> forM args match
-    ]
-  match (ObjectBinder bs) = concat <$> sequence
-    [ return "{\n"
-    , withIndent $ prettyPrintMany prettyPrintObjectPropertyBinder bs
-    , currentIndent
-    , return "}"
-    ]
-  match (ArrayBinder bs) = concat <$> sequence
-    [ return "["
-    , unwords <$> mapM prettyPrintBinder' bs
-    , return "]"
-    ]
-  match (NamedBinder ident binder) = ((show ident ++ "@") ++) <$> prettyPrintBinder' binder
-  match (PositionedBinder _ _ binder) = prettyPrintBinder' binder
+  prettyPrintObjectPropertyBinder :: (String, Binder) -> String
+  prettyPrintObjectPropertyBinder (key, binder) = prettyPrintObjectKey key ++ ": " ++ prettyPrintBinder binder
+prettyPrintBinderAtom (ArrayBinder bs) =
+  "[ " 
+  ++ intercalate ", " (map prettyPrintBinder bs)
+  ++ " ]"
+prettyPrintBinderAtom (NamedBinder ident binder) = show ident ++ "@" ++ prettyPrintBinder binder
+prettyPrintBinderAtom (PositionedBinder _ _ binder) = prettyPrintBinderAtom binder
+prettyPrintBinderAtom b = parens (prettyPrintBinder b)
 
 -- |
 -- Generate a pretty-printed string representing a Binder
 --
 prettyPrintBinder :: Binder -> String
-prettyPrintBinder = fromMaybe (error "Incomplete pattern") . flip evalStateT (PrinterState 0) . prettyPrintBinder'
-
-prettyPrintBinder' :: Binder -> StateT PrinterState Maybe String
-prettyPrintBinder' = runKleisli $ runPattern matchBinder
-  where
-  matchBinder :: Pattern PrinterState Binder String
-  matchBinder = prettyPrintBinderAtom <+> fmap parens matchBinder
-
-prettyPrintObjectPropertyBinder :: (String, Binder) -> StateT PrinterState Maybe String
-prettyPrintObjectPropertyBinder (key, binder) = concat <$> sequence
-    [ return $ prettyPrintObjectKey key ++ ": "
-    , prettyPrintBinder' binder
-    ]
-
-prettyPrintObjectProperty :: (String, Maybe Expr) -> StateT PrinterState Maybe String
-prettyPrintObjectProperty (key, value) = concat <$> sequence
-    [ return $ prettyPrintObjectKey key ++ ": "
-    , maybe (pure "_") prettyPrintValue' value
-    ]
+prettyPrintBinder (ConstructorBinder ctor []) = show ctor
+prettyPrintBinder (ConstructorBinder ctor args) = show ctor ++ " " ++ unwords (map prettyPrintBinderAtom args)
+prettyPrintBinder (PositionedBinder _ _ binder) = prettyPrintBinder binder
+prettyPrintBinder b = prettyPrintBinderAtom b
