Lombiq / Orchard-Vue.js

Orchard Core module that contains Vue.js and related helpers.
BSD 3-Clause "New" or "Revised" License
18 stars 11 forks source link

Liquid Template with vue taghelper creates error for SFC in my Module (OSOE-713) #110

Closed bashuss closed 11 months ago

bashuss commented 1 year ago

Detailed Description

I copied the demo-sfc code including its dependency demo-repeater in my Vue project that basically recreates the sfc part of the vue sample project. So the SFCs go to the Folder Assets/Scripts/VueComponents and are being built into the wwwroot/vue folder as scripts. I registered the components and its dependencies inside ResourceManagementOptionsConfiguration.cs as in the sample project except the lines 17-20 which are about registering the DemoApp (I only use the SFC related code). This code properly executes on startup. Inside OrchardCore, I created a new blank ContentType and an instance of it. For this type I created a template:

{% comment %}
<vue-component-app area="@FeatureIds.Area"
                   name="@ResourceNames.DemoSfc"
                   id="unique-id"
                   class="additional-class-for-styling"
                   model="@(new { Value = 10 })" />
{% endcomment %}

{% capture my_model %}{ Value: 10 }{% endcapture %}
{% assign modeljson = my_model | jsonparse %}

{% helper "vue-component-app", area:"bhussnaetter.VueModules", name:"demo-sfc", id:"myapp", class:"myapp", model:modeljson %}

This code creates the Stacktrace below. Something seems to be missing in the registration.

When I use the same code with my module disabled and Lombiq.VueJs.Samples enabled and the area property set to "Lombiq.VueJs.Samples", it works properly.

What am I missing in my module?

Stacktrace

ArgumentNullException: Value cannot be null. (Parameter 'source') System.Linq.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument) System.Linq.Enumerable.Select<TSource, TResult>(IEnumerable source, Func<TSource, TResult> selector) System.Linq.Enumerable+SelectManySingleSelectorIterator<TSource, TResult>.MoveNext() System.Linq.Enumerable+SelectManySingleSelectorIterator<TSource, TResult>.MoveNext() System.Linq.Lookup<TKey, TElement>.Create(IEnumerable source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector, IEqualityComparer comparer) System.Linq.Enumerable.ToLookup<TSource, TKey, TElement>(IEnumerable source, Func<TSource, TKey> keySelector, Func<TSource, TElement> elementSelector) OrchardCore.ResourceManagement.ResourceManifestExtensions.SingleResourceTypeToLookup(IEnumerable resourceManifest, string resourceType) Lombiq.VueJs.TagHelpers.VueComponentTagHelper.FindResourceNames() Lombiq.VueJs.TagHelpers.VueComponentTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output) Lombiq.VueJs.TagHelpers.VueComponentAppTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output) OrchardCore.DisplayManagement.Liquid.Tags.FluidTagHelper.WriteToAsync(string identifier, List arguments, IReadOnlyList statements, TextWriter writer, TextEncoder encoder, TemplateContext context) Fluid.Parser.FluidTemplate.Awaited(ValueTask task, TextWriter writer, TextEncoder encoder, TemplateContext context, IReadOnlyList statements, int startIndex) OrchardCore.DisplayManagement.Liquid.LiquidViewTemplateExtensions.RenderAsync(LiquidViewTemplate template, TextWriter writer, TextEncoder encoder, LiquidTemplateContext context, object model) OrchardCore.Liquid.Services.LiquidTemplateManager.RenderHtmlContentAsync(string source, TextEncoder encoder, object model, IEnumerable<KeyValuePair<string, FluidValue>> properties) OrchardCore.Templates.Services.TemplatesShapeBindingResolver+<>cDisplayClass8_0+<b0>d.MoveNext() OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.gAwaited|11_0(Task task) OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.ExecuteAsync(DisplayContext context) OrchardCore.DisplayManagement.Implementation.DefaultHtmlDisplay.ExecuteAsync(DisplayContext context) CallSite.Target(Closure , CallSite , object ) AspNetCoreGeneratedDocument.Views_Item_Display.ExecuteAsync() Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, bool invokeViewStarts) Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, string contentType, Nullable statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, string contentType, Nullable statusCode) Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result) Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|30_0<TFilter, TFilterAsync>(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|28_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.gAwaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) Microsoft.AspNetCore.Routing.EndpointMiddleware.gAwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger) SixLabors.ImageSharp.Web.Middleware.ImageSharpMiddleware.Invoke(HttpContext httpContext, bool retry) OrchardCore.Liquid.ScriptsMiddleware.Invoke(HttpContext httpContext) Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) OrchardCore.Diagnostics.DiagnosticsStartupFilter+<>cDisplayClass3_0+<b1>d.MoveNext() Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context) OrchardCore.ContentPreview.PreviewStartupFilter+<>c+<b0_1>d.MoveNext() OrchardCore.Modules.ModularTenantRouterMiddleware.Invoke(HttpContext httpContext) OrchardCore.Modules.ModularTenantContainerMiddleware+<>cDisplayClass4_0+<b__0>d.MoveNext() OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func<ShellScope, Task> execute, bool activateShell) OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func<ShellScope, Task> execute, bool activateShell) OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func<ShellScope, Task> execute, bool activateShell) OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func<ShellScope, Task> execute, bool activateShell) OrchardCore.Environment.Shell.Scope.ShellScope.UsingAsync(Func<ShellScope, Task> execute, bool activateShell) OrchardCore.Modules.ModularTenantContainerMiddleware.Invoke(HttpContext httpContext) Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Jira issue

