Component inspector
General purpose of tool is showing components boundaries and its DOM fragment with some details. It could be extended by features like source fragment locating and opening file in editor. See Additional features section for details.
Generally it's an adoptation of basis.js tool for other component frameworks and libraries. Here is ready to use builds:
You also could setup custom API for your own component solution.
- Install
- Usage
- Ready to use builds
- API free build
- isComponentRootNode(node)
- getComponentNameByNode(node)
- getInstanceByNode(node)
- getInstanceRootNode(instance)
- getInstanceClass(instance)
- getInstanceLocation(instance)
- getInstanceRenderLocation(instance)
- getNodeLocation(node)
- viewAttributeFilter(attribute)
- showDefaultInfo()
- getAdditionalInstanceInfo(instance, helpers)
- getLocation(value)
- getDevInfo(value[, property])
- buildComponentInfo(instance, node)
- logComponentInfo(info)
- isOpenFileSupported()
- openFile(filename)
- Custom info sections
- Make your own build
- Additional features
- License
Install
npm install component-inspector --save-dev
Usage
There are 3 options to use Component Inspector:
- use one of ready-to-use build for certain framework or library
- use API free build and init inspector with custom API
- make you own build as ready-to-use solution (PRs are welcome)
Ready to use builds
In case you use one of ready-to-use editions (i.e. React
or Backbone
) there is no any public API. Just include apropriate script and here you are.
React
- Component Inspector in action (video)
- Online Demo (w/o open in editor feature)
- Setup example
You should include component-inspector
script before React
script.
<script src="node_modules/component-inspector/dist/react.js"></script>
<script src="react.js"></script>
Backbone
- Component Inspector in action (video)
You should include component-inspector
script right after Backbone
script.
<script src="backbone.js"></script>
<script src="node_modules/component-inspector/dist/backbone.js"></script>
API free build
In case you want to use inspector with your own API adapter you should use dist/base.js
script. When script included into html only one public function available – initComponentInspector(api)
. This function could be invoked just once. It takes API methods that override defaults.
<script src="node_modules/component-inspector/dist/base.js"></script>
<script>
initComponentInspector({
isComponentRootNode: function(node){
return Boolean(node && node.__view);
},
getInstanceByNode: function(node){
if (node) {
return node.__view;
}
},
getInstanceRootNode: function(instance){
if (instance) {
return instance.rootNode;
}
}
});
</script>
All methods are optional. Most important method is isComponentRootNode
that checks DOM node is component root node. Also getInstanceByNode
and getInstanceRootNode
could be used to resolve DOM fragment owner (component instance) and owners root element. Other methods could provide additional information.
isComponentRootNode(node)
Returns true
if DOM node is root node of some component. Uses to determine component boundaries.
getComponentNameByNode(node)
Returns component name if possible. This name using in component short details.
getInstanceByNode(node)
Function to resolve component owner by DOM node if any. Usually it's some kind of view
instance.
getInstanceRootNode(instance)
Should return instance's root DOM node. It's vice versa function of getInstanceByNode(node)
. Using on DOM mutations to determine actual DOM node to inspect, as root DOM node could be changed on component re-render.
getInstanceClass(instance)
Should returns reference to instance class. Returns instance.constructor
by default.
getInstanceLocation(instance)
Returns instance source location if possible. Normally no need to override it, as it returns api.getLocation(instance)
by default.
getInstanceRenderLocation(instance)
By default it returns source location of instance.render
method. You can override it if needed.
getNodeLocation(node)
If there is source location where DOM node was created this function should returns it. Uses in DOM viewer when hover nodes.
viewAttributeFilter(attribute)
Allow to filter output attributes in DOM viewer. For example, React adds data-reactid
attribute to every element, but we don't want to show it. In this case, method could be:
viewAttributeFilter: function(attr){
return attr.name !== 'data-reactid';
}
showDefaultInfo()
Show default info block or not. True by default.
getAdditionalInstanceInfo(instance, helpers)
Allow provide additional info for related objects. Should provide array of objects (configs for sections) or nothing. See more details in Custom info sections.
getLocation(value)
Should returns source location, where value was defined. By default return loc
value from getDevInfo()
getDevInfo(value[, property])
Function provides dev info attached to value. It should returns info as is if no property
argument and property value otherwise.
getDevInfo(value);
// { "loc": "file.js:1:2:3:4", ... }
getDevInfo(value, 'loc');
// "file.js:1:2:3:4"
buildComponentInfo(instance, node)
Customize component info object build. Default behaviour:
api.buildComponentInfo = function(instance, node){
return {
instance: instance,
node: node
};
};
logComponentInfo(info)
Defines actions on component info logging. Default actions:
api.logComponentInfo = function(info){
window.$component = info;
console.log(info);
};
isOpenFileSupported()
Returns true
if open file in editor
is supported. In this case click by source location links will call openFile
method.
openFile(filename)
Should make some action to open specified filename in editor.
Custom info sections
There several ways to create custom info sections for inspectors sidebar. For all cases you should define getAdditionalInstanceInfo()
method. You are free to use any framework or library to create a section.
NOTE: If you don't want to output default section with instance info use override
showDefaultInfo
API method tofunction(){ return false; }
.
Location links
Most simple way is specify links list for some nested object. Here is example for model attached to instance:
api.getAdditionalInstanceInfo = function(instance){
if (instance && instance.model) {
return [{
name: 'Model',
locations: [
{ type: 'instance', loc: this.getLocation(instance.model) },
{ type: 'class', loc: this.getLocation(instance.model.constructor) }
]
}];
}
};
HTML view
If view should be created by some HTML markup, you can use section.html(name, html)
helper:
api.getAdditionalInstanceInfo = function(instance, section){
return [
section.html('Custom section', '<h1>Hello world</h1>')
];
};
Pass empty string or null
if you don't want to output a header of section.
DOM view
Any DOM node may to be specified as section. Use section.dom(name, nodeOrElement)
helper in this case:
var view = document.createElement('h1');
view.appendChild(document.createTextNode('Hello world!'));
api.getAdditionalInstanceInfo = function(instance, section){
return [
section.dom('Custom section', view)
];
};
If DOM node attach approach is using (e.g. React
), function should be passed to section.dom()
helper:
api.getAdditionalInstanceInfo = function(instance, section){
return [
section.dom('Custom section', function(container){
container.innerHTML = '<h1>Hello world!</h1>';
container.onclick = function(){
alert('Example!');
};
}),
section.dom(null, function(container){
React.render(<h1>Hello world!</h1>, container);
})
];
};
Pass empty string or null
if you don't want to output a header of section.
Links to source
To make a reference to source location add data-loc
attribute to an element. You can use getLocation()
method to fetch location for an object.
api.getAdditionalInstanceInfo = function(instance, section){
return [
section.html(null, 'This is a <span data-loc="' + this.getLocation(instance) + '">link</span>')
];
};
In this case the <span>
becomes clickable (if open in editor feature is set up) and popup with source fragment will be shown on hover.
Make your own build
You can make your own ready-to-use edition, see React or Backbone as examples.
Implementation
First of all, you should include basis.js
core (as inspector was originaly implemented using it) in your base html file and specify path to your implementation module.
<script src="node_modules/component-inspector/node_modules/basisjs/src/basis.js" basis-config="
noConflict: true, // prevents export names to global scope
devpanel: false,
modules: {
myapi: { // your implementation module
autoload: true,
filename: 'path/to/implementation.js'
}
}
"></script>
Example of implementation.js
:
var initInspector = require('./inspector/index.js');
initInspector({
isComponentRootNode: function(node){
// ..
},
getInstanceByNode: function(node){
// ..
},
getInstanceRootNode: function(instance){
// ..
}
});
Build
Install basisjs-tools
npm install basisjs-tools --save-dev
Run build with --single-file
option.
node node_modules/basisjs-tools/bin/basis --file path/to/myinspector.html --single-file --pack --same-filenames
As result you'll get single JavaScript file (myinspector.js
in this example) that contains everything. Include this script in your application page.
Additional features
Component inspector shows component bounds and its DOM fragment only. More details could be shown if some sort of meta-data (like source code location) is available.
There is example of default view for React component:
Compare it to the same view but with meta-data available:
You can use ready-to-use plugin for babel babel-plugin-source-wrapper to instrument your code with necessary API included. You free to use your own implementation. See documentation for details.
By default interface to get meta-data should be called $devinfo
with at least single method get()
. Inspector expects get
method returns an object or falsy value if no data.
window.$devinfo = {
get: function(ref){
// return some information for `ref`
}
};
If API has name other than $devinfo
, it should be defined via global variable DEVINFO_API_NAME
.
window.DEVINFO_API_NAME = 'customApiName';
Locating component's source
When meta-data is available for inspecting value, inspector expects location is storing in loc
property as string filename:startLine:startColumn:endLine:endColumn
.
window.$devinfo.get(obj);
// > { "loc": "app.js:...", ... }
If value definition source location is found some additional features became available: fetching source fragments and opening file in editor.
Fetching source fragments
Component inspector includes solution for retrieving original source code and highlight it. It's all possible if value location provided.
How does it work:
- retrieve source code by request to server for original filename (or get it from cache if
basisjs-tools
server is used) - extract original source code with aware of source maps
- cache the result
- highligh code
- get required source fragment and show it in popup
Since original files are fetching by request to server, make sure those files are available for downloading by your server.
NOTE: Take in account if source file was changed since downloaded by browser, inspector could show wrong (outdated) source fragment. Page refresh should solve the problem.
NOTE: Support for external or server API for source fragment extraction is coming in next releases (see issue).
Opening file in editor
Ready-to-use solutions to provide opening in editor
feature on server:
express
extension express-open-in-editor, that also could be used with webpack-dev-server.- in case you don't depend on dev-server, you can use basisjs-tools that provides this feature out of box
- add your own implementation for your dev-server using open-in-editor
In case dev-server supports opening in editor
feature, all location references become active links. A click on any of those opens a file in editor with cursor set at the start of the code fragment. You'll see a hint if feature is supported.
One possible way to provide this feature is set global variable OPEN_FILE_URL
.
window.OPEN_FILE_URL = '/open-in-editor';
When location link is clicked inspector sends request to server (see implementation for details):
GET /open-in-editor?file=/path/to/file.js:1:2:3:4
NOTE: Take in account if source file was changed since downloaded by browser, inspector could "open" file on wrong (outdated) position. Page refresh should solve the problem.
License
MIT