Fable
Fable copied to clipboard
Differentiating development and production builds from F# code
Description
I'm looking for a way to alter my Fable application depending on whether it is being used in a development or production context. In my case, I want to use a different service implementation during development, but another example might be including a debug page only in development builds.
Importantly, I need to ensure the development-only code is not bundled in the production build. Therefore, simple config parameters will not suffice.
I cannot find any documentation on how to achieve this, perhaps using compilation symbols (e.g. FABLE_COMPILER_DEVELOPMENT
) or the likes. Any help appreciated.
Hi @kentcb! We usually use the DEBUG
symbol for these purposes. By default, Fable will use DEBUG
for watch compilations but if necessary you can define your own symbols with --define
or a configuration as with dotnet builds (e.g. -c Debug
or -c Release
).
For example, this is how it's used in Elmish.HMR so HMR is only activated in development builds: https://github.com/elmish/hmr/blob/a87be3c097237eea136312e3e3e6cc5c739e4b14/src/hmr.fs#L182-L187
Note:
DEBUG
being used in packages is a Fable-unique feature, given packages a compiled from sources too, it won't work in dotnet builds.
Thanks @alfonsogarciacaro. Hmm, I had tried that and it looks like it will work from an IDE perspective (relevant code is dimmed), but Fable still bundles the code wrapped in #if DEBUG
. Maybe there's something funky going on with our setup - I'll dig deeper and get back to you.
@alfonsogarciacaro Yeah, I'm just not sure what's going on yet. I can see from the Fable compiler source how it chooses a configuration, can confirm from build output that it is indeed Release, my configurations look correct (Release
only defines RELEASE
, definitely not DEBUG
symbol), but I'm ending up with my #if DEBUG
code in the output anyway. I tried --no-cache
but didn't help.
If I run with --verbose
I see this in my output:
F# PROJECT: src\App.fsproj
--define:TRACE
--define:DEBUG
--define:NET
--define:NET6_0
<snip>
But have not been able to figure out why DEBUG
is being defined yet. Unfortunately, it's a little convoluted how we run Fable (you know) and I've not been able to just run it directly yet (getting errors for reasons I don't understand). So may pick this up Monday.
Hello @kentcb,
How are you invoking Fable ?
- If you are using Fable 3 can you please share the CLI invoked.
- If you are using Fable 2 can you please share the CLI + webpack (or any bundler) configuration
Can you please check that you didn't set DEBUG
configuration inside of your *.fsproj
?
Hi @MangelMaxime. It's Fable 3.7.11, invoked as:
dotnet fable C:\<redacted>\src -o C:\<redacted>\.build\web\fable --exclude FablePlugins --precompiledLib C:\<redacted>\.build\web\fable --noParallelTypeCheck -s --watch --watchDelay 500 --define <redacted>_PLATFORM_IS_WEB --run C:\<redacted>\node_modules\.bin\webpack-dev-server --config C:\<redacted>\webpack.config.js
Can you please check that you didn't set DEBUG configuration inside of your *.fsproj?
Yeah, definitely haven't done that. We do have a custom configuration, but I had also tried removing that and going with defaults (Debug/Release) to no avail. Unfortunately, got pulled onto higher urgency stuff for the next day or so, but will definitely getting back to this when I can.
Sorry, totally screwed up that fable command in my haste. Will get back to you once production fires are out.
Here is the corrected command:
dotnet fable C:\<redacted>\src -o C:\<redacted>\.build\web\fable --exclude FablePlugins --verbose --precompiledLib C:\<redacted>\.build\web\fable --noCache --define <redacted>_PLATFORM_IS_WEB --run C:\<redacted>\node_modules\.bin\webpack --config C:\<redacted>\webpack.config.js
OK, got a chance to debug through and can see that the DEBUG
symbol must be defined somewhere in one of our reference projects:
Will need to go through them one-by-one to find which is the culprit.
I don't think it's ideal that Fable behaves like this, but not sure if it's as simple as ignoring DEBUG
symbol for production builds.
OK, the project causing the issue is multi-targeting:
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
This is causing Dotnet.ProjInfo.Inspect.getProjectInfos
to go awry, returning --define:DEBUG
in its args. If I change the project file to:
<TargetFramework>net6.0</TargetFramework
I no longer see --define:DEBUG
coming through (yet to do a full run though because we have other multi-targeting projects). Interestingly, even if I multi-target one framework it returns --define:DEBUG
:
<TargetFrameworks>net6.0</TargetFrameworks>
Confirmed. If I remove the multi-targeting and build, my #if DEBUG
code blocks are not in the bundle. I don't have the time to debug Dotnet.ProjInfo right now, but completely understand if no one else does either, in which case I'll try to get to it sometime in the next couple of weeks.
Oh my, I would have probably never caught this! Thanks a lot for your investigation @kentcb!
It's very unfortunate the bug is coming from Dotnet.Projinfo. It's been working well but it's been a while since the project is not maintained. I've tried a couple of times to upgrade to Ionide.ProjInfo but I always encountered issues (don't remember exactly now). We could give it a new try or as a last resort, put the code from Dotnet.ProjInfo in Fable's repo and make the necessary fixes (although this increases the maintenance load for Fable).
I've taken a shot at upgrading to Ionide.ProjInfom that may work, but it's largely untested at the moment. #3097
This great @IanManske, thanks a lot! I'll take a look 👍 It's possible that we need to retarget the PR to snake_island because we want to release Fable 4 soon as it's getting difficult to keep the branch in sync with main, but I can do that :)
@alfonsogarciacaro Sounds good! I'll work on retargeting to snake_island.
Fable 4 has been released and the project cracker layer has been completed re-done.
Can you please check if the problem is fixed fine with the latest Fable 4 version?