bashuss commented 1 year ago

I found two reasons for my module not to work:

My original module use a working vue sfc, which I registered in the way I understood from your example. As this did not work, I copied in the files from the sample and added the registration in first position. But I did not remove my original registration, which just looked the same to me.

Now I removed the original registration, so only the sample registration was in place. This changed the error message.

Then I discovered that the ".vue" files, that I copied from the sample, had the wrong build type. Changing the build type to "embedded ressource" made it work.

So now I have to find out, what the problem is with my registration. My SFCs do have multiple dependencies which then again depend on other SFCs. But each SFCs should be usable on its own. Imho the registration for this case is not properly explained from the sample code.

sarahelsaig commented 1 year ago

Now I removed the original registration, so only the sample registration was in place. This changed the error message.

What's the new error message?

Then I discovered that the ".vue" files, that I copied from the sample, had the wrong build type. Changing the build type to "embedded ressource" made it work.

Yes, you have to mark each vue file as embedded in the csproj file, as mentioned here. Orchard Core can only harvest the <template> part of the vue file if it's embedded.

So now I have to find out, what the problem is with my registration. My SFCs do have multiple dependencies which then again depend on other SFCs. But each SFCs should be usable on its own.

Every SFC that has child components must be registered as a resource and list the component dependencies. Otherwise you will have client side errors regarding missing component. Could you paste your ResourceManagementOptionsConfiguration.cs?

bashuss commented 1 year ago

Now I removed the original registration, so only the sample registration was in place. This changed the error message.

What's the new error message?

The message was some kind of "not found" message, which is why I discovered that vue files were not embedded.

So now I have to find out, what the problem is with my registration. My SFCs do have multiple dependencies which then again depend on other SFCs. But each SFCs should be usable on its own.

Every SFC that has child components must be registered as a resource and list the component dependencies. Otherwise you will have client side errors regarding missing component. Could you paste your ResourceManagementOptionsConfiguration.cs?

`

_manifest
    .DefineSingleFileComponent(App)
    .SetDependencies(CalendarBookSession, CalendarConfigureSchedule);

//_manifest
//  .DefineSingleFileComponent(CalendarBookSession)
//  .SetDependencies(CalendarWithSelections, ListOpenSlots);

//_manifest
//  .DefineSingleFileComponent(CalendarConfigureSchedule)
//  .SetDependencies(CalendarWithSelections, EditSchedule);

//_manifest
//  .DefineSingleFileComponent(CalendarWithSelections)
//  .SetDependencies(CalendarMonth);

//_manifest
//  .DefineSingleFileComponent(CalendarMonth);

//_manifest
//  .DefineSingleFileComponent(EditSchedule);
//_manifest
//  .DefineSingleFileComponent(ListOpenSlots);`

This is the way it works. But as I wanted to use the other components directly, I also had the out-commented code active in the beginning, which rendered the hole system inoperational. But this is the way I thought it should be done according to the demo project.

As I do not need to override the vue templates till now, I actually startet embedding pre-compiled vue components, which was easier to understand and to debug for me. But this might be just because I am quite new to vue and have only intermediate experience in OrchardCore developement.

sarahelsaig commented 11 months ago

Hi @bashuss , sorry for the delay. I missed your previous comment.

First, the problem doesn't seem to be related to Liquid. This works in the OSOCE solution for example: image (Note that in the model JSON it has to be value, not Value. If you pass in a C# object in Razor the names automatically get converted to camelCase, but the same doesn't happen with Liquid JSON objects.)

Regarding your second snippet, the problem was your final 3 ._manifest.DefineSingleFileComponent() calls that didn't have .SetDependencies(). As mentioned in the sample comments here you don't need to define an SFC resource if it doesn't have child components. I have never actually tried what happens if you do, until now. It turns out the documentation was a bit imprecise, actually you must not have such entries. Having them caused the ArgumentNullException you mentioned in the issue description. This is a a weirdness of stock Orchard Core, if you don't call .SetDependencies(), the internal list is null instead of an empty list as we'd expect. Your ResourceManagementOptionsConfiguration should look like this:

_manifest
    .DefineSingleFileComponent(App)
    .SetDependencies(CalendarBookSession, CalendarConfigureSchedule);

_manifest
    .DefineSingleFileComponent(CalendarBookSession)
    .SetDependencies(CalendarWithSelections, ListOpenSlots);

_manifest
    .DefineSingleFileComponent(CalendarConfigureSchedule)
    .SetDependencies(CalendarWithSelections, EditSchedule);

_manifest
    .DefineSingleFileComponent(CalendarWithSelections)
    .SetDependencies(CalendarMonth);

So include every SFC that has child components, but none that doesn't.

The "not found" message you mentioned was because you commented out the resource definitions for CalendarBookSession, CalendarConfigureSchedule, and CalendarWithSelections. so their dependencies were not included.

I've adjusted our resource management wrapper to handle this edge case, and I clarified the comment in the sample project. So now you can safely uncomment all your DefineSingleFileComponents. Any definition that doesn't have .SetDependencies() is still unnecessary, but now they will be ignored instead of causing an exception.