git.iko.soy/rowtype-yoga/purescript-native
on 8063c4d help sign in
612 lines 22 kB history raw
1-- |
2-- Bundles compiled PureScript modules for the browser.
3--
4-- This module takes as input the individual generated modules from 'Language.PureScript.Make' and
5-- performs dead code elimination, filters empty modules,
6-- and generates the final Javascript bundle.
7module Language.PureScript.Bundle
8 ( bundle
9 , ModuleIdentifier(..)
10 , moduleName
11 , ModuleType(..)
12 , ErrorMessage(..)
13 , printErrorMessage
14 , getExportedIdentifiers
15 ) where
17import Prelude.Compat
19import Control.Monad
20import Control.Monad.Error.Class
22import Data.Char (chr, digitToInt)
23import Data.Generics (everything, everywhere, mkQ, mkT)
24import Data.Graph
25import Data.List (nub, stripPrefix)
26import Data.Maybe (mapMaybe, catMaybes)
27import Data.Version (showVersion)
28import qualified Data.Set as S
30import Language.JavaScript.Parser
31import Language.JavaScript.Parser.AST
33import qualified Paths_purescript as Paths
35-- | The type of error messages. We separate generation and rendering of errors using a data
36-- type, in case we need to match on error types later.
37data ErrorMessage
38 = UnsupportedModulePath String
39 | InvalidTopLevel
40 | UnableToParseModule String
41 | UnsupportedExport
42 | ErrorInModule ModuleIdentifier ErrorMessage
43 deriving (Show, Read)
45-- | Modules are either "regular modules" (i.e. those generated by psc) or foreign modules.
46data ModuleType
47 = Regular
48 | Foreign
49 deriving (Show, Read, Eq, Ord)
51showModuleType :: ModuleType -> String
52showModuleType Regular = "Regular"
53showModuleType Foreign = "Foreign"
55-- | A module is identified by its module name and its type.
56data ModuleIdentifier = ModuleIdentifier String ModuleType deriving (Show, Read, Eq, Ord)
58moduleName :: ModuleIdentifier -> String
59moduleName (ModuleIdentifier name _) = name
61-- | A piece of code is identified by its module and its name. These keys are used to label vertices
62-- in the dependency graph.
63type Key = (ModuleIdentifier, String)
65-- | An export is either a "regular export", which exports a name from the regular module we are in,
66-- or a reexport of a declaration in the corresponding foreign module.
67--
68-- Regular exports are labelled, since they might re-export an operator with another name.
69data ExportType
70 = RegularExport String
71 | ForeignReexport
72 deriving (Show, Eq, Ord)
74-- | There are four types of module element we are interested in:
75--
76-- 1) Require statements
77-- 2) Member declarations
78-- 3) Export lists
79-- 4) Everything else
80--
81-- Each is labelled with the original AST node which generated it, so that we can dump it back
82-- into the output during codegen.
83data ModuleElement
84 = Require JSStatement String (Either String ModuleIdentifier)
85 | Member JSStatement Bool String JSExpression [Key]
86 | ExportsList [(ExportType, String, JSExpression, [Key])]
87 | Other JSStatement
88 deriving (Show)
90-- | A module is just a list of elements of the types listed above.
91data Module = Module ModuleIdentifier [ModuleElement] deriving (Show)
93-- | Prepare an error message for consumption by humans.
94printErrorMessage :: ErrorMessage -> [String]
95printErrorMessage (UnsupportedModulePath s) =
96 [ "A CommonJS module has an unsupported name (" ++ show s ++ ")."
97 , "The following file names are supported:"
98 , " 1) index.js (psc native modules)"
99 , " 2) foreign.js (psc foreign modules)"
100 ]
101printErrorMessage InvalidTopLevel =
102 [ "Expected a list of source elements at the top level." ]
103printErrorMessage (UnableToParseModule err) =
104 [ "The module could not be parsed:"
105 , err
106 ]
107printErrorMessage UnsupportedExport =
108 [ "An export was unsupported. Exports can be defined in one of two ways: "
109 , " 1) exports.name = ..."
110 , " 2) exports = { ... }"
111 ]
112printErrorMessage (ErrorInModule mid e) =
113 ("Error in module " ++ displayIdentifier mid ++ ":")
114 : ""
115 : map (" " ++) (printErrorMessage e)
116 where
117 displayIdentifier (ModuleIdentifier name ty) =
118 name ++ " (" ++ showModuleType ty ++ ")"
120-- | Calculate the ModuleIdentifier which a require(...) statement imports.
121checkImportPath :: String -> ModuleIdentifier -> S.Set String -> Either String ModuleIdentifier
122checkImportPath "./foreign" m _ =
123 Right (ModuleIdentifier (moduleName m) Foreign)
124checkImportPath name _ names
125 | Just name' <- stripPrefix "../" name
126 , name' `S.member` names = Right (ModuleIdentifier name' Regular)
127checkImportPath name _ _ = Left name
129-- | Compute the dependencies of all elements in a module, and add them to the tree.
130--
131-- Members and exports can have dependencies. A dependency is of one of the following forms:
132--
133-- 1) module.name or member["name"]
134--
135-- where module was imported using
136--
137-- var module = require("Module.Name");
138--
139-- 2) name
140--
141-- where name is the name of a member defined in the current module.
142withDeps :: Module -> Module
143withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
144 where
145 -- | Collects all modules which are imported, so that we can identify dependencies of the first type.
146 imports :: [(String, ModuleIdentifier)]
147 imports = mapMaybe toImport es
148 where
149 toImport :: ModuleElement -> Maybe (String, ModuleIdentifier)
150 toImport (Require _ nm (Right mid)) = Just (nm, mid)
151 toImport _ = Nothing
153 -- | Collects all member names in scope, so that we can identify dependencies of the second type.
154 boundNames :: [String]
155 boundNames = mapMaybe toBoundName es
156 where
157 toBoundName :: ModuleElement -> Maybe String
158 toBoundName (Member _ _ nm _ _) = Just nm
159 toBoundName _ = Nothing
161 -- | Calculate dependencies and add them to the current element.
162 expandDeps :: ModuleElement -> ModuleElement
163 expandDeps (Member n f nm decl _) = Member n f nm decl (nub $ dependencies modulePath decl)
164 expandDeps (ExportsList exps) = ExportsList (map expand exps)
165 where
166 expand (ty, nm, n1, _) = (ty, nm, n1, nub (dependencies modulePath n1))
167 expandDeps other = other
169 dependencies :: ModuleIdentifier -> JSExpression -> [(ModuleIdentifier, String)]
170 dependencies m = everything (++) (mkQ [] toReference)
171 where
172 toReference :: JSExpression -> [(ModuleIdentifier, String)]
173 toReference (JSMemberDot mn _ nm)
174 | JSIdentifier _ mn' <- mn
175 , JSIdentifier _ nm' <- nm
176 , Just mid <- lookup mn' imports
177 = [(mid, nm')]
178 toReference (JSMemberSquare mn _ nm _)
179 | JSIdentifier _ mn' <- mn
180 , Just nm' <- fromStringLiteral nm
181 , Just mid <- lookup mn' imports
182 = [(mid, nm')]
183 toReference (JSIdentifier _ nm)
184 | nm `elem` boundNames
185 = [(m, nm)]
186 toReference _ = []
188-- String literals include the quote chars
189fromStringLiteral :: JSExpression -> Maybe String
190fromStringLiteral (JSStringLiteral _ str) = Just $ strValue str
191fromStringLiteral _ = Nothing
193strValue :: String -> String
194strValue str = go $ drop 1 str
195 where
196 go ('\\' : 'b' : xs) = '\b' : go xs
197 go ('\\' : 'f' : xs) = '\f' : go xs
198 go ('\\' : 'n' : xs) = '\n' : go xs
199 go ('\\' : 'r' : xs) = '\r' : go xs
200 go ('\\' : 't' : xs) = '\t' : go xs
201 go ('\\' : 'v' : xs) = '\v' : go xs
202 go ('\\' : '0' : xs) = '\0' : go xs
203 go ('\\' : 'x' : a : b : xs) = chr (a' + b') : go xs
204 where
205 a' = 16 * digitToInt a
206 b' = digitToInt b
207 go ('\\' : 'u' : a : b : c : d : xs) = chr (a' + b' + c' + d') : go xs
208 where
209 a' = 16 * 16 * 16 * digitToInt a
210 b' = 16 * 16 * digitToInt b
211 c' = 16 * digitToInt c
212 d' = digitToInt d
213 go ('\\' : x : xs) = x : go xs
214 go "\"" = ""
215 go "'" = ""
216 go (x : xs) = x : go xs
217 go "" = ""
219commaList :: JSCommaList a -> [a]
220commaList JSLNil = []
221commaList (JSLOne x) = [x]
222commaList (JSLCons l _ x) = commaList l ++ [x]
224trailingCommaList :: JSCommaTrailingList a -> [a]
225trailingCommaList (JSCTLComma l _) = commaList l
226trailingCommaList (JSCTLNone l) = commaList l
228-- | Attempt to create a Module from a Javascript AST.
229--
230-- Each type of module element is matched using pattern guards, and everything else is bundled into the
231-- Other constructor.
232toModule :: forall m. (MonadError ErrorMessage m) => S.Set String -> ModuleIdentifier -> JSAST -> m Module
233toModule mids mid top
234 | JSAstProgram smts _ <- top = Module mid <$> traverse toModuleElement smts
235 | otherwise = err InvalidTopLevel
236 where
237 err = throwError . ErrorInModule mid
239 toModuleElement :: JSStatement -> m ModuleElement
240 toModuleElement stmt
241 | Just (importName, importPath) <- matchRequire mids mid stmt
242 = pure (Require stmt importName importPath)
243 toModuleElement stmt
244 | Just (exported, name, decl) <- matchMember stmt
245 = pure (Member stmt exported name decl [])
246 toModuleElement stmt
247 | Just props <- matchExportsAssignment stmt
248 = (ExportsList <$> traverse toExport (trailingCommaList props))
249 where
250 toExport :: JSObjectProperty -> m (ExportType, String, JSExpression, [Key])
251 toExport (JSPropertyNameandValue name _ [val]) =
252 (,,val,[]) <$> exportType val
253 <*> extractLabel' name
254 toExport _ = err UnsupportedExport
256 exportType :: JSExpression -> m ExportType
257 exportType (JSMemberDot f _ _)
258 | JSIdentifier _ "$foreign" <- f
259 = pure ForeignReexport
260 exportType (JSMemberSquare f _ _ _)
261 | JSIdentifier _ "$foreign" <- f
262 = pure ForeignReexport
263 exportType (JSIdentifier _ s) = pure (RegularExport s)
264 exportType _ = err UnsupportedExport
266 extractLabel' = maybe (err UnsupportedExport) pure . extractLabel
268 toModuleElement other = pure (Other other)
270-- Get a list of all the exported identifiers from a foreign module.
271--
272-- TODO: what if we assign to exports.foo and then later assign to
273-- module.exports (presumably overwriting exports.foo)?
274getExportedIdentifiers :: (MonadError ErrorMessage m)
275 => String
276 -> JSAST
277 -> m [String]
278getExportedIdentifiers mname top
279 | JSAstProgram stmts _ <- top = concat <$> traverse go stmts
280 | otherwise = err InvalidTopLevel
281 where
282 err = throwError . ErrorInModule (ModuleIdentifier mname Foreign)
284 go stmt
285 | Just props <- matchExportsAssignment stmt
286 = traverse toIdent (trailingCommaList props)
287 | Just (True, name, _) <- matchMember stmt
288 = pure [name]
289 | otherwise
290 = pure []
292 toIdent (JSPropertyNameandValue name _ [_]) =
293 extractLabel' name
294 toIdent _ =
295 err UnsupportedExport
297 extractLabel' = maybe (err UnsupportedExport) pure . extractLabel
299-- Matches JS statements like this:
300-- var ModuleName = require("file");
301matchRequire :: S.Set String
302 -> ModuleIdentifier
303 -> JSStatement
304 -> Maybe (String, Either String ModuleIdentifier)
305matchRequire mids mid stmt
306 | JSVariable _ jsInit _ <- stmt
307 , [JSVarInitExpression var varInit] <- commaList jsInit
308 , JSIdentifier _ importName <- var
309 , JSVarInit _ jsInitEx <- varInit
310 , JSMemberExpression req _ argsE _ <- jsInitEx
311 , JSIdentifier _ "require" <- req
312 , [ Just importPath ] <- map fromStringLiteral (commaList argsE)
313 , importPath' <- checkImportPath importPath mid mids
314 = Just (importName, importPath')
315 | otherwise
316 = Nothing
318-- Matches JS member declarations.
319matchMember :: JSStatement -> Maybe (Bool, String, JSExpression)
320matchMember stmt
321 -- var foo = expr;
322 | JSVariable _ jsInit _ <- stmt
323 , [JSVarInitExpression var varInit] <- commaList jsInit
324 , JSIdentifier _ name <- var
325 , JSVarInit _ decl <- varInit
326 = Just (False, name, decl)
327 -- exports.foo = expr; exports["foo"] = expr;
328 | JSAssignStatement e (JSAssign _) decl _ <- stmt
329 , Just name <- accessor e
330 = Just (True, name, decl)
331 | otherwise
332 = Nothing
333 where
334 accessor :: JSExpression -> Maybe String
335 accessor (JSMemberDot exports _ nm)
336 | JSIdentifier _ "exports" <- exports
337 , JSIdentifier _ name <- nm
338 = Just name
339 accessor (JSMemberSquare exports _ nm _)
340 | JSIdentifier _ "exports" <- exports
341 , Just name <- fromStringLiteral nm
342 = Just name
343 accessor _ = Nothing
345-- Matches assignments to module.exports, like this:
346-- module.exports = { ... }
347matchExportsAssignment :: JSStatement -> Maybe JSObjectPropertyList
348matchExportsAssignment stmt
349 | JSAssignStatement e (JSAssign _) decl _ <- stmt
350 , JSMemberDot module' _ exports <- e
351 , JSIdentifier _ "module" <- module'
352 , JSIdentifier _ "exports" <- exports
353 , JSObjectLiteral _ props _ <- decl
354 = Just props
355 | otherwise
356 = Nothing
358extractLabel :: JSPropertyName -> Maybe String
359extractLabel (JSPropertyString _ nm) = Just $ strValue nm
360extractLabel (JSPropertyIdent _ nm) = Just nm
361extractLabel _ = Nothing
363-- | Eliminate unused code based on the specified entry point set.
364compile :: [Module] -> [ModuleIdentifier] -> [Module]
365compile modules [] = modules
366compile modules entryPoints = filteredModules
367 where
368 (graph, _, vertexFor) = graphFromEdges verts
370 -- | The vertex set
371 verts :: [(ModuleElement, Key, [Key])]
372 verts = do
373 Module mid els <- modules
374 concatMap (toVertices mid) els
375 where
376 -- | Create a set of vertices for a module element.
377 --
378 -- Some special cases worth commenting on:
379 --
380 -- 1) Regular exports which simply export their own name do not count as dependencies.
381 -- Regular exports which rename and reexport an operator do count, however.
382 --
383 -- 2) Require statements don't contribute towards dependencies, since they effectively get
384 -- inlined wherever they are used inside other module elements.
385 toVertices :: ModuleIdentifier -> ModuleElement -> [(ModuleElement, Key, [Key])]
386 toVertices p m@(Member _ _ nm _ deps) = [(m, (p, nm), deps)]
387 toVertices p m@(ExportsList exps) = mapMaybe toVertex exps
388 where
389 toVertex (ForeignReexport, nm, _, ks) = Just (m, (p, nm), ks)
390 toVertex (RegularExport nm, nm1, _, ks) | nm /= nm1 = Just (m, (p, nm1), ks)
391 toVertex _ = Nothing
392 toVertices _ _ = []
394 -- | The set of vertices whose connected components we are interested in keeping.
395 entryPointVertices :: [Vertex]
396 entryPointVertices = catMaybes $ do
397 (_, k@(mid, _), _) <- verts
398 guard $ mid `elem` entryPoints
399 return (vertexFor k)
401 -- | The set of vertices reachable from an entry point
402 reachableSet :: S.Set Vertex
403 reachableSet = S.fromList (concatMap (reachable graph) entryPointVertices)
405 filteredModules :: [Module]
406 filteredModules = map filterUsed modules
407 where
408 filterUsed :: Module -> Module
409 filterUsed (Module mid ds) = Module mid (map filterExports (go ds))
410 where
411 go :: [ModuleElement] -> [ModuleElement]
412 go [] = []
413 go (d : rest)
414 | not (isDeclUsed d) = go rest
415 | otherwise = d : go rest
417 -- | Filter out the exports for members which aren't used.
418 filterExports :: ModuleElement -> ModuleElement
419 filterExports (ExportsList exps) = ExportsList (filter (\(_, nm, _, _) -> isKeyUsed (mid, nm)) exps)
420 filterExports me = me
422 isDeclUsed :: ModuleElement -> Bool
423 isDeclUsed (Member _ _ nm _ _) = isKeyUsed (mid, nm)
424 isDeclUsed _ = True
426 isKeyUsed :: Key -> Bool
427 isKeyUsed k
428 | Just me <- vertexFor k = me `S.member` reachableSet
429 | otherwise = False
431-- | Topologically sort the module dependency graph, so that when we generate code, modules can be
432-- defined in the right order.
433sortModules :: [Module] -> [Module]
434sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (topSort graph))
435 where
436 (graph, nodeFor, _) = graphFromEdges $ do
437 m@(Module mid els) <- modules
438 return (m, mid, mapMaybe getKey els)
440 getKey :: ModuleElement -> Maybe ModuleIdentifier
441 getKey (Require _ _ (Right mi)) = Just mi
442 getKey _ = Nothing
444-- | A module is empty if it contains no exported members (in other words,
445-- if the only things left after dead code elimination are module imports and
446-- "other" foreign code).
447--
448-- If a module is empty, we don't want to generate code for it.
449isModuleEmpty :: Module -> Bool
450isModuleEmpty (Module _ els) = all isElementEmpty els
451 where
452 isElementEmpty :: ModuleElement -> Bool
453 isElementEmpty (ExportsList exps) = null exps
454 isElementEmpty Require{} = True
455 isElementEmpty (Other _) = True
456 isElementEmpty _ = False
458-- | Generate code for a set of modules, including a call to main().
459--
460-- Modules get defined on the global PS object, as follows:
461--
462-- var PS = { };
463-- (function(exports) {
464-- ...
465-- })(PS["Module.Name"] = PS["Module.Name"] || {});
466--
467-- In particular, a module and its foreign imports share the same namespace inside PS.
468-- This saves us from having to generate unique names for a module and its foreign imports,
469-- and is safe since a module shares a namespace with its foreign imports in PureScript as well
470-- (so there is no way to have overlaps in code generated by psc).
471codeGen :: Maybe String -- ^ main module
472 -> String -- ^ namespace
473 -> [Module] -- ^ input modules
474 -> String
475codeGen optionsMainModule optionsNamespace ms = renderToString (JSAstProgram (prelude : concatMap moduleToJS ms ++ maybe [] runMain optionsMainModule) JSNoAnnot)
476 where
477 moduleToJS :: Module -> [JSStatement]
478 moduleToJS (Module mn ds) = wrap (moduleName mn) (indent (concatMap declToJS ds))
479 where
480 declToJS :: ModuleElement -> [JSStatement]
481 declToJS (Member n _ _ _ _) = [n]
482 declToJS (Other n) = [n]
483 declToJS (Require _ nm req) =
484 [
485 JSVariable lfsp
486 (cList [
487 JSVarInitExpression (JSIdentifier sp nm)
488 (JSVarInit sp $ either require (moduleReference sp . moduleName) req )
489 ]) (JSSemi JSNoAnnot)
490 ]
491 declToJS (ExportsList exps) = map toExport exps
493 where
494 toExport :: (ExportType, String, JSExpression, [Key]) -> JSStatement
495 toExport (_, nm, val, _) =
496 JSAssignStatement
497 (JSMemberSquare (JSIdentifier lfsp "exports") JSNoAnnot
498 (str nm) JSNoAnnot)
499 (JSAssign sp)
500 val
501 (JSSemi JSNoAnnot)
503 -- comma lists are reverse-consed
504 cList :: [a] -> JSCommaList a
505 cList [] = JSLNil
506 cList [x] = JSLOne x
507 cList l = go $ reverse l
508 where
509 go [x] = JSLOne x
510 go (h:t)= JSLCons (go t) JSNoAnnot h
511 go [] = error "Invalid case in comma-list"
513 indent :: [JSStatement] -> [JSStatement]
514 indent = everywhere (mkT squash)
515 where
516 squash JSNoAnnot = (JSAnnot (TokenPn 0 0 2) [])
517 squash (JSAnnot pos ann) = JSAnnot (keepCol pos) (map splat ann)
519 splat (CommentA pos s) = CommentA (keepCol pos) s
520 splat (WhiteSpace pos w) = WhiteSpace (keepCol pos) w
521 splat ann = ann
523 keepCol (TokenPn _ _ c) = TokenPn 0 0 (if c >= 0 then c + 2 else 2)
525 prelude :: JSStatement
526 prelude = JSVariable (JSAnnot tokenPosnEmpty [ CommentA tokenPosnEmpty $ "// Generated by psc-bundle " ++ showVersion Paths.version
527 , WhiteSpace tokenPosnEmpty "\n" ])
528 (cList [
529 JSVarInitExpression (JSIdentifier sp optionsNamespace)
530 (JSVarInit sp (emptyObj sp))
531 ]) (JSSemi JSNoAnnot)
533 require :: String -> JSExpression
534 require mn =
535 JSMemberExpression (JSIdentifier JSNoAnnot "require") JSNoAnnot (cList [ str mn ]) JSNoAnnot
537 moduleReference :: JSAnnot -> String -> JSExpression
538 moduleReference a mn =
539 JSMemberSquare (JSIdentifier a optionsNamespace) JSNoAnnot
540 (str mn) JSNoAnnot
542 str :: String -> JSExpression
543 str s = JSStringLiteral JSNoAnnot $ "\"" ++ s ++ "\""
546 emptyObj :: JSAnnot -> JSExpression
547 emptyObj a = JSObjectLiteral a (JSCTLNone JSLNil) JSNoAnnot
549 wrap :: String -> [JSStatement] -> [JSStatement]
550 wrap mn ds =
551 [
552 JSMethodCall (JSExpressionParen lf (JSFunctionExpression JSNoAnnot JSIdentNone JSNoAnnot
553 (JSLOne (JSIdentName JSNoAnnot "exports")) JSNoAnnot
554 (JSBlock sp (lfHead ds) lf)) -- \n not quite in right place
555 JSNoAnnot)
556 JSNoAnnot
557 (JSLOne (JSAssignExpression (moduleReference JSNoAnnot mn) (JSAssign sp)
558 (JSExpressionBinary (moduleReference sp mn) (JSBinOpOr sp) (emptyObj sp))))
559 JSNoAnnot
560 (JSSemi JSNoAnnot)
561 ]
562 where
563 lfHead (h:t) = (addAnn (WhiteSpace tokenPosnEmpty "\n ") h) : t
564 lfHead x = x
566 addAnn :: CommentAnnotation -> JSStatement -> JSStatement
567 addAnn a (JSExpressionStatement (JSStringLiteral ann s) _) =
568 (JSExpressionStatement (JSStringLiteral (appendAnn a ann) s) (JSSemi JSNoAnnot))
569 addAnn _ x = x
571 appendAnn a JSNoAnnot = (JSAnnot tokenPosnEmpty [a])
572 appendAnn a (JSAnnot _ anns) = JSAnnot tokenPosnEmpty (a:anns ++ [WhiteSpace tokenPosnEmpty " "])
574 runMain :: String -> [JSStatement]
575 runMain mn =
576 [JSMethodCall
577 (JSMemberDot (moduleReference lf mn) JSNoAnnot
578 (JSIdentifier JSNoAnnot "main"))
579 JSNoAnnot (cList []) JSNoAnnot (JSSemi JSNoAnnot)]
581 lf :: JSAnnot
582 lf = JSAnnot tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n" ]
585 lfsp :: JSAnnot
586 lfsp = JSAnnot tokenPosnEmpty [ WhiteSpace tokenPosnEmpty "\n " ]
588 sp :: JSAnnot
589 sp = JSAnnot tokenPosnEmpty [ WhiteSpace tokenPosnEmpty " " ]
591-- | The bundling function.
592-- This function performs dead code elimination, filters empty modules
593-- and generates and prints the final Javascript bundle.
594bundle :: (MonadError ErrorMessage m)
595 => [(ModuleIdentifier, String)] -- ^ The input modules. Each module should be javascript rendered from 'Language.PureScript.Make' or @psc@.
596 -> [ModuleIdentifier] -- ^ Entry points. These module identifiers are used as the roots for dead-code elimination
597 -> Maybe String -- ^ An optional main module.
598 -> String -- ^ The namespace (e.g. PS).
599 -> m String
600bundle inputStrs entryPoints mainModule namespace = do
601 input <- forM inputStrs $ \(ident, js) -> do
602 ast <- either (throwError . ErrorInModule ident . UnableToParseModule) pure $ parse js (moduleName ident)
603 return (ident, ast)
605 let mids = S.fromList (map (moduleName . fst) input)
607 modules <- traverse (fmap withDeps . uncurry (toModule mids)) input
609 let compiled = compile modules entryPoints
610 sorted = sortModules (filter (not . isModuleEmpty) compiled)
612 return (codeGen mainModule namespace sorted)