Ever since form script loading was made async, I have tried different ways to ensure that form scripts will be loaded in the sequence I want, and not how CRM wants it to be, a.k.a random. CRM2015 Update 1 has introduced Turbo Forms. Turbo Forms are supposed to drastically improve form render time. The problem of managing script dependencies, is still left to the developer. You basically have two options:
Components
1. Form Load sequence entity, that stores the entity name and scripts that are to be loaded
2. A blank javascript webresource, whose name is in this pattern: [entityname].crmform.min.js
3. A plugin the runs on post-RetrieveMultiple on webresource entity
How it is wired up
[entityname].crmform.min.js is added to the form, that requires bunding and minification. This script just contains a comment, and nothing else.
Create a Form Load Sequence record, that specifies the entity name and the scripts that loaded be minified for this entity.
Register the plugin on post RetrieveMultiple of webresource entity.
How it works
If you have read the plugin code already, you already know. But you haven't here is how it works:
The plugin retrieves the Query key on the PluginExecutionContext's InputParameter that contains the QueryExpression object. It then checks, if there is a condition on this QueryExpression with name like crmform.min.js. If so, it retrieves the correct Form Load sequence record for the current entity.
Using the script sequence specified, it also retrieves the script webresources, concatenates and minifies them. This concatenated script is then used to update the OutputParameter's BusinessEntityCollection. The plugin also checks if the javascript webresources specified in the form load sequence entity, actually exists. It displays an error, if it doesn't.
ExecutionContext for RetrieveMultiple on webresource - Post Stage
Performance
There is a bit of overhead, as the plugin has to retrieve and minify the webresources.
Scripts minified at build stage and added to the form
It takes 291ms.
Script dynamically bundled and minified by plugin
It takes 481ms.
Take this numbers, with a pinch of salt, as I found the performance can vary quite a bit depending on the time of the day, as I tested this in CRMOnline. I also tested this with cache disabled.
Failures along the way
I tried these approaches which, didn't quite workout, but I want to document them for future reference.
- In build stage, bundle and minify the scripts in the order of dependencies and use this in the form.
- Write self contained scripts, without any dependencies.
Components
1. Form Load sequence entity, that stores the entity name and scripts that are to be loaded
2. A blank javascript webresource, whose name is in this pattern: [entityname].crmform.min.js
3. A plugin the runs on post-RetrieveMultiple on webresource entity
How it is wired up
[entityname].crmform.min.js is added to the form, that requires bunding and minification. This script just contains a comment, and nothing else.
Create a Form Load Sequence record, that specifies the entity name and the scripts that loaded be minified for this entity.
Register the plugin on post RetrieveMultiple of webresource entity.
How it works
If you have read the plugin code already, you already know. But you haven't here is how it works:
The plugin retrieves the Query key on the PluginExecutionContext's InputParameter that contains the QueryExpression object. It then checks, if there is a condition on this QueryExpression with name like crmform.min.js. If so, it retrieves the correct Form Load sequence record for the current entity.
Using the script sequence specified, it also retrieves the script webresources, concatenates and minifies them. This concatenated script is then used to update the OutputParameter's BusinessEntityCollection. The plugin also checks if the javascript webresources specified in the form load sequence entity, actually exists. It displays an error, if it doesn't.
ExecutionContext for RetrieveMultiple on webresource - Post Stage
<Profile> <Configuration i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" /> <ConstructorDurationInMilliseconds>2</ConstructorDurationInMilliseconds> <ConstructorException i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" /> <ConstructorStartTime>2015-05-22T14:04:15.5180427Z</ConstructorStartTime> <Context> <z:anyType i:type="PluginExecutionContext" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <BusinessUnitId>bf0d0d94-ddc9-e411-80db-c4346bad5414</BusinessUnitId> <CorrelationId>b668ab80-1dda-4676-8615-8d82e9ea0ff0</CorrelationId> <Depth>1</Depth> <InitiatingUserId>80151b28-fb9a-4d38-a920-d8f4d33ffcc2</InitiatingUserId> <InputParameters xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic"> <a:KeyValuePairOfstringanyType> <b:key>Query</b:key> <b:value i:type="a:QueryExpression"> <a:ColumnSet> <a:AllColumns>false</a:AllColumns> <a:Columns xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays"> <c:string>webresourceid</c:string> <c:string>name</c:string> <c:string>content</c:string> <c:string>webresourcetype</c:string> <c:string>silverlightversion</c:string> </a:Columns> </a:ColumnSet> <a:Criteria> <a:Conditions> <a:ConditionExpression> <a:AttributeName>name</a:AttributeName> <a:Operator>Equal</a:Operator> <a:Values xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays"> <c:anyType i:type="d:string" xmlns:d="http://www.w3.org/2001/XMLSchema">ryr_formexperiment.crmform.min.js</c:anyType> </a:Values> <a:EntityName i:nil="true" /> </a:ConditionExpression> </a:Conditions> <a:FilterOperator>And</a:FilterOperator> <a:Filters /> </a:Criteria> <a:Distinct>false</a:Distinct> <a:EntityName>webresource</a:EntityName> <a:LinkEntities /> <a:Orders /> <a:PageInfo> <a:Count>0</a:Count> <a:PageNumber>0</a:PageNumber> <a:PagingCookie i:nil="true" /> <a:ReturnTotalRecordCount>false</a:ReturnTotalRecordCount> </a:PageInfo> <a:NoLock>false</a:NoLock> </b:value> </a:KeyValuePairOfstringanyType> </InputParameters> <IsExecutingOffline>false</IsExecutingOffline> <IsInTransaction>false</IsInTransaction> <IsOfflinePlayback>false</IsOfflinePlayback> <IsolationMode>2</IsolationMode> <MessageName>RetrieveMultiple</MessageName> <Mode>0</Mode> <OperationCreatedOn>2015-05-22T14:04:15.0754261Z</OperationCreatedOn> <OperationId>00000000-0000-0000-0000-000000000000</OperationId> <OrganizationId>32d64867-9081-49c2-8196-6304db7d47e1</OrganizationId> <OrganizationName>Contoso</OrganizationName> <OutputParameters xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic"> <a:KeyValuePairOfstringanyType> <b:key>BusinessEntityCollection</b:key> <b:value i:type="a:EntityCollection"> <a:Entities> <a:Entity> <a:Attributes> <a:KeyValuePairOfstringanyType> <b:key>webresourceid</b:key> <b:value i:type="z:guid">02fe0b47-8800-e511-80ef-c4346bada558</b:value> </a:KeyValuePairOfstringanyType> <a:KeyValuePairOfstringanyType> <b:key>name</b:key> <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema">ryr_formexperiment.crmform.min.js</b:value> </a:KeyValuePairOfstringanyType> <a:KeyValuePairOfstringanyType> <b:key>content</b:key> <b:value i:type="c:string" xmlns:c="http://www.w3.org/2001/XMLSchema">Ly9yeXJfZm9ybWV4cGVyaW1lbnQuY3JtZm9ybS5taW4uanM=</b:value> </a:KeyValuePairOfstringanyType> <a:KeyValuePairOfstringanyType> <b:key>webresourcetype</b:key> <b:value i:type="a:OptionSetValue"> <a:Value>3</a:Value> </b:value> </a:KeyValuePairOfstringanyType> </a:Attributes> <a:EntityState i:nil="true" /> <a:FormattedValues> <a:KeyValuePairOfstringstring> <b:key>webresourcetype</b:key> <b:value>Script (JScript)</b:value> </a:KeyValuePairOfstringstring> </a:FormattedValues> <a:Id>02fe0b47-8800-e511-80ef-c4346bada558</a:Id> <a:KeyAttributes xmlns:c="http://schemas.microsoft.com/xrm/7.1/Contracts" /> <a:LogicalName>webresource</a:LogicalName> <a:RelatedEntities /> <a:RowVersion>1308124</a:RowVersion> </a:Entity> </a:Entities> <a:EntityName>webresource</a:EntityName> <a:MinActiveRowVersion>-1</a:MinActiveRowVersion> <a:MoreRecords>false</a:MoreRecords> <a:PagingCookie><cookie page="1"><webresourceid last="{02FE0B47-8800-E511-80EF-C4346BADA558}" first="{02FE0B47-8800-E511-80EF-C4346BADA558}" /></cookie></a:PagingCookie> <a:TotalRecordCount>-1</a:TotalRecordCount> <a:TotalRecordCountLimitExceeded>false</a:TotalRecordCountLimitExceeded> </b:value> </a:KeyValuePairOfstringanyType> </OutputParameters> <OwningExtension xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts"> <a:Id>34846b5c-8b00-e511-8101-c4346bade5b0</a:Id> <a:KeyAttributes xmlns:b="http://schemas.microsoft.com/xrm/7.1/Contracts" xmlns:c="http://schemas.datacontract.org/2004/07/System.Collections.Generic" /> <a:LogicalName>sdkmessageprocessingstep</a:LogicalName> <a:Name>RYR.Experiments.PreRetrieveMultipleWebResource: RetrieveMultiple of webresource (Profiler)</a:Name> <a:RowVersion i:nil="true" /> </OwningExtension> <PostEntityImages xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" /> <PreEntityImages xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" /> <PrimaryEntityId>00000000-0000-0000-0000-000000000000</PrimaryEntityId> <PrimaryEntityName>webresource</PrimaryEntityName> <RequestId i:nil="true" /> <SecondaryEntityName>none</SecondaryEntityName> <SharedVariables xmlns:a="http://schemas.microsoft.com/xrm/2011/Contracts" xmlns:b="http://schemas.datacontract.org/2004/07/System.Collections.Generic" /> <UserId>80151b28-fb9a-4d38-a920-d8f4d33ffcc2</UserId> <ParentContext i:nil="true" /> <Stage>40</Stage> </z:anyType> </Context> <ExecutionDurationInMilliseconds>14</ExecutionDurationInMilliseconds> <ExecutionException i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" /> <ExecutionStartTime>2015-05-22T14:04:15.5180427Z</ExecutionStartTime> <HasServiceEndpointNotificationService>true</HasServiceEndpointNotificationService> <IsContextReplay>false</IsContextReplay> <IsolationMode>2</IsolationMode> <OperationType>Plugin</OperationType> <ProfileVersion>1.1</ProfileVersion> <ReplayEvents xmlns:a="http://schemas.datacontract.org/2004/07/PluginProfiler.Plugins" /> <SecureConfiguration i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" /> <TypeName>RYR.Experiments.PreRetrieveMultipleWebResource</TypeName> <WorkflowInputParameters xmlns:a="http://schemas.datacontract.org/2004/07/PluginProfiler.Plugins" /> <WorkflowOutputParameters xmlns:a="http://schemas.datacontract.org/2004/07/PluginProfiler.Plugins" /> <WorkflowStepId i:nil="true" xmlns:i="http://www.w3.org/2001/XMLSchema-instance" /> </Profile>
Performance
There is a bit of overhead, as the plugin has to retrieve and minify the webresources.
Scripts minified at build stage and added to the form
It takes 291ms.
Script dynamically bundled and minified by plugin
It takes 481ms.
Take this numbers, with a pinch of salt, as I found the performance can vary quite a bit depending on the time of the day, as I tested this in CRMOnline. I also tested this with cache disabled.
Failures along the way
I tried these approaches which, didn't quite workout, but I want to document them for future reference.
- Adding a non-existent form script into the Form Experiment's FormXml doesn't work, even though there is plugin to take care of the RetrieveMultiple request. CRM doesn't allow this to happen. The Javascript webresource has to exist, if you want to add this to a form. Here is what you'll have to add to the root node of the formxml, to hook up the webresource and associated onsave and onload event handlers for the form
<formLibraries> <Library name='[JSWEBRESNAME]' libraryUniqueId='[NEWGUID]' /> </formLibraries> <events> <event name='onload' application='false' active='false'> <Handlers> <Handler functionName='[FUNCNAME]' libraryName='[JSWEBRESNAME]' handlerUniqueId='[NEWGUID]' enabled='true' parameters='' passExecutionContext='false' /> </Handlers> </event> <event name='onsave' application='false' active='false'> <Handlers> <Handler functionName='[FUNCNAME]' libraryName='[JSWEBRESNAME]' handlerUniqueId='[NEWGUID]' enabled='true' parameters='' passExecutionContext='false' /> </Handlers> </event> </events>
- I tried to use YUICompressor.NET, but it is not strongly signed and had a dependency on Ecmascript.net, which is also not strongly signed. It seems too much pain to sign these third party assemblies and merge this with my plugin. If you love pain, have a look at http://buffered.io/posts/net-fu-signing-an-unsigned-assembly-without-delay-signing/ on how this can be done. I ended up using JSMin.net as it is just two files in total, and you can just add this to you project and build, instead of merging third-party assemblies.
- http://nycrmdev.blogspot.com.au/2015/01/an-alternative-approach-to-loading-form.html
- http://nycrmdev.blogspot.com.au/2014/04/using-requirejs-in-crm2013.html
No comments:
Post a Comment