New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@wessberg/di-compiler

Package Overview
Dependencies
Maintainers
1
Versions
95
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wessberg/di-compiler - npm Package Compare versions

Comparing version 2.2.6 to 3.0.0

dist/cjs/common/common.cjs

13

dist/esm/index.d.ts

@@ -1,10 +0,3 @@

import * as TS from "typescript";
interface DiOptions {
program: TS.Program;
typescript?: typeof TS;
}
/**
* CustomTransformer that associates constructor arguments with any given class declaration
*/
declare function di({ typescript, ...rest }: DiOptions): TS.CustomTransformers;
export { DiOptions, di };
export * from "./common/common";
export * from "./common/di-options";
export * from "./common/transform-options";

@@ -1,851 +0,2 @@

import { evaluate } from 'ts-evaluator';
import * as TS from 'typescript';
import { ensureNodeFactory } from 'compatfactory';
const CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER = `___CTOR_ARGS___`;
const DI_CONTAINER_NAME = "DIContainer";
/**
* A TypeNode such as IFoo<string> should still yield the service name "IFoo".
* This helper generates a proper service name from a TypeNode
*/
function pickServiceOrImplementationName(node, context) {
const { typescript } = context;
if (typescript.isTypeReferenceNode(node)) {
return pickServiceOrImplementationName(node.typeName, context);
}
else if (typescript.isIndexedAccessTypeNode(node)) {
return `${pickServiceOrImplementationName(node.objectType, context)}[${pickServiceOrImplementationName(node.indexType, context)}]`;
}
else {
return node.getFullText().trim();
}
}
function visitClassLikeDeclaration(options) {
const { node, childContinuation, continuation, context } = options;
const { typescript, factory } = context;
const constructorDeclaration = node.members.find(typescript.isConstructorDeclaration);
// If there are no constructor declaration for the ClassLikeDeclaration, there's nothing to do
if (constructorDeclaration == null) {
return childContinuation(node);
}
const updatedClassMembers = [
...node.members.map(continuation),
factory.createGetAccessorDeclaration(undefined, [
factory.createModifier(typescript.SyntaxKind.PublicKeyword),
factory.createModifier(typescript.SyntaxKind.StaticKeyword),
], factory.createComputedPropertyName(factory.createIdentifier(`Symbol.for("${CONSTRUCTOR_ARGUMENTS_SYMBOL_IDENTIFIER}")`)), [], undefined, factory.createBlock([
factory.createReturnStatement(getParameterTypeNamesAsArrayLiteral(constructorDeclaration.parameters, context)),
])),
];
if (typescript.isClassDeclaration(node)) {
return factory.updateClassDeclaration(node, node.decorators, node.modifiers, node.name, node.typeParameters, node.heritageClauses, updatedClassMembers);
}
else {
return factory.updateClassExpression(node, node.decorators, node.modifiers, node.name, node.typeParameters, node.heritageClauses, updatedClassMembers);
}
}
/**
* Takes ConstructorParams for the given NodeArray of ParameterDeclarations
*/
function getParameterTypeNamesAsArrayLiteral(parameters, context) {
const { factory } = context;
const constructorParams = [];
for (let i = 0; i < parameters.length; i++) {
const parameter = parameters[i];
// If the parameter has no type, there's nothing to extract
if (parameter.type == null) {
constructorParams[i] = factory.createIdentifier("undefined");
}
else {
constructorParams[i] = factory.createNoSubstitutionTemplateLiteral(pickServiceOrImplementationName(parameter.type, context));
}
}
return factory.createArrayLiteralExpression(constructorParams);
}
// For some TypeScript versions, such as 3.1, these helpers are not exposed by TypeScript,
// so they will have to be duplicated and reused from here in these rare cases
const HELPERS = {
importDefaultHelper: {
name: "typescript:commonjsimportdefault",
scoped: false,
text: '\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { "default": mod };\n};',
},
importStarHelper: {
name: "typescript:commonjsimportstar",
scoped: false,
text: '\nvar __importStar = (this && this.__importStar) || function (mod) {\n if (mod && mod.__esModule) return mod;\n var result = {};\n if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];\n result["default"] = mod;\n return result;\n};',
},
};
function getImportDefaultHelper(typescript) {
var _a;
return (_a = typescript.importDefaultHelper) !== null && _a !== void 0 ? _a : HELPERS.importDefaultHelper;
}
function getImportStarHelper(typescript) {
var _a;
return (_a = typescript.importStarHelper) !== null && _a !== void 0 ? _a : HELPERS.importStarHelper;
}
function moduleKindSupportsImportHelpers(moduleKind = TS.ModuleKind.CommonJS, typescript) {
switch (moduleKind) {
case typescript.ModuleKind.CommonJS:
case typescript.ModuleKind.UMD:
case typescript.ModuleKind.AMD:
return true;
default:
return false;
}
}
function moduleKindDefinesDependencies(moduleKind = TS.ModuleKind.CommonJS, typescript) {
switch (moduleKind) {
case typescript.ModuleKind.UMD:
case typescript.ModuleKind.AMD:
return true;
default:
return false;
}
}
function getUnscopedHelperName(context, helperName) {
const typescript = context.typescript;
if ("getUnscopedHelperName" in typescript) {
return typescript.getUnscopedHelperName(helperName);
}
else if ("createEmitHelperFactory" in typescript) {
return typescript
.createEmitHelperFactory(context.transformationContext)
.getUnscopedHelperName(helperName);
}
else {
return typescript.getHelperName(helperName);
}
}
function getRootBlockInsertionPosition(rootBlock, typescript) {
let insertPosition = 0;
for (let i = 0; i < rootBlock.statements.length; i++) {
const statement = rootBlock.statements[i];
const isUseStrict = typescript.isExpressionStatement(statement) &&
typescript.isStringLiteralLike(statement.expression) &&
statement.expression.text === "use strict";
const isEsModuleSymbol = typescript.isExpressionStatement(statement) &&
typescript.isCallExpression(statement.expression) &&
typescript.isPropertyAccessExpression(statement.expression.expression) &&
typescript.isIdentifier(statement.expression.expression.expression) &&
typescript.isIdentifier(statement.expression.expression.name) &&
statement.expression.expression.expression.text === "Object" &&
statement.expression.expression.name.text === "defineProperty" &&
statement.expression.arguments.length >= 2 &&
typescript.isIdentifier(statement.expression.arguments[0]) &&
statement.expression.arguments[0].text === "exports" &&
typescript.isStringLiteralLike(statement.expression.arguments[1]) &&
statement.expression.arguments[1].text === "__esModule";
if (isUseStrict || isEsModuleSymbol) {
insertPosition = Math.max(insertPosition, i + 1);
}
}
return insertPosition;
}
function getDefineArrayLiteralExpression(sourceFile, context) {
const { program, typescript } = context;
const compilerOptions = program.getCompilerOptions();
switch (compilerOptions.module) {
case typescript.ModuleKind.ESNext:
case typescript.ModuleKind.ES2015:
case typescript.ModuleKind.ES2020:
// There are no such thing for these module types
return undefined;
// If we're targeting UMD, the root block won't be the root scope, but the Function Body of an iife
case typescript.ModuleKind.UMD: {
for (const statement of sourceFile.statements) {
if (typescript.isExpressionStatement(statement) &&
typescript.isCallExpression(statement.expression) &&
typescript.isParenthesizedExpression(statement.expression.expression) &&
typescript.isFunctionExpression(statement.expression.expression.expression) &&
statement.expression.expression.expression.parameters.length === 1) {
const [firstParameter] = statement.expression.expression.expression.parameters;
if (typescript.isIdentifier(firstParameter.name)) {
if (firstParameter.name.text === "factory") {
for (const subStatement of statement.expression.expression
.expression.body.statements) {
if (typescript.isIfStatement(subStatement) &&
subStatement.elseStatement != null &&
typescript.isIfStatement(subStatement.elseStatement) &&
typescript.isBlock(subStatement.elseStatement.thenStatement)) {
for (const subSubStatement of subStatement.elseStatement
.thenStatement.statements) {
if (typescript.isExpressionStatement(subSubStatement) &&
typescript.isCallExpression(subSubStatement.expression) &&
subSubStatement.expression.arguments.length === 2 &&
typescript.isIdentifier(subSubStatement.expression.expression) &&
subSubStatement.expression.expression.text === "define") {
const [firstSubSubStatementExpressionArgument] = subSubStatement.expression.arguments;
if (typescript.isArrayLiteralExpression(firstSubSubStatementExpressionArgument)) {
return firstSubSubStatementExpressionArgument;
}
}
}
}
}
}
}
}
}
break;
}
case typescript.ModuleKind.AMD: {
for (const statement of sourceFile.statements) {
if (typescript.isExpressionStatement(statement) &&
typescript.isCallExpression(statement.expression) &&
typescript.isIdentifier(statement.expression.expression) &&
statement.expression.expression.text === "define" &&
statement.expression.arguments.length === 2) {
const [firstArgument, secondArgument] = statement.expression.arguments;
if (typescript.isArrayLiteralExpression(firstArgument)) {
if (typescript.isFunctionExpression(secondArgument) &&
secondArgument.parameters.length >= 2) {
const [firstParameter, secondParameter] = secondArgument.parameters;
if (typescript.isIdentifier(firstParameter.name) &&
typescript.isIdentifier(secondParameter.name) &&
firstParameter.name.text === "require" &&
secondParameter.name.text === "exports") {
return firstArgument;
}
}
}
}
}
break;
}
}
return undefined;
}
function getRootBlock(sourceFile, context) {
const { program, typescript } = context;
const compilerOptions = program.getCompilerOptions();
switch (compilerOptions.module) {
// If we're targeting UMD, the root block won't be the root scope, but the Function Body of an iife
case typescript.ModuleKind.UMD: {
for (const statement of sourceFile.statements) {
if (typescript.isExpressionStatement(statement) &&
typescript.isCallExpression(statement.expression) &&
statement.expression.arguments.length === 1) {
const [firstArgument] = statement.expression.arguments;
if (typescript.isFunctionExpression(firstArgument) &&
firstArgument.parameters.length === 2) {
const [firstParameter, secondParameter] = firstArgument.parameters;
if (typescript.isIdentifier(firstParameter.name) &&
typescript.isIdentifier(secondParameter.name) &&
firstParameter.name.text === "require" &&
secondParameter.name.text === "exports") {
return firstArgument.body;
}
}
}
}
break;
}
// If we're targeting AMD, the root block won't be the root scope, but the Function Body of the
// anonymous function provided as a second argument to the define() function
case typescript.ModuleKind.AMD: {
for (const statement of sourceFile.statements) {
if (typescript.isExpressionStatement(statement) &&
typescript.isCallExpression(statement.expression) &&
typescript.isIdentifier(statement.expression.expression) &&
statement.expression.expression.text === "define" &&
statement.expression.arguments.length === 2) {
const [, secondArgument] = statement.expression.arguments;
if (typescript.isFunctionExpression(secondArgument) &&
secondArgument.parameters.length >= 2) {
const [firstParameter, secondParameter] = secondArgument.parameters;
if (typescript.isIdentifier(firstParameter.name) &&
typescript.isIdentifier(secondParameter.name) &&
firstParameter.name.text === "require" &&
secondParameter.name.text === "exports") {
return secondArgument.body;
}
}
}
}
break;
}
}
return sourceFile;
}
function isImportedSymbolImported(importedSymbol, rootBlock, context) {
const compilerOptions = context.program.getCompilerOptions();
const typescript = context.typescript;
switch (compilerOptions.module) {
case typescript.ModuleKind.ES2020:
case typescript.ModuleKind.ES2015:
case typescript.ModuleKind.ESNext: {
for (const statement of rootBlock.statements) {
if (!typescript.isImportDeclaration(statement))
continue;
if (!typescript.isStringLiteralLike(statement.moduleSpecifier)) {
continue;
}
if (statement.moduleSpecifier.text !== importedSymbol.moduleSpecifier) {
continue;
}
if (statement.importClause == null) {
continue;
}
if ("isDefaultImport" in importedSymbol) {
if (importedSymbol.isDefaultImport) {
if (statement.importClause.name == null) {
continue;
}
if (statement.importClause.name.text !== importedSymbol.name) {
continue;
}
return true;
}
else {
if (statement.importClause.namedBindings == null)
continue;
if (!typescript.isNamedImports(statement.importClause.namedBindings)) {
continue;
}
for (const importSpecifier of statement.importClause.namedBindings
.elements) {
if (importSpecifier.name.text !== importedSymbol.name)
continue;
return true;
}
}
}
else if ("isNamespaceImport" in importedSymbol) {
if (statement.importClause.namedBindings == null)
continue;
if (!typescript.isNamespaceImport(statement.importClause.namedBindings)) {
continue;
}
if (statement.importClause.namedBindings.name.text !==
importedSymbol.name) {
continue;
}
return true;
}
}
return false;
}
case typescript.ModuleKind.CommonJS:
case typescript.ModuleKind.AMD:
case typescript.ModuleKind.UMD: {
for (const statement of rootBlock.statements) {
if (!typescript.isVariableStatement(statement))
continue;
for (const declaration of statement.declarationList.declarations) {
if (!typescript.isIdentifier(declaration.name))
continue;
if (declaration.name.text !== importedSymbol.name)
continue;
return true;
}
}
}
}
// TODO: Add support for other module systems
return false;
}
function generateImportStatementForImportedSymbolInContext(importedSymbol, context) {
const compilerOptions = context.program.getCompilerOptions();
const { factory, typescript } = context;
switch (compilerOptions.module) {
case typescript.ModuleKind.ES2020:
case typescript.ModuleKind.ES2015:
case typescript.ModuleKind.ESNext: {
return factory.createImportDeclaration(undefined, undefined, "isDefaultImport" in importedSymbol
? factory.createImportClause(false, !importedSymbol.isDefaultImport
? undefined
: factory.createIdentifier(importedSymbol.name), importedSymbol.isDefaultImport
? undefined
: factory.createNamedImports([
factory.createImportSpecifier(false, importedSymbol.propertyName === importedSymbol.name
? undefined
: factory.createIdentifier(importedSymbol.propertyName), factory.createIdentifier(importedSymbol.name)),
]))
: "isNamespaceImport" in importedSymbol
? factory.createImportClause(false, undefined, factory.createNamespaceImport(factory.createIdentifier(importedSymbol.name)))
: undefined, factory.createStringLiteral(importedSymbol.moduleSpecifier));
}
case typescript.ModuleKind.CommonJS:
case typescript.ModuleKind.AMD:
case typescript.ModuleKind.UMD: {
const requireCall = factory.createCallExpression(factory.createIdentifier("require"), undefined, [factory.createStringLiteral(importedSymbol.moduleSpecifier)]);
let wrappedRequireCall = requireCall;
// We'll need to use a helper, '__importDefault', and wrap the require call with it
if (compilerOptions.esModuleInterop === true &&
(("isDefaultImport" in importedSymbol &&
importedSymbol.isDefaultImport) ||
(!("isDefaultImport" in importedSymbol) &&
importedSymbol.isNamespaceImport))) {
// If tslib is being used, we can do something like 'require("tslib").__import{Default|Star}(<requireCall>)'
if (compilerOptions.importHelpers === true) {
wrappedRequireCall = factory.createCallExpression(factory.createPropertyAccessExpression(factory.createCallExpression(factory.createIdentifier("require"), undefined, [factory.createStringLiteral("tslib")]), getUnscopedHelperName(context, "isDefaultImport" in importedSymbol
? "__importDefault"
: "__importStar")), undefined, [requireCall]);
}
// Otherwise, we'll have to make sure that the helper is being inlined in an transformation step later
else {
// We've already requested the __importDefault helper in the before transformer under these
// circumstances
wrappedRequireCall = factory.createCallExpression(getUnscopedHelperName(context, "isDefaultImport" in importedSymbol
? "__importDefault"
: "__importStar"), undefined, [requireCall]);
}
}
return factory.createVariableStatement(undefined, factory.createVariableDeclarationList([
factory.createVariableDeclaration(factory.createIdentifier(importedSymbol.name), undefined, undefined, wrappedRequireCall),
], typescript.NodeFlags.Const));
}
}
// TODO: Handle other module types as well
return undefined;
}
function visitCallExpression(options) {
var _a, _b, _c;
const { node, childContinuation, continuation, context, addTslibDefinition, requireImportedSymbol, } = options;
const { typescript, factory, transformationContext } = context;
const diMethod = getDiMethodKind(node.expression, context);
if (diMethod != null) {
switch (diMethod) {
case "get" /* GET */:
case "has" /* HAS */: {
// If no type arguments are given, don't modify the node at all
if (node.typeArguments == null || node.typeArguments[0] == null) {
return childContinuation(node);
}
return factory.updateCallExpression(node, node.expression, node.typeArguments, [
factory.createObjectLiteralExpression([
factory.createPropertyAssignment("identifier", factory.createStringLiteral(node.typeArguments[0].getFullText().trim())),
]),
]);
}
case "registerSingleton" /* REGISTER_SINGLETON */:
case "registerTransient" /* REGISTER_TRANSIENT */: {
let [typeArg, implementationArg] = ((_a = node.typeArguments) !== null && _a !== void 0 ? _a : []);
// If not implementation is provided, use the type argument *as* the implementation
if (implementationArg == null) {
implementationArg = typeArg;
}
// If another implementation is passed, used that one instead
if (node.arguments.length > 0) {
implementationArg = node.arguments[0];
}
if (typeArg == null || implementationArg == null) {
return childContinuation(node);
}
const typeArgText = pickServiceOrImplementationName(typeArg, context);
const implementationArgText = pickServiceOrImplementationName(implementationArg, context);
// If the Implementation is a TypeNode, and if it originates from an ImportDeclaration, it may be stripped from the file since Typescript won't Type-check the updates from
// a CustomTransformer and such a node would normally be removed from the imports.
// to fix it, add an ImportDeclaration if needed
if (typescript.isTypeNode(implementationArg)) {
const matchingImport = findMatchingImportDeclarationForIdentifier(implementationArgText, options);
if (matchingImport != null &&
typescript.isStringLiteralLike(matchingImport.importDeclaration.moduleSpecifier)) {
switch (matchingImport.kind) {
case "default": {
const compilerOptions = context.program.getCompilerOptions();
// Log a request for the __importDefault helper already if we will
// need it in a later transformation step
if (moduleKindSupportsImportHelpers(compilerOptions.module, typescript) &&
compilerOptions.esModuleInterop === true &&
compilerOptions.importHelpers !== true) {
transformationContext.requestEmitHelper(getImportDefaultHelper(typescript));
}
// Log a request for adding 'tslib' to the define([...]) array for the current
// module system if it relies on declaring dependencies (such as UMD, AMD, and SystemJS does)
if (moduleKindDefinesDependencies(compilerOptions.module, typescript) &&
compilerOptions.esModuleInterop === true &&
compilerOptions.importHelpers === true) {
addTslibDefinition();
}
requireImportedSymbol({
isDefaultImport: true,
moduleSpecifier: matchingImport.importDeclaration.moduleSpecifier.text,
name: matchingImport.identifier.text,
propertyName: matchingImport.identifier.text,
});
break;
}
case "namedImport": {
requireImportedSymbol({
isDefaultImport: false,
moduleSpecifier: matchingImport.importDeclaration.moduleSpecifier.text,
name: matchingImport.importSpecifier.name.text,
propertyName: (_c = (_b = matchingImport.importSpecifier.propertyName) === null || _b === void 0 ? void 0 : _b.text) !== null && _c !== void 0 ? _c : matchingImport.importSpecifier.name.text,
});
break;
}
case "namespace": {
const compilerOptions = context.program.getCompilerOptions();
// Log a request for the __importStar helper already if you will
// need it in a later transformation step
if (moduleKindSupportsImportHelpers(compilerOptions.module, typescript) &&
compilerOptions.esModuleInterop === true &&
compilerOptions.importHelpers !== true) {
transformationContext.requestEmitHelper(getImportStarHelper(typescript));
}
requireImportedSymbol({
isNamespaceImport: true,
moduleSpecifier: matchingImport.importDeclaration.moduleSpecifier.text,
name: matchingImport.identifier.text,
});
break;
}
}
}
}
return factory.updateCallExpression(node, node.expression, node.typeArguments, [
typescript.isTypeNode(implementationArg)
? factory.createIdentifier("undefined")
: continuation(implementationArg),
factory.createObjectLiteralExpression([
factory.createPropertyAssignment("identifier", factory.createNoSubstitutionTemplateLiteral(typeArgText)),
...(!typescript.isTypeNode(implementationArg)
? []
: [
factory.createPropertyAssignment("implementation", factory.createIdentifier(rewriteImplementationName(implementationArgText, options))),
]),
]),
]);
}
}
}
return childContinuation(node);
}
function findMatchingImportDeclarationForIdentifier(identifier, options) {
var _a;
const { sourceFile, context: { typescript }, } = options;
// Find the matching import
const importDeclarations = sourceFile.statements.filter(typescript.isImportDeclaration);
for (const importDeclaration of importDeclarations) {
if (importDeclaration.importClause == null)
continue;
// Default import
if (((_a = importDeclaration.importClause.name) === null || _a === void 0 ? void 0 : _a.text) === identifier) {
return {
importDeclaration,
kind: "default",
identifier: importDeclaration.importClause.name,
};
}
else if (importDeclaration.importClause.namedBindings != null) {
if (typescript.isNamespaceImport(importDeclaration.importClause.namedBindings)) {
if (importDeclaration.importClause.namedBindings.name.text === identifier) {
return {
importDeclaration,
kind: "namespace",
identifier: importDeclaration.importClause.namedBindings.name,
};
}
}
else {
for (const importSpecifier of importDeclaration.importClause
.namedBindings.elements) {
if (importSpecifier.name.text === identifier) {
return {
importDeclaration,
kind: "namedImport",
importSpecifier: importSpecifier,
};
}
}
}
}
}
// No import was matched
return undefined;
}
function rewriteImplementationName(name, options) {
var _a;
const { context: { typescript }, } = options;
const compilerOptions = options.context.program.getCompilerOptions();
switch (compilerOptions.module) {
case typescript.ModuleKind.ES2020:
case typescript.ModuleKind.ES2015:
case typescript.ModuleKind.ESNext:
return name;
case typescript.ModuleKind.CommonJS:
case typescript.ModuleKind.AMD:
case typescript.ModuleKind.UMD: {
// Find the matching import
const match = findMatchingImportDeclarationForIdentifier(name, options);
if (match == null) {
return name;
}
switch (match.kind) {
case "default":
return `${name}.default`;
case "namespace":
return name;
case "namedImport":
return `${name}.${((_a = match.importSpecifier.propertyName) !== null && _a !== void 0 ? _a : match.importSpecifier.name)
.text}`;
}
// Fall back to returning the original name
return name;
}
default:
// TODO: Add support for SystemJS here
return name;
}
}
function getDiMethodKind(node, context) {
if (!context.typescript.isPropertyAccessExpression(node) &&
!context.typescript.isElementAccessExpression(node)) {
return undefined;
}
// Don't proceed unless the left-hand expression is the DIServiceContainer
const type = context.typeChecker.getTypeAtLocation(node.expression);
if (type == null ||
type.symbol == null ||
type.symbol.escapedName !== DI_CONTAINER_NAME) {
return undefined;
}
let name;
// If it is an element access expression, evaluate the argument expression
if (context.typescript.isElementAccessExpression(node)) {
const evaluationResult = context.evaluate(node.argumentExpression);
// If no value could be computed, or if the value isn't of type string, do nothing
if (!evaluationResult.success ||
typeof evaluationResult.value !== "string") {
return undefined;
}
else {
name = evaluationResult.value;
}
}
else {
name = node.name.text;
}
switch (name) {
case "get" /* GET */:
case "has" /* HAS */:
case "registerSingleton" /* REGISTER_SINGLETON */:
case "registerTransient" /* REGISTER_TRANSIENT */:
return name;
default:
return undefined;
}
}
function visitNode$1(options) {
if (options.context.typescript.isClassLike(options.node)) {
return visitClassLikeDeclaration({ ...options, node: options.node });
}
else if (options.context.typescript.isCallExpression(options.node)) {
return visitCallExpression({ ...options, node: options.node });
}
return options.childContinuation(options.node);
}
function beforeTransformer(context) {
return (transformationContext) => {
var _a;
const factory = ensureNodeFactory((_a = transformationContext.factory) !== null && _a !== void 0 ? _a : context.typescript);
return (sourceFile) => transformSourceFile$1(sourceFile, {
...context,
transformationContext,
factory,
});
};
}
function transformSourceFile$1(sourceFile, context) {
const requiredImportedSymbolSet = new Set();
/**
* An optimization in which every imported symbol is converted into
* a string that can be matched against directly to guard against
* duplicates
*/
const requiredImportedSymbolSetFlags = new Set();
context.sourceFileToAddTslibDefinition.set(sourceFile.fileName, false);
context.sourceFileToRequiredImportedSymbolSet.set(sourceFile.fileName, requiredImportedSymbolSet);
const computeImportedSymbolFlag = (symbol) => [
"name",
"propertyName",
"moduleSpecifier",
"isNamespaceImport",
"isDefaultImport",
]
.map((property) => { var _a; return `${property}:${(_a = symbol[property]) !== null && _a !== void 0 ? _a : false}`; })
.join("|");
const visitorOptions = {
context,
addTslibDefinition: () => {
context.sourceFileToAddTslibDefinition.set(sourceFile.fileName, true);
},
requireImportedSymbol: (importedSymbol) => {
// Guard against duplicates and compute a string so we can do
// constant time lookups to compare against existing symbols
const flag = computeImportedSymbolFlag(importedSymbol);
if (requiredImportedSymbolSetFlags.has(flag))
return;
requiredImportedSymbolSetFlags.add(flag);
requiredImportedSymbolSet.add(importedSymbol);
},
continuation: (node) => visitNode$1({
...visitorOptions,
sourceFile,
node,
}),
childContinuation: (node) => context.typescript.visitEachChild(node, (cbNode) => visitNode$1({
...visitorOptions,
sourceFile,
node: cbNode,
}), context.transformationContext),
};
return visitorOptions.continuation(sourceFile);
}
function visitRootBlock(options) {
var _a;
const { node, sourceFile, context } = options;
const { typescript } = context;
const leadingExtraStatements = [];
for (const importedSymbol of (_a = context.sourceFileToRequiredImportedSymbolSet.get(sourceFile.fileName)) !== null && _a !== void 0 ? _a : new Set()) {
if (isImportedSymbolImported(importedSymbol, node, context))
continue;
const missingImportStatement = generateImportStatementForImportedSymbolInContext(importedSymbol, context);
if (missingImportStatement != null) {
leadingExtraStatements.push(missingImportStatement);
}
}
const insertPosition = getRootBlockInsertionPosition(node, typescript);
return [
...node.statements.slice(0, insertPosition),
...leadingExtraStatements,
...node.statements.slice(insertPosition),
];
}
function visitRootBlockSourceFile(options) {
const { node, context } = options;
const { factory } = context;
return factory.updateSourceFile(node, visitRootBlock(options), node.isDeclarationFile, node.referencedFiles, node.typeReferenceDirectives, node.hasNoDefaultLib, node.libReferenceDirectives);
}
function visitRootBlockBlock(options) {
const { node, context } = options;
const { factory } = context;
return factory.updateBlock(node, visitRootBlock(options));
}
function visitDefineArrayLiteralExpression(options) {
var _a;
const { node, sourceFile, context } = options;
const { typescript, factory } = context;
const trailingExtraExpressions = [];
for (const importedSymbol of (_a = context.sourceFileToRequiredImportedSymbolSet.get(sourceFile.fileName)) !== null && _a !== void 0 ? _a : new Set()) {
// Skip the node if it is already declared as a dependency
if (node.elements.some((element) => typescript.isStringLiteralLike(element) &&
element.text === importedSymbol.moduleSpecifier)) {
continue;
}
trailingExtraExpressions.push(factory.createStringLiteral(importedSymbol.moduleSpecifier));
}
if (context.sourceFileToAddTslibDefinition.get(sourceFile.fileName) === true) {
trailingExtraExpressions.push(factory.createStringLiteral("tslib"));
}
if (trailingExtraExpressions.length < 1) {
return node;
}
return factory.updateArrayLiteralExpression(node, [
...node.elements,
...trailingExtraExpressions,
]);
}
function visitNode(options) {
const { node, childContinuation, defineArrayLiteralExpression, rootBlock, context: { typescript }, } = options;
if (typescript.isSourceFile(node) && rootBlock === node) {
return visitRootBlockSourceFile({ ...options, node });
}
else if (typescript.isBlock(node) && rootBlock === node) {
return visitRootBlockBlock({ ...options, node });
}
else if (typescript.isArrayLiteralExpression(node) &&
defineArrayLiteralExpression === node) {
return visitDefineArrayLiteralExpression({
...options,
node,
});
}
return childContinuation(options.node);
}
function afterTransformer(context) {
return (transformationContext) => {
var _a;
const factory = ensureNodeFactory((_a = transformationContext.factory) !== null && _a !== void 0 ? _a : context.typescript);
return (sourceFile) => transformSourceFile(sourceFile, {
...context,
transformationContext,
factory,
});
};
}
function transformSourceFile(sourceFile, context) {
// For TypeScript versions below 3.5, there may be instances
// where EmitHelpers such as __importDefault or __importStar is duplicated.
// For these TypeScript versions, well have to guard against this behavior
if (sourceFile.emitNode != null && sourceFile.emitNode.helpers != null) {
const seenNames = new Set();
const filtered = sourceFile.emitNode.helpers.filter((helper) => {
if (seenNames.has(helper.name))
return false;
seenNames.add(helper.name);
return true;
});
// Reassign the emitNodes if they changed
if (filtered.length !== sourceFile.emitNode.helpers.length) {
sourceFile.emitNode.helpers = filtered;
}
}
const visitorOptions = {
context,
defineArrayLiteralExpression: getDefineArrayLiteralExpression(sourceFile, context),
rootBlock: getRootBlock(sourceFile, context),
continuation: (node) => visitNode({
...visitorOptions,
sourceFile,
node,
}),
childContinuation: (node) => context.typescript.visitEachChild(node, (cbNode) => visitNode({
...visitorOptions,
sourceFile,
node: cbNode,
}), context.transformationContext),
};
return visitorOptions.continuation(sourceFile);
}
/**
* CustomTransformer that associates constructor arguments with any given class declaration
*/
function di({ typescript = TS, ...rest }) {
const typeChecker = rest.program.getTypeChecker();
// Prepare a VisitorContext
const visitorContext = {
...rest,
typescript,
typeChecker,
sourceFileToAddTslibDefinition: new Map(),
sourceFileToRequiredImportedSymbolSet: new Map(),
evaluate: (node) => evaluate({
node,
typeChecker,
typescript,
}),
};
return {
before: [beforeTransformer(visitorContext)],
after: [afterTransformer(visitorContext)],
};
}
export { di };
export { di, transform } from './common/common.js';
//# sourceMappingURL=index.js.map
The MIT License (MIT)
Copyright © 2021 [Frederik Wessberg](mailto:frederikwessberg@hotmail.com) ([@FredWessberg](https://twitter.com/FredWessberg)) ([Website](https://github.com/wessberg))
Copyright © 2022 [Frederik Wessberg](mailto:frederikwessberg@hotmail.com) ([@FredWessberg](https://twitter.com/FredWessberg)) ([Website](https://github.com/wessberg))

@@ -5,0 +5,0 @@ Permission is hereby granted, free of charge, to any person obtaining a copy

{
"name": "@wessberg/di-compiler",
"version": "2.2.6",
"description": "A Custom Transformer for Typescript that enables compile-time Dependency Injection",
"scripts": {
"generate:sandhog": "sandhog all --yes",
"generate:changelog": "standard-changelog --first-release",
"generate:all": "pnpm run generate:sandhog && pnpm run generate:changelog",
"clean": "rimraf dist",
"lint": "tsc --noEmit && eslint \"src/**/*.ts\" --color",
"prettier": "prettier --write \"{src,test,documentation}/**/*.{js,ts,json,html,xml,css,md}\"",
"test": "ava",
"prebuild": "pnpm run clean",
"build": "pnpm run prebuild && pnpm run rollup",
"prewatch": "pnpm run clean",
"watch": "pnpm run prewatch && pnpm run rollup -- --watch",
"rollup": "rollup -c rollup.config.js",
"preversion": "pnpm run lint && pnpm run build",
"version": "pnpm run preversion && pnpm run generate:all && git add .",
"release": "np --no-cleanup --no-yarn",
"update:check": "pnpx npm-check-updates -x typescript-* --dep dev,prod",
"update:commit": "pnpx npm-check-updates -u -x typescript-* --dep dev,prod && pnpm update && pnpm install"
},
"keywords": [
"DI",
"dependency injection",
"ioc",
"inversion",
"service",
"container",
"newable",
"reflection",
"singleton",
"transient",
"compiler"
],
"files": [
"dist/**/*.*",
"register/*.*"
],
"contributors": [
{
"name": "Frederik Wessberg",
"email": "frederikwessberg@hotmail.com",
"url": "https://github.com/wessberg",
"imageUrl": "https://avatars2.githubusercontent.com/u/20454213?s=460&v=4",
"role": "Lead Developer",
"twitter": "FredWessberg",
"github": "wessberg"
}
],
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.9",
"@types/prettier": "^2.4.2",
"@types/semver": "^7.3.9",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@wessberg/ts-config": "^1.1.3",
"rollup-plugin-ts": "^2.0.4",
"semver": "^7.3.5",
"ava": "^3.15.0",
"eslint": "^8.2.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-jsdoc": "^37.0.3",
"husky": "^7.0.4",
"np": "^7.5.0",
"pnpm": "^6.22.2",
"prettier": "^2.4.1",
"pretty-quick": "^3.1.2",
"rimraf": "^3.0.2",
"rollup": "^2.60.0",
"sandhog": "^1.0.43",
"standard-changelog": "^2.0.27",
"crosspath": "^1.0.0",
"ts-node": "^10.4.0",
"npm-check-updates": "^12.0.2",
"typescript": "^4.5.2",
"typescript-3-4-1": "npm:typescript@3.4.1",
"typescript-3-5-1": "npm:typescript@3.5.1",
"typescript-3-6-2": "npm:typescript@3.6.2",
"typescript-3-7-2": "npm:typescript@3.7.2",
"typescript-3-8-3": "npm:typescript@3.8.3",
"typescript-3-9-2": "npm:typescript@3.9.2",
"typescript-4-0-3": "npm:typescript@4.0.3",
"typescript-4-1-2": "npm:typescript@4.1.2",
"typescript-4-2-4": "npm:typescript@4.2.4",
"typescript-4-3-5": "npm:typescript@4.3.5",
"typescript-4-4-2": "npm:typescript@4.4.2",
"tslib": "^2.3.1"
},
"dependencies": {
"ts-evaluator": "^0.1.0",
"compatfactory": "^0.0.12"
},
"peerDependencies": {
"typescript": ">=3.x || >= 4.x"
},
"repository": {
"type": "git",
"url": "https://github.com/wessberg/di-compiler.git"
},
"bugs": {
"url": "https://github.com/wessberg/di-compiler/issues"
},
"main": "./dist/cjs/index.js",
"module": "./dist/esm/index.js",
"browser": "./dist/esm/index.js",
"types": "./dist/esm/index.d.ts",
"typings": "./dist/esm/index.d.ts",
"es2015": "./dist/esm/index.js",
"engines": {
"node": ">=8.0.0"
},
"ava": {
"files": [
"test/**/**.test.ts"
],
"verbose": true,
"timeout": "40s",
"extensions": [
"ts"
],
"environmentVariables": {
"NODE_OPTIONS": "--max_old_space_size=4096"
},
"require": [
"ts-node/register/transpile-only"
]
}
"name": "@wessberg/di-compiler",
"version": "3.0.0",
"description": "A Custom Transformer for Typescript that enables compile-time Dependency Injection",
"scripts": {
"generate:sandhog": "sandhog all --yes",
"generate:changelog": "standard-changelog --first-release",
"generate:all": "pnpm run generate:sandhog && pnpm run generate:changelog",
"clean": "rimraf dist",
"lint": "tsc --noEmit && eslint \"src/**/*.ts\" --color",
"prettier": "prettier --write \"{src,test,documentation}/**/*.{js,ts,json,html,xml,css,md}\"",
"test": "ava",
"prebuild": "pnpm run clean",
"build": "pnpm run prebuild && pnpm run rollup",
"prewatch": "pnpm run clean",
"watch": "pnpm run prewatch && pnpm run rollup:watch",
"rollup": "rollup -c rollup.config.mjs",
"rollup:watch": "rollup -c rollup.config.mjs --watch",
"preversion": "pnpm run lint && pnpm run build",
"version": "pnpm run preversion && pnpm run generate:all && git add .",
"release": "np --no-cleanup --no-yarn",
"update:check": "pnpx npm-check-updates -x typescript-*,ava --dep dev,prod",
"update:commit": "pnpx npm-check-updates -u -x typescript-*,ava --dep dev,prod && pnpm update && pnpm install"
},
"keywords": [
"DI",
"dependency injection",
"ioc",
"inversion",
"service",
"container",
"newable",
"reflection",
"singleton",
"transient",
"compiler"
],
"files": [
"dist/**/*.*",
"loader.*",
"ts-node-loader.*"
],
"contributors": [
{
"name": "Frederik Wessberg",
"email": "frederikwessberg@hotmail.com",
"url": "https://github.com/wessberg",
"imageUrl": "https://avatars2.githubusercontent.com/u/20454213?s=460&v=4",
"role": "Lead Developer",
"twitter": "FredWessberg",
"github": "wessberg"
}
],
"license": "MIT",
"devDependencies": {
"@types/node": "^18.6.2",
"@types/prettier": "^2.6.4",
"@types/semver": "^7.3.10",
"@typescript-eslint/eslint-plugin": "^5.31.0",
"@typescript-eslint/parser": "^5.31.0",
"@wessberg/ts-config": "^2.0.4",
"@wessberg/prettier-config": "^1.0.0",
"rollup-plugin-ts": "^3.0.2",
"semver": "^7.3.7",
"ava": "3.15.0",
"eslint": "^8.20.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsdoc": "^39.3.4",
"husky": "^8.0.1",
"np": "^7.6.2",
"pnpm": "^7.7.0",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"rimraf": "^3.0.2",
"rollup": "^2.77.2",
"sandhog": "^2.0.2",
"standard-changelog": "^2.0.27",
"ts-node": "^10.9.1",
"npm-check-updates": "^16.0.3",
"@esbuild-kit/esm-loader": "^2.4.2",
"typescript": "^4.7.4",
"typescript-3-4-1": "npm:typescript@3.4.1",
"typescript-3-5-1": "npm:typescript@3.5.1",
"typescript-3-6-2": "npm:typescript@3.6.2",
"typescript-3-7-2": "npm:typescript@3.7.2",
"typescript-3-8-3": "npm:typescript@3.8.3",
"typescript-3-9-2": "npm:typescript@3.9.2",
"typescript-4-0-3": "npm:typescript@4.0.3",
"typescript-4-1-2": "npm:typescript@4.1.2",
"typescript-4-2-4": "npm:typescript@4.2.4",
"typescript-4-3-5": "npm:typescript@4.3.5",
"typescript-4-4-2": "npm:typescript@4.4.2",
"typescript-4-5-4": "npm:typescript@4.5.4",
"typescript-4-6-4": "npm:typescript@4.6.4"
},
"dependencies": {
"crosspath": "^2.0.0",
"helpertypes": "^0.0.18",
"pirates": "^4.0.5",
"get-tsconfig": "4.2.0",
"ts-evaluator": "^1.0.7",
"compatfactory": "^1.0.1"
},
"peerDependencies": {
"typescript": ">=3.x || >= 4.x"
},
"repository": {
"type": "git",
"url": "https://github.com/wessberg/di-compiler.git"
},
"bugs": {
"url": "https://github.com/wessberg/di-compiler/issues"
},
"exports": {
"./loader": {
"import": "./dist/esm/loader.js",
"require": "./dist/cjs/loader.cjs"
},
"./ts-node-loader": {
"require": "./ts-node-loader.cjs"
},
".": {
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.cjs"
}
},
"type": "module",
"types": "./dist/esm/index.d.ts",
"main": "./dist/cjs/index.cjs",
"module": "./dist/esm/index.js",
"engines": {
"node": ">=14.19.0"
},
"prettier": "@wessberg/prettier-config",
"ava": {
"files": [
"test/**/**.test.ts"
],
"verbose": true,
"timeout": "400s",
"nonSemVerExperiments": {
"configurableModuleFormat": true
},
"extensions": {
"ts": "module"
},
"nodeArguments": [
"--loader=ts-node/esm"
],
"environmentVariables": {
"FORCE_COLOR": "3"
}
}
}

@@ -17,4 +17,5 @@ <!-- SHADOW_SECTION_LOGO_START -->

<a href="https://www.npmjs.com/package/%40wessberg%2Fdi-compiler"><img alt="NPM version" src="https://badge.fury.io/js/%40wessberg%2Fdi-compiler.svg" /></a>
<a href="https://david-dm.org/wessberg/di-compiler"><img alt="Dependencies" src="https://img.shields.io/david/wessberg%2Fdi-compiler.svg" /></a>
<img alt="Dependencies" src="https://img.shields.io/librariesio/github/wessberg%2Fdi-compiler.svg" />
<a href="https://github.com/wessberg/di-compiler/graphs/contributors"><img alt="Contributors" src="https://img.shields.io/github/contributors/wessberg%2Fdi-compiler.svg" /></a>
<a href="https://github.com/prettier/prettier"><img alt="code style: prettier" src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg" /></a>
<a href="https://opensource.org/licenses/MIT"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-yellow.svg" /></a>

@@ -31,6 +32,10 @@ <a href="https://www.patreon.com/bePatron?u=11315442"><img alt="Support on Patreon" src="https://img.shields.io/badge/patreon-donate-green.svg" /></a>

This is a [`CustomTransformer`](https://github.com/Microsoft/TypeScript/pull/13940) for Typescript that enables you to use [the DI library](https://github.com/wessberg/di).
This library enables you to use [the DI library](https://github.com/wessberg/di) by providing several ways to transform your source code into a representation that it expects.
You can use it as a [Node.js loader](#usage-as-a-nodejs-loader), as [an API](#usage-as-an-api), and even as a [`Custom Transformer`](#usage-as-a-typescript-custom-transformer) for Typescript.
This has been implemented as a TypeScript Custom Transformer in order to be so low-level that it can be used as the underlying implementation in anything you want, whether it be directly with Typescript's Compiler APIs, Webpack loaders, Rollup plugins, or something else.
Integration with popular tools such as Webpack, esbuild, Rollup, or something else is easy, and this README provides several examples of ways it can be achieved.
It is optimized for _performance_, but how fast it can go depends on your setup. Please see the [Optimization](#optimization) section for details on how to tweak `DI-Compiler`
so that it works most efficiently.
<!-- SHADOW_SECTION_FEATURES_START -->

@@ -46,2 +51,3 @@

- It doesn't ask you to reflect metadata or to annotate your classes with decorators. "It just works".
- Works without a TypeScript program, so you can use it with tools like babel, esbuild, and SWC for the best possible performance.

@@ -56,5 +62,5 @@ <!-- SHADOW_SECTION_FEATURE_IMAGE_START -->

| <a href="https://usebubbles.com"><img alt="Bubbles" src="https://uploads-ssl.webflow.com/5d682047c28b217055606673/5e5360be16879c1d0dca6514_icon-thin-128x128%402x.png" height="70" /></a> | <a href="https://github.com/cblanc"><img alt="Christopher Blanchard" src="https://avatars0.githubusercontent.com/u/2160685?s=400&v=4" height="70" /></a> | <a href="https://github.com/ideal-postcodes"><img alt="Ideal Postcodes" src="https://avatars.githubusercontent.com/u/4996310?s=200&v=4" height="70" /></a> | <a href="https://www.xerox.com"><img alt="Xerox" src="https://avatars.githubusercontent.com/u/9158512?s=200&v=4" height="70" /></a> | <a href="https://changelog.me"><img alt="Trent Raymond" src="https://avatars.githubusercontent.com/u/1509616?v=4" height="70" /></a> |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| [Bubbles](https://usebubbles.com)<br><strong>Twitter</strong>: [@usebubbles](https://twitter.com/usebubbles) | [Christopher Blanchard](https://github.com/cblanc) | [Ideal Postcodes](https://github.com/ideal-postcodes) | [Xerox](https://www.xerox.com) | [Trent Raymond](https://changelog.me) |
| <a href="https://usebubbles.com"><img alt="Bubbles" src="https://uploads-ssl.webflow.com/5d682047c28b217055606673/5e5360be16879c1d0dca6514_icon-thin-128x128%402x.png" height="70" /></a> | <a href="https://www.xerox.com"><img alt="Xerox" src="https://avatars.githubusercontent.com/u/9158512?s=200&v=4" height="70" /></a> | <a href="https://changelog.me"><img alt="Trent Raymond" src="https://avatars.githubusercontent.com/u/1509616?v=4" height="70" /></a> | <a href="https://scrubtheweb.com"><img alt="scrubtheweb" src="https://avatars.githubusercontent.com/u/41668218?v=4" height="70" /></a> | <a href="https://github.com/hjoelh"><img alt="Joel" src="https://avatars.githubusercontent.com/u/68335961?v=4" height="70" /></a> |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| [Bubbles](https://usebubbles.com)<br><strong>Twitter</strong>: [@usebubbles](https://twitter.com/usebubbles) | [Xerox](https://www.xerox.com) | [Trent Raymond](https://changelog.me) | [scrubtheweb](https://scrubtheweb.com) | [Joel](https://github.com/hjoelh) |

@@ -82,4 +88,11 @@ ### Patreon

- [Usage](#usage)
- [Usage as an API](#usage-as-an-api)
- [Usage as a Node.js loader](#usage-as-a-nodejs-loader)
- [Loader SourceMaps](#loader-sourcemaps)
- [Loader caching](#loader-caching)
- [Customizing DI-Compiler when used as a loader](#customizing-di-compiler-when-used-as-a-loader)
- [Usage as a TypeScript Custom Transformer](#usage-as-a-typescript-custom-transformer)
- [Usage with TypeScript's Compiler APIs](#usage-with-typescripts-compiler-apis)
- [Usage with ts-node](#usage-with-ts-node)
- [Usage with ts-nodes programmatic API](#usage-with-ts-nodes-programmatic-api)
- [Usage with ttypescript](#usage-with-ttypescript)
- [Usage with Rollup](#usage-with-rollup)

@@ -93,5 +106,9 @@ - [Usage with rollup-plugin-ts](#usage-with-rollup-plugin-ts)

- [Options](#options)
- [Optimization](#optimization)
- [Optimization 1: Activate `preserveValueImports` in your tsconfig CompilerOptions](#optimization-1-activate-preservevalueimports-in-your-tsconfig-compileroptions)
- [Optimization 2: Pass in one or more identifiers to consider instances of DIContainer instead of relying on partial evaluation](#optimization-2-pass-in-one-or-more-identifiers-to-consider-instances-of-dicontainer-instead-of-relying-on-partial-evaluation)
- [Contributing](#contributing)
- [Maintainers](#maintainers)
- [FAQ](#faq)
- [DI-Compiler doesn't correctly update all my calls to the DIContainer methods](#di-compiler-doesnt-correctly-update-all-my-calls-to-the-dicontainer-methods)
- [How does it work, exactly?](#how-does-it-work-exactly)

@@ -136,46 +153,168 @@ - [License](#license)

Since this is a Custom Transformer, it can be used practically anywhere you use TypeScript.
There are multiple ways you can use DI-compiler, depending on your setup:
The most obvious place would be to use it directly with Typescript's compiler APIs:
- [As an API](#usage-as-an-api)
- [As a TypeScript Custom Transformer](#usage-as-a-typescript-custom-transformer)
- [As a modern Node.js loader](#usage-as-a-nodejs-loader)
### Usage with TypeScript's Compiler APIs
### Usage as an API
There's several ways to do this, but here's a simple example:
The simplest possible way to use DI-Compiler is with its `transform` function:
```typescript
import {createProgram, getDefaultCompilerOptions, createCompilerHost} from "typescript";
import {di} from "@wessberg/di-compiler";
```ts
import {transform} from "@wessberg/di-compiler";
const {code} = transform(`\
import {DIContainer} from "@wessberg/di";
const container = new DIContainer();
class Foo {}
container.registerSingleton<Foo>();
`);
```
const compilerOptions = getDefaultCompilerOptions();
const compilerHost = createCompilerHost(compilerOptions);
In this example, the compiler knows that `container` is an instance of [DIContainer](https://github.com/wessberg/di) based on the source text. However, you may be importing an instance of DIContainer
from another file, in which case the compiler may not be able to statically infer that an identifier is an instance of DIContainer. For example:
// Create a Typescript program
const program = createProgram(
["my-file-1.ts", "my-file-2.ts"],
compilerOptions,
compilerHost
```ts
transform(`\
import {container} from "./services";
// There may not be a way to statically determine whether or
// not \`container\` is an instance of DIContainer at this point
container.get<Foo>();
`);
```
To help the compiler out, and to improve performance, you can pass in one or more identifiers in the source text that should be considered instances of DIContainer:
```ts
transform(
`\
import {container} from "./services";
container.get<Foo>();
`,
{
// Treat identifiers named `container` as instances of DIContainer
identifier: "container"
}
);
```
// Transform the SourceFiles within the program, and pass them through the 'di' transformer
program.emit(undefined, undefined, undefined, undefined, di({program}));
If you want a source map to be generated, make sure to pass that option in as a TypeScript [CompilerOption](https://www.typescriptlang.org/tsconfig#sourceMap):
```ts
const {code, map} = transform(`...`, {
compilerOptions: {
sourceMap: true
}
});
```
### Usage with ts-node
You can pass in a cache to use as an option. This must be a data structure that conforms to that of a standard JavaScript [Map data structure](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map):
One of the simplest ways to use DI-compiler is with [`ts-node`](https://github.com/TypeStrong/ts-node):
```ts
import {transform, type TransformResult} from "@wessberg/di-compiler";
const cache = new Map<string, TransformResult>();
transform(`...`, {
cache
});
```
node -r @wessberg/di-compiler/register
### Usage as a Node.js loader
A very convenient way to use `DI-Compiler` is as a loader directly with Node.js.
If your codebase is based on **native ESM**, and **if you use Node.js v.18.6.0 or newer**, pass it as a loader via the command line
```
node --loader @wessberg/di-compiler/loader
```
You can also do it programmatically. Here's an example using CommonJS:
This is not enough on its own to teach Node.js to understand TypeScript syntax, so you'll still need to couple it with a loader like [`ts-node`](https://github.com/TypeStrong/ts-node), [`tsx`](https://github.com/esbuild-kit/tsx) or [`esm-loader`](https://github.com/esbuild-kit/esm-loader).
```typescript
const {di} = require("@wessberg/rollup-plugin-ts");
For example, here's how to use it with the native ESM loader for [`ts-node`](https://github.com/TypeStrong/ts-node):
require("ts-node").register({
transformers: program => di({program})
});
```
node --loader @wessberg/di-compiler/loader --loader ts-node/esm
```
And, here's how to use it with [`tsx`](https://github.com/esbuild-kit/tsx):
```
node --loader @wessberg/di-compiler/loader --loader tsx
```
Finally, here's how you can use it with [`esm-loader`](https://github.com/esbuild-kit/esm-loader):
```
node --loader @wessberg/di-compiler/loader --loader @esbuild-kit/esm-loader
```
Alternatively, if you don't use ESM in your project, or if you're running an older version of Node.js, DI-Compiler can be used as a loader too.
For example, here's how to use it in combination with [`ts-node`](https://github.com/TypeStrong/ts-node) in a CommonJS project:
```
node -r @wessberg/di-compiler/loader -r ts-node
```
> In all of the above configurations, for both ESM and CommonJS loaders, there is no TypeScript _Program_ context, nor is there a Type checker, so `DI-Compiler` will attempt to determinate programmatically whether or not the identifiers across your files reference instances of `DIContainer` or not, by performing partial evaluation on compile time. Please see the [Optimization](#optimization) section for details on how this process can be optimized.
A convenience loader is exposed that combines `ts-node` and DI-Compiler in a single loader for CommonJS projects, which also exposes a TypeScript Program to DI-Compiler, which is the most robust way to use it as a loader, at the expense of slower performance:
```
node -r @wessberg/di-compiler/ts-node-loader
```
> Note: You must install ts-node yourself for this configuration to work, as it is not listed as a dependency of DI-Compiler.
### Loader SourceMaps
By default, SourceMaps will be generated and inlined inside the loaded modules if the `sourceMap` option is `true` inside the resolved tsconfig.
### Loader caching
By default, DI-Compiler maintains a disk cache of transformation results from previously evaluated files. That means that successive loads of the same files will
be extremely fast.
### Customizing DI-Compiler when used as a loader
You can pass in a few options to DI-Compiler via command line options:
| Environment Variable | Description |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `DI_COMPILER_TSCONFIG_PATH` | The path to the `tsconfig.json` file to use |
| `DI_COMPILER_IDENTIFIER` | A comma-separated list of identifiers that should be considered instances of DIContainer when transforming the source files |
| `DI_COMPILER_DISABLE_CACHE` | If set, no disk caching will be used. |
Alternatively, you can add a `di` property to your `tsconfig` where you can customize its behavior without setting environment variables:
```json
// Inside your tsconfig.json
{
"di": {
"identifier": "container",
"disableCache": false
},
"compilerOptions": {
// ...
}
}
```
### Usage as a TypeScript Custom Transformer
You can use the DI-Compiler anywhere TypeScript [`Custom Transformers`](https://github.com/Microsoft/TypeScript/pull/13940) can be used.
One advantage of this approach is that you often have access to a TypeScript _Program_, which can be leveraged by the DI-Compiler to fully understand
the structure of your program and specifically the type hierarchy and whether or not an identifier is an instance of [DIContainer](https://github.com/wessberg/di), for example.
A few examples of ways to use DI-Compiler as a Custom Transformer include:
- [With TypeScript's Compiler APIs](#usage-with-typescripts-compiler-apis)
- [With ts-nodes programmatic API](#usage-with-ts-nodes-programmatic-api)
- [With ttypescript](#usage-with-ttypescript)
- [With Rollup](#usage-with-rollup)
### Usage with TypeScript's Compiler APIs
There's several ways to do this, but here's a simple example:
```typescript

@@ -189,7 +328,3 @@ import {createProgram, getDefaultCompilerOptions, createCompilerHost} from "typescript";

// Create a Typescript program
const program = createProgram(
["my-file-1.ts", "my-file-2.ts"],
compilerOptions,
compilerHost
);
const program = createProgram(["my-file-1.ts", "my-file-2.ts"], compilerOptions, compilerHost);

@@ -200,25 +335,62 @@ // Transform the SourceFiles within the program, and pass them through the 'di' transformer

### Usage with ts-nodes programmatic API
[`ts-node`](https://github.com/TypeStrong/ts-node) can also be used programmatically. Here's an example of how you may combine it with DI-Compiler:
```typescript
import {di} from "@wessberg/di-compiler";
require("ts-node").register({
transformers: program => di({program})
});
```
### Usage with ttypescript
To use DI-compiler with [`ttypescript`](https://github.com/cevek/ttypescript), create a file that wraps the invocation of `di`:
```typescript:transformer.ts
import type { Program } from 'typescript'
import { di } from "@wessberg/di-compiler";
const transformer = (program: Program) => di({ program })
export default transformer
```
Then add a record to the `plugins` array of your `tsconfig.json` that maps a key named `transform` to the relative path to the file you just created:
```json
{
"compilerOptions": {
"plugins": [{"transform": "path/to/transformer.ts"}]
}
}
```
### Usage with Rollup
There are two popular TypeScript plugins for Rollup that support Custom Transformers:
There are a few TypeScript plugins for Rollup that support Custom Transformers, and DI-Compiler can be easily integrated with them:
- [rollup-plugin-ts](https://github.com/wessberg/rollup-plugin-ts)
- [rollup-plugin-typescript2](https://github.com/ezolenko/rollup-plugin-typescript2)
- [Usage with rollup-plugin-ts](#usage-with-rollup-plugin-ts)
- [Usage with rollup-plugin-typescript2](#usage-with-rollup-plugin-typescript2)
#### Usage with rollup-plugin-ts
To use DI-Compiler with [rollup-plugin-ts](https://github.com/wessberg/rollup-plugin-ts), all you have to do is pass it to the list of transformers given as a plugin option:
```typescript
import ts from "@wessberg/rollup-plugin-ts";
import ts from "rollup-plugin-ts";
import {di} from "@wessberg/di-compiler";
export default {
input: "...",
output: [
/* ... */
],
plugins: [
ts({
transformers: [di]
})
]
input: "...",
output: [
/* ... */
],
plugins: [
ts({
transformers: [di]
})
]
};

@@ -229,2 +401,4 @@ ```

Here's how you may integrate DI-Compiler with [rollup-plugin-typescript2](https://github.com/ezolenko/rollup-plugin-typescript2):
```typescript

@@ -235,11 +409,11 @@ import ts from "rollup-plugin-typescript2";

export default {
input: "...",
output: [
/* ... */
],
plugins: [
ts({
transformers: [service => di({program: service.getProgram()})]
})
]
input: "...",
output: [
/* ... */
],
plugins: [
ts({
transformers: [service => di({program: service.getProgram()})]
})
]
};

@@ -250,27 +424,29 @@ ```

There are two popular TypeScript loaders for Webpack that support Custom Transformers:
There are two popular TypeScript loaders for Webpack that support Custom Transformers, and you can use DI-Compiler with both of them:
- [awesome-typescript-loader](https://github.com/s-panferov/awesome-typescript-loader)
- [ts-loader](https://github.com/TypeStrong/ts-loader)
- [With awesome-typescript-loader](#usage-with-awesome-typescript-loader)
- [With ts-loader](#usage-with-ts-loader)
#### Usage with awesome-typescript-loader
Here's how it can be used with [awesome-typescript-loader](https://github.com/s-panferov/awesome-typescript-loader):
```typescript
import {di} from "@wessberg/di-compiler";
const config = {
// ...
module: {
rules: [
{
// Match .mjs, .js, .jsx, and .tsx files
test: /(\.mjs)|(\.[jt]sx?)$/,
loader: "awesome-typescript-loader",
options: {
// ...
getCustomTransformers: program => di({program})
}
}
]
}
// ...
// ...
module: {
rules: [
{
// Match .mjs, .js, .jsx, and .tsx files
test: /(\.mjs)|(\.[jt]sx?)$/,
loader: "awesome-typescript-loader",
options: {
// ...
getCustomTransformers: program => di({program})
}
}
]
}
// ...
};

@@ -281,20 +457,22 @@ ```

[ts-loader](https://github.com/TypeStrong/ts-loader) can be used in exactly the same way as `awesome-typescript-loader`:
```typescript
import {di} from "@wessberg/di";
const config = {
// ...
module: {
rules: [
{
// Match .mjs, .js, .jsx, and .tsx files
test: /(\.mjs)|(\.[jt]sx?)$/,
loader: "ts-loader",
options: {
// ...
getCustomTransformers: program => di({program})
}
}
]
}
// ...
// ...
module: {
rules: [
{
// Match .mjs, .js, .jsx, and .tsx files
test: /(\.mjs)|(\.[jt]sx?)$/,
loader: "ts-loader",
options: {
// ...
getCustomTransformers: program => di({program})
}
}
]
}
// ...
};

@@ -305,22 +483,117 @@ ```

You can also use DI-compiler with the [`ava`](https://github.com/avajs/ava) test runner
with the `require` property in the `ava` configuration:
You can also use DI-compiler with the [`ava`](https://github.com/avajs/ava) test runner by using DI-Compiler as a loader.
See [this section](#usage-as-a-nodejs-loader) for more details on how to configure it.
```json5
For a CommonJS project, you can use the `require` property in the `ava` configuration. For example:
```json
{
// Other options...
extensions: ["ts"],
require: ["@wessberg/di-compiler/register"]
"ava": {
// Other options...
"extensions": ["ts"],
"require": [
"@wessberg/di-compiler/loader",
// For teaching Node.js about TypeScript specific syntax and extensions
"ts-node"
]
}
}
```
Whereas for an ESM project, the syntax is a little different:
```json
{
"ava": {
// Other options...
"extensions": {
"ts": "module"
},
"nodeArguments": [
"--loader=@wessberg/di-compiler/loader",
// For teaching Node.js about TypeScript specific syntax and extensions.
"--loader=ts-node/esm"
]
}
}
```
> Note, we use ts-node in this example, but we could have used other tools like tsx or esm-loader just as well.
## Options
You can provide options to the `di` Custom Transformer to configure its behavior:
The [`transform`](#usage-as-an-api) function, as well as the [`di` Custom Transformer](#usage-as-a-typescript-custom-transformer) takes
the same set of base options to configure tehir behavior. All of these options are optional:
| Option | Description |
| ------------------------- | ---------------------------------------------------------------------- |
| `program` | A full TypeScript program (required). |
| `typescript` _(optional)_ | If given, the TypeScript version to use internally for all operations. |
| Option | Type | Description |
| ----------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `program` | TypeScript Program | A full TypeScript program. When given, a Typechecker will be used to understand the type hierarchy of the application and to determine whether or not identifiers are instances of `DIContainer`. |
| `typescript` | TypeScript module | If given, the TypeScript version to use internally for all operations. |
| `identifier` | `string[]` or `string` | One or more identifiers in the source text that should be considered instances of DIContainer. Note: If a Program is passed, this option will be ignored. |
| `compilerOptions` | TypeScript CompilerOptions | A TypeScript [Compiler Options](https://www.typescriptlang.org/tsconfig) record. Note: If a Program is passed, this option will be ignored. |
## Optimization
Even though DI-Compiler is built for speed, there are ways you can speed it up significantly.
### Optimization 1: Activate `preserveValueImports` in your tsconfig CompilerOptions
By default, TypeScript will discard imported bindings of value types that are unused.
This means that the following example:
```ts
import {Foo} from "./foo";
container.registerSingleton<Foo>();
```
Would actually compile into code that would crash on runtime:
```js
// Error: Foo is not defined
container.registerSingleton(undefined, {identifier: "Foo", implementation: Foo});
```
To work around this, DI-Compiler has to track the imports of the files, _and_ add them back
in after transpilation, which comes at a cost.
You can optimize this by activating the `preserveValueImports` option in your tsconfig:
```json
{
"compilerOptions": {
"preserveValueImports": true
}
}
```
By doing that, you instruct TypeScript to leave unused value imports be. DI-Compiler will recognize
this and opt out of all the internal logic for adding imported bindings back in.
### Optimization 2: Pass in one or more identifiers to consider instances of DIContainer instead of relying on partial evaluation
> Note: This optimization is irrelevant if a Typescript Program is passed to DI-Compiler.
As [described here](#usage-as-an-api), it may not always be possible to statically infer whether or not an identifier is in fact an instance of [DIContainer](https://github.com/wessberg/di) when DI-Compiler does not have access to a Typechecker.
Or, it may simply be slow, in case a lot of Nodes have to be visited in order to determine it.
To make it more robust and much faster simultaneously, pass in one or more identifiers as the `identifier` option that should be considered instances of DIContainer:
```ts
import {di, transform} from "@wessberg/di-compiler";
// Example when using the transform function
transform(
`\
import {container} from "./services";
container.get<Foo>();
`,
{
// Treat identifiers named `container` as instances of DIContainer
identifier: "container"
}
);
```
See [this section](#customizing-di-compiler-when-used-as-a-loader) for details on how to pass the option when DI-Compiler is used as a loader.
<!-- SHADOW_SECTION_CONTRIBUTING_START -->

@@ -350,2 +623,12 @@

#### DI-Compiler doesn't correctly update all my calls to the DIContainer methods
If you pass a Program to DI-Compiler (such as you typically do when you use it as a Custom Transformer), this means that the Typechecker wasn't able to determine that one or mode identifiers in your
code was in fact instances of DIContainer. Please verify that TypeScript correctly tracks the type of the objects on which you invoke the relevant DIContainer methods.
If you _don't_ pass a Program to DI-Compiler, then you're relying on DI-Compiler being able to statically infer whether or not identifiers are instances of DIContainer without having access to multiple files
inside your application. This will very often lead to problems if you reference an instance of DIContainer from another file inside your application. To fix it, pass one or mode identifiers that should be
considered instances of DIContainer as an option. Please see [this section](#optimization-2-pass-in-one-or-more-identifiers-to-consider-instances-of-dicontainer-instead-of-relying-on-partial-evaluation) for details
on how you can do that.
#### How does it work, exactly?

@@ -359,4 +642,4 @@

import {DIContainer} from "@wessberg/di";
import {MyInterface} from "./my-interface";
import {MyImplementation} from "./my-implementation";
import {MyInterface} from "./my-interface.js";
import {MyImplementation} from "./my-implementation.js";

@@ -372,4 +655,4 @@ const container = new DIContainer();

container.registerSingleton(undefined, {
identifier: `MyInterface`,
implementation: MyImplementation
identifier: `MyInterface`,
implementation: MyImplementation
});

@@ -376,0 +659,0 @@ ```

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc