Skip to content

Commit

Permalink
Track Angular polyfills
Browse files Browse the repository at this point in the history
  • Loading branch information
davidlj95 committed Jan 13, 2025
1 parent 5a77dcc commit 6753510
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 130 deletions.
6 changes: 2 additions & 4 deletions packages/knip/fixtures/plugins/angular/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@
"outputPath": "dist/knip-angular-example",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": [
"zone.js"
],
"polyfills": "src/polyfill.js",
"tsConfig": "tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
Expand Down Expand Up @@ -103,4 +101,4 @@
}
}
}
}
}
Empty file.
2 changes: 1 addition & 1 deletion packages/knip/fixtures/plugins/angular2/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,4 @@
}
}
}
}
}
10 changes: 7 additions & 3 deletions packages/knip/fixtures/plugins/angular3/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@
"ssr": {
"entry": "src/server.ts"
},
"server": "src/main.server-for-non-prod.ts"
"server": "src/main.server-for-non-prod.ts",
"polyfills": [
"src/polyfill.js"
]
},
"configurations": {
"production": {
"server": "src/main.server.ts",
"scripts": ["src/script.js"]
},
"development": {
"scripts": ["src/script-for-non-prod.js"]
"scripts": ["src/script-for-non-prod.js"],
"polyfills": ["src/polyfill-for-non-prod.js"]
}
}
},
Expand All @@ -43,4 +47,4 @@
}
}
}
}
}
Empty file.
Empty file.
230 changes: 116 additions & 114 deletions packages/knip/src/plugins/angular/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type { IsPluginEnabled, Plugin, ResolveConfig } from '../../types/config.js';
import { type Input, toConfig, toDependency, toEntry, toProductionEntry } from '../../util/input.js';
import { join } from '../../util/path.js';
import { hasDependency } from '../../util/plugin.js';
import type {IsPluginEnabled, Plugin, ResolveConfig} from '../../types/config.js';
import {type Input, toConfig, toDependency, toEntry, toProductionEntry} from '../../util/input.js';
import {join} from '../../util/path.js';
import {hasDependency} from '../../util/plugin.js';
import * as karma from '../karma/helpers.js';
import type {
AngularCLIWorkspaceConfiguration,
KarmaTarget,
Project,
WebpackBrowserSchemaForBuildFacade,
AngularCLIWorkspaceConfiguration,
KarmaTarget,
Project,
WebpackBrowserSchemaForBuildFacade,
} from './types.js';

// https://angular.io/guide/workspace-config
Expand All @@ -16,117 +16,119 @@ const title = 'Angular';

const enablers = ['@angular/cli'];

const isEnabled: IsPluginEnabled = ({ dependencies }) => hasDependency(dependencies, enablers);
const isEnabled: IsPluginEnabled = ({dependencies}) => hasDependency(dependencies, enablers);

const config = ['angular.json'];

