aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/PursLoader/Loader.purs
blob: e1b9e0f36fa8dede7c11121cb0a5232b1bd1501b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
module PursLoader.Loader
  ( Effects()
  , Effects_()
  , loader
  , loaderFn
  ) where

import Prelude (Unit(), ($), (>>=), (<$>), (<*>), (++), bind, const, id, pure, void, unit)

import Control.Apply ((*>))
import Control.Bind (join)
import Control.Monad.Eff (Eff(), foreachE)
import Control.Monad.Eff.Console (CONSOLE())
import Control.Monad.Eff.Exception (EXCEPTION(), Error(), error, message)

import Data.Array ((!!))
import Data.Bifunctor (lmap)
import Data.Either (Either(..), either)
import Data.Foreign.Class (read)
import Data.Function (Fn2(), mkFn2)
import Data.Maybe (Maybe(), maybe)
import Data.Nullable (toMaybe)
import Data.String.Regex (Regex(), match, noFlags, regex)

import Node.Encoding (Encoding(UTF8))
import Node.Process (stderr)
import Node.Stream (writeString)

import Unsafe.Coerce (unsafeCoerce)

import PursLoader.LoaderRef
  ( AsyncCallback()
  , LoaderRef()
  , Loader()
  , async
  , cacheable
  , query
  , clearDependencies
  , addDependency
  , resourcePath
  )

import PursLoader.Debug (debug)
import PursLoader.LoaderUtil (parseQuery)
import PursLoader.Options (Options(..))
import PursLoader.Path (dirname, relative)
import PursLoader.Plugin as Plugin

type Effects eff = (console :: CONSOLE, err :: EXCEPTION | eff)

type Effects_ eff = Effects (loader :: Loader | eff)

loader :: forall eff. LoaderRef -> String -> Eff (Effects_ eff) Unit
loader ref source = do
  callback <- async ref

  cacheable ref

  debug "Invoke PureScript plugin compilation"

  pluginContext.compile (compile callback)
  where
  pluginContext :: Plugin.Context (Effects_ eff)
  pluginContext = (unsafeCoerce ref).purescriptWebpackPluginContext

  compile :: AsyncCallback (Effects eff) -> Plugin.Compile (Effects_ eff)
  compile callback error' { srcMap, ffiMap, graph, output } = do
    clearDependencies ref

    either (const $ pure unit) (\a -> debug ("Adding PureScript dependency " ++ a)) name

    addDependency ref (resourcePath ref)

    void $ writeString stderr UTF8 output (pure unit)

    maybe (pure unit) (\a -> void $ writeString stderr UTF8 (message a) (pure unit)) (toMaybe error')

    either (const $ callback (pure fixedError) "") id
           (handle <$> name <*> dependencies <*> exports)
    where
    fixedError :: Error
    fixedError = error "PureScript compilation has failed."

    handle :: String -> Array String -> String -> Eff (Effects_ eff) Unit
    handle name' deps res = do
      debug ("Adding PureScript transitive dependencies for " ++ name')
      addTransitive name'
      foreachE deps addTransitive
      debug "Generated loader result"
      debug res
      callback (const fixedError <$> toMaybe error') res

    exports :: Either Error String
    exports = (\a b -> "module.exports = require('" ++ a ++ "')['" ++ b ++ "'];") <$> path <*> name

    dependencies :: Either Error (Array String)
    dependencies = name >>= Plugin.dependenciesOf graph

    addTransitive :: String -> Eff (Effects_ eff) Unit
    addTransitive dep = addDep (Plugin.get srcMap dep) *> addDep (Plugin.get ffiMap dep)
      where
      addDep :: Maybe String -> Eff (Effects_ eff) Unit
      addDep = maybe (pure unit) (addDependency ref)

    name :: Either Error String
    name =
      maybe (Left $ error "Failed to parse module name") Right
            (join $ match re source >>= \as -> as !! 1)
      where
      re :: Regex
      re = regex "(?:^|\\n)module\\s+([\\w\\.]+)" noFlags { ignoreCase = true }

    path :: Either Error String
    path = (\(Options opts) -> relative resourceDir opts.bundleOutput) <$> options
      where
      options :: Either Error Options
      options =
        lmap (const $ error "Failed to parse loader query")
             (read $ parseQuery (query ref))

      resourceDir :: String
      resourceDir = dirname (resourcePath ref)

loaderFn :: forall eff. Fn2 LoaderRef String (Eff (Effects_ eff) Unit)
loaderFn = mkFn2 loader