使用Webpack降低Sass-loader的构建时间

mc9*_*c92 6 build sass webpack sass-loader webpacker

摘要

当我们切换到使用Webpack来处理SASS文件时,我们注意到在某些情况下,构建时间变得非常缓慢。在使用SpeedMeasurePlugin测量了构建的不同部分的性能之后,似乎sass-loader是罪魁祸首……它很容易花费10s来构建(在进行一些修复之前通常花费20s),这比我们更长。 d喜欢。

我很好奇,如果人们还有其他策略可以优化构建我没有涵盖的Sass资产。在这一点上,我已经经历了很多(对于其中一些来说是多次),但似乎仍然无法将构建时间降低得足够低。就目标而言,当前的大规模重建(例如更改许多文件中使用的组件)可能很容易花费10-12秒,我希望尽可能将其降低到5s。

尝试过的解决方案

我们尝试了多种不同的解决方案,其中一些可行,而其他解决方案却无济于事。

  • HardSourcePlugin-此工具相当有效。根据该构建的缓存,它能够将构建时间减少几秒钟。
  • 删除重复的导入(例如将相同的“ variables.sass”文件导入多个位置)-这也将构建时间减少了几秒钟
  • 将我们的SASS和SCSS组合更改为仅SCSS-我不确定为什么这样做有帮助,但似乎确实节省了我们的构建时间。也许因为所有文件都是相同的文件类型,所以更容易编译?(很可能在这里发生了其他事情,以至于混淆了结果,但似乎确实能持续地提供帮助)。
  • fast-sass-loader代替sass- loader-很多人都建议这样做,但是当我开始使用它时,它似乎根本没有改变构建时间。我不确定为什么……也许有配置问题。
  • 利用缓存加载器 -这似乎也没有任何改善。
  • Sass的禁用源地图-这似乎产生了很大的影响,将构建时间减少了一半(从应用更改时开始)
  • 尝试includePaths用于从node_modules加载的SASS-在我发现的git问题上建议使用此方法,在该问题上sass-loader遇到了称为“自定义进口商”的问题。我的理解是,通过使用includePaths,SASS能够依靠那些提供的绝对路径,而不是使用效率低下的算法来解析路径到诸如node_modules的位置

从一些简要的统计数据来看,我们似乎在150个SASS文件中散布了约1.6万行SASS代码。有些文件的代码量很大,而其他代码的代码量却很少,这些文件的LOC平均值约为107 LOC /文件。

以下是正在使用的配置。该应用程序是Rails应用程序,因此许多Webpack配置都是通过Webpacker gem处理的。

