Skip to content

Commit

Permalink
refactor(scripts): replace eval with static analysis (#1978)
Browse files Browse the repository at this point in the history
- rather than evaluating feature files to find them all, logic now parses code and resolves imports to find entire graph
- this makes it easier to shift towards native esm, as the evaluation logic was commonjs-specific
  • Loading branch information
AviVahl authored Aug 9, 2023
1 parent 9d27cdc commit 0373a52
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 42 deletions.
2 changes: 2 additions & 0 deletions packages/scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
},
"peerDependencies": {
"html-webpack-plugin": "^4.0.0 || ^5.0.0",
"typescript": ">=5.0.0",
"webpack": "^4.0.0 || ^5.0.0"
},
"dependencies": {
"@file-services/node": "^8.1.1",
"@file-services/resolve": "^8.1.1",
"@file-services/types": "^8.1.1",
"@wixc3/common": "^13.2.0",
"@wixc3/create-disposables": "^2.2.0",
Expand Down
16 changes: 9 additions & 7 deletions packages/scripts/src/analyze-feature/load-features-from-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { IConfigDefinition } from '@wixc3/engine-runtime-node';
import { SetMultiMap } from '@wixc3/patterns';
import type { INpmPackage } from '@wixc3/resolve-directory-context';
import {
isFeatureFile,
parseConfigFileName,
parseContextFileName,
parseEnvFileName,
Expand All @@ -13,9 +14,9 @@ import {
} from '../build-constants.js';
import { loadFeatureDirectory } from '../load-feature-directory.js';
import type { IFeatureDefinition, IFeatureModule } from '../types.js';
import { evaluateModule } from '../utils/evaluate-module.js';
import { resolveModuleGraph } from '../utils/resolve-module-graph.js';
import type { DirFeatures } from './find-features.js';
import { analyzeFeatureModule, computeUsedContext, getFeatureModules } from './module-utils.js';
import { analyzeFeatureModule, computeUsedContext } from './module-utils.js';
import { findPackageOfDirs, scopeToPackage, type IPackageDescriptor } from './package-utils.js';

/**
Expand Down Expand Up @@ -164,11 +165,12 @@ function getImportedFeatures(roots: DirFeatures, fs: IFileSystemSync): DirFeatur
dirs: new Set<string>(),
files: new Set<string>(),
};
// find all require()'ed feature files from initial ones
const featureModules = getFeatureModules(evaluateModule(roots.files));
for (const { filename } of featureModules) {
addNew(roots.files, imported.files, filename);
addNew(roots.dirs, imported.dirs, fs.dirname(filename));
// find all imported feature files from initial ones
const filePathsInGraph = Object.keys(resolveModuleGraph(Array.from(roots.files)));
const featureFilePaths = filePathsInGraph.filter((filePath) => isFeatureFile(fs.basename(filePath)));
for (const filePath of featureFilePaths) {
addNew(roots.files, imported.files, filePath);
addNew(roots.dirs, imported.dirs, fs.dirname(filePath));
}
return imported;
}
Expand Down
9 changes: 1 addition & 8 deletions packages/scripts/src/analyze-feature/module-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type FeatureClass,
} from '@wixc3/engine-core';
import { basename } from 'node:path';
import { isFeatureFile, parseFeatureFileName } from '../build-constants.js';
import { parseFeatureFileName } from '../build-constants.js';
import type { IFeatureDefinition, IFeatureModule } from '../types.js';
import { instanceOf } from '../utils/instance-of.js';
import { parseContextualEnv, parseEnv } from './parse-env.js';
Expand Down Expand Up @@ -53,13 +53,6 @@ export function analyzeFeatureModule(filePath: string, moduleExports: unknown):
return featureFile;
}

export const getFeatureModules = (module: NodeJS.Module) =>
flattenTree(
module,
(m) => m.children,
(m) => isFeatureFile(basename(m.filename)),
);

export function computeUsedContext(featureName: string, features: Map<string, IFeatureDefinition>) {
const featureToDef = new Map<FeatureClass, IFeatureDefinition>();
for (const featureDef of features.values()) {
Expand Down
26 changes: 0 additions & 26 deletions packages/scripts/src/utils/evaluate-module.ts

This file was deleted.

79 changes: 79 additions & 0 deletions packages/scripts/src/utils/resolve-module-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { ResolvedRequests, createDependencyResolver, createRequestResolver } from '@file-services/resolve';
import fs from 'node:fs';
import path from 'node:path';
import ts from 'typescript';
import { isCodeModule } from '../build-constants.js';

const {
SyntaxKind: { ImportKeyword },
forEachChild,
isCallExpression,
isExportDeclaration,
isIdentifier,
isImportDeclaration,
isStringLiteral,
} = ts;

export function resolveModuleGraph(filePaths: string | string[]): Record<string, ResolvedRequests> {
const resolveRequest = createRequestResolver({ fs: { ...fs, ...path } });
const dependencyResolver = createDependencyResolver({
extractRequests(filePath) {
if (!isCodeModule(path.basename(filePath))) {
return [];
}
const fileContents = fs.readFileSync(filePath, 'utf8');
const sourceFile = ts.createSourceFile(filePath, fileContents, ts.ScriptTarget.ESNext);
return extractModuleRequests(sourceFile);
},
resolveRequest(filePath, request) {
const contextPath = path.dirname(filePath);
return resolveRequest(contextPath, request).resolvedFile;
},
});
return dependencyResolver(filePaths, true);
}

function extractModuleRequests(sourceFile: ts.SourceFile): string[] {
const specifiers: string[] = [];

const dynamicImportsFinder = (node: ts.Node): void => {
if (isCallExpression(node)) {
if (
(isDynamicImportKeyword(node.expression) || isRequireIdentifier(node.expression)) &&
node.arguments.length === 1 &&
isStringLiteral(node.arguments[0]!)
) {
const [{ text }] = node.arguments;
specifiers.push(text);
return;
}
}
forEachChild(node, dynamicImportsFinder);
};

const importsFinder = (node: ts.Node) => {
const isNonTypeImport = isImportDeclaration(node) && !node.importClause?.isTypeOnly;
const isNonTypeExport = isExportDeclaration(node) && !node.isTypeOnly;

if ((isNonTypeImport || isNonTypeExport) && node.moduleSpecifier && isStringLiteral(node.moduleSpecifier)) {
const originalTarget = node.moduleSpecifier.text;
specifiers.push(originalTarget);
return;
}

// if not a static import/re-export, might be a dynamic import
// so run that recursive visitor on `node`
forEachChild(node, dynamicImportsFinder);
};

forEachChild(sourceFile, importsFinder);
return specifiers;
}

function isRequireIdentifier(expression: ts.Expression): expression is ts.Identifier {
return isIdentifier(expression) && expression.text === 'require';
}

function isDynamicImportKeyword({ kind }: ts.Expression) {
return kind === ImportKeyword;
}
9 changes: 8 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@
dependencies:
"@file-services/types" "^8.1.1"

"@file-services/resolve@^8.1.1":
version "8.1.1"
resolved "https://registry.yarnpkg.com/@file-services/resolve/-/resolve-8.1.1.tgz#20e15a9d15f57239fd4c4963e8269bacc6cee2ea"
integrity sha512-4gGz6Dv4Cpcj9Puf5Xm4KQhOKM3tvJ+/T6mxMuaVdXp5BKQagB7LkGou1MV0OUQOa3Nclwrd/69JPw2A7zZmAg==
dependencies:
type-fest "^4.1.0"

"@file-services/types@^7.4.0":
version "7.4.0"
resolved "https://registry.yarnpkg.com/@file-services/types/-/types-7.4.0.tgz#ec99b51f8cf39c839058fd5d045aaf5dd87b6ed9"
Expand Down Expand Up @@ -7698,7 +7705,7 @@ type-fest@^3.8.0:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.13.1.tgz#bb744c1f0678bea7543a2d1ec24e83e68e8c8706"
integrity sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==

type-fest@^4.0.0, type-fest@^4.2.0:
type-fest@^4.0.0, type-fest@^4.1.0, type-fest@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.2.0.tgz#e259430307710e77721ecf6f545840acad72195f"
integrity sha512-5zknd7Dss75pMSED270A1RQS3KloqRJA9XbXLe0eCxyw7xXFb3rd+9B0UQ/0E+LQT6lnrLviEolYORlRWamn4w==
Expand Down

0 comments on commit 0373a52

Please sign in to comment.