Aug 23, 2019

Be schizophrenic at debugging SPFx too ! (Edge Canary - revision)

Hi SharePoint devs,

A few months ago, I wrote this article about being able to manage your user profiles for SPFx debugging purposes using VSCode and Google Chrome. You might have heard about this new revamp of Microsoft Edge now based on Chromium (the Open Source core of Google Chrome), It is currently still in preview under the codename "Canary". So far, I haven't ran into any major issue using it and, I really like it, the best of what I liked in Chrome but Microsoft oriented. I dream about when it will become the standard browser in corporations ! Anyway, since it is based on Chromium, it brings the awesome feature of profiles which I think we probably are all using in Chrome ! That means we can leverage the same kind of tricks to use it as browser when debugging with VSCode ! In order to be as concrete as possible, I copy pasted the section of my previous article here under and will update the relevant pieces according to Edge Canary

The idea : Multiple profiles for debugging in various user scenarios

On the Microsoft Docs, you can find a procedure that explains how to setup a debugging environment using VSCode and Chrome (or Edge).

The technique explained further relies on Edge Canary profiles so, basically, we can follow either Chrome or Edge procedure. If you have never seen this article before,  reading it will help you better understand what follows. The important part here is that you install the "Debugger for Edge" extension that supports both EdgeHTML and Chromium variants of Edge Basically, in .vscode\launch.json file, you can add and edit debug configurations, by default you have 2 entries "Local Workbench" to debug locally and "Hosted Workbench" to debug your solution within the Workbench hosted on SharePoint (so you can really interact with SharePoint APIs)

debug_workbench

First we need to run gulp serve --nobrowser in order to serve locally the solution to debug. If you give that "Hosted Workbench" a try (you need to select it in the debug pane) and press F5 or click the "Play" button in VSCode. Chrome opens up in incognito mode, and obviously prompts you for logging in.

incognito_login

Since you are in incognito mode, none of your preferences, sessions, history will be kept, which means, that when you stop debugging and restart, you have once again to log in.

Hey, aren't we supposed to talk about Edge Canary ?

You're right, let's switch to it right now !

canary-profiles

In my Microsoft Edge canary settings, I have a bunch of profiles I use for my various accounts, They keep persisted sessions, cookies, passwords, etc... First of all, we will change our configuration to use Microsoft Edge Canary instead. In order to do that, in the .vscode\launch.json file replace the type value of the debug configuration you want from "chrome" to "edge", but this will make you use the EdgeHTML version (the initial version of Edge), to use the Canary version, you will need to set also another version property set to "canary". Maybe I missed an important part... make sure you have Microsoft Edge Canary version properly installed :D ! You can get it there https://www.microsoftedgeinsider.com/en-us/download

You will also have to update the remote debug port to 2015 that is the one used by edge and just FYI, the -incognito option switch of Google Chrome is now called -inPrivate but we won't use it !

The first update of a debug configuration in the launch.json file should look like

{
 "name":"Hosted workbench",
 "type":"edge",
 "version":"canary",
 "request":"launch",
 "url":"https://tenant.sharepoint.com/sites/labs/_layouts/workbench.aspx",
 "webRoot":"${workspaceRoot}",
 "sourceMaps":true,
 "sourceMapPathOverrides":{
 "webpack:///.././src/*":"${webRoot}/src/*",
 "webpack:///../../../src/*":"${webRoot}/src/*",
 "webpack:///../../../../src/*":"${webRoot}/src/*",
 "webpack:///../../../../../src/*":"${webRoot}/src/*"
 },
 "runtimeArgs": [
 "--remote-debugging-port=2015",
 "-inPrivate"
 ] 
}

Okay, now we can run Edge Canary as our browser for debugging, but you already probably noticed that every time you start a new debug "session", you loose every preferences and settings ?

The Chrome Edge Canary (Chromium) User Data Directory

Edge Canary keeps all those profiles information in the local app data of your user %USERPROFILE%\AppData\Local\Microsoft\Edge SxS\User Data

default_canary_userdata

Yes, they are all named "Profile X" but they really contain the data of the profiles I gave names to in Chrome Edge Canary. So why can't we see them when debugging with VS Code ? If you check out the other possible entries of the config, you'll see something interesting !

launch_json_userdatadir

This userDataDir property default value is true which means VSCode will always recreate a temporary user directory and not use the one in your AppData. Let's play with this setting, let's try with a value set to false. Hey that seems to work, it opens up a new tab in my current Chrome Edge Canary session ! But wait... Yeah, that was too easy, after 10 secs, I get the following in VS Code

debug_connect_error_vscode

After a bit of searching, I learned it is actually some kind of limitation in Chromium, the --remote-debugging option cannot be used on the default Chromium "scope" if any browser window has been previously opened... Well, that is most likely the case, except if you use a different browser to read documentations etc...

