Skip to content

Commit 0b61849

Browse files
committed
feat: update performance
1 parent 9294ce5 commit 0b61849

File tree

3 files changed

+72
-53
lines changed

3 files changed

+72
-53
lines changed

src/migrations/standalone/0000-initialize-add-icons.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ export const initializeAddIcons = async (
1717
return;
1818
}
1919

20+
// パフォーマンス最適化: getFullText()を一度だけ呼び出す
2021
const enableProdMode = prodModeSource
2122
.getStatements()
22-
.find((source) => source.getFullText().includes("enableProdMode()"));
23+
.find((source) => {
24+
const text = source.getFullText();
25+
return text.includes("enableProdMode()");
26+
});
2327
if (!enableProdMode) {
2428
// If the project does not base angular standalone structured, do nothing.
2529
return;
@@ -34,7 +38,8 @@ export const initializeAddIcons = async (
3438
if (importIconSpecifier) {
3539
// Remove the addIcons import specifier.
3640
const addIcons = prodModeSource.getStatements().find((l) => {
37-
return l.getFullText().includes("addIcons");
41+
const text = l.getFullText();
42+
return text.includes("addIcons");
3843
});
3944
if (addIcons) {
4045
// already initialize

src/migrations/standalone/0001-remove-add-icons.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Project } from "ts-morph";
1+
import { Project, SourceFile } from "ts-morph";
22
import { CliOptions } from "../../types/cli-options";
33

44
import { saveFileChanges } from "../../utils/log-utils";
@@ -7,6 +7,8 @@ export const removeAddIcons = async (
77
project: Project,
88
cliOptions: CliOptions,
99
) => {
10+
const filesToSave: SourceFile[] = [];
11+
1012
for (const sourceFile of project.getSourceFiles()) {
1113
const importAddIcons = sourceFile.getImportDeclaration("ionicons");
1214
if (!importAddIcons) {
@@ -26,13 +28,19 @@ export const removeAddIcons = async (
2628
continue;
2729
}
2830

31+
// パフォーマンス最適化: getFullText()を一度だけ呼び出す
2932
const addIcons = constructor.getStatements().find((l) => {
30-
return l.getFullText().includes("addIcons");
33+
const text = l.getFullText();
34+
return text.includes("addIcons");
3135
});
3236

3337
if (addIcons) {
3438
addIcons.remove();
3539
}
36-
await saveFileChanges(sourceFile, cliOptions);
40+
filesToSave.push(sourceFile);
3741
}
42+
43+
await Promise.all(
44+
filesToSave.map((sourceFile) => saveFileChanges(sourceFile, cliOptions)),
45+
);
3846
};

src/migrations/standalone/0002-generate-use-icons.ts

Lines changed: 54 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -14,52 +14,65 @@ import { kebabCaseToCamelCase } from "../../utils/string-utils";
1414
import path from "node:path";
1515
import iconsData from "ionicons/dist/ionicons.json";
1616

17+
// パフォーマンス最適化: モジュールレベルで一度だけ作成
18+
const IONIC_COMPONENTS_SET = new Set(IONIC_COMPONENTS);
19+
const ICON_NAME_REGEX = /{{\s*'([^']+)'\s*}}/;
20+
1721
export const generateUseIcons = async (
1822
project: Project,
1923
cliOptions: CliOptions,
2024
): Promise<boolean> => {
21-
const skippedIconsHtmlAll: string[] = [];
22-
const ionIconsAll: string[] = [];
23-
const sourceIonIcons = iconsData.icons.map((icon) => icon.name);
25+
const skippedIconsHtmlAll = new Set<string>();
26+
const ionIconsAll = new Set<string>();
27+
const sourceIonIcons = new Set(iconsData.icons.map((icon) => icon.name));
2428

2529
for (const sourceFile of project.getSourceFiles()) {
26-
if (sourceFile.getFilePath().includes("node_modules")) {
30+
const filePath = sourceFile.getFilePath();
31+
32+
// node_modulesを早期除外(パス解析を使用)
33+
if (filePath.includes("node_modules")) {
2734
continue;
2835
}
2936

30-
if (sourceFile.getFilePath().endsWith(".html")) {
37+
if (filePath.endsWith(".html")) {
3138
const htmlAsString = sourceFile.getFullText();
3239

3340
const { skippedIconsHtml, ionIcons } = detectIonicComponentsAndIcons(
3441
htmlAsString,
35-
sourceFile.getFilePath(),
36-
);
37-
skippedIconsHtmlAll.push(
38-
...skippedIconsHtml,
39-
...ionIcons.filter((icon) => !sourceIonIcons.includes(icon)),
42+
filePath,
4043
);
41-
ionIconsAll.push(
42-
...ionIcons.filter((icon) => sourceIonIcons.includes(icon)),
43-
);
44-
} else if (sourceFile.getFilePath().endsWith(".ts")) {
44+
for (const icon of skippedIconsHtml) {
45+
skippedIconsHtmlAll.add(icon);
46+
}
47+
for (const icon of ionIcons) {
48+
if (sourceIonIcons.has(icon)) {
49+
ionIconsAll.add(icon);
50+
} else {
51+
skippedIconsHtmlAll.add(icon);
52+
}
53+
}
54+
} else if (filePath.endsWith(".ts")) {
4555
const templateAsString = getComponentTemplateAsString(sourceFile);
4656
if (templateAsString) {
4757
const { skippedIconsHtml, ionIcons } = detectIonicComponentsAndIcons(
4858
templateAsString,
49-
sourceFile.getFilePath(),
50-
);
51-
skippedIconsHtmlAll.push(
52-
...skippedIconsHtml,
53-
...ionIcons.filter((icon) => !sourceIonIcons.includes(icon)),
54-
);
55-
ionIconsAll.push(
56-
...ionIcons.filter((icon) => sourceIonIcons.includes(icon)),
59+
filePath,
5760
);
61+
for (const icon of skippedIconsHtml) {
62+
skippedIconsHtmlAll.add(icon);
63+
}
64+
for (const icon of ionIcons) {
65+
if (sourceIonIcons.has(icon)) {
66+
ionIconsAll.add(icon);
67+
} else {
68+
skippedIconsHtmlAll.add(icon);
69+
}
70+
}
5871
}
5972
}
6073
}
6174

62-
const uniqueSkippedIconsHtmlAll = Array.from(new Set(skippedIconsHtmlAll));
75+
const uniqueSkippedIconsHtmlAll = Array.from(skippedIconsHtmlAll);
6376
uniqueSkippedIconsHtmlAll.sort();
6477
if (uniqueSkippedIconsHtmlAll.length > 0) {
6578
console.warn(
@@ -68,7 +81,7 @@ export const generateUseIcons = async (
6881
);
6982
}
7083

71-
const uniqueIonIconsAll = Array.from(new Set(ionIconsAll));
84+
const uniqueIonIconsAll = Array.from(ionIconsAll);
7285
uniqueIonIconsAll.sort();
7386
const uniqueIconCamelCase = uniqueIonIconsAll.map((ionIcon) =>
7487
kebabCaseToCamelCase(ionIcon),
@@ -88,8 +101,9 @@ export const generateUseIcons = async (
88101
/**
89102
* If the number of exported icons is the same as the number of source icons
90103
*/
104+
const exportItemsSet = new Set(exportItems);
91105
const newIcons = uniqueIconCamelCase.filter(
92-
(icon) => !exportItems.includes(icon),
106+
(icon) => !exportItemsSet.has(icon),
93107
);
94108
if (newIcons.length === 0) {
95109
console.info(`[Dev] No new icons to add or change to use-icons.ts`);
@@ -122,8 +136,8 @@ function detectIonicComponentsAndIcons(htmlAsString: string, filePath: string) {
122136
const ast = parse(htmlAsString, { filePath });
123137
const nodes = ast.templateNodes;
124138

125-
const ionicComponents: string[] = [];
126-
const ionIcons: string[] = [];
139+
const ionicComponents = new Set<string>();
140+
const ionIcons = new Set<string>();
127141
const skippedIconsHtml: string[] = [];
128142

129143
let hasRouterLinkWithHref = false;
@@ -137,10 +151,8 @@ function detectIonicComponentsAndIcons(htmlAsString: string, filePath: string) {
137151
) {
138152
const tagName = node.type === "Template" ? node.tagName : node.name;
139153

140-
if (IONIC_COMPONENTS.includes(tagName)) {
141-
if (!ionicComponents.includes(tagName)) {
142-
ionicComponents.push(tagName);
143-
}
154+
if (IONIC_COMPONENTS_SET.has(tagName)) {
155+
ionicComponents.add(tagName);
144156

145157
const routerLink =
146158
node.attributes.find(
@@ -163,9 +175,7 @@ function detectIonicComponentsAndIcons(htmlAsString: string, filePath: string) {
163175

164176
if (staticNameAttribute) {
165177
const iconName = staticNameAttribute.value;
166-
if (!ionIcons.includes(iconName)) {
167-
ionIcons.push(iconName);
168-
}
178+
ionIcons.add(iconName);
169179
} else {
170180
const boundNameAttribute = node.inputs.find(
171181
(a: any) => a.name === attribute,
@@ -174,40 +184,36 @@ function detectIonicComponentsAndIcons(htmlAsString: string, filePath: string) {
174184
if (boundNameAttribute) {
175185
const skippedIcon = node.sourceSpan.toString();
176186

177-
const iconNameRegex = /{{\s*'([^']+)'\s*}}/;
178187
/**
179188
* Attempt to find the icon name from the bound name attribute
180189
* when the developer has a template like this:
181190
* <ion-icon name="'user'"></ion-icon>
182191
*/
183-
const iconNameMatch = skippedIcon.match(iconNameRegex);
192+
const iconNameMatch = skippedIcon.match(ICON_NAME_REGEX);
184193

185194
const deepGetIconConditional = (
186195
ast: typeof boundNameAttribute.value.ast,
187-
icons: string[],
188-
): string[] => {
196+
iconsSet: Set<string>,
197+
): void => {
189198
if (ast.trueExp.type === "LiteralPrimitive") {
190-
icons.push(ast.trueExp.value);
199+
iconsSet.add(ast.trueExp.value);
191200
} else if (ast.trueExp.type === "Conditional") {
192-
deepGetIconConditional(ast.trueExp, icons);
201+
deepGetIconConditional(ast.trueExp, iconsSet);
193202
} else {
194203
skippedIconsHtml.push(skippedIcon);
195204
}
196205

197206
if (ast.falseExp.type === "LiteralPrimitive") {
198-
icons.push(ast.falseExp.value);
207+
iconsSet.add(ast.falseExp.value);
199208
} else if (ast.falseExp.type === "Conditional") {
200-
deepGetIconConditional(ast.falseExp, icons);
209+
deepGetIconConditional(ast.falseExp, iconsSet);
201210
} else {
202211
skippedIconsHtml.push(skippedIcon);
203212
}
204-
return icons;
205213
};
206214

207215
if (iconNameMatch) {
208-
if (!ionIcons.includes(iconNameMatch[1])) {
209-
ionIcons.push(iconNameMatch[1]);
210-
}
216+
ionIcons.add(iconNameMatch[1]);
211217
} else if (boundNameAttribute.value.ast.type === "Conditional") {
212218
deepGetIconConditional(boundNameAttribute.value.ast, ionIcons);
213219
} else {
@@ -270,8 +276,8 @@ function detectIonicComponentsAndIcons(htmlAsString: string, filePath: string) {
270276
}
271277

272278
return {
273-
ionicComponents,
274-
ionIcons,
279+
ionicComponents: Array.from(ionicComponents),
280+
ionIcons: Array.from(ionIcons),
275281
skippedIconsHtml,
276282
hasRouterLinkWithHref,
277283
hasRouterLink,

0 commit comments

Comments
 (0)