🗑

使用していないコンポーネントを大まかに見つけて削除するスクリプト

2022/07/02に公開

React でコンポーネントを作りすぎて、どれを使っていないかわからなくなったので作ってみた。

コード
#!/bin/sh

# Description:
#     This command is used to identify unused components in a SPA project.
#     Unused files are written to "unsed.txt" with absolute paths.
#     It will identify unused components in the following ways:
#
#         1. Match any extension of ".js", ".jsx", ".ts", ".tsx".
#         2. Matching file is called in the form of "import from".
#         3. Obtains the characters after "from" and determines if they match the file name.
#            If the file name is "index", do not include the file.
#         4. Compare the file directly under the specified directory with the characters after "from".
#         5. If there is no match, it is assumed to be an unused component.
#         6. Output to file
#
# Usage:
#     source main.sh [options] <directory-path>
#
# Options:
#     -d, --double: using double quotes
#     -s, --single: using single quotes
#     -o, --output <file-name>: output to file
#     -D, --delete: delete the unused files
#
# Examples:
#     source main.sh -s -o output.txt ./src
#
# Warning:
#     This command does not perfectly output unused files.
#     The "-D, --delete" option should be avoided if possible.
#     If you do use it, please use it only in an environment where you can revert deleted files.



# Use single or double quotes, depending on the situation
delimiter="'"               # Use single quotes by default
outputFileName="unused.txt" # default output file name

usingModulePaths=()
targetFilePaths=()
unusedModulePaths=()
targetExtensions=(".js" ".jsx" ".ts" ".tsx")

while [ $# -gt 0 ]; do
    case "$1" in
        -d|--double)
            delimiter="\""
            ;;
        -s|--single)
            delimiter="'"
            ;;
        -o|--output)
            shift
            outputFileName="$1"
            ;;
        -D|--delete)
            delete=true
            ;;
        *)
            # if no option, assume it is the directory path
            if [ -z "$directoryPath" ]; then
                directoryPath="$1"
            else
                echo "Invalid option: $1"
                exit 1
            fi
            ;;
    esac
    shift
done

# Confirmation regarding delete option
if [ "$delete" = true ]; then
    echo "Are you sure you want to delete the unused files?"
    echo "Type 'yes' to continue: "
    read answer
    if [ "$answer" != "yes" ]; then
        echo "Aborting..."
        exit 1
    fi
fi

# Note: To use double quotation marks, rewrite the code as follows
#     moduleNameRetriver: 2
#     split($0, splited_line, "'$delimiter'");
#     split($0, splited_line, "\'$delimiter'");
moduleNameRetriver='
BEGIN {
    FS = "'$delimiter'";
}

/^import.+from/{
    split($0, splited_line, FS);
    module_path = splited_line[2];

    split(module_path, splited_module_path, "/");
    splited_module_path_length = length(splited_module_path);

    print(splited_module_path[splited_module_path_length]);
}
'

splitter='
{
    split($1, arr, "/");
    arr_length = length(arr);
    print(arr[arr_length]);
}
'

for fileAbsolutePath in `find $directoryPath -type f`; do
    fileName=$(echo $fileAbsolutePath | awk $splitter)
    fileExtension=${fileName##*.}
    fileNameWithoutExtension=${fileName%.*}

    if ! [[ "${targetExtensions[@]}" =~ "$fileExtension" ]]; then
        continue
    fi

    targetFilePaths+=($fileNameWithoutExtension)
    usingModuleName=$(awk -F "$delimiter" $moduleNameRetriver $fileAbsolutePath)
    usingModulePaths+=($usingModuleName)
done

# Remove duplicates
usingModulePaths=($(echo "${usingModulePaths[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))

# Store unmatched modules in array
for targetFilePath in ${targetFilePaths[@]}; do
    if [[ ! ${usingModulePaths[@]} =~ $targetFilePath ]]; then
        unusedModulePaths+=($targetFilePath)
    fi
done

# Remove duplicates
unusedModulePaths=($(echo "${unusedModulePaths[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))

# Output absolute paths of unused modules to file
for fileAbsolutePath in `find $directoryPath -type f`; do
    fileName=$(echo $fileAbsolutePath | awk $splitter)
    fileNameWithoutExtension=${fileName%.*}

    for unusedModulePath in ${unusedModulePaths[@]}; do
        if [[ $fileNameWithoutExtension == $unusedModulePath ]]; then
            if [ "$fileNameWithoutExtension" != "index" ]; then
                echo $fileAbsolutePath >> $outputFileName
                if [ "$delete" = true ]; then
                    rm $fileAbsolutePath
                fi
            fi
        fi
    done
done

使い方

$ source main.sh [options] <directory-path>

オプション

-d, --double: ダブルクォーテーションを使う
-s, --single: シングルクォーテーションを使う
-o, --output <file-name>: 出力するファイル名を指定
-D, --delete: コンポーネントを削除

コマンドを実行して影響が出るのは 22 個中、以下のファイルだけでした。

  • main.tsx
  • vite-env.d.ts

完璧には弾けないので、-Dオプションを使ったら、以下のように変更を破棄する必要があるのは注意です。

$ git checkout main.tsx vite-env.d.ts

削除したコンポーネント内で使われていたものは使用しているコンポーネントとして認識されるので、
場合によっては2回ぐらいやるのが良いかもしれないです。

Discussion