Implemented basic importing of wasp code from external code.

This commit is contained in:
Martin Sosic 2020-01-21 19:34:59 +01:00
parent c8c7e0a1e4
commit 17a82848df
11 changed files with 169 additions and 55 deletions

View File

@ -0,0 +1,28 @@
import React from 'react'
import NewTaskForm from '@wasp/entities/task/components/NewTaskForm'
import TaskList from '@wasp/entities/task/components/List'
import * as config from './config'
export default class Todo extends React.Component {
render = () => {
return <div className="mainContainer">
<h1> { config.appName } </h1>
<NewTaskForm
onCreate={task => this.props.addTask(task)}
submitButtonLabel={'Create new task'}
/>
<div className="taskListContainer">
<TaskList editable />
</div>
<div className="footer">
{ this.props.taskList.filter(task => !task.isDone).length } items left
</div>
</div>
}
}

View File

@ -1,4 +0,0 @@
export const hello = () => {
return "Hello!"
}

View File

@ -1,6 +1,6 @@
// Goal of this file is to re-create a TODO app from http://todomvc.com
import * as config from "config" // Imports from external code dir (src/).
import Todo from "Todo" // Imports from external code dir (src/).
// -- Entities
entity Task {
@ -34,22 +34,10 @@ page Main {
css=},
content: {=jsx
<div className="mainContainer">
<h1> { config.appName } </h1>
<NewTaskForm
onCreate={task => this.props.addTask(task)}
submitButtonLabel={'Create new task'}
/>
<div className="taskListContainer">
<TaskList editable />
</div>
<div className="footer">
{ this.props.taskList.filter(task => !task.isDone).length } items left
</div>
</div>
<Todo
addTask={this.props.addTask}
taskList={this.props.taskList}>
</Todo>
jsx=}
}

View File

@ -50,6 +50,7 @@ library:
- split
- unordered-containers
- path
- regex-compat
executables:
stic-exe:

View File

@ -1,48 +1,30 @@
module Generator.ExternalCode
( generateExternalCodeDir
, externalCodeDirPathInSrc
) where
import System.FilePath (takeExtension)
import qualified System.FilePath as FP
import qualified Path
import Path ((</>), reldir)
import qualified Path.Aliases as Path
import CompileOptions (CompileOptions)
import qualified CompileOptions
import Wasp (Wasp)
import qualified Wasp
import qualified Generator.FileDraft as FD
import qualified Generator.Common as Common
import qualified ExternalCode
import qualified Generator.FileDraft as FD
import qualified Generator.ExternalCode.Common as Common
import Generator.ExternalCode.Js (generateJsFile)
externalCodeDirPathInSrc :: Path.RelDir
externalCodeDirPathInSrc = [reldir|ext-src|]
generateExternalCodeDir :: CompileOptions -> Wasp -> [FD.FileDraft]
generateExternalCodeDir compileOptions wasp =
map (generateFile compileOptions) (Wasp.getExternalCodeFiles wasp)
-- | Returns path relative to the root of the generated project.
getExtCodeFileDstPath :: ExternalCode.File -> Path.RelFile
getExtCodeFileDstPath file = Common.srcDirPath </> externalCodeDirPathInSrc </>
ExternalCode.getFilePathInExtCodeDir file
getExtCodeFileSrcPath :: CompileOptions -> ExternalCode.File -> Path.AbsFile
getExtCodeFileSrcPath compileOptions file = CompileOptions.externalCodeDirPath compileOptions </>
ExternalCode.getFilePathInExtCodeDir file
generateFile :: CompileOptions -> ExternalCode.File -> FD.FileDraft
generateFile compileOptions file
| extension `elem` ["js", "jsx"] = generateJsFile file
| otherwise = FD.createCopyFileDraft (getExtCodeFileDstPath file) (getExtCodeFileSrcPath compileOptions file)
| extension `elem` [".js", ".jsx"] = generateJsFile file
| otherwise = FD.createCopyFileDraft (Common.getExtCodeFileDstPath file)
(Common.getExtCodeFileSrcPath compileOptions file)
where
extension = takeExtension $ Path.toFilePath $ getExtCodeFileSrcPath compileOptions file
-- TODO: Now here we do preprocessing!
generateJsFile :: ExternalCode.File -> FD.FileDraft
generateJsFile file = FD.createTextFileDraft (getExtCodeFileDstPath file) (ExternalCode.getFileText file)
extension = FP.takeExtension $ Path.toFilePath $ Common.getExtCodeFileSrcPath compileOptions file

View File

@ -0,0 +1,31 @@
module Generator.ExternalCode.Common
( externalCodeDirPathInSrc
, getExtCodeFileDstPath
, getExtCodeFileSrcPath
) where
import Path ((</>), reldir)
import qualified Path.Aliases as Path
import CompileOptions (CompileOptions)
import qualified CompileOptions
import qualified ExternalCode
import qualified Generator.Common as Common
externalCodeDirPathInSrc :: Path.RelDir
externalCodeDirPathInSrc = [reldir|ext-src|]
-- | Returns path where external code file will be generated,
-- relative to the root of the generated project.
getExtCodeFileDstPath :: ExternalCode.File -> Path.RelFile
getExtCodeFileDstPath file = Common.srcDirPath </> externalCodeDirPathInSrc </>
ExternalCode.getFilePathInExtCodeDir file
-- | Returns absolute path of the original external code file.
getExtCodeFileSrcPath :: CompileOptions -> ExternalCode.File -> Path.AbsFile
getExtCodeFileSrcPath compileOptions file = CompileOptions.externalCodeDirPath compileOptions </>
ExternalCode.getFilePathInExtCodeDir file

View File

