-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Validates tailwindcss
if Tailwind is used
#2465
base: main
Are you sure you want to change the base?
Conversation
@@ -149,10 +148,6 @@ resolveRef spec ref = | |||
$ find ((== refName ref) . fst) $ | |||
getDecls spec | |||
|
|||
doesConfigFileExist :: AppSpec -> Path' (Rel WaspProjectDir) File' -> Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I needed the information if Tailwind is used (which is computed using doesConfigFileExist
) before we have the AppSpec so I pulled it out of here and rewritten it so it doesn't need the AppSpec.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is it that you needed it before?
Are we getting into a situtation where we need a lot of info from AppSpec before AppSpec exists, pointing at some potential design issue, or do you think this is ok?
|
||
-- | Establishes the mapping of what config files to copy and where from/to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ormolu
doesConfigFileExist tailwindConfigFile | ||
&& doesConfigFileExist postcssConfigFile | ||
where | ||
doesConfigFileExist :: Path' (Rel WaspProjectDir) File' -> Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is only used for isTailwindUsed
so I inlined it even though it might be useful in the future - let's pull it out when it becomes necessary?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you know it is a general utility, I would pull it out immediately, since then it is easier for somebody else to see it there and use it. If it was more tied down to this specific place then mabye not, but it seems quite ready to isolate right now, and I can easily imagine somebody else will want to check if certain congfi file exists, so yeah, let's go for it right now.
validatePackageJson :: P.PackageJson -> [ErrorMsg] | ||
validatePackageJson packageJson = | ||
validatePackageJson :: Bool -> P.PackageJson -> [ErrorMsg] | ||
validatePackageJson isTailwindUsed packageJson = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to thread through the isTailwindUsed
info unfortunately. Some sort of Reader would be useful here, but I didn't want to over-engineer it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get what you mean, and I agree it would be much going for Reader now.
That said, having just a Bool like this is not very nice, what if we add one or two more conditionals like this, would we have 2 more arguments? I would immediately go for grouping this into a record, even it if has just one field for now. Might be called something like ValidationConfig
or ValidationPreconditions
or ValidationContext
(I like that one!) or whatever you think is the best name. That will also make moving to Reader easier in the future if we want it, since you would just move that record into Reader. But it will also make this function immediately more readable and extendable.
waspc/src/Wasp/Project/Analyze.hs
Outdated
@@ -61,25 +66,25 @@ analyzeWaspProject waspDir options = do | |||
analyzeWaspFile waspDir prismaSchemaAst waspFilePath >>= \case | |||
Left errors -> return (Left errors, []) | |||
Right declarations -> | |||
EC.analyzeExternalConfigs waspDir (getSrcTsConfigInWaspProjectDir waspFilePath) >>= \case | |||
EC.analyzeExternalConfigs isTailwindUsed waspDir (getSrcTsConfigInWaspProjectDir waspFilePath) >>= \case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wasn't sure if I wanted to thread the configFiles
all the way down to validatePackageJson
and the call the G.CF.isTailwindUsed
check. Since validatePackageJson
doesn't need the list of configs, I decided not to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm maybe they don't need it all, but this function is called analyzeExternalConfigs
and now it takes a bool, waspDir, and then a specific config file (srcTsConfig). That is now quite messy, because you could look at those args as:
arg#1: info about config file
arg#2: context
arg#3: config file
Usually in Haskell we aim for "context" args at start, and then "core" args after that.
Since this function is about config files, any amount of them, then I would also find it more natural that it takes a list or record of config files, note one per argument.
So what I would consider is turning this function's signature into :: WaspDir -> ConfigFiles -> ReturnType
(I used a bit pseudo names here).
And it's not a problem if it takes a bit too much info -> it will analyze what it needs to analyze. We don't want to guess how it will analyze them, that is implementation detail.
where | ||
validate = validateDep packageJson | ||
|
||
tailwindValidations = | ||
[ validate ("tailwindcss", show tailwindCssVersion) IsListedWithExactVersion | isTailwindUsed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Tailwind is used, validate that the tailwindcss
package is present in the package.json
with the correct version.
Signed-off-by: Mihovil Ilakovac <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All makes sense, but I believe we can improve data flow / function signatures a bit, left comments on that!
validatePackageJson :: P.PackageJson -> [ErrorMsg] | ||
validatePackageJson packageJson = | ||
validatePackageJson :: Bool -> P.PackageJson -> [ErrorMsg] | ||
validatePackageJson isTailwindUsed packageJson = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I get what you mean, and I agree it would be much going for Reader now.
That said, having just a Bool like this is not very nice, what if we add one or two more conditionals like this, would we have 2 more arguments? I would immediately go for grouping this into a record, even it if has just one field for now. Might be called something like ValidationConfig
or ValidationPreconditions
or ValidationContext
(I like that one!) or whatever you think is the best name. That will also make moving to Reader easier in the future if we want it, since you would just move that record into Reader. But it will also make this function immediately more readable and extendable.
waspc/src/Wasp/Project/Analyze.hs
Outdated
@@ -61,25 +66,25 @@ analyzeWaspProject waspDir options = do | |||
analyzeWaspFile waspDir prismaSchemaAst waspFilePath >>= \case | |||
Left errors -> return (Left errors, []) | |||
Right declarations -> | |||
EC.analyzeExternalConfigs waspDir (getSrcTsConfigInWaspProjectDir waspFilePath) >>= \case | |||
EC.analyzeExternalConfigs isTailwindUsed waspDir (getSrcTsConfigInWaspProjectDir waspFilePath) >>= \case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm maybe they don't need it all, but this function is called analyzeExternalConfigs
and now it takes a bool, waspDir, and then a specific config file (srcTsConfig). That is now quite messy, because you could look at those args as:
arg#1: info about config file
arg#2: context
arg#3: config file
Usually in Haskell we aim for "context" args at start, and then "core" args after that.
Since this function is about config files, any amount of them, then I would also find it more natural that it takes a list or record of config files, note one per argument.
So what I would consider is turning this function's signature into :: WaspDir -> ConfigFiles -> ReturnType
(I used a bit pseudo names here).
And it's not a problem if it takes a bit too much info -> it will analyze what it needs to analyze. We don't want to guess how it will analyze them, that is implementation detail.
waspc/src/Wasp/Project/Analyze.hs
Outdated
Psl.Schema.Schema -> | ||
[AS.Decl] -> | ||
IO (Either [CompileError] AS.AppSpec, [CompileWarning]) | ||
constructAppSpec waspDir options externalConfigs parsedPrismaSchema decls = do | ||
constructAppSpec waspDir options externalConfigs configFiles parsedPrismaSchema decls = do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What confuses me a bit is that this function takes both external configs and congif file relocators. Actually those two kind of make sense -> I guess it takes them, and then rules how to relocate them, I am not up to date with this piece of code completely so I am not quite sure -> but then for relocators you use configFiles
name and that confuses me absolutely, because you said relocators but now it is files.
@@ -23,11 +23,12 @@ data ExternalConfigs = ExternalConfigs | |||
deriving (Show) | |||
|
|||
analyzeExternalConfigs :: | |||
Bool -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, seeing a Bool like this, where I can't tell from signature at all what it could be, is a bit suspicious. I commented more above why this one is probably not the right choice here.
@@ -18,11 +18,11 @@ import Wasp.Project.Common | |||
) | |||
import qualified Wasp.Util.IO as IOUtil | |||
|
|||
analyzePackageJsonFile :: Path' Abs (Dir WaspProjectDir) -> IO (Either [String] P.PackageJson) | |||
analyzePackageJsonFile waspProjectDir = runExceptT $ do | |||
analyzePackageJsonFile :: Bool -> Path' Abs (Dir WaspProjectDir) -> IO (Either [String] P.PackageJson) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably rework this signature also, with similar approach as to how I suggested in other comments.
doesConfigFileExist tailwindConfigFile | ||
&& doesConfigFileExist postcssConfigFile | ||
where | ||
doesConfigFileExist :: Path' (Rel WaspProjectDir) File' -> Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you know it is a general utility, I would pull it out immediately, since then it is easier for somebody else to see it there and use it. If it was more tied down to this specific place then mabye not, but it seems quite ready to isolate right now, and I can easily imagine somebody else will want to check if certain congfi file exists, so yeah, let's go for it right now.
@@ -149,10 +148,6 @@ resolveRef spec ref = | |||
$ find ((== refName ref) . fst) $ | |||
getDecls spec | |||
|
|||
doesConfigFileExist :: AppSpec -> Path' (Rel WaspProjectDir) File' -> Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is it that you needed it before?
Are we getting into a situtation where we need a lot of info from AppSpec before AppSpec exists, pointing at some potential design issue, or do you think this is ok?
@Martinsos could you take another look? I've read through your comments and took another stab at the implementation, let me summarize my thought process and what I did. First, I'll jump into my thoughts about the way we support Tailwind and then I'll go into the
We were going for the magical DX where we install Tailwind dep in the background when we detect the Tailwind config files. Users didn't have to install the In general, I feel like the mechanism of configuring your Wasp project by Wasp detecting presence of certain files like Given that I think we should get rid of Also, I liked your suggestion to have a I've also tried to introduce the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@infomiho I think this is a step in right direction, but you might have went a bit too far with it.
The point of the "context" was to enable passing around some data without having to pass every single argument individually, where arguments were really implementation details of other, deeper layers of logic, specifically specific config files like tailwind and so on -> top-level external config files fuction doesn't want to list every congif file as its argument, but take them as one big data chunk that it can later dissect and delegate to smaller functions if it wants.
That was kind of the idea. Also you put in some stuff that might not even belong there, like waspDir -> I am not sure about that yet though. But I would say, look at the idea of "context" as a way to wire stuff through while controlling amount of information that different layers of logic need to know about, and also look at it more from the "site of call" then from its own position -> the context shouldn't be a standalone thing that is used in other places, conceptually, unless it is indeed super clear and standalone concept (which I don't think is true here), but instead should be kind of dependant / modeled by the usage site / consumer, in this case your validation logic in ExternalCOnfig module.
I commented more in the individual comments, read them all before you start acting on any of them, to get the full picture.
We can also discuss further if needed, if I was unclear about something -> I have a feeling here what is right / wrong, but I am a bit vague in telling you exactly what it should look like, because I don't have a clear picture myself yet, I would have to get my hands dirtier if I wanted to figure that out exactly.
WaspProjectDir, | ||
) | ||
|
||
data ExternalConfigAnalysisContext = ExternalConfigAnalysisContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I was suggesting implementing ValidationContext
, I was kind of expecting it to be coming from the side of validation code, where it is used -> tailored for what it needs. It seems to me you however went in a bit different direction -> you created a general context object that contains information that somebody analysing external config files might need. Which means you are guess, from the perspective of ExternalConfig, what the analysis of them might need. That might befine, but it is a bit unusual / different, it feels like you might be trying to guess too much about their usage, and therefore that will result in different parts of the code all using this Context while it is not really tailored for any of them specifically.
Just to be clear, what I imagined was that you have ValidationContext
which has just isTailwindUsed
in it and that is it, IF that is all you need in the validation part of the code.
waspDir being here for example is a bit weird. That is such a super general information, it is not related to external configs only and I could imagine most of the funcitons already having it in their context, meaning you are likely passing it twice. While _srcTsConfigPath
feels better. Relocators feel also a bit weird since it seems to me they are more often the object of manipulation than a contextual thing -> I might be wrong on that one though, I could see how they could be part of the context. But that is the issue -> here we are guessing what somebody might need. I don't think ExternalConfig module should be troubling itself with that, it feels wrong.
) | ||
import qualified Wasp.Project.ExternalConfig.ExternalConfigAnalysisContext as ECC | ||
import qualified Wasp.Util.IO as IOUtil | ||
import Wasp.Util.Json (parseJsonWithComments) | ||
|
||
analyzeSrcTsConfigFile :: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So here, I don't think you gained much by using the Context data type -> arguments passed seemed not likely to change, and were reasonable for the situation -> it takes waspDir, and it takes srcTsConfigFile, which is the very object of this function. Now you put the main argument, srcTsConfigFile, into Context, which is weird. If anything should go into context, it is waspDir only, but even that is not needed in this case I think.
|
||
-- | Establishes the mapping of what config files to copy and where from/to. | ||
-- Establishes the mapping of what config files to copy and where from/to. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This comment is outdated then since this is only about tailwind now?
isTailwindUsed spec = | ||
doesConfigFileExist spec tailwindConfigFile | ||
&& doesConfigFileExist spec postcssConfigFile | ||
isTailwindUsed :: [CF.ConfigFileRelocator] -> Bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This list of config file relocators is a bit confusing argument here, compared to previous app spec -> before I would get app spec and this function would tell me if tailwind is used. OK. Now, it gets a list of config file relocators which is kind of an impelmentation detail and then tells me based on that if it is used. I guess then it assumes that tehse are all the confg file relocators there are out there? I mean, it is just a list, it could contain anything, you didn't specify any expectations here. Feels like some additional instructions are missing, if used like this. Also feels like maybe this is not the rigth way to go, but I am not sure without digging deeper.
|
||
validatePackageJson :: P.PackageJson -> [ErrorMsg] | ||
validatePackageJson packageJson = | ||
validatePackageJson :: ECC.ExternalConfigAnalysisContext -> P.PackageJson -> [ErrorMsg] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok yeah, this context makes kind of sense here. Providnig waspDir via it still feels a bit weird, but the rest is ok I believe. But here we are in ExternalConfig module, and that ExternalConfigAnalysisContext is really crafted directly for this place of usage. Which i think makes sense. But then I wouldn't use it in othe rplaces also, especially oustide of ExternalConfig module, because it is too specific for those places. I would even consider moving the data type directly here into this file, and have it be local here in a way.
_decls = declarations | ||
} | ||
|
||
data AppSpecConstructionContext = AppSpecConstructionContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't call this Context -> Context is supporting data for the "main" data. Well, context. This is the "meat", all that is needed to construct the app spec. Context might be just part of it -> waspDir, compileOptions, ... . Maybe everything expect for decls? Because decls are the main part for sure. Maybe also prisma schema should not be in context.
But here you don't gain much with this data type all together, so I probably wouldn't do it at all, no data AppSpecConstructionContext
-> it is anyway not something you will be sending around. Let's put it this way -> there isn't enough reason to create it. If there was, it would be clearer.
But if you need to validate the package.json and tsconfig.json files before generating the project, you could also do that after the construction of AppSpec and still before the generation of the project, that is our Validate.hs step. Or is there some other reason to do it before? |
For more details: #2464