{
  "mode": "production",
  "output": {
    "filename": "js/[name].js",
    "chunkFilename": "js/[name].js",
    "hotUpdateChunkFilename": "js/[id]-[hash].hot-update.js",
    "path": "myApp/public/packs",
    "publicPath": "/packs/"
  },
  "resolve": {
    "extensions": [".mjs", ".js", ".sass", ".scss", ".css", ".module.sass", ".module.scss", ".module.css", ".png", ".svg", ".gif", ".jpeg", ".jpg"],
    "plugins": [{
      "topLevelLoader": {}
    }],
    "modules": ["myApp/app/assets/javascript", "myApp/app/assets/css", "node_modules"]
  },
  "resolveLoader": {
    "modules": ["node_modules"],
    "plugins": [{}]
  },
  "node": {
    "dgram": "empty",
    "fs": "empty",
    "net": "empty",
    "tls": "empty",
    "child_process": "empty"
  },
  "devtool": "source-map",
  "stats": "normal",
  "bail": true,
  "optimization": {
    "minimizer": [{
      "options": {
        "test": {},
        "extractComments": false,
        "sourceMap": true,
        "cache": true,
        "parallel": true,
        "terserOptions": {
          "output": {
            "ecma": 5,
            "comments": false,
            "ascii_only": true
          },
          "parse": {
            "ecma": 8
          },
          "compress": {
            "ecma": 5,
            "warnings": false,
            "comparisons": false
          },
          "mangle": {
            "safari10": true
          }
        }
      }
    }],
    "splitChunks": {
      "chunks": "all",
      "name": false
    },
    "runtimeChunk": true
  },
  "externals": {
    "moment": "moment"
  },
  "entry": {
    "entry1": "myApp/app/assets/javascript/packs/entry1.js",
    "entry2": "myApp/app/assets/javascript/packs/entry2.js",
    "entry3": "myApp/app/assets/javascript/packs/entry3.js",
    "entry4": "myApp/app/assets/javascript/packs/entry4.js",
    "entry5": "myApp/app/assets/javascript/packs/entry5.js",
    "entry6": "myApp/app/assets/javascript/packs/entry6.js",
    "entry7": "myApp/app/assets/javascript/packs/entry7.js",
    "entry8": "myApp/app/assets/javascript/packs/entry8.js",
    "landing": "myApp/app/assets/javascript/packs/landing.js",
    "entry9": "myApp/app/assets/javascript/packs/entry9.js",
    "entry10": "myApp/app/assets/javascript/packs/entry10.js",
    "entry11": "myApp/app/assets/javascript/packs/entry11.js",
    "entry12": "myApp/app/assets/javascript/packs/entry12.js",
    "entry13": "myApp/app/assets/javascript/packs/entry13.js",
    "entry14": "myApp/app/assets/javascript/packs/entry14.js",
    "entry15": "myApp/app/assets/javascript/packs/entry15.js"
  },
  "module": {
    "strictExportPresence": true,
    "rules": [{
      "parser": {
        "requireEnsure": false
      }
    }, {
      "test": {},
      "use": [{
        "loader": "file-loader",
        "options": {
          "context": "app/assets/javascript"
        }
      }]
    }, {
      "test": {},
      "use": ["myApp/node_modules/mini-css-extract-plugin/dist/loader.js", {
        "loader": "css-loader",
        "options": {
          "sourceMap": true,
          "importLoaders": 2,
          "localIdentName": "[name]__[local]___[hash:base64:5]",
          "modules": false
        }
      }, {
        "loader": "postcss-loader",
        "options": {
          "config": {
            "path": "myApp"
          },
          "sourceMap": true
        }
      }],
      "sideEffects": true,
      "exclude": {}
    }, {
      "test": {},
      "use": ["myApp/node_modules/mini-css-extract-plugin/dist/loader.js", {
        "loader": "css-loader",
        "options": {
          "sourceMap": true,
          "importLoaders": 2,
          "localIdentName": "[name]__[local]___[hash:base64:5]",
          "modules": false
        }
      }, {
        "loader": "postcss-loader",
        "options": {
          "config": {
            "path": "myApp"
          },
          "sourceMap": false,
          "plugins": [null, null]
        }
      }, {
        "loader": "sass-loader",
        "options": {
          "sourceMap": false,
          "sourceComments": true
        }
      }],
      "sideEffects": true,
      "exclude": {}
    }, {
      "test": {},
      "use": ["myApp/node_modules/mini-css-extract-plugin/dist/loader.js", {
        "loader": "css-loader",
        "options": {
          "sourceMap": true,
          "importLoaders": 2,
          "localIdentName": "[name]__[local]___[hash:base64:5]",
          "modules": true
        }
      }, {
        "loader": "postcss-loader",
        "options": {
          "config": {
            "path": "myApp"
          },
          "sourceMap": true
        }
      }],
      "sideEffects": false,
      "include": {}
    }, {
      "test": {},
      "use": ["myApp/node_modules/mini-css-extract-plugin/dist/loader.js", {
        "loader": "css-loader",
        "options": {
          "sourceMap": true,
          "importLoaders": 2,
          "localIdentName": "[name]__[local]___[hash:base64:5]",
          "modules": true
        }
      }, {
        "loader": "postcss-loader",
        "options": {
          "config": {
            "path": "myApp"
          },
          "sourceMap": true
        }
      }, {
        "loader": "sass-loader",
        "options": {
          "sourceMap": true
        }
      }],
      "sideEffects": false,
      "include": {}
    }, {
      "test": {},
      "include": {},
      "exclude": {},
      "use": [{
        "loader": "babel-loader",
        "options": {
          "babelrc": false,
          "presets": [
            ["@babel/preset-env", {
              "modules": false
            }]
          ],
          "cacheDirectory": "tmp/cache/webpacker/babel-loader-node-modules",
          "cacheCompression": true,
          "compact": false,
          "sourceMaps": false
        }
      }]
    }, {
      "test": {},
      "include": ["myApp/app/assets/javascript", "myApp/app/assets/css"],
      "exclude": {},
      "use": [{
        "loader": "babel-loader",
        "options": {
          "cacheDirectory": "tmp/cache/webpacker/babel-loader-node-modules",
          "cacheCompression": true,
          "compact": true
        }
      }]
    }, {
      "test": "myApp/node_modules/jquery/dist/jquery.js",
      "use": [{
        "loader": "expose-loader",
        "options": "jQuery"
      }, {
        "loader": "expose-loader",
        "options": "$"
      }]
    }, {
      "test": "myApp/node_modules/popper.js/dist/umd/popper.js",
      "use": [{
        "loader": "expose-loader",
        "options": "Popper"
      }]
    }, {
      "test": "myApp/node_modules/scroll-depth/jquery.scrolldepth.js",
      "use": [{
        "loader": "expose-loader",
        "options": "scrollDepth"
      }]
    }]
  },
  "plugins": [{
    "environment_variables_plugin": "values don't really matter in this case I think"
  }, {
    "options": {},
    "pathCache": {},
    "fsOperations": 0,
    "primed": false
  }, {
    "options": {
      "filename": "css/[name]-[contenthash:8].css",
      "chunkFilename": "css/[name]-[contenthash:8].chunk.css"
    }
  }, {}, {
    "options": {
      "test": {},
      "cache": true,
      "compressionOptions": {
        "level": 9
      },
      "filename": "[path].gz[query]",
      "threshold": 0,
      "minRatio": 0.8,
      "deleteOriginalAssets": false
    }
  }, {
    "pluginDescriptor": {
      "name": "OptimizeCssAssetsWebpackPlugin"
    },
    "options": {
      "assetProcessors": [{
        "phase": "compilation.optimize-chunk-assets",
        "regExp": {}
      }],
      "assetNameRegExp": {},
      "cssProcessorOptions": {},
      "cssProcessorPluginOptions": {}
    },
    "phaseAssetProcessors": {
      "compilation.optimize-chunk-assets": [{
        "phase": "compilation.optimize-chunk-assets",
        "regExp": {}
      }],
      "compilation.optimize-assets": [],
      "emit": []
    },
    "deleteAssetsMap": {}
  }, {
    "definitions": {
      "$": "jquery",
      "jQuery": "jquery",
      "jquery": "jquery",
      "window.$": "jquery",
      "window.jQuery": "jquery",
      "window.jquery": "jquery",
      "Popper": ["popper.js", "default"]
    }
  }, {
    "definitions": {
      "process.env": {
        "MY_DEFINED_ENV_VARS": "my defined env var values"
      }
    }
  }, {
    "options": {}
  }]
}
Run Code Online (Sandbox Code Playgroud)

