Skip to content

Commit

Permalink
fix(entries): use all strings for component reference graph
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdbradley committed Feb 13, 2018
1 parent d928c2c commit 6629aa1
Show file tree
Hide file tree
Showing 11 changed files with 143 additions and 120 deletions.
19 changes: 12 additions & 7 deletions src/compiler/build/build-results.ts
@@ -1,4 +1,4 @@
import { BuildBundle, BuildComponent, BuildCtx, BuildEntry, BuildResults, BuildStats, CompilerCtx, Config } from '../../declarations';
import { BuildBundle, BuildComponent, BuildCtx, BuildEntry, BuildResults, BuildStats, CompilerCtx, Config, EntryComponent } from '../../declarations';
import { cleanDiagnostics } from '../../util/logger/logger-util';
import { DEFAULT_STYLE_MODE, ENCAPSULATION } from '../../util/constants';
import { hasError, normalizePath } from '../util';
Expand Down Expand Up @@ -31,19 +31,24 @@ export function generateBuildResults(config: Config, compilerCtx: CompilerCtx, b
en.entryBundles = en.entryBundles || [];
en.moduleFiles = en.moduleFiles || [];

const entryCmps: EntryComponent[] = [];
buildCtx.entryPoints.forEach(ep => {
entryCmps.push(...ep);
});

const buildEntry: BuildEntry = {
entryId: en.entryKey,

components: en.moduleFiles.map(m => {
const entryCmp = entryCmps.find(ec => {
return ec.tag === m.cmpMeta.tagNameMeta;
});
const dependencyOf = ((entryCmp && entryCmp.dependencyOf) || []).slice().sort();

const buildCmp: BuildComponent = {
tag: m.cmpMeta.tagNameMeta,
dependencies: m.cmpMeta.dependencies.slice(),
dependencyOf: en.moduleFiles.reduce((dependencyOf, otherModule) => {
if (otherModule.cmpMeta.dependencies.includes(m.cmpMeta.tagNameMeta)) {
dependencyOf.push(otherModule.cmpMeta.tagNameMeta);
}
return dependencyOf;
}, [] as string[]).sort()
dependencyOf: dependencyOf
};
return buildCmp;
}),
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/build/build-utils.ts
Expand Up @@ -22,8 +22,8 @@ export function getBuildContext(config: Config, compilerCtx: CompilerCtx, watche
const buildCtx: BuildCtx = {
requiresFullBuild: requiresFullBuild,
buildId: compilerCtx.activeBuildId,
componentRefs: [],
moduleGraph: [],
sourceStrings: [],
moduleGraphs: [],
diagnostics: [],
entryPoints: [],
entryModules: [],
Expand Down
42 changes: 36 additions & 6 deletions src/compiler/entries/component-dependencies.ts
@@ -1,9 +1,34 @@
import { ComponentMeta, ComponentReference, ModuleFiles, ModuleGraph } from '../../declarations';
import { CompilerCtx, ComponentMeta, ComponentReference, ModuleFiles, ModuleGraph, SourceString } from '../../declarations';
import { getComponentRefsFromSourceStrings } from './component-references';


export function calcComponentDependencies(moduleFiles: ModuleFiles, moduleGraphs: ModuleGraph[], componentRefs: ComponentReference[]) {
Object.keys(moduleFiles).forEach(filePath => {
const moduleFile = moduleFiles[filePath];
export function calcModuleGraphImportPaths(compilerCtx: CompilerCtx, moduleGraphs: ModuleGraph[]) {
// figure out the actual source's file path
// cuz right now the import paths probably don't have the extension on them
moduleGraphs.forEach(mg => {
mg.importPaths = mg.importPaths.map(importPath => {
if (importPath.startsWith('.') || importPath.startsWith('/')) {
for (const srcExt of SRC_EXTS) {
const srcFilePath = importPath + srcExt;
if (compilerCtx.moduleFiles[srcFilePath]) {
return srcFilePath;
}
}
}
return importPath;
});
});
}

const SRC_EXTS = ['.tsx', '.ts', '.js'];


export function calcComponentDependencies(allModuleFiles: ModuleFiles, moduleGraphs: ModuleGraph[], sourceStrings: SourceString[]) {
// figure out all the component references seen in each file
const componentRefs = getComponentRefsFromSourceStrings(allModuleFiles, sourceStrings);

Object.keys(allModuleFiles).forEach(filePath => {
const moduleFile = allModuleFiles[filePath];
if (moduleFile.cmpMeta) {
getComponentDependencies(moduleGraphs, componentRefs, filePath, moduleFile.cmpMeta);
}
Expand All @@ -15,8 +40,13 @@ function getComponentDependencies(moduleGraphs: ModuleGraph[], componentRefs: Co
// we may have already figured out some dependencies (collections aready have this info)
cmpMeta.dependencies = cmpMeta.dependencies || [];

// push on any new tags we found through component references
cmpMeta.dependencies.push(...componentRefs.filter(cr => cr.filePath === filePath).map(cr => cr.tag));
// figure out if this file has any components in it
const refTags = componentRefs.filter(cr => cr.filePath === filePath).map(cr => cr.tag);
refTags.forEach(tag => {
if (tag !== cmpMeta.tagNameMeta && !cmpMeta.dependencies.includes(tag)) {
cmpMeta.dependencies.push(tag);
}
});

const importsInspected: string[] = [];

Expand Down
58 changes: 58 additions & 0 deletions src/compiler/entries/component-references.ts
@@ -0,0 +1,58 @@
import { ComponentReference, ModuleFiles, SourceString } from '../../declarations';


export function getComponentRefsFromSourceStrings(allModuleFiles: ModuleFiles, sourceStrings: SourceString[]) {
const componentRefs: ComponentReference[] = [];

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

sourceStrings.forEach(src => {
if (typeof src.str !== 'string') {
return;
}

src.str = src.str.trim().toLowerCase();

if (tags.some(tag => src.str === tag)) {
// exact match, we're good
// probably something like h('ion-button') or
// var tag = 'ion-button'; document.createElement(tag);
componentRefs.push({
tag: src.str,
filePath: src.filePath
});

} else if (src.str.includes('<')) {
// string could be HTML
// could be something like elm.innerHTML = '<ion-button>';

// replace any whitespace with a ~ character
// this is especially important for newlines and tabs
// for tag with attributes and has a newline in the tag
src.str = src.str.toLowerCase().replace(/\s/g, '~');

const foundTags = tags.filter(tag => {
return src.str.includes('<' + tag + '>') ||
src.str.includes('</' + tag + '>') ||
src.str.includes('<' + tag + '~');
});

foundTags.forEach(foundTag => {
componentRefs.push({
tag: foundTag,
filePath: src.filePath
});
});
}

// just to free up memory
src.str = src.filePath = null;
});

sourceStrings.length = 0;

return componentRefs;
}
7 changes: 7 additions & 0 deletions src/compiler/entries/entry-modules.ts
@@ -1,4 +1,5 @@
import { BuildCtx, CompilerCtx, ComponentMeta, Config, ConfigBundle, EntryModule, EntryPoint, ModuleFile } from '../../declarations';
import { calcComponentDependencies, calcModuleGraphImportPaths } from './component-dependencies';
import { catchError } from '../util';
import { DEFAULT_STYLE_MODE, ENCAPSULATION } from '../../util/constants';
import { generateComponentEntries } from './entry-components';
Expand All @@ -9,6 +10,12 @@ import { validateComponentTag } from '../config/validate-component';
export function generateEntryModules(config: Config, compilerCtx: CompilerCtx, buildCtx: BuildCtx) {
buildCtx.entryModules = [];

// figure out all the actual import paths (basically which extension each import uses)
calcModuleGraphImportPaths(compilerCtx, buildCtx.moduleGraphs);

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

try {
const allModules = Object.keys(compilerCtx.moduleFiles).map(filePath => compilerCtx.moduleFiles[filePath]);

Expand Down
14 changes: 5 additions & 9 deletions src/compiler/transpile/test/transpile.spec.ts
Expand Up @@ -292,15 +292,12 @@ describe('transpile', () => {
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/no-find.tsx': `@Component({ tag: 'no-find' }) export class NoFind {}`
'/src/new-dir/cmp-c.tsx': `@Component({ tag: 'cmp-c' }) export class CmpC {}`
}, { clearFileCache: true });

await c.fs.writeFile('/src/cmp-a.tsx', `
@Component({ tag: 'cmp-a' }) export class CmpA {
render() {
someFunction('no-find');
if (true) {
return (
h('cmp-b')
Expand All @@ -327,7 +324,7 @@ describe('transpile', () => {
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/no-find.tsx': `@Component({ tag: 'no-find' }) export class NoFind {}`
'/src/new-dir/cmp-d.tsx': `@Component({ tag: 'cmp-d' }) export class CmpD {}`
}, { clearFileCache: true });

await c.fs.writeFile('/src/cmp-a.tsx', `
Expand All @@ -336,7 +333,8 @@ describe('transpile', () => {
document.createElement('cmp-b');
var doc = document;
doc.createElementNS('cmp-c');
doc.someFunction('no-find');
var tag = 'cmp-d';
document.createElement(tag);
}
}
`, { clearFileCache: true });
Expand All @@ -346,7 +344,7 @@ describe('transpile', () => {
expect(r.diagnostics).toEqual([]);

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

it('get component dependencies from html string literals', async () => {
Expand All @@ -366,8 +364,6 @@ describe('transpile', () => {
constructor() {
this.el.innerHTML = '<cmp-b></cmp-b>';
$.append('<cmp-c></cmp-c>');
console.log('no-find');
console.log(' no-find ');
}
}
`, { clearFileCache: true });
Expand Down
96 changes: 10 additions & 86 deletions src/compiler/transpile/transformers/component-dependencies.ts
@@ -1,24 +1,18 @@
import { BuildCtx, CompilerCtx } from '../../../declarations';
import { BuildCtx, CompilerCtx, SourceString } from '../../../declarations';
import { MEMBER_TYPE } from '../../../util/constants';
import { normalizePath } from '../../util';
import * as ts from 'typescript';


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

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, filePath: string): ts.VisitResult<ts.Node> {
if (node.kind === ts.SyntaxKind.CallExpression) {
callExpression(buildCtx, allComponentTags, filePath, node as ts.CallExpression);

} else if (node.kind === ts.SyntaxKind.StringLiteral) {
stringLiteral(buildCtx, allComponentTags, filePath, node as ts.StringLiteral);
if (node.kind === ts.SyntaxKind.StringLiteral) {
buildCtx.sourceStrings.push({
str: (node as ts.StringLiteral).text,
filePath: filePath
});
}

return ts.visitEachChild(node, (node) => {
Expand All @@ -27,15 +21,15 @@ export function componentDependencies(compilerCtx: CompilerCtx, buildCtx: BuildC
}

return (tsSourceFile) => {
addPropConnects(compilerCtx, buildCtx, tsSourceFile.fileName);
addPropConnects(compilerCtx, buildCtx.sourceStrings, tsSourceFile.fileName);

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


function addPropConnects(compilerCtx: CompilerCtx, buildCtx: BuildCtx, filePath: string) {
function addPropConnects(compilerCtx: CompilerCtx, sourceStrings: SourceString[], filePath: string) {
const moduleFile = compilerCtx.moduleFiles[filePath];

const cmpMeta = (moduleFile && moduleFile.cmpMeta);
Expand All @@ -47,81 +41,11 @@ function addPropConnects(compilerCtx: CompilerCtx, buildCtx: BuildCtx, filePath:
Object.keys(cmpMeta.membersMeta).forEach(memberName => {
const memberMeta = cmpMeta.membersMeta[memberName];
if (memberMeta.memberType === MEMBER_TYPE.PropConnect) {
buildCtx.componentRefs.push({
tag: memberMeta.ctrlId,
sourceStrings.push({
str: memberMeta.ctrlId,
filePath: filePath
});
}
});
}
}


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(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(buildCtx, allComponentTags, filePath, (node.expression as ts.PropertyAccessExpression).name as ts.Identifier, node.arguments);
}
}
}
}


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) {
addComponentReference(buildCtx, allComponentTags, filePath, (args[0] as ts.StringLiteral).text);
}
}
}


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

if (typeof t === 'string' && t.includes('<')) {
t = t.toLowerCase()
.replace(/\s/g, '~');

const foundTags = allComponentTags
.filter(tag => {
return t.includes('<' + tag + '>') ||
t.includes('<' + tag + '~');
});

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


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

if (allComponentTags.includes(tag)) {
buildCtx.componentRefs.push({
tag: tag,
filePath: normalizePath(filePath)
});
}
}
}


const TAG_CALL_EXPRESSIONS = [
'h',
'createElement',
'createElementNS'
];
2 changes: 1 addition & 1 deletion src/compiler/transpile/transformers/module-graph.ts
Expand Up @@ -40,7 +40,7 @@ export function moduleGraph(config: Config, buildCtx: BuildCtx): ts.TransformerF

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

buildCtx.moduleGraph.push(moduleGraph);
buildCtx.moduleGraphs.push(moduleGraph);

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

0 comments on commit 6629aa1

Please sign in to comment.