Skip to content

Commit

Permalink
fix(entries): component dependencies from module imports
Browse files Browse the repository at this point in the history
Closes #513
  • Loading branch information
adamdbradley committed Feb 11, 2018
1 parent 38c9201 commit 3eac82a
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 36 deletions.
2 changes: 2 additions & 0 deletions src/compiler/build/build-utils.ts
Expand Up @@ -22,6 +22,8 @@ export function getBuildContext(config: Config, compilerCtx: CompilerCtx, watche
const buildCtx: BuildCtx = {
requiresFullBuild: requiresFullBuild,
buildId: compilerCtx.activeBuildId,
componentRefs: [],
moduleGraph: [],
diagnostics: [],
entryPoints: [],
entryModules: [],
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/collections/upgrade-collection.ts
Expand Up @@ -55,7 +55,7 @@ function createDoUpgrade(config: Config, compilerCtx: CompilerCtx, buildCtx: Bui
case CompilerUpgrade.Add_Component_Dependencies:
config.logger.debug(`Add_Component_Dependencies, ${manifest.collectionName}, compiled by v${manifest.compiler.version}`);
return (transformContext: ts.TransformationContext) => {
return componentDependencies(compilerCtx.moduleFiles)(transformContext);
return componentDependencies(compilerCtx, buildCtx)(transformContext);
};
}
return () => (tsSourceFile: ts.SourceFile) => (tsSourceFile);
Expand Down
43 changes: 43 additions & 0 deletions src/compiler/entries/component-dependencies.ts
@@ -0,0 +1,43 @@
import { ComponentMeta, ComponentReference, ModuleFiles, ModuleGraph } from '../../declarations';


export function calcComponentDependencies(moduleFiles: ModuleFiles, moduleGraphs: ModuleGraph[], componentRefs: ComponentReference[]) {
Object.keys(moduleFiles).forEach(filePath => {
const moduleFile = moduleFiles[filePath];
if (moduleFile.cmpMeta) {
getComponentDependencies(moduleGraphs, componentRefs, filePath, moduleFile.cmpMeta);
}
});
}


function getComponentDependencies(moduleGraphs: ModuleGraph[], componentRefs: ComponentReference[], filePath: string, cmpMeta: ComponentMeta) {
cmpMeta.dependencies = componentRefs.filter(cr => cr.filePath === filePath).map(cr => cr.tag);

const moduleGraph = moduleGraphs.find(mg => mg.filePath === filePath);
if (moduleGraph) {
getComponentDepsFromImports(moduleGraphs, componentRefs, moduleGraph, cmpMeta);
}

cmpMeta.dependencies.sort();
}


function getComponentDepsFromImports(moduleGraphs: ModuleGraph[], componentRefs: ComponentReference[], moduleGraph: ModuleGraph, cmpMeta: ComponentMeta) {
moduleGraph.importPaths.forEach(importPath => {
const subModuleGraph = moduleGraphs.find(mg => {
return (mg.filePath === importPath) ||
(mg.filePath === importPath + '.ts') ||
(mg.filePath === importPath + '.tsx') ||
(mg.filePath === importPath + '.js');
});

if (subModuleGraph) {
const tags = componentRefs.filter(cr => cr.filePath === subModuleGraph.filePath).map(cr => cr.tag);

cmpMeta.dependencies.push(...tags);

getComponentDepsFromImports(moduleGraphs, componentRefs, subModuleGraph, cmpMeta);
}
});
}
56 changes: 56 additions & 0 deletions src/compiler/transpile/test/transpile.spec.ts
Expand Up @@ -187,6 +187,62 @@ describe('transpile', () => {
expect(content).toContain(`"str": { "type": String, "attr": "str" }`);
});

it('get component dependencies from imports', async () => {
c.config.bundles = [ { components: ['cmp-a'] } ];
await c.fs.writeFiles({
'/src/new-dir/cmp-b.tsx': `@Component({ tag: 'cmp-b' }) export class CmpB {}`,
'/src/new-dir/cmp-c.tsx': `@Component({ tag: 'cmp-c' }) export class CmpC {}`,
'/src/new-dir/cmp-d.tsx': `@Component({ tag: 'cmp-d' }) export class CmpD {}`,
'/src/new-dir/cmp-e.tsx': `@Component({ tag: 'cmp-e' }) export class CmpE {}`,
'/src/util-1.tsx': `
import { getImportedCmpC } from './util-2';
export function getCmpB() {
const el = document.createElement("cmp-b");
return el;
}
export function getCmpC() {
return getImportedCmpC();
}
`,
'/src/util-2.tsx': `
import { getJsxCmpD } from './util-3';
export function getImportedCmpC() {
return {
cmpC: document.createElement("cmp-c"),
cmpD: getJsxCmpD()
};
}
`,
'/src/util-3.tsx': `
export function getJsxCmpD() {
return <cmp-d/>;
}
export function getJsxCmpE() {
return document.createElement('cmp-e');
}
`
}, { clearFileCache: true });

await c.fs.writeFile('/src/cmp-a.tsx', `
import { getCmpB, getCmpC } from './util-1';
@Component({ tag: 'cmp-a' }) export class CmpA {
componentWillLoad() {
getCmpB();
}
componentDidLoad() {
getCmpC();
}
}
`, { clearFileCache: true });
await c.fs.commit();

const r = await c.build();
expect(r.diagnostics).toEqual([]);

expect(r.components[0].dependencies).toEqual(['cmp-b', 'cmp-c', 'cmp-d', 'cmp-e']);
});

it('get CallExpression component dependencies', async () => {
c.config.bundles = [ { components: ['cmp-a'] } ];
await c.fs.writeFiles({
Expand Down
78 changes: 45 additions & 33 deletions src/compiler/transpile/transformers/component-dependencies.ts
@@ -1,83 +1,92 @@
import { ComponentMeta, ModuleFiles } from '../../../declarations';
import { BuildCtx, CompilerCtx } from '../../../declarations';
import { MEMBER_TYPE } from '../../../util/constants';
import { normalizePath } from '../../util';
import * as ts from 'typescript';


export function componentDependencies(allModuleFiles: ModuleFiles): ts.TransformerFactory<ts.SourceFile> {
export function componentDependencies(compilerCtx: CompilerCtx, buildCtx: BuildCtx): ts.TransformerFactory<ts.SourceFile> {

const allComponentTags = Object.keys(allModuleFiles)
.map(filePath => allModuleFiles[filePath].cmpMeta)
const allComponentTags = Object.keys(compilerCtx.moduleFiles)
.map(filePath => compilerCtx.moduleFiles[filePath].cmpMeta)
.filter(cmpMeta => cmpMeta && cmpMeta.tagNameMeta)
.map(cmpMeta => cmpMeta.tagNameMeta);

return (transformContext) => {

function visit(node: ts.Node, cmpMeta: ComponentMeta): ts.VisitResult<ts.Node> {
function visit(node: ts.Node, filePath: string): ts.VisitResult<ts.Node> {
if (node.kind === ts.SyntaxKind.CallExpression) {
callExpression(allComponentTags, cmpMeta, node as ts.CallExpression);
callExpression(buildCtx, allComponentTags, filePath, node as ts.CallExpression);

} else if (node.kind === ts.SyntaxKind.StringLiteral) {
stringLiteral(allComponentTags, cmpMeta, node as ts.StringLiteral);
stringLiteral(buildCtx, allComponentTags, filePath, node as ts.StringLiteral);
}

return ts.visitEachChild(node, (node) => {
return visit(node, cmpMeta);
return visit(node, filePath);
}, transformContext);
}

return (tsSourceFile) => {
const moduleFile = allModuleFiles[tsSourceFile.fileName];
addPropConnects(compilerCtx, buildCtx, tsSourceFile.fileName);

if (moduleFile && moduleFile.cmpMeta) {
moduleFile.cmpMeta.dependencies = moduleFile.cmpMeta.dependencies || [];
return visit(tsSourceFile, tsSourceFile.fileName) as ts.SourceFile;
};
};
}

if (moduleFile.cmpMeta.membersMeta) {
Object.keys(moduleFile.cmpMeta.membersMeta).forEach(memberName => {
const memberMeta = moduleFile.cmpMeta.membersMeta[memberName];
if (memberMeta.memberType === MEMBER_TYPE.PropConnect) {
moduleFile.cmpMeta.dependencies.push(memberMeta.ctrlId);
}
});
}

return visit(tsSourceFile, moduleFile.cmpMeta) as ts.SourceFile;
function addPropConnects(compilerCtx: CompilerCtx, buildCtx: BuildCtx, filePath: string) {
const moduleFile = compilerCtx.moduleFiles[filePath];

const cmpMeta = (moduleFile && moduleFile.cmpMeta);
if (!cmpMeta) {
return;
}

if (cmpMeta.membersMeta) {
Object.keys(cmpMeta.membersMeta).forEach(memberName => {
const memberMeta = cmpMeta.membersMeta[memberName];
if (memberMeta.memberType === MEMBER_TYPE.PropConnect) {
buildCtx.componentRefs.push({
tag: memberMeta.ctrlId,
filePath: filePath
});
}
return tsSourceFile;
};
};
});
}
}


function callExpression(allComponentTags: string[], cmpMeta: ComponentMeta, node: ts.CallExpression) {
function callExpression(buildCtx: BuildCtx, allComponentTags: string[], filePath: string, node: ts.CallExpression) {
if (node && node.arguments) {

if (node.expression.kind === ts.SyntaxKind.Identifier) {
// h('tag')
callExpressionArg(allComponentTags, cmpMeta, node.expression as ts.Identifier, node.arguments);
callExpressionArg(buildCtx, allComponentTags, filePath, node.expression as ts.Identifier, node.arguments);

} else if (node.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
// document.createElement('tag')

if ((node.expression as ts.PropertyAccessExpression).name) {
// const
callExpressionArg(allComponentTags, cmpMeta, (node.expression as ts.PropertyAccessExpression).name as ts.Identifier, node.arguments);
callExpressionArg(buildCtx, allComponentTags, filePath, (node.expression as ts.PropertyAccessExpression).name as ts.Identifier, node.arguments);
}
}
}
}


function callExpressionArg(allComponentTags: string[], cmpMeta: ComponentMeta, callExpressionName: ts.Identifier, args: ts.NodeArray<ts.Expression>) {
function callExpressionArg(buildCtx: BuildCtx, allComponentTags: string[], filePath: string, callExpressionName: ts.Identifier, args: ts.NodeArray<ts.Expression>) {
if (TAG_CALL_EXPRESSIONS.includes(callExpressionName.escapedText as string)) {

if (args[0] && args[0].kind === ts.SyntaxKind.StringLiteral) {
addDependency(allComponentTags, cmpMeta, (args[0] as ts.StringLiteral).text);
addComponentReference(buildCtx, allComponentTags, filePath, (args[0] as ts.StringLiteral).text);
}
}
}


function stringLiteral(allComponentTags: string[], cmpMeta: ComponentMeta, node: ts.StringLiteral) {
function stringLiteral(buildCtx: BuildCtx, allComponentTags: string[], filePath: string, node: ts.StringLiteral) {
let t = node.text;

if (typeof t === 'string' && t.includes('<')) {
Expand All @@ -91,18 +100,21 @@ function stringLiteral(allComponentTags: string[], cmpMeta: ComponentMeta, node:
});

foundTags.forEach(foundTag => {
addDependency(allComponentTags, cmpMeta, foundTag);
addComponentReference(buildCtx, allComponentTags, filePath, foundTag);
});
}
}


function addDependency(allComponentTags: string[], cmpMeta: ComponentMeta, tag: string) {
function addComponentReference(buildCtx: BuildCtx, allComponentTags: string[], filePath: string, tag: string) {
if (typeof tag === 'string') {
tag = tag.toLowerCase().trim();

if (!cmpMeta.dependencies.includes(tag) && allComponentTags.includes(tag)) {
cmpMeta.dependencies.push(tag);
if (allComponentTags.includes(tag)) {
buildCtx.componentRefs.push({
tag: tag,
filePath: normalizePath(filePath)
});
}
}
}
Expand Down
49 changes: 49 additions & 0 deletions src/compiler/transpile/transformers/module-graph.ts
@@ -0,0 +1,49 @@
import { BuildCtx, Config, ModuleGraph } from '../../../declarations/index';
import { normalizePath, pathJoin } from '../../util';
import * as ts from 'typescript';


export function moduleGraph(config: Config, buildCtx: BuildCtx): ts.TransformerFactory<ts.SourceFile> {

return (transformContext) => {

function visitImport(moduleGraph: ModuleGraph, dirPath: string, importNode: ts.ImportDeclaration) {
if (importNode.moduleSpecifier) {
let importPath = importNode.moduleSpecifier.getText().replace(/\'|\"|\`/g, '');

if (importPath.startsWith('.') || importPath.startsWith('/')) {
importPath = pathJoin(config, dirPath, importPath);
}

moduleGraph.importPaths.push(importPath);
}

return importNode;
}

function visit(moduleGraph: ModuleGraph, dirPath: string, node: ts.Node): ts.VisitResult<ts.Node> {
switch (node.kind) {
case ts.SyntaxKind.ImportDeclaration:
return visitImport(moduleGraph, dirPath, node as ts.ImportDeclaration);
default:
return ts.visitEachChild(node, (node) => {
return visit(moduleGraph, dirPath, node);
}, transformContext);
}
}

return (tsSourceFile) => {
const moduleGraph: ModuleGraph = {
filePath: normalizePath(tsSourceFile.fileName),
importPaths: []
};

const dirPath = config.sys.path.dirname(tsSourceFile.fileName);

buildCtx.moduleGraph.push(moduleGraph);

return visit(moduleGraph, dirPath, tsSourceFile) as ts.SourceFile;
};
};

}
10 changes: 8 additions & 2 deletions src/compiler/transpile/transpile.ts
Expand Up @@ -3,11 +3,13 @@ import { BuildCtx, CompilerCtx, Config, Diagnostic, FsWriteResults, ModuleFiles,
import { componentDependencies } from './transformers/component-dependencies';
import { gatherMetadata } from './datacollection/index';
import { generateComponentTypesFile } from './create-component-types';
import { calcComponentDependencies } from '../entries/component-dependencies';
import { getComponentsDtsSrcFilePath } from '../build/distribution';
import { getTsHost } from './compiler-host';
import { getUserTsConfig } from './compiler-options';
import { hasError, normalizePath } from '../util';
import { loadTypeScriptDiagnostics } from '../../util/logger/logger-typescript';
import { moduleGraph } from './transformers/module-graph';
import { normalizeAssetsDir } from '../component-plugins/assets-plugin';
import { normalizeStyles } from './normalize-styles';
import { removeDecorators } from './transformers/remove-decorators';
Expand Down Expand Up @@ -109,15 +111,19 @@ function transpileProgram(program: ts.Program, tsHost: ts.CompilerHost, config:
program.emit(undefined, tsHost.writeFile, undefined, false, {
before: [
removeDecorators(),
addComponentMetadata(compilerCtx.moduleFiles)
addComponentMetadata(compilerCtx.moduleFiles),
moduleGraph(config, buildCtx)
],
after: [
removeImports(),
removeStencilImports(),
componentDependencies(compilerCtx.moduleFiles)
componentDependencies(compilerCtx, buildCtx)
]
});

// figure out how modules and components connect
calcComponentDependencies(compilerCtx.moduleFiles, buildCtx.moduleGraph, buildCtx.componentRefs);

if (!config.suppressTypeScriptErrors) {
// suppressTypeScriptErrors mainly for unit testing
const tsDiagnostics: ts.Diagnostic[] = [];
Expand Down
2 changes: 2 additions & 0 deletions src/declarations/build.ts
Expand Up @@ -3,6 +3,8 @@ import * as d from './index';

export interface BuildCtx {
graphData?: GraphData;
componentRefs?: d.ComponentReference[];
moduleGraph?: d.ModuleGraph[];
buildId: number;
requiresFullBuild: boolean;
diagnostics: d.Diagnostic[];
Expand Down
10 changes: 10 additions & 0 deletions src/declarations/entry.ts
Expand Up @@ -27,3 +27,13 @@ export interface EntryComponent {
tag: string;
dependencyOf?: string[];
}

export interface ComponentReference {
tag: string;
filePath: string;
}

export interface ModuleGraph {
filePath: string;
importPaths: string[];
}

0 comments on commit 3eac82a

Please sign in to comment.