UNPKG

noflo

Version:

Flow-Based Programming environment for JavaScript

302 lines (257 loc) 9.5 kB
# NoFlo - Flow-Based Programming for JavaScript # (c) 2013-2016 TheGrid (Rituwall Inc.) # (c) 2013 Henri Bergius, Nemein # NoFlo may be freely distributed under the MIT license # # This is the browser version of the ComponentLoader. internalSocket = require './InternalSocket' nofloGraph = require './Graph' utils = require './Utils' {EventEmitter} = require 'events' class ComponentLoader extends EventEmitter constructor: (@baseDir, @options = {}) -> @components = null @componentLoaders = [] @checked = [] @revalidate = false @libraryIcons = {} @processing = false @ready = false getModulePrefix: (name) -> return '' unless name return '' if name is 'noflo' name = name.replace /\@[a-z\-]+\//, '' if name[0] is '@' name.replace 'noflo-', '' getModuleComponents: (moduleName) -> return unless @checked.indexOf(moduleName) is -1 @checked.push moduleName try definition = require "/#{moduleName}/component.json" catch e if moduleName.substr(0, 1) is '/' return @getModuleComponents "noflo-#{moduleName.substr(1)}" return for dependency of definition.dependencies @getModuleComponents dependency.replace '/', '-' return unless definition.noflo prefix = @getModulePrefix definition.name if definition.noflo.icon @libraryIcons[prefix] = definition.noflo.icon if moduleName[0] is '/' moduleName = moduleName.substr 1 if definition.noflo.loader # Run a custom component loader loaderPath = "/#{moduleName}/#{definition.noflo.loader}" @componentLoaders.push loaderPath loader = require loaderPath @registerLoader loader, -> if definition.noflo.components for name, cPath of definition.noflo.components if cPath.indexOf('.coffee') isnt -1 cPath = cPath.replace '.coffee', '.js' if cPath.substr(0, 2) is './' cPath = cPath.substr 2 @registerComponent prefix, name, "/#{moduleName}/#{cPath}" if definition.noflo.graphs for name, cPath of definition.noflo.graphs @registerGraph prefix, name, "/#{moduleName}/#{cPath}" listComponents: (callback) -> if @processing @once 'ready', => callback null, @components return return callback null, @components if @components @ready = false @processing = true setTimeout => @components = {} @getModuleComponents @baseDir @processing = false @ready = true @emit 'ready', true callback null, @components if callback , 1 load: (name, callback, metadata) -> unless @ready @listComponents (err) => return callback err if err @load name, callback, metadata return component = @components[name] unless component # Try an alias for componentName of @components if componentName.split('/')[1] is name component = @components[componentName] break unless component # Failure to load callback new Error "Component #{name} not available with base #{@baseDir}" return if @isGraph component if typeof process isnt 'undefined' and process.execPath and process.execPath.indexOf('node') isnt -1 # nextTick is faster on Node.js process.nextTick => @loadGraph name, component, callback, metadata else setTimeout => @loadGraph name, component, callback, metadata , 0 return @createComponent name, component, metadata, (err, instance) => return callback err if err if not instance callback new Error "Component #{name} could not be loaded." return instance.baseDir = @baseDir if name is 'Graph' @setIcon name, instance callback null, instance # Creates an instance of a component. createComponent: (name, component, metadata, callback) -> implementation = component # If a string was specified, attempt to `require` it. if typeof implementation is 'string' try implementation = require implementation catch e return callback e # Attempt to create the component instance using the `getComponent` method. if typeof implementation.getComponent is 'function' instance = implementation.getComponent metadata # Attempt to create a component using a factory function. else if typeof implementation is 'function' instance = implementation metadata else callback new Error "Invalid type #{typeof(implementation)} for component #{name}." return instance.componentName = name if typeof name is 'string' callback null, instance isGraph: (cPath) -> return true if typeof cPath is 'object' and cPath instanceof nofloGraph.Graph return false unless typeof cPath is 'string' cPath.indexOf('.fbp') isnt -1 or cPath.indexOf('.json') isnt -1 loadGraph: (name, component, callback, metadata) -> graphImplementation = require @components['Graph'] graphSocket = internalSocket.createSocket() graph = graphImplementation.getComponent metadata graph.loader = @ graph.baseDir = @baseDir graph.inPorts.graph.attach graphSocket graph.componentName = name if typeof name is 'string' graphSocket.send component graphSocket.disconnect() graph.inPorts.remove 'graph' @setIcon name, graph callback null, graph setIcon: (name, instance) -> # See if component has an icon return if not instance.getIcon or instance.getIcon() # See if library has an icon [library, componentName] = name.split '/' if componentName and @getLibraryIcon library instance.setIcon @getLibraryIcon library return # See if instance is a subgraph if instance.isSubgraph() instance.setIcon 'sitemap' return instance.setIcon 'square' return getLibraryIcon: (prefix) -> if @libraryIcons[prefix] return @libraryIcons[prefix] return null normalizeName: (packageId, name) -> prefix = @getModulePrefix packageId fullName = "#{prefix}/#{name}" fullName = name unless packageId fullName registerComponent: (packageId, name, cPath, callback) -> fullName = @normalizeName packageId, name @components[fullName] = cPath do callback if callback registerGraph: (packageId, name, gPath, callback) -> @registerComponent packageId, name, gPath, callback registerLoader: (loader, callback) -> loader @, callback setSource: (packageId, name, source, language, callback) -> unless @ready @listComponents (err) => return callback err if err @setSource packageId, name, source, language, callback return if language is 'coffeescript' unless window.CoffeeScript return callback new Error 'CoffeeScript compiler not available' try source = CoffeeScript.compile source, bare: true catch e return callback e else if language in ['es6', 'es2015'] unless window.babel return callback new Error 'Babel compiler not available' try source = babel.transform(source).code catch e return callback e # We eval the contents to get a runnable component try # Modify require path for NoFlo since we're inside the NoFlo context source = source.replace "require('noflo')", "require('./NoFlo')" source = source.replace 'require("noflo")', 'require("./NoFlo")' # Eval so we can get a function implementation = eval "(function () { var exports = {}; #{source}; return exports; })()" catch e return callback e unless implementation or implementation.getComponent return callback new Error 'Provided source failed to create a runnable component' @registerComponent packageId, name, implementation, -> callback null getSource: (name, callback) -> unless @ready @listComponents (err) => return callback err if err @getSource name, callback return component = @components[name] unless component # Try an alias for componentName of @components if componentName.split('/')[1] is name component = @components[componentName] name = componentName break unless component return callback new Error "Component #{name} not installed" if typeof component isnt 'string' return callback new Error "Can't provide source for #{name}. Not a file" nameParts = name.split '/' if nameParts.length is 1 nameParts[1] = nameParts[0] nameParts[0] = '' if @isGraph component nofloGraph.loadFile component, (err, graph) -> return callback err if err return callback new Error 'Unable to load graph' unless graph callback null, name: nameParts[1] library: nameParts[0] code: JSON.stringify graph.toJSON() language: 'json' return path = window.require.resolve component unless path return callback new Error "Component #{name} is not resolvable to a path" callback null, name: nameParts[1] library: nameParts[0] code: window.require.modules[path].toString() language: utils.guessLanguageFromFilename component clear: -> @components = null @checked = [] @revalidate = true @ready = false @processing = false exports.ComponentLoader = ComponentLoader