pac*_*a94 0

我认为有几个问题:

1:可能是因为您没有指定要测试的文件(因此 webpack 可能会搜索所有内容):

...
"test": {},
  "use": ["myApp/node_modules/mini-css-extract-plugin/dist/loader.js", {
   "loader": "css-loader"
}
...
Run Code Online (Sandbox Code Playgroud)

尝试test: {}test: /\.(sa|sc|c)ss$/test: /\.module.(sa|sc|c)ss$/if using css 模块替换

1b:test也尝试修改其他加载器中的密钥,例如 babel 通常只需要查找 js/ts(x) 文件,因此如果是这种情况请指定。

1c:也尝试使用包含/排除属性

2:该配置中有 4 个 css-loader 实例 - 除非您是服务器端渲染,否则您只需要两个(一个用于 sc/sa/css 模块,一个用于普通 sa/sc/ss)

这是一个示例 css 加载器配置,我希望它对您有所帮助(提示:每个项目加载 css 的方式通常有所不同,因此请务必检查 webpack/css-loader 文档中的模块选项)

{
  test: /\.(sa|sc|c)ss$/,
  use: [
    {
      loader: MiniCssExtractPlugin.loader,
      options: {
        esModule: true,
        modules: {
          namedExport: true,
        },
      },
    },
    {
      loader: 'css-loader',
      options: {
        sourceMap: !isProd,
        importLoaders: 2,
        esModule: true,
        modules: {
          auto: true,
          namedExport: true,
        },
      },
    },
    {
      loader: 'postcss-loader',
      options: {
        sourceMap: !isProd,
      },
    },
    {
      loader: 'sass-loader',
      options: {
        sourceMap: !isProd,
      },
    },
  ]
}
Run Code Online (Sandbox Code Playgroud)

陷阱:webpack 加载器是反向处理的,因此对于此示例配置,处理顺序是 sass -> postcss -> css -> minicss

不幸的是,对于 webpack 的 css 问题很少有直接/直接的答案,需要深入研究文档并找出您的项目需要哪些选项。