Announcement

Collapse
No announcement yet.

Plugin to help with requireJS...

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    Plugin to help with requireJS...

    In order to produce JS files by class/namespace/module and load things dynamically at run time, we have built a Bridge.NET plugin. We're trying to control the grouping of classes, etc.

    One of our issues is that we are sharing a project with a non-Bridge solution. This project is our contract interface and all of our DTOs. The other side is a WCF project, so it has some data annotation attributes. Specifically, we use the KnownTypeAttribute a LOT!!

    For example, we have a base class called BaseIdOfInt.

    public partial class BaseIdOfInt
    {
    [DataMember]
    public int Value { get; set; }
    }

    There are hundreds of subclasses. Each of those subclass files has a partial class definition for BaseIdOfInt that adds the KnownType attribute, like:


    {
    [KnownType(typeof(BankAccountId))]
    public partial class BaseIdOfInt { }
    
    
    }
    
    public class BankAccountId : BaseIdOfInt
    {
    }
    These seem to produce a circlular referernce. The BaseIdOfInt class says it depends on all of the other classes because of the ctor of the attribute, while each of the sub types inherit from the base.
    if (kvp.Value.TypeDefinition.HasCustomAttributes)
    {
    bool isKT = false;
    var td = kvp.Value.TypeDefinition;
    foreach (CustomAttribute attribute in td.CustomAttributes)
    {
    if (attribute.AttributeType.Name == "KnownTypeAttribute")
    {
    isKT = true;
    break;
    }
    }
    
    if (isKT)
    td.CustomAttributes.Clear();
    
    }

    In our plugin, we override BeforeTypesEmit and we look to see if there's any classes to skip, etc. I've attached the entire thing. I wanted to find some way to simply skip all of the KnownTypeAttribute stuff as it makes no difference to the Javascript on the client. I want to do something like this, but it's not working. Am I barking up the wrong tree?
    Last edited by kfinke; 2019-08-05 @ 01:51 PM.

    #2

    using Bridge.Contract;
    using ICSharpCode.NRefactory.MonoCSharp;
    using ICSharpCode.NRefactory.TypeSystem;
    using Newtonsoft.Json;
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.IO;
    using System.Linq;
    using System.Security.Cryptography;
    using System.Text;
    using Mono.Cecil;

    namespace Rio.UI.Bridge.Plugins
    {
    public class ModuleInfo
    {
    public ModuleInfo(string hash)
    {
    this.Hash = hash;
    this.Dependencies = new List<string>();
    }

    public string Hash { get; }
    public List<string> Dependencies { get; }
    }

    [Export(typeof(IPlugin))]
    public class AmdModulePerClassPlugin : AbstractPlugin
    {
    private ConcurrentDictionary<string, ModuleInfo> dependencyMap = new ConcurrentDictionary<string, ModuleInfo>();
    private HashSet<string> manuallyLoadedModules = new HashSet<string>();
    private IAssemblyInfo config;

    public override void OnConfigRead(IAssemblyInfo config)
    {
    // grab the config so we can use the output directory later when writing out the dependency map
    // and set the manual loading mask
    this.config = config;
    base.OnConfigRead(config);
    }

    public override void BeforeTypesEmit(IEmitter emitter, IList<ITypeInfo> types)
    {
    // if you need to debug the plugin, this can be helpful
    // uncomment the code below, and when this code runs a debugger prompt will pop up
    // allowing you to attach a debugger to the bridge build
    //if (!System.Diagnostics.Debugger.IsAttached)
    //{
    // System.Diagnostics.Debugger.Launch();
    //}

    foreach (var kvp in emitter.BridgeTypes)
    {
    if (kvp.Value.Type != null && kvp.Value.Type.Name == "App")
    {
    // don't modularize App, it is the entry point
    continue;
    }

    if (kvp.Value.TypeDefinition.HasCustomAttributes)
    {
    bool isKT = false;
    var td = kvp.Value.TypeDefinition;
    foreach (CustomAttribute attribute in td.CustomAttributes)
    {
    if (attribute.AttributeType.Name == "KnownTypeAttribute")
    {
    isKT = true;
    break;
    }
    }

    if (isKT)
    td.CustomAttributes.Clear();

    }
    if (kvp.Value.Module == null &&
    kvp.Value.TypeInfo != null &&
    kvp.Value.TypeInfo.Module == null && // if its already a module, leave it be
    kvp.Value.Type != null &&
    !kvp.Value.Type.Namespace.StartsWith("System") &&
    !kvp.Value.Type.Namespace.StartsWith("Bridge") &&
    !kvp.Value.Type.Namespace.StartsWith("Retyped") &&
    !kvp.Value.Type.Namespace.StartsWith("Microsoft"))
    {
    // used this pattern when outputing code by classpath, moved away from outputing by class path
    // because I could not find a solution to circular dependencies
    //var moduleName = "/" + string.Join("/", this.ouputDirectory.Split('\\').Skip(2)) + "/" + kvp.Value.Type.Namespace.Replace(".", "/") + "/" + kvp.Value.Type.Name + ".js";

    var moduleName = kvp.Value.Type.FullName.Replace('.', '_');
    var module = new Module(moduleName, ModuleType.AMD, emitter);
    kvp.Value.Module = module;

    if (kvp.Value.TypeInfo != null)
    {
    // sometimes the TypeInfo has already been initialized, so make sure to set the module on it too
    kvp.Value.TypeInfo.Module = module;
    }

    if (kvp.Value.Type.GetAllBaseTypes().Any(t => t.FullName.EndsWith(".IRoutable")) &&
    kvp.Value.Type.Kind == TypeKind.Class &&
    !kvp.Value.TypeDefinition.IsAbstract)
    {
    // Note this is going to manually load abstract base types like RouteHandler and RouteContributor,
    // but I think that is OK. Interfaces extending IRoutable will be auto loaded.
    this.manuallyLoadedModules.Add(moduleName);
    }
    }
    }

    this.config.Loader.ManualLoadingMask = string.Join(";", manuallyLoadedModules);

    base.BeforeTypesEmit(emitter, types);
    }

    public override void AfterTypeEmit(IEmitter emitter, ITypeInfo type)
    {
    if (type.Module != null)
    {
    var info = this.dependencyMap.GetOrAdd(type.Module.OriginalNa me, new ModuleInfo(emitter.Output.ToString().CalculateMD5( )));

    foreach (var dep in emitter.CurrentDependencies)
    {
    if (!this.manuallyLoadedModules.Contains(dep.Dependen cyName))
    {
    info.Dependencies.Add(dep.DependencyName);
    }
    }

    }

    base.AfterTypeEmit(emitter, type);
    }

    public override void AfterTypesEmit(IEmitter emitter, IList<ITypeInfo> types)
    {
    // write the dependency map to a json file in the output directory
    // the service worker will be able to use this to optimize loading
    var output = JsonConvert.SerializeObject(this.dependencyMap, Formatting.Indented);
    var outputPath = Path.Combine(this.config.Output, "dependencyMap.js");
    File.WriteAllText(outputPath, output);
    }
    }

    public static class Helpers
    {
    public static IEnumerable<IType> GetAllBaseTypes(this IType type)
    {
    foreach (var baseType in type.DirectBaseTypes)
    {
    yield return baseType;

    foreach (var baseBaseType in baseType.GetAllBaseTypes())
    {
    yield return baseBaseType;
    }
    }
    }

    public static string CalculateMD5(this string text)
    {
    var byteArray = Encoding.UTF8.GetBytes(text);
    using (var stream = new MemoryStream(byteArray))
    using (var md5 = MD5.Create())
    {
    var hash = md5.ComputeHash(stream);
    return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
    }
    }
    }
    }

    Comment


      #3
      Hi kfinke. Any chance you could edit your two posts above and re-paste the formatted code samples inside [CODE] tags? That would help with the readability of the code samples.

      Comment


        #4
        Agreed with geoffrey.mcgill, that'd be great if you helped us make the post look better (and help us understand your inquiry in the process). I simply can't go thru your code the way it looks now, sorry.

        But it doesn't mean I won't try to help you out. So far what I can think of is, you can use the "linked files" concept. See this blog post, it may help you understand the concept if you don't already: Visual Studio - Add file as link.

        That's not really the important part. As you have then the classes as partial, you could then in a local file to the Bridge project, add the External Attribute. This won't involve using plugins and maybe will have you have several small files (or maybe a single file with several partial classes grouped just to have the attribute); something like this:

        [Bridge.External]
        public partial class BaseIdOfInt
        {
        }
        As that's a partial class, you just don't need to repeat everything's within it, just have Bridge-specific code in that file. In the same way, you could have code that does not pertain/work in Bridge (and does not affect the whole software) in a local partial class bound only to the non-bridge project.

        Now, down to the plug in, knowing the above; as you probably has some heuristics to determine wheter you want or not emit some classes, your idea seems in the right track, just need to spend a little on reflecting thru the attributes bound to the class and then -inject- the ExternalAttribute so that parts of translator like this gets to know it should skip that entity.

        So, now you tell us, have you been barking at the right tree? Hopefully this could help you get what you need -- if I get it right.

        Hope this helps!

        Comment

        Working...
        X