Modern Web Resource Workspace in Dynamics 365 – Part 2: Declaration Files

With today advances in Front-end framework and tools, we can easily build a web component or application with better usability, manageability, extendibility, scalability, maintainability, and testability which are often lacked in plain HTML, JavaScript development. In this series, I will setup a modern workspace to work with Web Resources in D365 using most relevant technologies:

  1. TypeScriptESLintPrettier in Visual Studio Code. (Part 1)
  2. Using declaration files: @types/xrmXrmDefinitelyTyped (Part 2 – this post)
  3. Debugging with Fiddler (Part 3)
  4. Deployment (Part 4)

Problems with JavaScript form scripting

In Dynamics 365, we use JavaScript functions as event handlers to offer additional logic and actions when users interact with the UI. In our JavaScript functions, we often use magic strings and infer types with the Client API library to interact with every thing from UI components/elements such as attributes, controls, tabs, etc. to library objects such as executionContext, formContext, and Xrm. That leads us to run time errors because of typos and/or changes in metadata which should be identified at compile time.

This is where TypeScript with Declaration Files steps in.

What are Declaration Files?

The declaration files (files with .d.ts extension) describe how a JavaScript library/API can be used by declaring which types and functions are available, without specifying any of the actual library implementation.

The TypeScript compiler uses declaration files to infer types and check validity of your code for the described library. The definitions in declaration files are only used by the compiler at compile-time, and is not included in the resulting code. This means that it does not slow down, or add to the outputted code JavaScript in any way.

VS Code uses declaration files to provide IntelliSense for types, function calls, and variable support for TypeScript/JavaScript libraries. We’ll want to take advantages of both type checking and IntelliSense for the Client API library. There are currently two good sources of type definitions:

Speaking of IntelliSense, you may want to have a look at IntelliCode.

IntelliCode – AI-assisted IntelliSense

IntelliCode augments your IntelliSense by recommending commonly used completion items for your current context at the top of your list. These contextual recommendations are generated by common practices learned from having IntelliCode train across thousands of real-world projects. This saves you time by putting what you’re most likely to use at the top of your list, so you don’t have to search though alphabetical suggestions.

IntelliCode will change its recommendations depending on the context and sort your options by relevance in that specific situation. For instance, if an element, like map, is being used in a conditional statement or a loop, you will see methods that you would most likely use to create the condition. If you check the completions for map again inside the conditional, you’ll see a different suggestion list.

IntelliCode in action

Just install the Visual Studio IntelliCode extension from the Extensions tab in Visual Studio Code, and get benefitted from AI right away, no configuration needed for TypeScript/JavaScript.

@types/xrm from DefinitelyTyped

DefinitelyTyped is the most popular repository of Declaration Files for many many JavaScript libraries most of which do not provide their own declaration files (as they are not developed with TypeScript and are not committed to work with it). So it holds Declaration files maintained by the community.

By using DefinitelyTyped and the declaration files it contains you can use most of the popular JavaScript libraries as if they were TypeScript libraries in the sense that you will have type validation by the compiler (as the declaration file indicates).

The declaration files from DefinitelyTyped are built, tested, and packaged in @types published on NPM. @types/xrm is a package that defines types for the JavaScript Client XRM API in Dynamics 365. You can easily find every objects and functions, safely use them with IntelliSense.

In VS Code with web resource workspace opened, open View > Terminal (Ctrl+`), run below command:

npm i -D @types/xrm

Now, let’s try writing an event handler function for Form Onload event, and inspect the different before and after installing @types/xrm in IntelliSense suggestions:

@types/xrm in action

There is still one problem with @types/xrm. Let’s examine below code snippet:

namespace DCE {
  export function onLoad(executionContext: Xrm.Events.EventContext): void {
    const formContext = executionContext.getFormContext();
    const attrFirstName = formContext.getAttribute('firstname');
    Xrm.Navigation.openAlertDialog({ title: 'test title', text: attrFirstName.getValue() });
  }
}

The function get value from the firstname field and show a dialog with the value. Here, we are using magic string “firstname” and assuming the field is available in form context. Although the Client API is definitely typed with @types/xrm, there is no type definitions specific to our environment such available attributes, controls, tabs, sections, etc. and their corresponding types.

Now you can see that it is similar to problems when writing custom plugins / workflows. To solve the problem, we use CrmSvcUtil from CRM SDK to generate early-bound .NET classes in case of server side code. For client side code, we can use a similar tool, called XrmDefinitelyTyped.

XrmDefinitelyTyped

The first thing to be aware of is that @types/xrm and XrmDefinitelyTyped are incompatible with each other. You cannot have the DefinitelyTyped type definitions in the same compilation context as the XrmDefinitelyTyped definitions. That’s a problem because both of these sources are independently useful, depending on the context in which they are used.

XrmDefinitelyTyped (XDT) and was released to the public in April 2015. It has since then been expanded with lots of new features and functionality. It is open-source and can be found on GitHub.

It is the TypeScript equivalent of CrmSvcUtil, but instead of generating early-bound .NET classes for server-side code, it generates TypeScript interfaces for all your client-side coding.

When the tool run, it generates a base declaration file to interact with Client API pretty much the same as DefinitelyTyped declaration file. Moreover, it generates declaration files specific for each selected entities in your environment including entities metadata, entities forms, forms attributes, controls, tabs, sections, etc. Besides, there are more advanced and useful declaration files for:

  • Global and entity-specific option sets.
  • The standard SDK.REST.js API for querying CRM via OData.
  • The standard SDK.MetaData.js API for querying the metadata of CRM.
  • XrmQuery for the Web API and the old REST endpoint. XrmQuery makes it possible to perform fully type-safe queries to CRM.

Setup XrmDefinitelyTyped

With your web resource workspace opened in VS Code, if you have installed @types/xrm from the above section, remove it with below command in Terminal (Ctrl+`) before proceed further.

