├── .gitignore ├── LICENSE ├── README.md ├── Setup.hs ├── purescript-bundle-fast.cabal └── src ├── Main.hs ├── ProcessModule.hs └── ProcessRootModule.hs /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Bit Connor 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # purescript-bundle-fast 2 | 3 | Tested with PureScript version 0.7.1.0 4 | 5 | 6 | ## Synopsis 7 | 8 | A fast alternative to Purescript's `psc-bundle` to be used during development. 9 | 10 | 11 | ## About 12 | 13 | One great thing about programming in JavaScript is the speed of development. 14 | Just edit your source code file, and immediately reload your browser to 15 | instantly see your changes. 16 | 17 | With PureScript, you must go through a compilation process. Even if it only 18 | takes a few seconds, the lag becomes frustrating when trying to iterate 19 | rapidly. 20 | 21 | But we can try to bring the compilation time down to almost nothing! This 22 | project manages to do so for the `psc-bundle` stage of compilation. It is a 23 | tool called `psc-bundle-fast` that replaces the official `psc-bundle` tool that 24 | comes with PureScript. 25 | 26 | `psc-bundle-fast` should be used only during development. For production you 27 | should still use the official `psc-bundle` since it does dead code elimination 28 | and will produce smaller output files. 29 | 30 | 31 | ## Benchmarks 32 | 33 | So how much faster is it? Results for a sample project: 34 | 35 | | Command | Time | Output .js Size 36 | | --------------- | ------ | --------------- 37 | | psc-bundle | 1.458s | 108K 38 | | psc-bundle-fast | 0.091s | 464K 39 | 40 | That's 16x faster! It's bigger because it contains lots of library code that is 41 | not being used (regular `psc-bundle` strips this out). But for local 42 | development, the larger file size has negligible impact on load time, and no 43 | impact on performance. 44 | 45 | ### What about browserify and webpack? 46 | 47 | They are even slower than PureScript's `psc-bundle`. Feel free to run your own 48 | benchmarks (and tell us the results!) 49 | 50 | 51 | ## Installation 52 | 53 | You need GHC and cabal. 54 | 55 | $ cabal update 56 | $ cabal install purescript-bundle-fast 57 | 58 | 59 | ## Example Usage 60 | 61 | First, use `psc` as usual to compile your program: 62 | 63 | $ psc './bower_components/**/src/*.purs' \ 64 | --ffi './bower_components/**/src/*.js' \ 65 | './src/**/*.purs' \ 66 | --ffi './src/**/*.js' \ 67 | -o output 68 | 69 | Now, just for a comparison, here is how we'd use the regular `psc-bundle`: 70 | 71 | $ psc-bundle './output/**/*.js' -m Main --main Main -o app.js 72 | 73 | And here is how you would use `psc-bundle-fast` instead of the previous step: 74 | 75 | $ psc-bundle-fast -i output -m Main --main Main -o app.js 76 | 77 | 78 | ## Differences with `psc-bundle` and limitations 79 | 80 | Unlike `psc-bundle`, `psc-bundle-fast` does not use a real JavaScript parser. 81 | Therefore: 82 | 83 | 1. It is not able to perform dead code elimination the way that `psc-bundle` 84 | does, so it will produce output files that are larger. 85 | 86 | 2. It will not detect syntax errors in `foreign.js` files. (This is actually an 87 | advantage since the error messages that `psc-bundle` generates are 88 | confusing. It's more helpful to see the error that the browser shows). 89 | 90 | 3. `foreign.js` files that use `require` to load external JavaScript 91 | modules/libraries will not work. These `foreign.js` files will load, but if 92 | they are executed then an error will be triggered. If you need to a 93 | PureScript library that has such `require` usage, then you will need to 94 | externally load the required JavaScript library, and then create a stub 95 | function called "require" that hooks into it. (If you succeed to do this 96 | then share with us how you did it!) 97 | 98 | 4. The custom parser that `psc-bundle-fast` uses is brittle and relies on the 99 | specific format that `psc` outputs. If `psc` ever makes (even slight) 100 | changes to its output then `psc-bundle-fast` will break. 101 | 102 | 103 | ## Usage 104 | 105 | ``` 106 | psc-bundle-fast - Bundles compiled PureScript modules for the browser (fast 107 | version, for development) 108 | 109 | Usage: psc-bundle-fast (-i|--input-dir DIR) [-o|--output FILE] 110 | (-m|--module MODULE) [--main MODULE] [-n|--namespace ARG] 111 | 112 | Available options: 113 | --version Show the version number 114 | -h,--help Show this help text 115 | -i,--input-dir DIR The directory containing the compiled modules. This 116 | directory should contain a subdirectory for each 117 | compiled module(with the name of the module), and 118 | within each of those there should be an index.js (and 119 | optional foreign.js) file. The psc compiler usually 120 | calls the desired directory "output" 121 | -o,--output FILE The output .js file (Default is stdout) 122 | -m,--module MODULE Entry point module name(s). All code which is not a 123 | transitive dependency of an entry point module will 124 | be removed. 125 | --main MODULE Generate code to run the main method in the specified 126 | module. 127 | -n,--namespace ARG Specify the namespace that PureScript modules will be 128 | exported to when running in the 129 | browser. (default: "PS") 130 | ``` 131 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /purescript-bundle-fast.cabal: -------------------------------------------------------------------------------- 1 | name: purescript-bundle-fast 2 | version: 0.1.0.1 3 | synopsis: A fast alternative to Purescript's `psc-bundle` to be used during development 4 | description: See README.md 5 | homepage: https://github.com/bitc/purescript-bundle-fast 6 | bug-reports: https://github.com/bitc/purescript-bundle-fast/issues 7 | license: MIT 8 | license-file: LICENSE 9 | author: Bit Connor 10 | maintainer: mutantlemon@gmail.com 11 | -- copyright: 12 | category: Development 13 | build-type: Simple 14 | extra-source-files: README.md 15 | cabal-version: >=1.10 16 | 17 | source-repository head 18 | type: git 19 | location: git://github.com/bitc/purescript-bundle-fast.git 20 | 21 | executable psc-bundle-fast 22 | main-is: Main.hs 23 | other-modules: ProcessModule, 24 | ProcessRootModule 25 | other-extensions: CPP, OverloadedStrings, RecordWildCards 26 | 27 | build-depends: base ==4.*, 28 | containers, 29 | directory, 30 | filepath, 31 | optparse-applicative, 32 | text, 33 | vector 34 | 35 | hs-source-dirs: src 36 | cpp-options: -DCABAL 37 | ghc-options: -Wall -O2 38 | default-language: Haskell2010 39 | -------------------------------------------------------------------------------- /src/Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE OverloadedStrings #-} 3 | {-# LANGUAGE RecordWildCards #-} 4 | 5 | module Main (main) where 6 | 7 | import Data.Text (Text) 8 | import Data.Vector (Vector) 9 | import Options.Applicative as Opts 10 | import System.Directory (createDirectoryIfMissing) 11 | import System.FilePath ((), takeDirectory) 12 | import System.IO 13 | import qualified Data.Text as T 14 | import qualified Data.Text.IO as T 15 | import qualified Data.Vector as V 16 | import Data.Monoid ((<>)) 17 | 18 | import ProcessModule 19 | import ProcessRootModule 20 | 21 | #ifdef CABAL 22 | import Data.Version (showVersion) 23 | import qualified Paths_purescript_bundle_fast as Paths 24 | #endif 25 | 26 | programVersion :: String 27 | programVersion = 28 | #ifdef CABAL 29 | showVersion Paths.version 30 | #else 31 | "unknown-version (not built with cabal)" 32 | #endif 33 | 34 | 35 | -- | Command line options. 36 | data Options = Options 37 | { optionsInputDir :: FilePath 38 | , optionsOutputFile :: Maybe FilePath 39 | , optionsEntryPoints :: String 40 | , optionsMainModule :: Maybe String 41 | , optionsNamespace :: String 42 | } deriving Show 43 | 44 | app :: Options -> IO () 45 | app Options{..} = do 46 | case optionsOutputFile of 47 | Nothing -> process stdout 48 | Just filename -> do 49 | createDirectoryIfMissing True (takeDirectory filename) 50 | withFile filename WriteMode $ \h -> process h 51 | 52 | 53 | where 54 | process :: Handle -> IO () 55 | process outHandle = do 56 | T.hPutStrLn outHandle $ "// Generated by psc-bundle-fast " `T.append` T.pack programVersion 57 | T.hPutStrLn outHandle $ "var PS = { };" 58 | T.hPutStrLn outHandle $ "var require = function() { return function() { throw new Error('require is not supported in psc-bundle-fast'); }; };" 59 | 60 | processRootModule outHandle (T.pack optionsNamespace) (T.pack optionsEntryPoints) loadModule loadForeign 61 | 62 | case optionsMainModule of 63 | Nothing -> return () 64 | Just mainModule -> T.hPutStrLn outHandle (callMain (T.pack optionsNamespace) (T.pack mainModule)) 65 | 66 | loadModule :: ModuleName -> IO (Vector Text) 67 | loadModule moduleName = do 68 | contents <- T.readFile $ optionsInputDir T.unpack moduleName "index.js" 69 | return $ V.fromList (T.lines contents) 70 | 71 | loadForeign :: ModuleName -> IO Text 72 | loadForeign moduleName = T.readFile $ optionsInputDir T.unpack moduleName "foreign.js" 73 | 74 | callMain :: Namespace -> ModuleName -> Text 75 | callMain namespace modulename = T.concat [namespace, "[\"", modulename, "\"].main();"] 76 | 77 | 78 | -- | Command line options parser. 79 | options :: Parser Options 80 | options = Options <$> inputDir 81 | <*> optional outputFile 82 | <*> entryPoint 83 | <*> optional mainModule 84 | <*> namespace 85 | where 86 | inputDir :: Parser FilePath 87 | inputDir = strOption $ 88 | short 'i' 89 | <> metavar "DIR" 90 | <> long "input-dir" 91 | <> help "The directory containing the compiled modules. This directory should contain a subdirectory for each compiled module(with the name of the module), and within each of those there should be an index.js (and optional foreign.js) file. The psc compiler usually calls the desired directory \"output\"" 92 | 93 | outputFile :: Parser FilePath 94 | outputFile = strOption $ 95 | short 'o' 96 | <> metavar "FILE" 97 | <> long "output" 98 | <> help "The output .js file (Default is stdout)" 99 | 100 | entryPoint :: Parser String 101 | entryPoint = strOption $ 102 | short 'm' 103 | <> metavar "MODULE" 104 | <> long "module" 105 | <> help "Entry point module name(s). All code which is not a transitive dependency of an entry point module will be removed." 106 | 107 | mainModule :: Parser String 108 | mainModule = strOption $ 109 | long "main" 110 | <> metavar "MODULE" 111 | <> help "Generate code to run the main method in the specified module." 112 | 113 | namespace :: Parser String 114 | namespace = strOption $ 115 | short 'n' 116 | <> long "namespace" 117 | <> Opts.value "PS" 118 | <> showDefault 119 | <> help "Specify the namespace that PureScript modules will be exported to when running in the browser." 120 | 121 | -- | Make it go. 122 | main :: IO () 123 | main = do 124 | opts <- execParser (info (version <*> helper <*> options) infoModList) 125 | app opts 126 | where 127 | infoModList = fullDesc <> headerInfo <> footerInfo 128 | headerInfo = header "psc-bundle-fast - Bundles compiled PureScript modules for the browser (fast version, for development)" 129 | footerInfo = footer $ "psc-bundle-fast " ++ programVersion 130 | 131 | version :: Parser (a -> a) 132 | version = abortOption (InfoMsg programVersion) $ long "version" <> help "Show the version number" <> hidden 133 | -------------------------------------------------------------------------------- /src/ProcessModule.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module ProcessModule 4 | ( Namespace 5 | , ModuleName 6 | , processModule 7 | ) where 8 | 9 | import Data.Maybe 10 | import Data.Text (Text) 11 | import Data.Vector (Vector) 12 | import qualified Data.Text as T 13 | import qualified Data.Vector as V 14 | 15 | type ModuleName = Text 16 | 17 | type Namespace = Text 18 | 19 | processModule :: Namespace -> ModuleName -> IO Text -> Vector Text -> IO (Vector Text, [ModuleName]) 20 | processModule namespace modulename loadForeign ls = do 21 | let importSectionLoc = findImportSection ls 22 | exportSectionIndex = findExportSectionIndex ls 23 | 24 | (importLines, reqModules, needsForeign) = createImportSection namespace modulename ls importSectionLoc 25 | exportLines = createExportSection ls exportSectionIndex 26 | 27 | foreignModule <- if needsForeign then processForeign namespace modulename loadForeign 28 | else return V.empty 29 | 30 | let output = V.concat $ 31 | case importSectionLoc of 32 | Nothing -> case exportSectionIndex of 33 | Nothing -> 34 | [ foreignModule 35 | , V.singleton jsModuleHeader 36 | , ls 37 | , V.singleton (jsModuleFooter namespace modulename) 38 | ] 39 | Just ei -> 40 | [ foreignModule 41 | , V.singleton jsModuleHeader 42 | , V.slice 0 ei ls 43 | , exportLines 44 | , V.singleton (jsModuleFooter namespace modulename) 45 | ] 46 | Just (ii, ic) -> case exportSectionIndex of 47 | Nothing -> 48 | [ foreignModule 49 | , V.singleton jsModuleHeader 50 | , V.slice 0 ii ls 51 | , importLines 52 | , V.drop (ii+ic) ls 53 | , V.singleton (jsModuleFooter namespace modulename) 54 | ] 55 | Just ei -> 56 | [ foreignModule 57 | , V.singleton jsModuleHeader 58 | , V.slice 0 ii ls 59 | , importLines 60 | , V.slice (ii+ic) (ei - (ii + ic)) ls 61 | , exportLines 62 | , V.singleton (jsModuleFooter namespace modulename) 63 | ] 64 | 65 | return (output, reqModules) 66 | 67 | 68 | -- Searches for a string formatted as: 69 | -- 70 | -- var Data_Maybe = require("../Data.Maybe"); 71 | -- 72 | -- In this case will return: Just ("Data_Maybe", "Data.Maybe") 73 | -- 74 | parseImportStatement :: Text -> Maybe (Text, ModuleName) 75 | parseImportStatement l = 76 | case T.stripPrefix "var " l of 77 | Nothing -> Nothing 78 | Just r1 -> case T.breakOn " = require(" r1 of 79 | (_, "") -> Nothing 80 | (varName, r2) -> case T.breakOn ");" (T.drop (T.length " = require(") r2) of 81 | (_, "") -> Nothing 82 | (quotedModName, _) -> case () of 83 | _ | isSurroundedBy '"' quotedModName -> Just (varName, removeRelativePath (removeQuotes quotedModName)) 84 | _ | isSurroundedBy '\'' quotedModName -> Just (varName, removeRelativePath (removeQuotes quotedModName)) 85 | _ | otherwise -> Nothing 86 | 87 | where 88 | isSurroundedBy :: Char -> Text -> Bool 89 | isSurroundedBy c t = 90 | T.head t == c && T.last t == c 91 | 92 | removeQuotes :: Text -> Text 93 | removeQuotes = 94 | T.tail . T.init 95 | 96 | removeRelativePath :: Text -> Text 97 | removeRelativePath t = 98 | fromMaybe t (T.stripPrefix "../" t >>= T.stripSuffix "/index.js") 99 | 100 | formatImport :: Namespace -> (Text, Text) -> Text 101 | formatImport namespace (varName, modName) = 102 | T.concat ["var ", varName, " = ", namespace, "[\"", modName, "\"];"] 103 | 104 | -- Searches for a string formatted like the following examples: 105 | -- 106 | -- ">>=": $greater$greater$eq, 107 | -- bind: bind, 108 | -- showOrdering: showOrdering 109 | -- 110 | parseExportStatement :: Text -> Maybe (Text, Text) 111 | parseExportStatement line = 112 | case parseFieldName (T.stripStart line) of 113 | Nothing -> Nothing 114 | Just (fieldName, rest) -> case parseFieldValue rest of 115 | Nothing -> Nothing 116 | Just fieldValue -> Just (fieldName, fieldValue) 117 | 118 | where 119 | parseFieldName :: Text -> Maybe (Text, Text) 120 | parseFieldName l = 121 | case T.head l of 122 | '\"' -> case T.breakOn "\"" (T.tail l) of 123 | (_, "") -> Nothing 124 | (fieldName, rest) -> Just (fieldName, T.stripStart (T.tail rest)) 125 | _ -> case T.breakOn ":" l of 126 | (_, "") -> Nothing 127 | ("", _) -> Nothing 128 | (fieldName, rest) -> Just (T.stripEnd fieldName, rest) 129 | 130 | parseFieldValue :: Text -> Maybe Text 131 | parseFieldValue rest = 132 | case T.head rest of 133 | ':' -> case T.breakOn "," (T.stripStart (T.tail rest)) of 134 | (fieldVal, _) -> Just (T.stripEnd fieldVal) 135 | _ -> Nothing 136 | 137 | formatExportStatement :: (Text, Text) -> Text 138 | formatExportStatement (fieldName, fieldValue) = 139 | T.concat ["exports[\"", fieldName, "\"] = ", fieldValue, ";"] 140 | 141 | 142 | 143 | jsModuleHeader :: Text 144 | jsModuleHeader = "(function(exports) {" 145 | 146 | jsModuleFooter :: Namespace -> ModuleName -> Text 147 | jsModuleFooter namespace modulename = T.concat["})(", namespace, "[\"", modulename, "\"] = ", namespace, "[\"", modulename, "\"] || {});"] 148 | 149 | 150 | createImportSection :: 151 | Namespace 152 | -> ModuleName 153 | -> Vector Text 154 | -> Maybe (Int, Int) 155 | -> (Vector Text, [ModuleName], Bool) -- ^ (import lines, req modules, foreign.js module required) 156 | createImportSection _ _ _ Nothing = (V.empty, [], False) 157 | createImportSection namespace modulename ls (Just (firstIndex, numImports)) = 158 | let sliced = V.slice firstIndex numImports ls 159 | in V.foldl' next (V.empty, [], False) sliced 160 | where 161 | next :: (Vector Text, [ModuleName], Bool) -> Text -> (Vector Text, [ModuleName], Bool) 162 | next (createdLines, foundReqModules, needsForeign) line = 163 | case parseImportStatement line of 164 | Nothing -> (createdLines, foundReqModules, needsForeign) 165 | Just (varName, reqModule) -> case reqModule == "./foreign.js" of 166 | True -> let newLine = formatImport namespace (varName, modulename) -- Load the `modulename` instead of trying to load "./foreign" 167 | in (createdLines `V.snoc` newLine, foundReqModules, True) 168 | False -> let newLine = formatImport namespace (varName, reqModule) 169 | in (createdLines `V.snoc` newLine, reqModule:foundReqModules, needsForeign) 170 | 171 | createExportSection :: Vector Text -> Maybe Int -> Vector Text 172 | createExportSection _ Nothing = V.empty 173 | createExportSection ls (Just exportSectionIndex) = 174 | let sliced = V.drop exportSectionIndex ls 175 | in V.foldl' next V.empty sliced 176 | where 177 | next :: Vector Text -> Text -> Vector Text 178 | next createdLines line = 179 | case parseExportStatement line of 180 | Nothing -> createdLines 181 | Just (fieldName, fieldValue) -> 182 | let newLine = formatExportStatement (fieldName, fieldValue) 183 | in createdLines `V.snoc` newLine 184 | 185 | -- Loads the source code of the foreign.js file, using the supplied loader 186 | -- (usually from disk) and adds the necessary header and footer 187 | processForeign :: Namespace -> ModuleName -> IO Text -> IO (Vector Text) 188 | processForeign namespace modulename loadForeign = do 189 | let header = jsModuleHeader 190 | contents <- loadForeign 191 | let footer = jsModuleFooter namespace modulename 192 | return $ V.fromList [header, contents, footer] 193 | 194 | 195 | findImportSection :: Vector Text -> Maybe (Int, Int) 196 | findImportSection ls = 197 | case V.findIndex (isJust . parseImportStatement) ls of 198 | Nothing -> Nothing 199 | Just firstIndex -> case V.findIndex (isNothing . parseImportStatement) (V.drop firstIndex ls) of 200 | Nothing -> Nothing 201 | Just numImports -> Just (firstIndex, numImports) 202 | 203 | findExportSectionIndex :: Vector Text -> Maybe Int 204 | findExportSectionIndex ls = vectorFindLastIndex isExportLine ls 205 | where 206 | isExportLine l = 207 | let stripped = T.strip l 208 | in stripped == "module.exports = {" 209 | || stripped == "module.exports = {};" 210 | 211 | vectorFindLastIndex :: (a -> Bool) -> Vector a -> Maybe Int 212 | vectorFindLastIndex p v = find' ((V.length v) - 1) 213 | where 214 | find' (-1) = Nothing 215 | find' index = 216 | if p (v V.! index) then Just index 217 | else find' (index - 1) 218 | 219 | -------------------------------------------------------------------------------- /src/ProcessRootModule.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | 3 | module ProcessRootModule ( 4 | processRootModule 5 | ) where 6 | 7 | import Control.Monad (foldM) 8 | import Data.Graph 9 | import Data.Map (Map) 10 | import Data.Text (Text) 11 | import Data.Vector (Vector) 12 | import System.IO 13 | import qualified Data.Map as M 14 | import qualified Data.Text.IO as T 15 | 16 | import ProcessModule 17 | 18 | processRootModule :: 19 | Handle 20 | -> Namespace 21 | -> ModuleName 22 | -> (ModuleName -> IO (Vector Text)) 23 | -> (ModuleName -> IO Text) 24 | -> IO () 25 | processRootModule outHandle namespace rootModule loadModule loadForeign = do 26 | allModules <- processAllModules M.empty namespace rootModule loadModule loadForeign 27 | let sortedModules = sortModules allModules 28 | 29 | mapM_ writeModule sortedModules 30 | 31 | where 32 | writeModule (_, moduleLines) = 33 | mapM_ (T.hPutStrLn outHandle) moduleLines 34 | 35 | 36 | processAllModules :: 37 | Map ModuleName (Vector Text, [ModuleName]) 38 | -> Namespace 39 | -> ModuleName 40 | -> (ModuleName 41 | -> IO (Vector Text)) 42 | -> (ModuleName 43 | -> IO Text) 44 | -> IO (Map ModuleName (Vector Text, [ModuleName])) 45 | processAllModules collected namespace moduleName loadModule loadForeign = 46 | case M.lookup moduleName collected of 47 | Just _ -> return collected 48 | Nothing -> do 49 | ls <- loadModule moduleName 50 | (moduleContents, deps) <- processModule namespace moduleName (loadForeign moduleName) ls 51 | let newCollected = M.insert moduleName (moduleContents, deps) collected 52 | foldM processDep newCollected deps 53 | 54 | where 55 | processDep :: Map ModuleName (Vector Text, [ModuleName]) -> ModuleName -> IO (Map ModuleName (Vector Text, [ModuleName])) 56 | processDep c m = processAllModules c namespace m loadModule loadForeign 57 | 58 | sortModules :: Map ModuleName (Vector Text, [ModuleName]) -> [(ModuleName, Vector Text)] 59 | sortModules modules = 60 | let sorted = reverse (topSort graph) 61 | in map readVertex sorted 62 | 63 | where 64 | nodes = map nodeFunc (M.toList modules) 65 | nodeFunc (moduleName, (m, moduleDeps)) = (m, moduleName, moduleDeps) 66 | (graph, nodeFor, _) = graphFromEdges nodes 67 | readVertex v = 68 | let (finalModule, moduleName, _) = nodeFor v 69 | in (moduleName, finalModule) 70 | --------------------------------------------------------------------------------