@@ -6,6 +6,7 @@
6-- and generates the final Javascript bundle.
6-- and generates the final Javascript bundle.
7module Language.PureScript.Bundle
7module Language.PureScript.Bundle
9 , guessModuleIdentifier
10 , guessModuleIdentifier
10 , ModuleIdentifier(..)
11 , ModuleIdentifier(..)
11 , moduleName
12 , moduleName
@@ -19,6 +20,7 @@ import Prelude.Compat
20import Control.Monad
21import Control.Monad
21import Control.Monad.Error.Class
22import Control.Monad.Error.Class
23+import Control.Arrow ((&&&))
23import Data.Char (chr, digitToInt)
25import Data.Char (chr, digitToInt)
24import Data.Generics (everything, everywhere, mkQ, mkT)
26import Data.Generics (everything, everywhere, mkQ, mkT)
@@ -33,7 +35,9 @@ import Language.JavaScript.Parser.AST
34import qualified Paths_purescript as Paths
36import qualified Paths_purescript as Paths
36−import System.FilePath (takeFileName, takeDirectory)
38+import System.FilePath (takeFileName, takeDirectory, takeDirectory, makeRelative)
40+import SourceMap.Types
38-- | The type of error messages. We separate generation and rendering of errors using a data
42-- | The type of error messages. We separate generation and rendering of errors using a data
39-- type, in case we need to match on error types later.
43-- type, in case we need to match on error types later.
@@ -98,10 +102,11 @@ data ModuleElement
98 | Member JSStatement Bool String JSExpression [Key]
102 | Member JSStatement Bool String JSExpression [Key]
99 | ExportsList [(ExportType, String, JSExpression, [Key])]
103 | ExportsList [(ExportType, String, JSExpression, [Key])]
100 | Other JSStatement
104 | Other JSStatement
101 deriving (Show)
106 deriving (Show)
103-- | A module is just a list of elements of the types listed above.
108-- | A module is just a list of elements of the types listed above.
104−data Module = Module ModuleIdentifier [ModuleElement] deriving (Show)
109+data Module = Module ModuleIdentifier (Maybe FilePath) [ModuleElement] deriving (Show)
106-- | Prepare an error message for consumption by humans.
111-- | Prepare an error message for consumption by humans.
107printErrorMessage :: ErrorMessage -> [String]
112printErrorMessage :: ErrorMessage -> [String]
@@ -159,7 +164,7 @@ checkImportPath name _ _ = Left name
160-- where name is the name of a member defined in the current module.
165-- where name is the name of a member defined in the current module.
161withDeps :: Module -> Module
166withDeps :: Module -> Module
162−withDeps (Module modulePath es) = Module modulePath (map expandDeps es)
167+withDeps (Module modulePath fn es) = Module modulePath fn (map expandDeps es)
164 -- | Collects all modules which are imported, so that we can identify dependencies of the first type.
169 -- | Collects all modules which are imported, so that we can identify dependencies of the first type.
165 imports :: [(String, ModuleIdentifier)]
170 imports :: [(String, ModuleIdentifier)]
@@ -248,9 +253,9 @@ trailingCommaList (JSCTLNone l) = commaList l
249-- Each type of module element is matched using pattern guards, and everything else is bundled into the
254-- Each type of module element is matched using pattern guards, and everything else is bundled into the
250-- Other constructor.
255-- Other constructor.
251−toModule :: forall m. (MonadError ErrorMessage m) => S.Set String -> ModuleIdentifier -> JSAST -> m Module
256+toModule :: forall m. (MonadError ErrorMessage m) => S.Set String -> ModuleIdentifier -> Maybe FilePath -> JSAST -> m Module
252−toModule mids mid top
257+toModule mids mid filename top
253− | JSAstProgram smts _ <- top = Module mid <$> traverse toModuleElement smts
258+ | JSAstProgram smts _ <- top = Module mid filename <$> traverse toModuleElement smts
254 | otherwise = err InvalidTopLevel
259 | otherwise = err InvalidTopLevel
256 err = throwError . ErrorInModule mid
261 err = throwError . ErrorInModule mid
@@ -389,7 +394,7 @@ compile modules entryPoints = filteredModules
389 -- | The vertex set
394 -- | The vertex set
390 verts :: [(ModuleElement, Key, [Key])]
395 verts :: [(ModuleElement, Key, [Key])]
391 verts = do
396 verts = do
392− Module mid els <- modules
397+ Module mid _ els <- modules
393 concatMap (toVertices mid) els
398 concatMap (toVertices mid) els
395 -- | Create a set of vertices for a module element.
400 -- | Create a set of vertices for a module element.
@@ -425,14 +430,21 @@ compile modules entryPoints = filteredModules
425 filteredModules = map filterUsed modules
430 filteredModules = map filterUsed modules
427 filterUsed :: Module -> Module
432 filterUsed :: Module -> Module
428− filterUsed (Module mid ds) = Module mid (map filterExports (go ds))
433+ filterUsed (Module mid fn ds) = Module mid fn (map filterExports (go ds))
430 go :: [ModuleElement] -> [ModuleElement]
435 go :: [ModuleElement] -> [ModuleElement]
431 go [] = []
436 go [] = []
432 go (d : rest)
437 go (d : rest)
433− | not (isDeclUsed d) = go rest
438+ | not (isDeclUsed d) = skipDecl d : go rest
434 | otherwise = d : go rest
439 | otherwise = d : go rest
441+ skipDecl :: ModuleElement -> ModuleElement
442+ skipDecl (Require s _ _) = Skip s
443+ skipDecl (Member s _ _ _ _) = Skip s
444+ skipDecl (ExportsList _) = Skip (JSEmptyStatement JSNoAnnot)
445+ skipDecl (Other s) = Skip s
446+ skipDecl (Skip s) = Skip s
436 -- | Filter out the exports for members which aren't used.
448 -- | Filter out the exports for members which aren't used.
437 filterExports :: ModuleElement -> ModuleElement
449 filterExports :: ModuleElement -> ModuleElement
438 filterExports (ExportsList exps) = ExportsList (filter (\(_, nm, _, _) -> isKeyUsed (mid, nm)) exps)
450 filterExports (ExportsList exps) = ExportsList (filter (\(_, nm, _, _) -> isKeyUsed (mid, nm)) exps)
@@ -453,7 +465,7 @@ sortModules :: [Module] -> [Module]
453sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (topSort graph))
465sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (topSort graph))
455 (graph, nodeFor, _) = graphFromEdges $ do
467 (graph, nodeFor, _) = graphFromEdges $ do
456− m@(Module mid els) <- modules
468+ m@(Module mid _ els) <- modules
457 return (m, mid, mapMaybe getKey els)
469 return (m, mid, mapMaybe getKey els)
459 getKey :: ModuleElement -> Maybe ModuleIdentifier
471 getKey :: ModuleElement -> Maybe ModuleIdentifier
@@ -466,12 +478,13 @@ sortModules modules = map (\v -> case nodeFor v of (n, _, _) -> n) (reverse (top
467-- If a module is empty, we don't want to generate code for it.
479-- If a module is empty, we don't want to generate code for it.
468isModuleEmpty :: Module -> Bool
480isModuleEmpty :: Module -> Bool
469−isModuleEmpty (Module _ els) = all isElementEmpty els
481+isModuleEmpty (Module _ _ els) = all isElementEmpty els
471 isElementEmpty :: ModuleElement -> Bool
483 isElementEmpty :: ModuleElement -> Bool
472 isElementEmpty (ExportsList exps) = null exps
484 isElementEmpty (ExportsList exps) = null exps
473 isElementEmpty Require{} = True
485 isElementEmpty Require{} = True
474 isElementEmpty (Other _) = True
486 isElementEmpty (Other _) = True
487+ isElementEmpty (Skip _) = True
475 isElementEmpty _ = False
488 isElementEmpty _ = False
477-- | Generate code for a set of modules, including a call to main().
490-- | Generate code for a set of modules, including a call to main().
@@ -490,16 +503,62 @@ isModuleEmpty (Module _ els) = all isElementEmpty els
490codeGen :: Maybe String -- ^ main module
503codeGen :: Maybe String -- ^ main module
491 -> String -- ^ namespace
504 -> String -- ^ namespace
492 -> [Module] -- ^ input modules
505 -> [Module] -- ^ input modules
506+ -> Maybe String -- ^ output filename
493− -> String
507+ -> (Maybe SourceMapping, String)
494−codeGen optionsMainModule optionsNamespace ms = renderToString (JSAstProgram (prelude : concatMap moduleToJS ms ++ maybe [] runMain optionsMainModule) JSNoAnnot)
508+codeGen optionsMainModule optionsNamespace ms outFileOpt = (fmap sourceMapping outFileOpt, rendered)
496− moduleToJS :: Module -> [JSStatement]
510+ rendered = renderToString (JSAstProgram (prelude : concatMap fst modulesJS ++ maybe [] runMain optionsMainModule) JSNoAnnot)
497− moduleToJS (Module mn ds) = wrap (moduleName mn) (indent (concatMap declToJS ds))
511+
512+ sourceMapping :: String -> SourceMapping
513+ sourceMapping outFile = SourceMapping {
515+ smSourceRoot = Nothing,
516+ smMappings = concat $
517+ zipWith3 (\file (pos :: Int) positions ->
518+ map (\(porig, pgen) -> Mapping {
519+ mapOriginal = Just (Pos (fromIntegral $ porig + 1) 0)
520+ , mapSourceFile = pathToFile <$> file
521+ , mapGenerated = (Pos (fromIntegral $ pos + pgen) 0)
524+ (offsets (0,0) (Right 1 : positions)))
526+ (scanl (+) (3 + moduleLength [prelude]) (map (3+) moduleLengths)) -- 3 lines between each module & at top
530+ pathToFile = makeRelative (takeDirectory outFile)
532+ offsets (m, n) (Left d:rest) = offsets (m+d, n) rest
533+ offsets (m, n) (Right d:rest) = map ((m+) &&& (n+)) [0 .. d - 1] ++ offsets (m+d, n+d) rest
536+ moduleLength :: [JSStatement] -> Int
537+ moduleLength = everything (+) (mkQ 0 countw)
539+ countw :: CommentAnnotation -> Int
540+ countw (WhiteSpace _ s) = length (filter (== '\n') s)
543+ moduleLengths :: [Int]
544+ moduleLengths = map (sum . map (either (const 0) id) . snd) modulesJS
545+ moduleFns = map (\(Module _ fn _) -> fn) ms
547+ modulesJS = map moduleToJS ms
549+ moduleToJS :: Module -> ([JSStatement], [Either Int Int])
550+ moduleToJS (Module mn _ ds) = (wrap (moduleName mn) (indent (concat jsDecls)), lengths)
552+ (jsDecls, lengths) = unzip $ map declToJS ds
554+ withLength :: [JSStatement] -> ([JSStatement], Either Int Int)
555+ withLength n = (n, Right $ moduleLength n)
499− declToJS :: ModuleElement -> [JSStatement]
557+ declToJS :: ModuleElement -> ([JSStatement], Either Int Int)
500− declToJS (Member n _ _ _ _) = [n]
558+ declToJS (Member n _ _ _ _) = withLength [n]
501− declToJS (Other n) = [n]
559+ declToJS (Other n) = withLength [n]
560+ declToJS (Skip n) = ([], Left $ moduleLength [n])
502− declToJS (Require _ nm req) =
561+ declToJS (Require _ nm req) = withLength
504 JSVariable lfsp
563 JSVariable lfsp
@@ -507,9 +566,10 @@ codeGen optionsMainModule optionsNamespace ms = renderToString (JSAstProgram (p
507 (JSVarInit sp $ either require (moduleReference sp . moduleName) req )
566 (JSVarInit sp $ either require (moduleReference sp . moduleName) req )
508 ]) (JSSemi JSNoAnnot)
567 ]) (JSSemi JSNoAnnot)
510− declToJS (ExportsList exps) = map toExport exps
569+ declToJS (ExportsList exps) = withLength $ map toExport exps
513 toExport :: (ExportType, String, JSExpression, [Key]) -> JSStatement
573 toExport :: (ExportType, String, JSExpression, [Key]) -> JSStatement
514 toExport (_, nm, val, _) =
574 toExport (_, nm, val, _) =
515 JSAssignStatement
575 JSAssignStatement
@@ -612,26 +672,39 @@ codeGen optionsMainModule optionsNamespace ms = renderToString (JSAstProgram (p
612-- | The bundling function.
672-- | The bundling function.
613-- This function performs dead code elimination, filters empty modules
673-- This function performs dead code elimination, filters empty modules
614-- and generates and prints the final Javascript bundle.
674-- and generates and prints the final Javascript bundle.
615−bundle :: (MonadError ErrorMessage m)
675+bundleSM :: (MonadError ErrorMessage m)
616− => [(ModuleIdentifier, String)] -- ^ The input modules. Each module should be javascript rendered from 'Language.PureScript.Make' or @psc@.
676+ => [(ModuleIdentifier, Maybe FilePath, String)] -- ^ The input modules. Each module should be javascript rendered from 'Language.PureScript.Make' or @psc@.
617 -> [ModuleIdentifier] -- ^ Entry points. These module identifiers are used as the roots for dead-code elimination
677 -> [ModuleIdentifier] -- ^ Entry points. These module identifiers are used as the roots for dead-code elimination
618 -> Maybe String -- ^ An optional main module.
678 -> Maybe String -- ^ An optional main module.
619 -> String -- ^ The namespace (e.g. PS).
679 -> String -- ^ The namespace (e.g. PS).
680+ -> Maybe FilePath -- ^ The output file name (if there is one - in which case generate source map)
620− -> m String
681+ -> m (Maybe SourceMapping, String)
621−bundle inputStrs entryPoints mainModule namespace = do
682+bundleSM inputStrs entryPoints mainModule namespace outFilename = do
622 forM_ mainModule $ \mname ->
684 forM_ mainModule $ \mname ->
623− when (mname `notElem` map (moduleName . fst) inputStrs) (throwError (MissingMainModule mname))
685+ when (mname `notElem` map (moduleName . mid) inputStrs) (throwError (MissingMainModule mname))
624 forM_ entryPoints $ \mIdent ->
686 forM_ entryPoints $ \mIdent ->
625− when (mIdent `notElem` map fst inputStrs) (throwError (MissingEntryPoint (moduleName mIdent)))
687+ when (mIdent `notElem` map mid inputStrs) (throwError (MissingEntryPoint (moduleName mIdent)))
626− input <- forM inputStrs $ \(ident, js) -> do
688+ input <- forM inputStrs $ \(ident, filename, js) -> do
627 ast <- either (throwError . ErrorInModule ident . UnableToParseModule) pure $ parse js (moduleName ident)
689 ast <- either (throwError . ErrorInModule ident . UnableToParseModule) pure $ parse js (moduleName ident)
628− return (ident, ast)
690+ return (ident, filename, ast)
630− let mids = S.fromList (map (moduleName . fst) input)
692+ let mids = S.fromList (map (moduleName . mid) input)
632− modules <- traverse (fmap withDeps . uncurry (toModule mids)) input
694+ modules <- traverse (fmap withDeps . (\(a,fn,c) -> toModule mids a fn c)) input
634 let compiled = compile modules entryPoints
696 let compiled = compile modules entryPoints
635 sorted = sortModules (filter (not . isModuleEmpty) compiled)
697 sorted = sortModules (filter (not . isModuleEmpty) compiled)
637− return (codeGen mainModule namespace sorted)
699+ return (codeGen mainModule namespace sorted outFilename)
701+-- | The bundling function.
702+-- This function performs dead code elimination, filters empty modules
703+-- and generates and prints the final Javascript bundle.
704+bundle :: (MonadError ErrorMessage m)
705+ => [(ModuleIdentifier, String)] -- ^ The input modules. Each module should be javascript rendered from 'Language.PureScript.Make' or @psc@.
706+ -> [ModuleIdentifier] -- ^ Entry points. These module identifiers are used as the roots for dead-code elimination
707+ -> Maybe String -- ^ An optional main module.
708+ -> String -- ^ The namespace (e.g. PS).
710+bundle inputStrs entryPoints mainModule namespace = snd <$> bundleSM (map (\(a,b) -> (a,Nothing,b)) inputStrs) entryPoints mainModule namespace Nothing