grunt unused - 循环目录中的子文件夹

grunt unused - loop subfolders in directory

我正在尝试使用 grunt-unused 删除多个子目录中所有未使用的图像链接。为清楚起见,这是我的文件夹结构:

|-- dist
|  |-- site-1
|  |  |—-index.html
|  |  |—-style.css
|  |  |—-app.js
|  |  |—-image1.jpg
|  |  |—-image2.jpg
|  |  |—-image3.jpg
|  |  |—-image4.jpg
|  |-- site-2
|  |  |—-index.html
|  |  |—-style.css
|  |  |—-app.js
|  |  |—-image1.jpg
|  |  |—-image2.jpg
|  |  |—-image3.jpg
|  |  |—-image4.jpg
|  |-- site-3
|  |  |—-index.html
|  |  |—-style.css
|  |  |—-app.js
|  |  |—-image1.jpg
|  |  |—-image2.jpg
|  |  |—-image3.jpg
|  |  |—-image4.jpg

我写了一个 'forEach' 函数来定位这些子文件夹,然后引用每个文件夹,类似于 this post 但它不适用于未使用的任务。它不会遍历每个目录,并且控制台会在非常短的时间内检测到没有未使用的文件,就像它实际上没有做任何事情一样。

我哪里错了?

grunt.registerTask('prodOutput', function () {

    // read all subdirectories of dist/ folder, excluding the assets folder
    grunt.file.expand('dist/*', '!dist/assets/**').forEach(function (dir) {

        //get the current unused config
        var unusedConfig = grunt.config.get('unused') || {};

        //set options for grunt-unused, including the folder variable 'dir' as the reference of where to look
        unusedConfig[dir] = {
            option: {
                reference: dir + '/',
                directory: ['**/*.css', '**/*.html', '**/*.js'],
                remove: false, // set to true to delete unused files from project
                reportOutput: 'report.txt', // set to false to disable file output
                fail: false // set to true to make the task fail when unused files are found
            }
        };

        grunt.config.set('unused', unusedConfig);

    });

    grunt.task.run('unused');

});

问题

正如我前面评论中提到的...主要问题是您正在动态配置多个 Targets in your forEach Function prior to running the task, however grunt-unused 不支持多个目标配置。

另外 grunt-unused 期望包含 references/links 到其他文件(即 index.html)的文件与其引用的文件(例如图像)位于不同的 directory/folder , css, 等), 然而你的 post 中提供的文件夹结构是扁平化的。


解决方案

快速浏览了其他 g运行t 插件后,似乎没有一款能满足您的要求。我认为您可以实现此目的的唯一方法是编写您自己的自定义 Task/plugin 来处理此问题。

要实现此目的,您可以执行以下操作:

创建一个单独的 Javascript 模块导出 Registered MutliTask。将文件命名为 delete-unused.js 并将其保存到名为 tasks 的目录中,该目录与 Gruntfile.js.

位于同一顶级目录中

目录结构

你的目录结构应该是这样的:

.
├── dist
│   ├── site-1
│   ├── site-2
│   ├── ...
│   └── assets
│
├── Gruntfile.js
│
├── node_modules
│   └── ...
│
├── package.json
│
└── tasks
    └── delete-unused.js

删除-unused.js