npm uninstall @types/xrm

Then, install Nuget CLI if you haven’t.

Install XrmDefinitelyTyped from Nuget

  1. Create packages.config file in workspace directory with below content.
<?xml version="1.0" encoding="utf-8"?>
<packages>
  <package id="Delegate.XrmDefinitelyTyped" version="5.3.3" targetFramework="net46" />
</packages>

This file is to save Nuget package references, same as package.json for NPM packages.

  1. run nuget install:
nuget install packages.config -OutputDirectory packages

This will install packages specified in packages.config to packages folder.

Configure XrmDefinitelyTyped to your environment

  1. Open XrmDefinitelyTyped.exe.config file in downloaded XrmDefinitelyTyped folder packages\Delegate.XrmDefinitelyTyped.5.3.3\content\XrmDefinitelyTyped
  2. Update values corresponding to your environment:
    • url: URL to the Organization.svc
    • username: Username for the CRM system.
    • password: Password for the CRM system.
    • solutions: Comma-separated list of solutions names. Generates code for the entities found in these solutions.
    • entities: Comma-separated list of logical names of the entities it should generate code for. This is additive with the entities gotten via the solutions argument.
    • out: Output directory for the generated declaration files.
    • jsLib: Output directory for the JavaScript library files (XrmQuery files)
    • Learn more possible configuration here.
Sample XrmDefinitelyTyped.exe.config
  1. Run the tool to start generate declaration files, right click on Run.ps1 in the same folder with the config file > Open in Terminal
  2. Run below command to execute Run.ps1
./Run.ps1

Now have a look at workspace explorer to see everything has been created:

  • xrm.d.ts: The base declaration file.
  • metadata.d.ts: declare interfaces to work with metadata
  • dg.xrmquery.web.d.ts: declare interfaces to work with XrmQuery lib.
  • All available forms in contact entity

Consume XDT declaration files in Form Script

We just need to add triple slash reference to xrm base and the form we are working with, and you will see suggestions appear in IntelliSense.

/// <reference path='../typings/XRM/xrm.d.ts' />
/// <reference path='../typings/XRM/Form/contact/Main/Information.d.ts' />

namespace DCE {
  export function onLoad(executionContext: Xrm.ExecutionContext<any, any>): void {
    const formContext = <Form.contact.Main.Information>executionContext.getFormContext();
    const attrFirstName = formContext.getAttribute('firstname');
    Xrm.Navigation.openAlertDialog({ text: attrFirstName.getValue() });
  }
}
Generated attributes in Contact Information form

Conclusion

While XDT offers a great deal of advanced features compared to @type/xrm, it actually adds a dependency and increases project setup complexity. So the team should consider which is better fit based on project scale and complexity.

3 thoughts on “Modern Web Resource Workspace in Dynamics 365 – Part 2: Declaration Files

  1. Feels bad to be sitting here two years later and @types/xrm and XrmDefinitelyTyped are *still* incompatible. Even worse, the issue (as far as I can tell) is that both define an “xrm” namespace. XrmDT needs to just allow us to set the global namespace, or to surround it in another namespace (client/project specific, whatever). Makes no sense. I’d fix it myself, but the project is written in F#, so I looked for “Xrm” as a string and came up empty. There’s an open issue for it in XrmDT… https://github.com/delegateas/XrmDefinitelyTyped/issues/181

    Like

Leave a comment