webpack - 使用 Webpack 缓慢的 sass-loader 构建时间
问题描述
概括
当我们切换到使用 Webpack 处理 SASS 文件时,我们注意到在某些情况下我们的构建时间变得非常缓慢。在使用SpeedMeasurePlugin测量了构建的不同部分的性能之后,似乎sass-loader是罪魁祸首……它很容易花费 10 秒来构建(在我们进行一些修复之前它过去需要 20 秒),这比我们要长我喜欢。
我很好奇人们是否有其他优化构建 Sass 资产的策略,而我没有提到。在这一点上,我已经完成了相当多的工作(其中一些是多次),但似乎仍然无法使构建时间足够低。就目标而言,目前大型重建(例如对许多文件中使用的组件进行更改)很容易花费 10-12 秒,如果可能的话,我希望将其缩短到接近 5 秒。
尝试过的解决方案
我们尝试了许多不同的解决方案,有些有效,有些没有太大帮助。
- HardSourcePlugin - 这个插件运行得相当好。根据该构建的缓存,它能够将构建时间减少几秒钟。
- 删除重复的导入(例如将相同的“variables.sass”文件导入多个位置)——这也将构建时间减少了几秒钟
- 将 SASS 和 SCSS 的组合更改为仅 SCSS - 我不确定这有什么帮助,但它似乎确实缩短了我们的构建时间。也许是因为一切都是相同的文件类型,所以更容易编译?(可能这里发生了其他事情来混淆结果,但它似乎确实有帮助)。
- 用fast-sass-loader替换 sass- loader - 很多人推荐这个,但是当我让它工作时,它似乎根本没有改变构建时间。我不知道为什么......也许有一个配置问题。
- 利用缓存加载器- 这似乎也没有任何改进。
- 禁用 Sass 的源映射 - 这似乎产生了很大的影响,将构建时间减少了一半(从应用更改时开始)
- 尝试使用
includePaths
从 node_modules 加载的 SASS - 这是在我发现的一个git 问题上提出的,其中 sass-loader 遇到了他们称为“自定义导入器”的问题。我的理解是,通过使用 includePaths,SASS 能够依赖那些提供的绝对路径,而不是使用效率低下的算法来解析节点模块等位置的路径
从一些简短的统计数据来看,我们似乎有大约 16k 行 SASS 代码分布在 150 个 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": {}
}]
}
解决方案
推荐阅读
- c - 如何将 sigaction 与 4 个子进程一起使用?
- docker - Docker Desktop 未启动 - 序列不包含匹配元素
- python - 舰队和船的空间在右边而不是它们之间
- django - 如何将用户对象添加到多字段?
- python - 在 Python 中转换时间戳字符串的问题
- python - 将 CSV 文件导入 MySQL 仅加载 1 行
- websocket - 从 Kafka KTable (GlobalKTable) 写入 websocket
- java - 通过比较两个不同长度的字符串数组来创建新数组
- javascript - 在 DrawerNavigator 中反应本机注销
- python - 在 Python 中为字符串中的每个短语创建一个文本文件