const resolveConfig: ResolveConfig<AngularCLIWorkspaceConfiguration> = async (config, options) => {
const { cwd, configFilePath } = options;

if (!config?.projects) return [];

const inputs = new Set<Input>();

for (const project of Object.values(config.projects)) {
if (!project.architect) return [];
for (const [targetName, target] of Object.entries(project.architect)) {
const { options: opts, configurations: configs } = target;
const [packageName] = typeof target.builder === 'string' ? target.builder.split(':') : [];
if (typeof packageName === 'string') inputs.add(toDependency(packageName));
if (opts) {
if ('tsConfig' in opts && typeof opts.tsConfig === 'string') {
inputs.add(toConfig('typescript', opts.tsConfig, configFilePath));
}
}
const defaultEntriesByOption: EntriesByOption = opts ? entriesByOption(opts) : new Map();
const entriesByOptionByConfig: Map<string, EntriesByOption> = new Map(
configs ? Object.entries(configs).map(([name, opts]) => [name, entriesByOption(opts)]) : []
);
const productionEntriesByOption: EntriesByOption =
entriesByOptionByConfig.get(PRODUCTION_CONFIG_NAME) ?? new Map();
const normalizePath = (path: string) => join(cwd, path);
for (const [configName, entriesByOption] of entriesByOptionByConfig.entries()) {
for (const entries of entriesByOption.values()) {
for (const entry of entries) {
inputs.add(
targetName === BUILD_TARGET_NAME && configName === PRODUCTION_CONFIG_NAME
? toProductionEntry(normalizePath(entry))
: toEntry(normalizePath(entry))
const {cwd, configFilePath} = options;

if (!config?.projects) return [];

const inputs = new Set<Input>();

for (const project of Object.values(config.projects)) {
if (!project.architect) return [];
for (const [targetName, target] of Object.entries(project.architect)) {
const {options: opts, configurations: configs} = target;
const [packageName] = typeof target.builder === 'string' ? target.builder.split(':') : [];
if (typeof packageName === 'string') inputs.add(toDependency(packageName));
if (opts) {
if ('tsConfig' in opts && typeof opts.tsConfig === 'string') {
inputs.add(toConfig('typescript', opts.tsConfig, configFilePath));
}
}
const defaultEntriesByOption: EntriesByOption = opts ? entriesByOption(opts) : new Map();
const entriesByOptionByConfig: Map<string, EntriesByOption> = new Map(
configs ? Object.entries(configs).map(([name, opts]) => [name, entriesByOption(opts)]) : []
);
}
}
}
for (const [option, entries] of defaultEntriesByOption.entries()) {
for (const entry of entries) {
inputs.add(
targetName === BUILD_TARGET_NAME && !productionEntriesByOption.get(option)?.length
? toProductionEntry(normalizePath(entry))
: toEntry(normalizePath(entry))
);
}
}
if (target.builder === '@angular-devkit/build-angular:karma' && opts) {
const karmaBuilderOptions = opts as KarmaTarget;
// https://github.com/angular/angular-cli/blob/19.0.6/packages/angular_devkit/build_angular/src/builders/karma/schema.json#L143
const testFilePatterns = karmaBuilderOptions.include ?? ['**/*.spec.ts'];
for (const testFilePattern of testFilePatterns) {
inputs.add(toEntry(testFilePattern));
}
// https://github.com/angular/angular-cli/blob/19.0.6/packages/angular_devkit/build_angular/src/builders/karma/schema.json#L146
const excludedTestFilePatterns = karmaBuilderOptions.exclude ?? [];
for (const excludedTestFilePattern of excludedTestFilePatterns) {
inputs.add(toEntry(`!${excludedTestFilePattern}`));
}
const karmaConfig = karmaBuilderOptions.karmaConfig;
if (!karmaConfig) {
// Hardcoded default Karma config from Angular builder
// https://github.com/angular/angular-cli/blob/19.0.6/packages/angular_devkit/build_angular/src/builders/karma/index.ts#L115
karma
.inputsFromPlugins(
['karma-jasmine', 'karma-chrome-launcher', 'karma-jasmine-html-reporter', 'karma-coverage'],
options.manifest.devDependencies
)
.forEach(inputs.add, inputs);
karma.inputsFromFrameworks(['jasmine']).forEach(inputs.add, inputs);
}
if (karmaConfig && !karma.configFiles.includes(karmaConfig)) {
inputs.add(toConfig('karma', karmaConfig, options.configFilePath));
const productionEntriesByOption: EntriesByOption =
entriesByOptionByConfig.get(PRODUCTION_CONFIG_NAME) ?? new Map();
const normalizePath = (path: string) => join(cwd, path);
for (const [configName, entriesByOption] of entriesByOptionByConfig.entries()) {
for (const entries of entriesByOption.values()) {
for (const entry of entries) {
inputs.add(
targetName === BUILD_TARGET_NAME && configName === PRODUCTION_CONFIG_NAME
? toProductionEntry(normalizePath(entry))
: toEntry(normalizePath(entry))
);
}
}
}
for (const [option, entries] of defaultEntriesByOption.entries()) {
for (const entry of entries) {
inputs.add(
targetName === BUILD_TARGET_NAME && !productionEntriesByOption.get(option)?.length
? toProductionEntry(normalizePath(entry))
: toEntry(normalizePath(entry))
);
}
}
if (target.builder === '@angular-devkit/build-angular:karma' && opts) {
const karmaBuilderOptions = opts as KarmaTarget;
// https://github.com/angular/angular-cli/blob/19.0.6/packages/angular_devkit/build_angular/src/builders/karma/schema.json#L143
const testFilePatterns = karmaBuilderOptions.include ?? ['**/*.spec.ts'];
for (const testFilePattern of testFilePatterns) {
inputs.add(toEntry(testFilePattern));
}
// https://github.com/angular/angular-cli/blob/19.0.6/packages/angular_devkit/build_angular/src/builders/karma/schema.json#L146
const excludedTestFilePatterns = karmaBuilderOptions.exclude ?? [];
for (const excludedTestFilePattern of excludedTestFilePatterns) {
inputs.add(toEntry(`!${excludedTestFilePattern}`));
}
const karmaConfig = karmaBuilderOptions.karmaConfig;
if (!karmaConfig) {
// Hardcoded default Karma config from Angular builder
// https://github.com/angular/angular-cli/blob/19.0.6/packages/angular_devkit/build_angular/src/builders/karma/index.ts#L115
karma
.inputsFromPlugins(
['karma-jasmine', 'karma-chrome-launcher', 'karma-jasmine-html-reporter', 'karma-coverage'],
options.manifest.devDependencies
)
.forEach(inputs.add, inputs);
karma.inputsFromFrameworks(['jasmine']).forEach(inputs.add, inputs);
}
if (karmaConfig && !karma.configFiles.includes(karmaConfig)) {
inputs.add(toConfig('karma', karmaConfig, options.configFilePath));
}
}
}
}
}
}

return Array.from(inputs);
return Array.from(inputs);
};

const entriesByOption = (opts: TargetOptions): EntriesByOption =>
new Map(
Object.entries({
main: 'main' in opts && opts.main && typeof opts.main === 'string' ? [opts.main] : [],
scripts:
'scripts' in opts && opts.scripts && Array.isArray(opts.scripts)
? (opts.scripts as ScriptsBuildOption).map(scriptStringOrObject =>
typeof scriptStringOrObject === 'string' ? scriptStringOrObject : scriptStringOrObject.input
)
: [],
fileReplacements:
'fileReplacements' in opts && opts.fileReplacements && Array.isArray(opts.fileReplacements)
? (opts.fileReplacements as FileReplacementsBuildOption).map(fileReplacement =>
'with' in fileReplacement ? fileReplacement.with : fileReplacement.replaceWith
)
: [],
browser: 'browser' in opts && opts.browser && typeof opts.browser === 'string' ? [opts.browser] : [],
server: 'server' in opts && opts.server && typeof opts.server === 'string' ? [opts.server] : [],
ssrEntry:
'ssr' in opts &&
opts.ssr &&
typeof opts.ssr === 'object' &&
'entry' in opts.ssr &&
typeof opts.ssr.entry === 'string'
? [opts.ssr.entry]
: [],
})
);
new Map(
Object.entries({
main: 'main' in opts && opts.main && typeof opts.main === 'string' ? [opts.main] : [],
scripts:
'scripts' in opts && opts.scripts && Array.isArray(opts.scripts)
? (opts.scripts as ScriptsBuildOption).map(scriptStringOrObject =>
typeof scriptStringOrObject === 'string' ? scriptStringOrObject : scriptStringOrObject.input
)
: [],
polyfills:
'polyfills' in opts && opts.polyfills ? Array.isArray(opts.polyfills) ? opts.polyfills : [opts.polyfills] : [],
fileReplacements:
'fileReplacements' in opts && opts.fileReplacements && Array.isArray(opts.fileReplacements)
? (opts.fileReplacements as FileReplacementsBuildOption).map(fileReplacement =>
'with' in fileReplacement ? fileReplacement.with : fileReplacement.replaceWith
)
: [],
browser: 'browser' in opts && opts.browser && typeof opts.browser === 'string' ? [opts.browser] : [],
server: 'server' in opts && opts.server && typeof opts.server === 'string' ? [opts.server] : [],
ssrEntry:
'ssr' in opts &&
opts.ssr &&
typeof opts.ssr === 'object' &&
'entry' in opts.ssr &&
typeof opts.ssr.entry === 'string'
? [opts.ssr.entry]
: [],
})
);

type TargetOptions = Exclude<Target['options'], undefined>;
type Target = Architect[string];
Expand All @@ -142,9 +144,9 @@ const PRODUCTION_CONFIG_NAME = 'production';
const BUILD_TARGET_NAME = 'build';

export default {
title,
enablers,
isEnabled,
config,
resolveConfig,
} satisfies Plugin;
title,
enablers,
isEnabled,
config,
resolveConfig,
} satisfies Plugin;
6 changes: 3 additions & 3 deletions packages/knip/test/plugins/angular.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ test('Find dependencies with the Angular plugin', async () => {
devDependencies: 1,
unlisted: 1,
unresolved: 1,
processed: 3,
total: 3,
processed: 4,
total: 4,
});
});
});
10 changes: 5 additions & 5 deletions packages/knip/test/plugins/angular3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ test('Find dependencies with the Angular plugin (non-production)', async () => {
assert.deepEqual(counters, {
...baseCounters,
devDependencies: 1,
processed: 8,
total: 8,
processed: 10,
total: 10,
});
});

Expand All @@ -32,7 +32,7 @@ test('Find dependencies with the Angular plugin (production)', async () => {

assert.deepEqual(counters, {
...baseCounters,
processed: 4,
total: 4,
processed: 5,
total: 5,
});
});
});

0 comments on commit 6753510

Please sign in to comment.