So we cannot use our usual Edge Canary profiles

Basically, that's correct, we cannot use our default Chrome profiles configuration for debugging, at least, not easily... I guess it would be possible to hack the config files and copy/paste the profile directories... But I would not do that because I am pretty sure it will, one way or another, ruin my existing configuration :D Okay, but can't we have another persisted configuration at least ? YES WE CAN ! And that is exactly what we are going to do, the userDataDir option can actually take 3 types of value:

  • true: Use the "global" Chrome user data directory
  • false: Recreate a temporary user data directory
  • string: We can also specify the path of a folder to use as the user data directory.

Two approaches are now possible:

  • Either you create a folder within your solution with configuration dedicated to it (I did not really went deeper that way because in my cases, I want to reuse my profiles in different solutions, but I would think it might be a valid approach as well)
  • Use a shared folder that we can use in different solutions

Let's go for the shared folder.

Initialize a new user data (and profiles)

From a cmd prompt, type, for instance, the following command

start msedge --user-data-dir="C:\\Workspace\\tools\\edge-canary-dev-userdata"

You might probably want to update the path C:\Workspace\tools\edge-canary-dev-userdata by something that is relevant on your computer You can specify whatever folder path you want (even not existing), you can see a new Edge Canary instance opening, you can also check the path you specified to see it has been initialized with a bunch of files. In that new instance, you can manage profiles and create the ones you want. You can also already login to your tenant using the different user profiles you want or prepare any profile specific configuration !

console_start_new_canary edge_canary_config02 debug_with_userdir

We can now set the userDataDir value to the path of our new folder, hit F5 and ... Cool ! that works, it takes the context with my created profiles. Let's try to go a bit further !

Specify profile in debug config

It is actually possible to specify the profile to use in the Chrome command line using the --profile-directory=Profile 3 switch. As you can see you have to specify the name of the folder which is not the name you gave to the profiles, By exploring them, you probably can determine what folder matches what profile. An easiest way is to check out the _Local State _file in the new User Data Dir that is a JSON file that contains many config entries as well as the profiles. It will be trimmed and most likely unreadable, you will need to apply some formatting (e.g. using VS code built-in JSON formatting). You can also use this little PowerShell script I wrote that will return the matching values. It takes a single argument that is the path to the user data directory, again, by default I set some value that is relevant on my own machine. Feel free to update it according to your needs!

param(     [string]$UserDataDir="C:\workspace\tools\edge-canary-dev-userdata" )  $localStateJson = Get-Content "$UserDataDir\Local State" $localState = $localStateJson | ConvertFrom-Json $profileKeys = $localState.profile.info_cache | Get-Member | ? {$_.MemberType -eq "NoteProperty"} $infoCache = $localState.profile.info_cache  $allEntries = @() ForEach($key in $profileKeys) {     $profile = $key.Name     $profileDisplayName = $infoCache."$profile".name     $newEntry = @{         Profile=$profile;         Name=$profileDisplayName;     };       $allEntries += $newEntry }  $allEntries

And get the following result

mapping_profiles

See how I can extract values from a complex JSON file ? Oh God, I LOVE PowerShell, let's do some more ! Put the following script in the same folder as the previous one

param(
    [string]$ConfigBaseName = "Dev Profile ",
    [string]$Url = "https://your.sharepoint.com/sites/test/_layouts/workbench.aspx",
    [string]$UserDataDir = "C:\workspace\tools\edge-canary-dev-userdata"
)

# Get the profiles
$profiles = .\Get-EdgeCanaryVSCodeDebugProfiles.ps1 -UserDataDir $UserDataDir

$allConfigs = @()
ForEach ($profile in $profiles) {
    $profileName = $profile.Name
    $profileKey = $profile.Profile
    $newLaunchConfig = New-Object -TypeName psobject
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "name" -Value $configBaseName$profileName
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "type" -Value "edge"
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "version" -Value "canary"
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "request" -Value "launch"
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "url" -Value $url
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "webRoot" -Value "`${workspaceRoot}"
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "sourceMaps" -Value $True
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "userDataDir" -Value $userDataDir
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "sourceMapPathOverrides" -Value @{
        "webpack:///.././src/*"           = "`${webRoot}/src/*";
        "webpack:///../../../src/*"       = "`${webRoot}/src/*";
        "webpack:///../../../../src/*"    = "`${webRoot}/src/*";
        "webpack:///../../../../../src/*" = "`${webRoot}/src/*";
    }
    $newLaunchConfig | Add-Member -MemberType NoteProperty -Name "runtimeArgs" -Value @( "--remote-debugging-port=2015", "--profile-directory=$profileKey")

    $allConfigs += $newLaunchConfig
}

$allConfigs | ConvertTo-Json

And call it, for instance, with these arguments:

