From e1b1760c6c6061d44e0af40645ec33322bb38e8b Mon Sep 17 00:00:00 2001 From: Mike Greiling <mike@pixelcog.com> Date: Tue, 17 Dec 2019 08:36:27 +0000 Subject: [PATCH] Add basic webpack DLLPlugin support Introduces the ability to enable webpack DLL support by passing the flag WEBPACK_VENDOR_DLL=true while running webpack or webpack-dev-server. --- .gitlab/ci/frontend.gitlab-ci.yml | 2 + config/helpers/vendor_dll_hash.js | 23 ++++++++++ config/webpack.config.js | 43 ++++++++++++++++++- config/webpack.vendor.config.js | 71 +++++++++++++++++++++++++++++++ lib/gitlab/webpack/manifest.rb | 3 +- lib/tasks/gitlab/assets.rake | 8 ++++ package.json | 1 + 7 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 config/helpers/vendor_dll_hash.js create mode 100644 config/webpack.vendor.config.js diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml index 0c83f87eac665..5bd19309528fa 100644 --- a/.gitlab/ci/frontend.gitlab-ci.yml +++ b/.gitlab/ci/frontend.gitlab-ci.yml @@ -244,7 +244,9 @@ webpack-dev-server: dependencies: ["setup-test-env", "compile-assets pull-cache"] variables: WEBPACK_MEMORY_TEST: "true" + WEBPACK_VENDOR_DLL: "true" script: + - yarn webpack-vendor - node --expose-gc node_modules/.bin/webpack-dev-server --config config/webpack.config.js artifacts: name: webpack-dev-server diff --git a/config/helpers/vendor_dll_hash.js b/config/helpers/vendor_dll_hash.js new file mode 100644 index 0000000000000..cfd7be66ad33a --- /dev/null +++ b/config/helpers/vendor_dll_hash.js @@ -0,0 +1,23 @@ +const fs = require('fs'); +const path = require('path'); +const crypto = require('crypto'); + +const CACHE_PATHS = [ + './config/webpack.config.js', + './config/webpack.vendor.config.js', + './package.json', + './yarn.lock', +]; + +const resolvePath = file => path.resolve(__dirname, '../..', file); +const readFile = file => fs.readFileSync(file); +const fileHash = buffer => + crypto + .createHash('md5') + .update(buffer) + .digest('hex'); + +module.exports = () => { + const fileBuffers = CACHE_PATHS.map(resolvePath).map(readFile); + return fileHash(Buffer.concat(fileBuffers)).substr(0, 12); +}; diff --git a/config/webpack.config.js b/config/webpack.config.js index c0be2f66ca798..d85fa84c32f63 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -1,6 +1,6 @@ +const fs = require('fs'); const path = require('path'); const glob = require('glob'); -const fs = require('fs'); const webpack = require('webpack'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; @@ -8,8 +8,10 @@ const CompressionPlugin = require('compression-webpack-plugin'); const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const CopyWebpackPlugin = require('copy-webpack-plugin'); +const vendorDllHash = require('./helpers/vendor_dll_hash'); const ROOT_PATH = path.resolve(__dirname, '..'); +const VENDOR_DLL = process.env.WEBPACK_VENDOR_DLL && process.env.WEBPACK_VENDOR_DLL !== 'false'; const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache'); const IS_PRODUCTION = process.env.NODE_ENV === 'production'; const IS_DEV_SERVER = process.env.WEBPACK_DEV_SERVER === 'true'; @@ -113,6 +115,25 @@ if (IS_EE) { }); } +// if there is a compiled DLL with a matching hash string, use it +let dll; + +if (VENDOR_DLL && !IS_PRODUCTION) { + const dllHash = vendorDllHash(); + const dllCachePath = path.join(ROOT_PATH, `tmp/cache/webpack-dlls/${dllHash}`); + if (fs.existsSync(dllCachePath)) { + console.log(`Using vendor DLL found at: ${dllCachePath}`); + dll = { + manifestPath: path.join(dllCachePath, 'vendor.dll.manifest.json'), + cacheFrom: dllCachePath, + cacheTo: path.join(ROOT_PATH, `public/assets/webpack/dll.${dllHash}/`), + publicPath: `dll.${dllHash}/vendor.dll.bundle.js`, + }; + } else { + console.log(`Warning: No vendor DLL found at: ${dllCachePath}. DllPlugin disabled.`); + } +} + module.exports = { mode: IS_PRODUCTION ? 'production' : 'development', @@ -267,6 +288,11 @@ module.exports = { modules: false, assets: true, }); + + // tell our rails helper where to find the DLL files + if (dll) { + stats.dllAssets = dll.publicPath; + } return JSON.stringify(stats, null, 2); }, }), @@ -286,6 +312,21 @@ module.exports = { jQuery: 'jquery', }), + // reference our compiled DLL modules + dll && + new webpack.DllReferencePlugin({ + context: ROOT_PATH, + manifest: dll.manifestPath, + }), + + dll && + new CopyWebpackPlugin([ + { + from: dll.cacheFrom, + to: dll.cacheTo, + }, + ]), + !IS_EE && new webpack.NormalModuleReplacementPlugin(/^ee_component\/(.*)\.vue/, resource => { resource.request = path.join( diff --git a/config/webpack.vendor.config.js b/config/webpack.vendor.config.js new file mode 100644 index 0000000000000..bddbf067d7c63 --- /dev/null +++ b/config/webpack.vendor.config.js @@ -0,0 +1,71 @@ +const path = require('path'); +const webpack = require('webpack'); +const vendorDllHash = require('./helpers/vendor_dll_hash'); + +const ROOT_PATH = path.resolve(__dirname, '..'); + +const dllHash = vendorDllHash(); +const dllCachePath = path.join(ROOT_PATH, `tmp/cache/webpack-dlls/${dllHash}`); +const dllPublicPath = `/assets/webpack/dll.${dllHash}/`; + +module.exports = { + mode: 'development', + resolve: { + extensions: ['.js'], + }, + + context: ROOT_PATH, + + entry: { + vendor: [ + 'jquery', + 'pdfjs-dist/build/pdf', + 'pdfjs-dist/build/pdf.worker.min', + 'sql.js', + 'core-js', + 'echarts', + 'lodash', + 'underscore', + 'vuex', + 'pikaday', + 'vue/dist/vue.esm.js', + 'at.js', + 'jed', + 'mermaid', + 'katex', + 'three', + 'select2', + 'moment', + 'aws-sdk', + 'sanitize-html', + 'bootstrap/dist/js/bootstrap.js', + 'sortablejs/modular/sortable.esm.js', + 'popper.js', + 'apollo-client', + 'source-map', + 'mousetrap', + ], + }, + + output: { + path: dllCachePath, + publicPath: dllPublicPath, + filename: '[name].dll.bundle.js', + chunkFilename: '[name].dll.chunk.js', + library: '[name]_[hash]', + }, + + plugins: [ + new webpack.DllPlugin({ + path: path.join(dllCachePath, '[name].dll.manifest.json'), + name: '[name]_[hash]', + }), + ], + + node: { + fs: 'empty', // sqljs requires fs + setImmediate: false, + }, + + devtool: 'cheap-module-source-map', +}; diff --git a/lib/gitlab/webpack/manifest.rb b/lib/gitlab/webpack/manifest.rb index 1d2aff5e5b4f1..d2c01bbd55e24 100644 --- a/lib/gitlab/webpack/manifest.rb +++ b/lib/gitlab/webpack/manifest.rb @@ -12,11 +12,12 @@ class << self def entrypoint_paths(source) raise ::Webpack::Rails::Manifest::WebpackError, manifest["errors"] unless manifest_bundled? + dll_assets = manifest.fetch("dllAssets", []) entrypoint = manifest["entrypoints"][source] if entrypoint && entrypoint["assets"] # Can be either a string or an array of strings. # Do not include source maps as they are not javascript - [entrypoint["assets"]].flatten.reject { |p| p =~ /.*\.map$/ }.map do |p| + [dll_assets, entrypoint["assets"]].flatten.reject { |p| p =~ /.*\.map$/ }.map do |p| "/#{::Rails.configuration.webpack.public_path}/#{p}" end else diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index 7a42e4e92a051..3aa1dc403d6dd 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -8,6 +8,7 @@ namespace :gitlab do yarn:check gettext:po_to_json rake:assets:precompile + gitlab:assets:vendor webpack:compile gitlab:assets:fix_urls ].each(&Gitlab::TaskHelpers.method(:invoke_and_time_task)) @@ -49,5 +50,12 @@ namespace :gitlab do end end end + + desc 'GitLab | Assets | Compile vendor assets' + task :vendor do + unless system('yarn webpack-vendor') + abort 'Error: Unable to compile webpack DLL.'.color(:red) + end + end end end diff --git a/package.json b/package.json index e60ae6d5a80ec..aa11e35d3e6b2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js", "test": "node scripts/frontend/test", "webpack": "NODE_OPTIONS=\"--max-old-space-size=3584\" webpack --config config/webpack.config.js", + "webpack-vendor": "NODE_OPTIONS=\"--max-old-space-size=3584\" webpack --config config/webpack.vendor.config.js", "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js" }, "dependencies": { -- GitLab