module.exports = function(grunt) {

  'use strict';

  // Requirements
  var fs = require('fs');
  var path = require('path');

  grunt.registerMultiTask('unused', 'Delete unused assets', function() {

    // Default options. These are used when no options are
    // provided via the  initConfig({...}) papaparse task.
    var options = this.options({
      reportOutput: false,
      remove: false
    });

    var reportTxt = '';

    // Loop over each src directory path provided via the configs src array.
    this.data.src.forEach(function(dir) {

      // Log if the directory src path provided cannot be found.
      if (!grunt.file.isDir(dir)) {
        grunt.fail.warn('Directory not found: '.yellow + dir.yellow);
      }

      // Append a forward slash If the directory path provided
      // in the src Array does not end with one.
      if (dir.slice(-1) !== '/') {
        dir += '/';
      }

      // Generate the globbin pattern (only one level deep !).
      var glob = [dir, '*'].join('');

      // Create an Array of all top level folders (e.g. site-*)
      // in the dist directory and exclude the assets directory.
      var dirs = grunt.file.expand(glob).map(function(dir) {
        return dir;
      });

      // Loop over each directory.
      dirs.forEach(function(dir) {

        // Add the folders to exclude here.
        if (dir === './dist/assets') {
          return;
        }

        // Log status and update report
        grunt.log.write('\nProcessing folder ' + dir);
        reportTxt += '\nProcessing folder ' + dir;

        // Empty Array to be populated with unused asset references.
        var unused = [];

        // Define the path to index.html
        var pathToHtml = [dir, '/', 'index.html'].join('');

        // Create Array of file names and filepaths (exclude index.html)
        var assets = grunt.file.expand([dir + '/**/*.*', '!index.html'])
          .map(function(file) {
            return {
              fpath: file,
              fname: path.basename(file)
            };
          });

        // Log/Report missing 'index.html' and return early.
        if (!grunt.file.exists(pathToHtml)) {
          grunt.log.write('\n  >> Cannot find index.html in ' + dir + '/\n');
          reportTxt += '\n  >> Cannot find index.html in ' + dir + '/\n';
          return;
        }

        // Read the contents of index.html.
        var html = fs.readFileSync(pathToHtml, {
          encoding: 'utf8'
        });

        // Loop over each file asset to find if linked in index.html
        assets.forEach(function(asset) {

          // Backslash-escape the dot [.] in filename for RegExp object.
          var escapedFilename = asset.fname.replace('.', '\.');

          // Dynamically create a RegExp object to search for instances
          // of the asset filename in the contents of index.html.
          // This ensures the reference is an actual linked asset and
          // not plain text written elsewhere in the document.
          //
          // For an explanation of this Regular Expression visit:
          // https://regex101.com/r/XZpldm/4/
          var regex = new RegExp("(?:href=|src=|url\()(?:[\",']?)(.*"
              + escapedFilename + ")[\",',\)]+?", "g");

          // Search index.html using the regex
          if (html.search(regex) === -1 && asset.fname !== 'index.html') {
            unused.push(asset); // <-- Not found so add to list.
          }
        });

        // Log status and update report
        grunt.log.write('\n  ' + unused.length + ' unused assets found:\n');
        reportTxt += '\n  ' + unused.length + ' unused assets found:\n';

        //Delete the unused asset files.
        unused.forEach(function(asset) {
          if (options.remove) {
            grunt.file.delete(asset.fpath);

            // Log status and update report
            grunt.log.write('  deleted: ' + asset.fpath + '\n');
            reportTxt += '  deleted: ' + asset.fpath + '\n';
          } else {
            // Log status and update report
            grunt.log.write('    ' + asset.fpath + '\n');
            reportTxt += '    ' + asset.fpath + '\n';
          }
        });

      });

      if (options.reportOutput) {
        grunt.file.write(options.reportOutput, reportTxt);
      }

    });
  });
};

Gruntfile.js

按如下方式配置您的 Gruntfile.js

module.exports = function(grunt) {

  'use strict';

  grunt.initConfig({
    // ...
    unused: {
      options: {
        reportOutput: 'report.txt',
        remove: true
      },
      dist: {
        src: ['./dist/']
      }
    }

  });

  // Load the custom multiTask named `unused` - which is defined
  // in `delete-unused.js` stored in the directory named `tasks`.
  grunt.loadTasks('./tasks');

  // Register and add unused to the default Task.
  grunt.registerTask('default', [
    // ...
    'unused',
    // ...
  ]);

  // Or add it to another named Task.
  grunt.registerTask('foobar', [
    // ...
    'unused',
    // ...
  ]);

};

备注

  1. 不再使用grunt-unused插件,因此您可以通过运行通过您的CLi工具执行以下操作来卸载它:$ npm un -D grunt-unused

  2. delete-unused.js 自定义模块在 Gruntfile.js 中提供了类似的配置,但选项少于 grunt-unused 中的选项。配置中的 src 数组接受要处理的文件夹的路径 (即 ./dist/。而不是 glob 模式 - glob 模式是在 grunt-unused.js.

    内生成的
  3. reportOutputremove 的默认选项在 grunt-unused.js 中设置为 false。当您第一次 运行 任务时,我建议您最初在 Gruntfile.js 配置中将 remove 选项设置为 false。这将简单地将未使用的资产记录到控制台,并允许您检查它是否满足您的要求。显然,将 remove 选项设置为 true 将在重新 运行.

    时删除任何未使用的资产
  4. 我注意到在您的 post 中您提供了 glob 模式 '!dist/assets/**' 以排除 assets 文件夹被处理。不是通过 src 配置传递要排除的 glob 模式,而是在 delete-unused.js 中进行了硬编码。您会在以下行中看到它:

// Add the folders to exclude here.
if (dir === './dist/assets') {
    return;
}

如果 dist 文件夹中有您想要排除的其他目录,您需要将它们添加到那里:例如:

// Add the folders to exclude here.
if (dir === './dist/assets'
    || dir === './dist/foo'
    || dir === './dist/quux') {
    return;
}
  1. 此解决方案仅检查 site-* 文件夹中的资产是否在相应的 index.html 中 linked/referenced,而不检查它们是否在任何文件中被引用.css 文件。

  2. delete-unused.js 利用正则表达式查找资产是否实际链接到 index.html 并且不是在文档其他地方写的纯文本 (例如在一段文字中)。可在 here.

    中找到对所用自定义正则表达式的解释

希望对您有所帮助!