Skip to content

Commit

Permalink
feat: local dependencies
Browse files Browse the repository at this point in the history
  • Loading branch information
atanasster committed Nov 28, 2020
1 parent 0575fbb commit 628d09f
Show file tree
Hide file tree
Showing 27 changed files with 637 additions and 721 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
"program": "${workspaceFolder}/node_modules/.bin/jest",
"cwd": "${workspaceFolder}/core/instrument",
"args": [
"esm-template",
"extract-component",
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
Expand Down
25 changes: 22 additions & 3 deletions core/core/src/components.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CodeLocation, Imports } from './utility';
import { CodeLocation, ImportType, Imports } from './utility';

export type TypeValue =
| 'any'
Expand Down Expand Up @@ -90,6 +90,15 @@ export interface ComponentInfo {
props: PropTypes;
}

export type JSXNode = Partial<ImportType> & {
attributes?: string[];
children?: Partial<ImportType>[];
};
/**
* jsx tree of elements for the component
*/
export type JSXTree = JSXNode[];

/**
* component specified for stories or story files
*/
Expand Down Expand Up @@ -143,9 +152,19 @@ export interface Component {
*/
info?: ComponentInfo;
/**
* list of external imports
* list of component's file imports from external libraries
*/
externalDependencies?: Imports;

/**
* list of component's file imports from local (imported via relative import) files
*/
localDependencies?: Imports;

/**
* jsx component tree
*/
imports?: Imports;
jsx?: JSXTree;
}
/**
* given a component, return its name
Expand Down
25 changes: 17 additions & 8 deletions core/core/src/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,31 @@ export type StoryRenderFn = (
/**
* an import name
*/
export interface ImportName {
export interface ImportType {
/**
* the imported name from the import file
* component name
*/
name: string;
/**
* alias imported as. If a default import, the string 'default' is here.
* importedName - the original named import that was aliased
*/
importedName: string;
importedName: 'default' | 'namespace' | string;
/**
* imported from
*/
from: string;
/**
* key into components table
*/
key?: string;
}

export interface ImportTypes {
[key: string]: ImportType;
}

/**
* imports - library/file as key and the imported names as an array
*/
export interface Imports {
[key: string]: ImportName[];
[key: string]: Omit<ImportType, 'from'>[];
}

/**
Expand Down
57 changes: 57 additions & 0 deletions core/instrument/src/babel/analyze-component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import fs from 'fs';
import * as parser from '@babel/parser';
import traverse, { TraverseOptions } from '@babel/traverse';
import { Component, JSXNode, JSXTree } from '@component-controls/core';
import { collectAttributes } from './extract-attributes';
import { InstrumentOptions } from '../types';

type JSXLinkedNode = JSXNode & { parent?: JSXNode };
type JSXLinkedTree = JSXLinkedNode[];

export const traverseJSX = (jsx: JSXLinkedTree): TraverseOptions => {
let current: JSXLinkedNode = { children: jsx };
return {
JSXElement: {
enter(path) {
const node = path.node;
const name = node.openingElement.name as { name: string };
const attributes = collectAttributes(node.openingElement);
const jsxNode: JSXLinkedNode = {
name: name.name,
parent: current,
children: [],
attributes: Object.keys(attributes),
};
if (current.children) {
current.children.push(jsxNode);
}
current = jsxNode;
},

exit() {
current = current.parent as JSXLinkedNode;
},
},
};
};
export const analyze_components = (
component: Component,
filePath: string,
options?: InstrumentOptions,
): void => {
const { parser: parserOptions } = options || {};
const source = fs.readFileSync(filePath, 'utf8');
const ast = parser.parse(source, parserOptions);
const jsx: JSXLinkedTree = [];
traverse(ast, traverseJSX(jsx));
const mapJSXTree = (input?: JSXLinkedTree): JSXTree => {
return input
? // eslint-disable-next-line @typescript-eslint/no-unused-vars
input.map(({ parent, children, ...rest }) => ({
children: mapJSXTree(children),
...rest,
}))
: [];
};
component.jsx = mapJSXTree(jsx);
};
89 changes: 74 additions & 15 deletions core/instrument/src/babel/extract-component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import path from 'path';
import { File } from '@babel/types';
import { Component, Document, PackageInfo } from '@component-controls/core';
import { hashStoreId } from '../misc/hashStore';
import * as resolve from 'resolve';
import {
Component,
Document,
PackageInfo,
Imports,
ImportTypes,
} from '@component-controls/core';
import { componentKey } from '../misc/hashStore';
import { followImports } from './follow-imports';
import { analyze_components } from './analyze-component';
import { packageInfo } from '../misc/package-info';
import { readSourceFile } from '../misc/source-options';

Expand Down Expand Up @@ -32,7 +41,7 @@ export const extractComponent = async (
options,
initialAST,
);
const { components } = options || {};
const { components, resolver: resolveOptions } = options || {};
let component: Component;
let componentPackage: PackageInfo | undefined;
if (follow) {
Expand All @@ -42,7 +51,64 @@ export const extractComponent = async (
if (follow.from) {
component.from = follow.from;
}
if (follow.imports) {
const imports: ImportTypes = follow.imports;
const allImports = Object.keys(imports).reduce((acc: Imports, key) => {
const { name, from, importedName } = imports[key];
let importKey = undefined;
if (follow.filePath && from.startsWith('.')) {
try {
const fileName = resolve.sync(from, {
...resolveOptions,
basedir: path.dirname(follow.filePath),
});
const followImport = followImports(
importedName,
fileName,
undefined,
options,
undefined,
);
if (followImport?.filePath) {
importKey = componentKey(followImport.filePath, importedName);
}
} catch (e) {}
}

if (acc[from]) {
return {
...acc,
[from]: [
...acc[from],
{ name, from, importedName, key: importKey },
],
};
}
return {
...acc,
[from]: [{ name, from, importedName, key: importKey }],
};
}, {});
component.externalDependencies = Object.keys(allImports)
.filter(key => !key.startsWith('.'))
.reduce(
(acc, key) => ({ ...acc, [key]: (allImports as Imports)[key] }),
{},
);
component.localDependencies = Object.keys(allImports)
.filter(key => key.startsWith('.'))
.reduce((acc, key) => {
return { ...acc, [key]: (allImports as Imports)[key] };
}, {});
}
if (follow.importedName) {
component.importedName = follow.importedName;
}
if (follow.filePath) {
if (follow.imports) {
analyze_components(component, follow.filePath, options);
}

component.request = follow.filePath;
if (components && typeof components.resolvePropsFile === 'function') {
const propsFile = components.resolvePropsFile(
Expand All @@ -53,14 +119,6 @@ export const extractComponent = async (
component.propsInfoFile = propsFile;
}
}
}
if (follow.imports) {
component.imports = follow.imports;
}
if (follow.importedName) {
component.importedName = follow.importedName;
}
if (follow.filePath) {
const saveSource = readSourceFile(
components?.sourceFiles,
follow.source,
Expand Down Expand Up @@ -112,11 +170,12 @@ export const extractStoreComponent = async (
store.packages[componentPackage.fileHash] = componentPackage;
component.package = componentPackage.fileHash;
}
const componentKey = hashStoreId(
`${component.request ?? filePath}-${componentName}`,
const key = componentKey(
component.request ?? filePath,
componentName,
);
store.components[componentKey] = component;
doc.componentsLookup[componentName] = componentKey;
store.components[key] = component;
doc.componentsLookup[componentName] = key;
}
}
}
Expand Down
11 changes: 1 addition & 10 deletions core/instrument/src/babel/extract-imports.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,7 @@
import * as parser from '@babel/parser';
import { ImportTypes } from '@component-controls/core';
import traverse, { TraverseOptions } from '@babel/traverse';

export interface ImportType {
name: string;
importedName: 'default' | 'namespace' | string;
from: string;
}

export interface ImportTypes {
[key: string]: ImportType;
}

export const traverseImports = (results: ImportTypes): TraverseOptions => {
return {
ImportDeclaration: (path: any) => {
Expand Down
31 changes: 4 additions & 27 deletions core/instrument/src/babel/follow-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import { File } from '@babel/types';
import traverse from '@babel/traverse';
import {
CodeLocation,
Imports,
Story,
getASTSource,
ImportTypes,
} from '@component-controls/core';
import { extractFunctionParameters } from './extract-function-parameters';
import { sourceLocation } from '../misc/source-location';
import { ImportTypes, traverseImports } from './extract-imports';
import { traverseImports } from './extract-imports';

import {
traverseExports,
Expand All @@ -31,7 +31,7 @@ export interface FollowImportType {
loc?: CodeLocation;
source?: string;
imported?: string;
imports?: Imports;
imports?: ImportTypes;
node?: any;
path?: any;
}
Expand Down Expand Up @@ -87,30 +87,7 @@ export const followImports = (
result.source = source ? source : undefined;
result.node = findExport.node;
result.path = findExport.path;
const externalImports = Object.keys(imports)
.filter(key => !imports[key].from.startsWith('.'))
.reduce(
(
acc: {
[key: string]: {
name: string;
importedName: string;
}[];
},
key,
) => {
const { name, from, importedName } = imports[key];
if (acc[from]) {
return {
...acc,
[from]: [...acc[from], { name, importedName }],
};
}
return { ...acc, [from]: [{ name, importedName }] };
},
{},
);
result.imports = externalImports;
result.imports = imports;
return result;
} else {
const resolvedFilePath = resolve.sync(findExport.from, {
Expand Down
3 changes: 3 additions & 0 deletions core/instrument/src/misc/hashStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@ export const hashStoreId = (name: string): string =>
createHash('md5')
.update(name)
.digest('hex');

export const componentKey = (filePath: string, componentName: string): string =>
hashStoreId(`${filePath}-${componentName}`);
8 changes: 7 additions & 1 deletion core/instrument/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,13 @@ export const defaultResolveOptions: ResolveOptions = {

export const defaultParserOptions: ParserOptions = {
sourceType: 'module',
plugins: ['jsx', 'typescript', 'classProperties'],
plugins: [
'jsx',
'typescript',
'classProperties',
'dynamicImport',
'objectRestSpread',
],
};

/**
Expand Down
Loading

0 comments on commit 628d09f

Please sign in to comment.