总结
当我们开始使用Webpack处理SASS文件时,我们发现在某些情况下构建时间变得非常慢。通过使用SpeedMeasurePlugin来测量构建不同部分的性能,似乎sass-loader是罪魁祸首......它可以轻松地花费10秒来构建(在我们进行一些修复之前,它曾经需要20秒),这比我们想要的要长。
我很好奇是否有其他优化构建Sass资源的策略,我没有涉及到。我已经尝试了相当多的方法(其中一些多次尝试),但仍然无法将构建时间降低到足够低的水平。目前,对于大型重建(例如为多个文件中使用的组件进行更改),构建可能需要10-12秒,如果可能的话,我希望将其缩短到更接近5秒。
已尝试的解决方案
我们尝试了许多不同的解决方案,一些有效,另一些没有太大帮助。
- HardSourcePlugin - 这个插件效果不错。根据构建时的缓存情况,它能够将构建时间减少几秒钟。
- 移除重复的导入(比如在多个地方都导入相同的“variables.sass”文件)- 这也可以将构建时间缩短几秒钟。
- 将我们的SASS和 SCSS混合改为只使用SCSS - 我不确定为什么这样做会有帮助,但它似乎确实可以减少一点构建时间。也许因为所有文件都是相同的文件类型,所以编译更容易?(可能还有其他原因造成了这种结果,但它似乎总是有所帮助)。
- 使用fast-sass-loader 替换sass-loader - 很多人推荐这样做,但当我使用它时,它似乎并没有改变构建时间。我不知道为什么…也许有配置问题。
- 利用cache-loader - 这似乎也没有任何改进。
- 对Sass禁用源映射 - 这似乎有很大的影响,将构建时间减半(从应用更改之后开始计算)。
- 尝试使用
includePaths
导入从node_modules加载的SASS - 在一个我找到的git issue上建议这样做,因为sass-loader在处理他们称为“custom importers”的东西时存在问题。我的理解是,通过使用includePaths,SASS能够依赖提供的绝对路径,而不是使用一种低效的算法来解决到诸如node_modules等地方的路径。
根据简要统计,我们似乎有约16k行SASS代码分布在150个SASS文件中。有些文件有相当多的代码,而其他文件则较少,这些文件的简单平均每个文件大约有107行代码。
下面是正在使用的配置。应用程序是一个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": {}
}]
}
"test": {},
都是空的? - SuperDJtest
明显是由于JSON.stringify
- 尝试在对 Webpacker 环境配置进行字符串化之前定义RegExp.prototype.toJSON = RegExp.prototype.toString;
(用于调试)。 - Tim Krinssass-loader
本身。对于未来的读者,请检查您是否正在使用sass
包或node-sass
包。经过我的测试,我发现对于复杂项目,node-sass
非常慢,而sass
则快得多。 - Tim