diff --git a/builder/src/Generate.hs b/builder/src/Generate.hs index aff6a71f..6eaed5a4 100644 --- a/builder/src/Generate.hs +++ b/builder/src/Generate.hs @@ -60,7 +60,10 @@ debug root details (Build.Artifacts pkg ifaces roots modules) = let graph_ = objectsToGlobalGraph objects graph <- Task.io $ Lamdera.AppConfig.injectConfig graph_ let mains = gatherMains pkg objects roots + esmEnabled <- Task.io $ Lamdera.useEsm return $ JS.generate mode graph mains + & Lamdera.alternativeImplementationWhen esmEnabled + (JS.generateEsm mode graph mains) dev :: FilePath -> Details.Details -> Build.Artifacts -> Task B.Builder @@ -70,7 +73,10 @@ dev root details (Build.Artifacts pkg _ roots modules) = let graph_ = objectsToGlobalGraph objects graph <- Task.io $ Lamdera.AppConfig.injectConfig graph_ let mains = gatherMains pkg objects roots + esmEnabled <- Task.io $ Lamdera.useEsm return $ JS.generate mode graph mains + & Lamdera.alternativeImplementationWhen esmEnabled + (JS.generateEsm mode graph mains) prod :: FilePath -> Details.Details -> Build.Artifacts -> Task B.Builder @@ -84,7 +90,10 @@ prod root details (Build.Artifacts pkg _ roots modules) = & Lamdera.alternativeImplementationWhen longNamesEnabled (Mode.Prod (Mode.legibleFieldNames graph)) let mains = gatherMains pkg objects roots + esmEnabled <- Task.io $ Lamdera.useEsm return $ JS.generate mode graph mains + & Lamdera.alternativeImplementationWhen esmEnabled + (JS.generateEsm mode graph mains) repl :: FilePath -> Details.Details -> Bool -> Build.ReplArtifacts -> N.Name -> Task B.Builder diff --git a/compiler/src/Generate/JavaScript.hs b/compiler/src/Generate/JavaScript.hs index d2d0beaf..3d06be5f 100644 --- a/compiler/src/Generate/JavaScript.hs +++ b/compiler/src/Generate/JavaScript.hs @@ -1,6 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} module Generate.JavaScript ( generate + , generateEsm , generateForRepl , generateForReplEndpoint ) @@ -57,6 +58,19 @@ generate mode (Opt.GlobalGraph graph_ _) mains = <> "}(this));" <> "\n" <> Lamdera.Injection.elmPkgJs mode mains <> "\n" +generateEsm :: Mode.Mode -> Opt.GlobalGraph -> Mains -> B.Builder +generateEsm mode (Opt.GlobalGraph graph_ _) mains = + let + graph = Lamdera.Injection.graphModifications mode mains graph_ + state = Map.foldrWithKey (addMain mode graph) emptyState mains + in + Functions.functions + -- <> perfNote mode -- @NOTE given user never manages JS generation in Lamdera, hide the perf note + <> stateToBuilder state + <> toMainEsmExports mode mains + -- <> Lamdera.Injection.source mode mains -- shadowing not allowed in esm + <> "\n" <> Lamdera.Injection.elmPkgJs mode mains <> "\n" + addMain :: Mode.Mode -> Graph -> ModuleName.Canonical -> Opt.Main -> State -> State addMain mode graph home _ state = addGlobal mode graph state (Opt.Global home "main") @@ -546,6 +560,13 @@ toMainExports mode mains = in JsName.toBuilder export <> "(" <> exports <> ");" +toMainEsmExports :: Mode.Mode -> Mains -> B.Builder +toMainEsmExports mode mains = + let + exports = generateExports mode (Map.foldrWithKey addToTrie emptyTrie mains) + in + "export const Elm = " <> exports <> ";" + generateExports :: Mode.Mode -> Trie -> B.Builder generateExports mode (Trie maybeMain subs) = diff --git a/extra/Lamdera.hs b/extra/Lamdera.hs index 8d58f2d5..9d188ddb 100644 --- a/extra/Lamdera.hs +++ b/extra/Lamdera.hs @@ -45,6 +45,9 @@ module Lamdera , useLongNames_ , enableLongNames , useLongNames + , useEsm_ + , enableEsm + , useEsm , isTest , isLiveMode , setLiveMode @@ -460,6 +463,19 @@ enableLongNames = do debug $ "🗜️ enableLongNames" modifyMVar_ useLongNames_ (\_ -> pure True) +{-# NOINLINE useEsm_ #-} +useEsm_ :: MVar Bool +useEsm_ = unsafePerformIO $ newMVar False + +{-# NOINLINE useEsm #-} +useEsm :: IO Bool +useEsm = do + readMVar useEsm_ + +enableEsm :: IO () +enableEsm = do + debug $ "🗜️ enableEsm" + modifyMVar_ useEsm_ (\_ -> pure True) isTest :: IO Bool isTest = do diff --git a/extra/Lamdera/CLI/Check.hs b/extra/Lamdera/CLI/Check.hs index a5a6c7c0..008dc7c5 100644 --- a/extra/Lamdera/CLI/Check.hs +++ b/extra/Lamdera/CLI/Check.hs @@ -599,6 +599,7 @@ buildProductionJsFiles root inProduction_ versionInfo = do , _docs = Nothing , _noWire = False , _optimizeLegible = True + , _esm = False } Make.run ["src" "LFR.elm"] $ @@ -610,6 +611,7 @@ buildProductionJsFiles root inProduction_ versionInfo = do , _docs = Nothing , _noWire = False , _optimizeLegible = False + , _esm = False } Lamdera.AppConfig.writeUsage @@ -713,6 +715,7 @@ migrationCheck root nextVersion changedTypes = do , _docs = Nothing , _noWire = False , _optimizeLegible = False + , _esm = False } -- @TODO this is because the migrationCheck does weird terminal stuff that mangles the display... how to fix this? diff --git a/extra/Lamdera/Compile.hs b/extra/Lamdera/Compile.hs index ee78d5be..4bbf9b70 100644 --- a/extra/Lamdera/Compile.hs +++ b/extra/Lamdera/Compile.hs @@ -42,6 +42,7 @@ makeOptimizedWithCleanup cleanup root path = do , _docs = Nothing , _noWire = False , _optimizeLegible = False + , _esm = False } wait r remove tmp @@ -69,6 +70,7 @@ make_ root = do , _docs = Nothing , _noWire = False , _optimizeLegible = True + , _esm = False } wait r -- The compilation process ends by printing to terminal in a way that overwrites @@ -98,6 +100,7 @@ makeDev root paths = do , _docs = Nothing , _noWire = False , _optimizeLegible = False + , _esm = False } wait r -- The compilation process ends by printing to terminal in a way that overwrites @@ -127,6 +130,7 @@ makeDevHtml root paths = do , _docs = Nothing , _noWire = False , _optimizeLegible = False + , _esm = False } wait r -- The compilation process ends by printing to terminal in a way that overwrites @@ -158,6 +162,7 @@ makeHarnessDevJs root = do , _docs = Nothing , _noWire = False , _optimizeLegible = False + , _esm = False } wait r remove tmp diff --git a/terminal/src/Main.hs b/terminal/src/Main.hs index 059a29a2..6ab7eabc 100644 --- a/terminal/src/Main.hs +++ b/terminal/src/Main.hs @@ -214,6 +214,7 @@ make = |-- flag "docs" Make.docsFile "Generate a JSON file of documentation for a package. Eventually it will be possible to preview docs with `reactor` because it is quite hard to deal with these JSON files directly." |-- onOff "no-wire" "Explicitly disable Lamdera's wire codegen." |-- onOff "optimize-legible" "Same as --optimize but without identifier shortening, handy for debugging optimised code or for when identifiers are more useful than smaller JS compilations." + |-- onOff "esm" "Emit an ECMAScript module instead of an IIFE." in Terminal.Command "make" Uncommon details example (zeroOrMore elmFile) makeFlags Make.run diff --git a/terminal/src/Make.hs b/terminal/src/Make.hs index bd41f285..6253cd97 100644 --- a/terminal/src/Make.hs +++ b/terminal/src/Make.hs @@ -48,6 +48,7 @@ data Flags = , _docs :: Maybe FilePath , _noWire :: Bool -- @LAMDERA , _optimizeLegible :: Bool -- @LAMDERA + , _esm :: Bool -- @LAMDERA } @@ -69,11 +70,12 @@ type Task a = Task.Task Exit.Make a run :: [FilePath] -> Flags -> IO () -run paths flags@(Flags _ _ _ report _ noWire optimizeLegible) = +run paths flags@(Flags _ _ _ report _ noWire optimizeLegible esm) = do style <- getStyle report maybeRoot <- Stuff.findRoot Lamdera.onlyWhen noWire Lamdera.disableWire Lamdera.onlyWhen optimizeLegible Lamdera.enableLongNames + Lamdera.onlyWhen esm Lamdera.enableEsm Reporting.attemptWithStyle style Exit.makeToReport $ case maybeRoot of Just root -> runHelp root paths style flags @@ -81,7 +83,7 @@ run paths flags@(Flags _ _ _ report _ noWire optimizeLegible) = runHelp :: FilePath -> [FilePath] -> Reporting.Style -> Flags -> IO (Either Exit.Make ()) -runHelp root paths style (Flags debug optimize maybeOutput _ maybeDocs _ optimizeLegible) = +runHelp root paths style (Flags debug optimize maybeOutput _ maybeDocs _ optimizeLegible _) = BW.withScope $ \scope -> Stuff.withRootLock root $ Task.run $ do desiredMode <- getMode debug (optimize || optimizeLegible) @@ -304,6 +306,7 @@ parseOutput name | isDevNull name = Just DevNull | hasExt ".html" name = Just (Html name) | hasExt ".js" name = Just (JS name) + | hasExt ".mjs" name = Just (JS name) -- @LAMDERA | otherwise = Nothing @@ -334,11 +337,12 @@ isDevNull name = -- Clone of run that uses attemptWithStyle_cleanup run_cleanup :: IO () -> [FilePath] -> Flags -> IO () -run_cleanup cleanup paths flags@(Flags _ _ _ report _ noWire optimizeLegible) = +run_cleanup cleanup paths flags@(Flags _ _ _ report _ noWire optimizeLegible esm) = do style <- getStyle report maybeRoot <- Stuff.findRoot Lamdera.onlyWhen noWire Lamdera.disableWire Lamdera.onlyWhen optimizeLegible Lamdera.enableLongNames + Lamdera.onlyWhen esm Lamdera.enableEsm Reporting.attemptWithStyle_cleanup cleanup style Exit.makeToReport $ case maybeRoot of Just root -> runHelp root paths style flags diff --git a/test/Test/JsOutput.hs b/test/Test/JsOutput.hs index c055ef25..16d53eaf 100644 --- a/test/Test/JsOutput.hs +++ b/test/Test/JsOutput.hs @@ -36,6 +36,7 @@ suite = , _docs = Nothing , _noWire = True , _optimizeLegible = False + , _esm = False } fileContents <- readUtf8Text $ elmStuff ++ "/tmp.js" @@ -74,6 +75,7 @@ suite = , _docs = Nothing , _noWire = True , _optimizeLegible = False + , _esm = False } fileContents <- readUtf8Text $ elmStuff ++ "/tmp.js" @@ -123,6 +125,7 @@ suite = , _docs = Nothing , _noWire = True , _optimizeLegible = False + , _esm = False } fileContents <- readUtf8Text $ elmStuff ++ "/tmp.js" @@ -174,6 +177,7 @@ suite = , _docs = Nothing , _noWire = True , _optimizeLegible = False + , _esm = False } fileContents <- readUtf8Text $ elmStuff ++ "/tmp.js"