.\GenerateDebugProfilesLaunchConfig-MSEdgeCanary.ps1 -ConfigBaseName "DevProfile " -Url https://tenant.sharepoint.com/sites/labs/SitePages/Lab-testing.aspx | Clip

Update the URL of your tenant and page (it can be the workbench or any other page), you'll notice this command output is piped to Clip, which means the result is automatically put into your clipboard. You then simply have to paste it into your launch.json file and save it, if you want to keep existing configuration as well, you will have to fix the JSON format

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Local workbench",
            "type": "chrome",
            "request": "launch",
            "url": "https://localhost:4321/temp/workbench.html",
            "webRoot": "${workspaceRoot}",
            "sourceMaps": true,
            "sourceMapPathOverrides": {
                "webpack:///.././src/*": "${webRoot}/src/*",
                "webpack:///../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../../../src/*": "${webRoot}/src/*"
            },
            "runtimeArgs": [
                "--remote-debugging-port=9222"
            ]
        },
        {
            "name": "Hosted workbench",
            "type": "edge",
            "version": "canary",
            "request": "launch",
            "url": "https://tenant.sharepoint.com/sites/labs/_layouts/workbench.aspx",
            "webRoot": "${workspaceRoot}",
            "sourceMaps": true,
            "sourceMapPathOverrides": {
                "webpack:///.././src/*": "${webRoot}/src/*",
                "webpack:///../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../../../src/*": "${webRoot}/src/*"
            },
            "runtimeArgs": [
                "--remote-debugging-port=2015",
                "-inPrivate"
            ]
        },
        {
            "name": "DevProfile Owner",
            "type": "edge",
            "version": "canary",
            "request": "launch",
            "url": "https://tenant.sharepoint.com/sites/labs/SitePages/Lab-testing.aspx",
            "webRoot": "${workspaceRoot}",
            "sourceMaps": true,
            "userDataDir": "C:\\workspace\\tools\\edge-canary-dev-userdata",
            "sourceMapPathOverrides": {
                "webpack:///../../../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../../src/*": "${webRoot}/src/*",
                "webpack:///.././src/*": "${webRoot}/src/*"
            },
            "runtimeArgs": [
                "--remote-debugging-port=2015",
                "--profile-directory=Profile 1"
            ]
        },
        {
            "name": "DevProfile Member",
            "type": "edge",
            "version": "canary",
            "request": "launch",
            "url": "https://tenant.sharepoint.com/sites/labs/SitePages/Lab-testing.aspx",
            "webRoot": "${workspaceRoot}",
            "sourceMaps": true,
            "userDataDir": "C:\\workspace\\tools\\edge-canary-dev-userdata",
            "sourceMapPathOverrides": {
                "webpack:///../../../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../../src/*": "${webRoot}/src/*",
                "webpack:///.././src/*": "${webRoot}/src/*"
            },
            "runtimeArgs": [
                "--remote-debugging-port=2015",
                "--profile-directory=Profile 2"
            ]
        },
        {
            "name": "DevProfile Visitor",
            "type": "edge",
            "version": "canary",
            "request": "launch",
            "url": "https://tenant.sharepoint.com/sites/labs/SitePages/Lab-testing.aspx",
            "webRoot": "${workspaceRoot}",
            "sourceMaps": true,
            "userDataDir": "C:\\workspace\\tools\\edge-canary-dev-userdata",
            "sourceMapPathOverrides": {
                "webpack:///../../../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../../src/*": "${webRoot}/src/*",
                "webpack:///.././src/*": "${webRoot}/src/*"
            },
            "runtimeArgs": [
                "--remote-debugging-port=2015",
                "--profile-directory=Profile 3"
            ]
        },
        {
            "name": "DevProfile Guest Access",
            "type": "edge",
            "version": "canary",
            "request": "launch",
            "url": "https://tenant.sharepoint.com/sites/labs/SitePages/Lab-testing.aspx",
            "webRoot": "${workspaceRoot}",
            "sourceMaps": true,
            "userDataDir": "C:\\workspace\\tools\\edge-canary-dev-userdata",
            "sourceMapPathOverrides": {
                "webpack:///../../../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../src/*": "${webRoot}/src/*",
                "webpack:///../../../../src/*": "${webRoot}/src/*",
                "webpack:///.././src/*": "${webRoot}/src/*"
            },
            "runtimeArgs": [
                "--remote-debugging-port=2015",
                "--profile-directory=Profile 4"
            ]
        }
    ]
}

Go to the debug pane and see the new debug configurations

debug_configs

Select a config and start debugging, you already logged in with you pre-configured session and it will keep whatever you save in these profiles !

That's it

Happy to share the technique ALSO with EDGE CANARY, If you have ideas to improve it and concerns that I missed, please, let me know !

Cheers !

Yannick

Other posts