From 3eac82a203affc7eb6ce96708211ce0f37ceb44d Mon Sep 17 00:00:00 2001 From: Adam Bradley Date: Sun, 11 Feb 2018 17:03:33 -0600 Subject: [PATCH] fix(entries): component dependencies from module imports Closes #513 --- src/compiler/build/build-utils.ts | 2 + .../collections/upgrade-collection.ts | 2 +- .../entries/component-dependencies.ts | 43 ++++++++++ src/compiler/transpile/test/transpile.spec.ts | 56 +++++++++++++ .../transformers/component-dependencies.ts | 78 +++++++++++-------- .../transpile/transformers/module-graph.ts | 49 ++++++++++++ src/compiler/transpile/transpile.ts | 10 ++- src/declarations/build.ts | 2 + src/declarations/entry.ts | 10 +++ 9 files changed, 216 insertions(+), 36 deletions(-) create mode 100644 src/compiler/entries/component-dependencies.ts create mode 100644 src/compiler/transpile/transformers/module-graph.ts diff --git a/src/compiler/build/build-utils.ts b/src/compiler/build/build-utils.ts index c26800ab244..bc04fc03d16 100644 --- a/src/compiler/build/build-utils.ts +++ b/src/compiler/build/build-utils.ts @@ -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: [], diff --git a/src/compiler/collections/upgrade-collection.ts b/src/compiler/collections/upgrade-collection.ts index 5460b87e3d4..b20399564a8 100644 --- a/src/compiler/collections/upgrade-collection.ts +++ b/src/compiler/collections/upgrade-collection.ts @@ -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); diff --git a/src/compiler/entries/component-dependencies.ts b/src/compiler/entries/component-dependencies.ts new file mode 100644 index 00000000000..2d2ce533657 --- /dev/null +++ b/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); + } + }); +} diff --git a/src/compiler/transpile/test/transpile.spec.ts b/src/compiler/transpile/test/transpile.spec.ts index d3c0b23a6b7..093d296583d 100644 --- a/src/compiler/transpile/test/transpile.spec.ts +++ b/src/compiler/transpile/test/transpile.spec.ts @@ -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 ; + } + 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({ diff --git a/src/compiler/transpile/transformers/component-dependencies.ts b/src/compiler/transpile/transformers/component-dependencies.ts index 30afe58db95..8699a6d708f 100644 --- a/src/compiler/transpile/transformers/component-dependencies.ts +++ b/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 { +export function componentDependencies(compilerCtx: CompilerCtx, buildCtx: BuildCtx): ts.TransformerFactory { - 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 { + function visit(node: ts.Node, filePath: string): ts.VisitResult { 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) { +function callExpressionArg(buildCtx: BuildCtx, allComponentTags: string[], filePath: string, callExpressionName: ts.Identifier, args: ts.NodeArray) { 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('<')) { @@ -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) + }); } } } diff --git a/src/compiler/transpile/transformers/module-graph.ts b/src/compiler/transpile/transformers/module-graph.ts new file mode 100644 index 00000000000..fc78f99d08e --- /dev/null +++ b/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 { + + 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 { + 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; + }; + }; + +} diff --git a/src/compiler/transpile/transpile.ts b/src/compiler/transpile/transpile.ts index 81b49ce28f4..49cc8c6c38d 100644 --- a/src/compiler/transpile/transpile.ts +++ b/src/compiler/transpile/transpile.ts @@ -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'; @@ -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[] = []; diff --git a/src/declarations/build.ts b/src/declarations/build.ts index 0bb791cd437..8ad453b029f 100644 --- a/src/declarations/build.ts +++ b/src/declarations/build.ts @@ -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[]; diff --git a/src/declarations/entry.ts b/src/declarations/entry.ts index 3766b8a829c..834eaa75059 100644 --- a/src/declarations/entry.ts +++ b/src/declarations/entry.ts @@ -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[]; +}