@ -0,0 +1,40 @@
module Generator.ExternalCode.Js
( generateJsFile
-- FOR TESTING:
, resolveJsFileWaspImports
) where
import qualified Text.Regex as TR
import Data.Text (Text, pack, unpack)
import Path ((</>))
import qualified Path
import qualified Path.Aliases as Path
import qualified Path.Extra as Path
import qualified Generator.FileDraft as FD
import qualified ExternalCode
import qualified Generator.ExternalCode.Common as Common
generateJsFile :: ExternalCode.File -> FD.FileDraft
generateJsFile file = FD.createTextFileDraft (Common.getExtCodeFileDstPath file) text'
where
text = ExternalCode.getFileText file
text' = resolveJsFileWaspImports jsFilePathInSrcDir text
jsFilePathInSrcDir = Common.externalCodeDirPathInSrc </>
ExternalCode.getFilePathInExtCodeDir file
-- | Takes a file path where the external code js file will be generated, relative to generated src dir.
-- Also takes text of the file. Returns text where special @wasp imports have been replaced with
-- imports that will work.
-- TODO: I had hard time finding popular libraries for more advanced replacements, so for now
-- I just used very simple regex replacement, which might not work in some complicated situations
-- (it will also match on commens and strings and similar).
-- For the future, we should probably use some kind of better regex or even some kind of parser.
-- Possible candidates: replace-attoparsec.
resolveJsFileWaspImports :: Path.RelFile -> Text -> Text
resolveJsFileWaspImports jsFilePathInSrcDir jsFileText = pack $
TR.subRegex (TR.mkRegex "(from\\s+['\"])@wasp/")
(unpack jsFileText)
("\\1" ++ Path.reversePath (Path.parent jsFilePathInSrcDir) ++ "/")

View File

@ -14,12 +14,13 @@ import qualified System.FilePath as FP
import Path ((</>), relfile, reldir)
import qualified Path
import qualified Path.Aliases as Path
import qualified Path.Extra as Path
import qualified Util
import Wasp
import Generator.FileDraft
import qualified Generator.Entity as EntityGenerator
import Generator.ExternalCode (externalCodeDirPathInSrc)
import Generator.ExternalCode.Common (externalCodeDirPathInSrc)
import qualified Generator.Common as Common
@ -81,16 +82,11 @@ generatePageComponent wasp page = createTemplateFileDraft dstPath srcPath (Just
(fromJust $ Path.parseRelFile $ jsImportFrom jsImport))
]
-- | NOTE: If you modify this value, make sure to also accordingly update relPathFromPageToSrc.
-- For example, if pageDirPathInSrc = "foo/bar", then relPathFromPageToSrc should be "../../".
pageDirPathInSrc :: Path.RelDir
pageDirPathInSrc = [reldir|.|]
-- | Relative path from page to the /src directory.
-- It is the opposite of pageDirPathInSrc and should be updated together with it.
-- TODO: We could deduce this directly from the pageDirPathInSrc instead of hardcoding it.
-- NOTE: We are using FilePath instead of Path here because that allows us to assign values like "../".
relPathFromPageToSrc :: FilePath
relPathFromPageToSrc = "."
relPathFromPageToSrc = Path.reversePath pageDirPathInSrc
-- | Takes path relative to the src path of generated project and turns it into relative path that can be
-- used as "from" part of the import in the Page source file.

View File

@ -1,7 +1,9 @@
module Path.Extra
( (./)
, reversePath
) where
import Control.Exception (assert)
import System.FilePath as FP
import Path
@ -11,3 +13,17 @@ import Path
-- is to have it as a FilePath.
(./) :: Path Rel a -> FP.FilePath
(./) relPath = "." FP.</> (toFilePath relPath)
-- | For given path P, returns path P', such that (terminal pseudocode incoming)
-- `pwd == (cd P && cd P' && pwd)`, or to put it differently, such that
-- `cd P && cd P'` is noop (does nothing).
-- It will always be either "." (only if input is ".") or a series of ".."
-- (e.g. reversePath [reldir|foo/bar|] == "../..").
reversePath :: Path Rel Dir -> FilePath
reversePath path
| length parts == 0 = "."
| otherwise = assert (not (".." `elem` parts)) $
FP.joinPath $ map (const "..") parts
where
parts :: [String]
parts = filter (not . (== ".")) $ FP.splitDirectories $ toFilePath path

View File

@ -0,0 +1,19 @@
module Generator.ExternalCode.JsTest where
import Test.Tasty.Hspec
import Path (relfile)
import Generator.ExternalCode.Js
spec_resolveJsFileWaspImports :: Spec
spec_resolveJsFileWaspImports = do
([relfile|ext-src/extFile.js|], "import foo from 'bar'") ~> "import foo from 'bar'"
([relfile|ext-src/extFile.js|], "import foo from '@wasp/bar'") ~> "import foo from '../bar'"
([relfile|ext-src/a/extFile.js|], "import foo from \"@wasp/bar/foo\"") ~>
"import foo from \"../../bar/foo\""
where
(path, text) ~> expectedText =
it ((show path) ++ " " ++ (show text) ++ " -> " ++ (show expectedText)) $ do
resolveJsFileWaspImports path text `shouldBe` expectedText

17
test/Path/ExtraTest.hs Normal file
View File

@ -0,0 +1,17 @@
module Path.ExtraTest where
import Test.Tasty.Hspec
import Path (reldir)
import qualified Path.Extra as PE
spec_reversePath :: Spec
spec_reversePath = do
[reldir|.|] ~> "."
[reldir|foo|] ~> ".."
[reldir|foo/bar|] ~> "../.."
[reldir|./foo/bar/./test|] ~> "../../.."
where
path ~> expectedReversedPath = it ((show path) ++ " -> " ++ expectedReversedPath) $ do
PE.reversePath path `shouldBe` expectedReversedPath