From af47232edab6b3a7d9eecb6d0acafc98217bfb1c Mon Sep 17 00:00:00 2001 From: Andrew Seigner Date: Wed, 16 Jan 2019 12:59:30 -0800 Subject: [PATCH] Hash JS bundle to expire cache (#2058) JavaScript assets could be cached across Linkerd releases, showing an out of date ui, or a broken page. Modify the webpack build pipeline to add a hash to the JS bundle filename. Move all logic around webpack-dev-server state from Go into JS, via a templatized index_bundle.js file, generated at build time. Disable caching of index_bundle.js in Go, via a `Cache-Control` header. Fixes #1996 Signed-off-by: Andrew Seigner --- bin/web | 2 +- web/app/index_bundle.js.lodash.tmpl | 20 +++ web/app/package.json | 3 +- web/app/webpack-mv-plugin.js | 17 +++ web/app/webpack.config.js | 27 ++-- web/app/yarn.lock | 201 +++++++++++++++++++++++++--- web/main.go | 3 +- web/srv/handlers.go | 2 - web/srv/server.go | 50 +++---- web/templates/app.tmpl.html | 6 +- 10 files changed, 263 insertions(+), 68 deletions(-) create mode 100644 web/app/index_bundle.js.lodash.tmpl create mode 100644 web/app/webpack-mv-plugin.js diff --git a/bin/web b/bin/web index babe865f7..52cfece8f 100755 --- a/bin/web +++ b/bin/web @@ -55,7 +55,7 @@ function dev { cd $ROOT/web/app && yarn webpack-dev-server --port $DEV_PORT & cd $ROOT/web && \ - ../bin/go-run . --webpack-dev-server=http://localhost:$DEV_PORT $* + ../bin/go-run . $* } function build { diff --git a/web/app/index_bundle.js.lodash.tmpl b/web/app/index_bundle.js.lodash.tmpl new file mode 100644 index 000000000..8d921de08 --- /dev/null +++ b/web/app/index_bundle.js.lodash.tmpl @@ -0,0 +1,20 @@ +// based on https://gist.github.com/necolas/1025811 +(function(doc, script) { + let js, + fjs = doc.getElementsByTagName(script)[0], + add = function(url, id) { + if (doc.getElementById(id)) {return;} + js = doc.createElement(script); + js.src = url; + id && (js.id = id); + fjs.parentNode.insertBefore(js, fjs); + }; +<% for (let chunk in htmlWebpackPlugin.files.chunks) { %> + <% if (compilation.compiler.outputFileSystem.constructor.name === 'MemoryFileSystem') { %> + add('http://localhost:8080/<%= htmlWebpackPlugin.files.chunks[chunk].entry %>'); + <% } else { %> + let root = fjs.src.substring(0, fjs.src.indexOf("dist")); + add(root+'<%= htmlWebpackPlugin.files.chunks[chunk].entry %>'); + <% } %> +<% } %> +}(document, 'script')); diff --git a/web/app/package.json b/web/app/package.json index 69ed45588..bb6d1a192 100644 --- a/web/app/package.json +++ b/web/app/package.json @@ -33,6 +33,7 @@ "babel-preset-env": "1.7.0", "babel-preset-react-app": "3.1.1", "babel-runtime": "^6.26.0", + "clean-webpack-plugin": "1.0.0", "css-loader": "0.28.7", "enzyme": "3.7.0", "enzyme-adapter-react-16": "1.6.0", @@ -46,6 +47,7 @@ "eslint-plugin-react": "7.11.1", "file-loader": "2.0.0", "history": "4.7.2", + "html-webpack-plugin": "3.2.0", "jest": "23.6.0", "jest-dot-reporter": "1.0.7", "jest-enzyme": "7.0.0", @@ -53,7 +55,6 @@ "react-test-renderer": "16.5.2", "sinon": "7.0.0", "sinon-stub-promise": "4.0.0", - "speed-measure-webpack-plugin": "^1.2.5", "style-loader": "0.21.0", "url-loader": "1.0.1", "webpack": "4.20.2", diff --git a/web/app/webpack-mv-plugin.js b/web/app/webpack-mv-plugin.js new file mode 100644 index 000000000..b9db619f8 --- /dev/null +++ b/web/app/webpack-mv-plugin.js @@ -0,0 +1,17 @@ +// WebpackMvPlugin copies /dist/index_bundle.js.out to /dist/index_bundle.js +class WebpackMvPlugin { + apply(compiler) { + compiler.hooks.compilation.tap('webpack-mv-plugin', function(compilation) { + compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tapAsync( + 'webpack-mv-plugin', + function(htmlPluginData, cb) { + var out = htmlPluginData.plugin.childCompilationOutputName; + htmlPluginData.plugin.childCompilationOutputName = out.substring(0, out.indexOf('.out')); + cb(null, htmlPluginData); + } + ); + }); + }; +} + +module.exports = WebpackMvPlugin; diff --git a/web/app/webpack.config.js b/web/app/webpack.config.js index 1d0660b77..df6585755 100644 --- a/web/app/webpack.config.js +++ b/web/app/webpack.config.js @@ -1,22 +1,24 @@ /* global require, module, __dirname */ const path = require('path'); +const CleanWebpackPlugin = require('clean-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); const LodashModuleReplacementPlugin = require('lodash-webpack-plugin'); - -// analyze plugin speeds -const SpeedMeasurePlugin = require("speed-measure-webpack-plugin"); -const smp = new SpeedMeasurePlugin(); +const WebpackMvPlugin = require('./webpack-mv-plugin.js'); // uncomment here and in plugins to analyze webpack bundle size // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; -module.exports = smp.wrap({ +module.exports = { mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', entry: './js/index.js', + devServer: { + writeToDisk: true + }, output: { path: path.resolve(__dirname, 'dist'), publicPath: 'dist/', - filename: 'index_bundle.js' + filename: '[name].[contenthash].js' }, devtool: 'cheap-module-source-map', externals: { @@ -64,10 +66,19 @@ module.exports = smp.wrap({ }, plugins: [ // new BundleAnalyzerPlugin(), // uncomment to analyze bundle size + new CleanWebpackPlugin(['dist']), new LodashModuleReplacementPlugin({ // 'chain': true, 'collections': true, 'paths': true - }) + }), + // compile the bundle with hashed filename into index_bundle.js.out + new HtmlWebpackPlugin({ + inject: false, + filename: "index_bundle.js.out", + template: 'index_bundle.js.lodash.tmpl', + }), + // move /dist/index_bundle.js.out to /dist/index_bundle.js + new WebpackMvPlugin() ] -}); +}; diff --git a/web/app/yarn.lock b/web/app/yarn.lock index 22a25c9ab..a2f265498 100644 --- a/web/app/yarn.lock +++ b/web/app/yarn.lock @@ -1595,6 +1595,11 @@ bfj@^6.1.1: hoopy "^0.1.2" tryer "^1.0.0" +big.js@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" + integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== + big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -1883,6 +1888,14 @@ callsites@^2.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= +camel-case@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M= + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -2049,6 +2062,20 @@ classnames@2.2.6, classnames@^2.2.5: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== +clean-css@4.2.x: + version "4.2.1" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.1.tgz#2d411ef76b8569b6d0c84068dabe85b0aa5e5c17" + integrity sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g== + dependencies: + source-map "~0.6.0" + +clean-webpack-plugin@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-1.0.0.tgz#f184b9c26d12983d639828e0548ae2080e84b6a7" + integrity sha512-+f96f52UIET4tOFBbCqezx7KH+w7lz/p4fA1FEjf0hC6ugxqwZedBtENzekN2FnmoTF/bn1LrlkvebOsDZuXKw== + dependencies: + rimraf "^2.6.1" + cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -2154,6 +2181,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" +commander@2.17.x, commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== + commander@^2.11.0, commander@^2.18.0, commander@^2.19.0: version "2.19.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" @@ -2164,11 +2196,6 @@ commander@~2.13.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== -commander@~2.17.1: - version "2.17.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" - integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -2393,7 +2420,7 @@ css-loader@0.28.7: postcss-value-parser "^3.3.0" source-list-map "^2.0.0" -css-select@~1.2.0: +css-select@^1.1.0, css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= @@ -2812,6 +2839,13 @@ doctrine@^2.0.2, doctrine@^2.1.0: dependencies: esutils "^2.0.2" +dom-converter@~0.2: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + dom-helpers@^3.2.1, dom-helpers@^3.3.1: version "3.4.0" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8" @@ -2849,6 +2883,13 @@ domexception@^1.0.1: dependencies: webidl-conversions "^4.0.2" +domhandler@2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" + integrity sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ= + dependencies: + domelementtype "1" + domhandler@^2.3.0: version "2.4.2" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" @@ -2856,6 +2897,13 @@ domhandler@^2.3.0: dependencies: domelementtype "1" +domutils@1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" + integrity sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU= + dependencies: + domelementtype "1" + domutils@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" @@ -4056,6 +4104,11 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" +he@1.2.x: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + history@4.7.2, history@^4.7.2: version "4.7.2" resolved "https://registry.yarnpkg.com/history/-/history-4.7.2.tgz#22b5c7f31633c5b8021c7f4a8a954ac139ee8d5b" @@ -4133,6 +4186,32 @@ html-entities@^1.2.0: resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= +html-minifier@^3.2.3: + version "3.5.21" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.21.tgz#d0040e054730e354db008463593194015212d20c" + integrity sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA== + dependencies: + camel-case "3.0.x" + clean-css "4.2.x" + commander "2.17.x" + he "1.2.x" + param-case "2.1.x" + relateurl "0.2.x" + uglify-js "3.4.x" + +html-webpack-plugin@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" + integrity sha1-sBq71yOsqqeze2r0SS69oD2d03s= + dependencies: + html-minifier "^3.2.3" + loader-utils "^0.2.16" + lodash "^4.17.3" + pretty-error "^2.0.2" + tapable "^1.0.0" + toposort "^1.0.0" + util.promisify "1.0.0" + htmlparser2@^3.9.1: version "3.10.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" @@ -4145,6 +4224,16 @@ htmlparser2@^3.9.1: inherits "^2.0.1" readable-stream "^3.0.6" +htmlparser2@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" + integrity sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4= + dependencies: + domelementtype "1" + domhandler "2.1" + domutils "1.1" + readable-stream "1.0" + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -5316,7 +5405,7 @@ json3@^3.3.2: resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" integrity sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE= -json5@^0.5.1: +json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= @@ -5500,6 +5589,16 @@ loader-runner@^2.3.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.1.tgz#026f12fe7c3115992896ac02ba022ba92971b979" integrity sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw== +loader-utils@^0.2.16: + version "0.2.17" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + object-assign "^4.0.1" + loader-utils@^1.0.2, loader-utils@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" @@ -5633,7 +5732,7 @@ lodash@4.17.10: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== -lodash@4.17.11, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0: +lodash@4.17.11, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.3.0: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== @@ -5660,6 +5759,11 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3 dependencies: js-tokens "^3.0.0 || ^4.0.0" +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= + lru-cache@^4.0.1, lru-cache@^4.1.1: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -6045,6 +6149,13 @@ nise@^1.4.5: path-to-regexp "^1.7.0" text-encoding "^0.6.4" +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ== + dependencies: + lower-case "^1.1.1" + node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -6497,6 +6608,13 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" +param-case@2.1.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= + dependencies: + no-case "^2.2.0" + parse-asn1@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" @@ -7007,6 +7125,14 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= +pretty-error@^2.0.2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" + integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= + dependencies: + renderkid "^2.0.1" + utila "~0.4" + pretty-format@^23.6.0: version "23.6.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760" @@ -7425,6 +7551,16 @@ read-pkg@^2.0.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@1.0: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readable-stream@^3.0.6: version "3.1.1" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" @@ -7559,11 +7695,27 @@ regjsparser@^0.1.4: dependencies: jsesc "~0.5.0" +relateurl@0.2.x: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= + remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= +renderkid@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.2.tgz#12d310f255360c07ad8fde253f6c9e9de372d2aa" + integrity sha512-FsygIxevi1jSiPY9h7vZmBFUbAOcbYm9UwyiLNdVsLRs/5We9Ob5NMPbGYUTWiLq5L+ezlVdE0A8bbME5CWTpg== + dependencies: + css-select "^1.1.0" + dom-converter "~0.2" + htmlparser2 "~3.3.0" + strip-ansi "^3.0.0" + utila "^0.4.0" + repeat-element@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" @@ -8100,7 +8252,7 @@ source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -8154,13 +8306,6 @@ spdy@^4.0.0: select-hose "^2.0.0" spdy-transport "^3.0.0" -speed-measure-webpack-plugin@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/speed-measure-webpack-plugin/-/speed-measure-webpack-plugin-1.2.5.tgz#8179936eb8c5e891f7481bd5075a9ea9a0f74823" - integrity sha512-S/guYjC4Izn5wY2d0+M4zowED/F77Lxh9yjkTZ+XAr244pr9c1MYNcXcRe9lx2hmAj0GPbOrBXgOF2YIp/CZ8A== - dependencies: - chalk "^2.0.1" - split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -8301,6 +8446,11 @@ string_decoder@^1.0.0, string_decoder@^1.1.1: dependencies: safe-buffer "~5.1.0" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -8534,6 +8684,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +toposort@^1.0.0: + version "1.0.7" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" + integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= + tough-cookie@>=2.3.3, tough-cookie@^2.3.4: version "2.5.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" @@ -8627,7 +8782,7 @@ uglify-es@^3.3.4: commander "~2.13.0" source-map "~0.6.1" -uglify-js@^3.1.4: +uglify-js@3.4.x, uglify-js@^3.1.4: version "3.4.9" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.9.tgz#af02f180c1207d76432e473ed24a28f4a782bae3" integrity sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q== @@ -8701,6 +8856,11 @@ upath@^1.0.5: resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== +upper-case@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg= + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" @@ -8748,7 +8908,7 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -util.promisify@^1.0.0: +util.promisify@1.0.0, util.promisify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== @@ -8770,6 +8930,11 @@ util@^0.10.3: dependencies: inherits "2.0.3" +utila@^0.4.0, utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" diff --git a/web/main.go b/web/main.go index 5ad4063b5..68e12732d 100644 --- a/web/main.go +++ b/web/main.go @@ -25,7 +25,6 @@ func main() { staticDir := flag.String("static-dir", "app/dist", "directory to search for static files") uuid := flag.String("uuid", "", "unique linkerd install id") reload := flag.Bool("reload", true, "reloading set to true or false") - webpackDevServer := flag.String("webpack-dev-server", "", "use webpack to serve static assets; frontend will use this instead of static-dir") controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace") flags.ConfigureAndParse() @@ -42,7 +41,7 @@ func main() { stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) - server := srv.NewServer(*addr, *grafanaAddr, *templateDir, *staticDir, *uuid, *controllerNamespace, *singleNamespace, *webpackDevServer, *reload, client) + server := srv.NewServer(*addr, *grafanaAddr, *templateDir, *staticDir, *uuid, *controllerNamespace, *singleNamespace, *reload, client) go func() { log.Infof("starting HTTP server on %+v", *addr) diff --git a/web/srv/handlers.go b/web/srv/handlers.go index e5317f952..dcdc3602b 100644 --- a/web/srv/handlers.go +++ b/web/srv/handlers.go @@ -16,11 +16,9 @@ var proxyPathRegexp = regexp.MustCompile("/api/v1/namespaces/.*/proxy/") type ( renderTemplate func(http.ResponseWriter, string, string, interface{}) error - serveFile func(http.ResponseWriter, string, string, interface{}) error handler struct { render renderTemplate - serveFile serveFile apiClient pb.ApiClient uuid string controllerNamespace string diff --git a/web/srv/server.go b/web/srv/server.go index 77697e376..3bfd4cf50 100644 --- a/web/srv/server.go +++ b/web/srv/server.go @@ -1,7 +1,6 @@ package srv import ( - "fmt" "html/template" "net/http" "path" @@ -22,19 +21,13 @@ const ( type ( // Server encapsulates the Linkerd control plane's web dashboard server. Server struct { - templateDir string - staticDir string - reload bool - templateContext templateContext - templates map[string]*template.Template - router *httprouter.Router + templateDir string + reload bool + templates map[string]*template.Template + router *httprouter.Router } - templateContext struct { - WebpackDevServer string - } templatePayload struct { - Context templateContext Contents interface{} } appParams struct { @@ -64,15 +57,12 @@ func NewServer( uuid string, controllerNamespace string, singleNamespace bool, - webpackDevServer string, reload bool, apiClient pb.ApiClient, ) *http.Server { server := &Server{ - templateDir: templateDir, - staticDir: staticDir, - templateContext: templateContext{webpackDevServer}, - reload: reload, + templateDir: templateDir, + reload: reload, } server.router = &httprouter.Router{ @@ -85,7 +75,6 @@ func NewServer( handler := &handler{ apiClient: apiClient, render: server.RenderTemplate, - serveFile: server.serveFile, uuid: uuid, controllerNamespace: controllerNamespace, singleNamespace: singleNamespace, @@ -116,9 +105,8 @@ func NewServer( server.router.GET("/top", handler.handleIndex) server.router.GET("/routes", handler.handleIndex) server.router.GET("/profiles/new", handler.handleProfileDownload) - server.router.ServeFiles( - "/dist/*filepath", // add catch-all parameter to match all files in dir - filesonly.FileSystem(server.staticDir)) + // add catch-all parameter to match all files in dir + server.router.GET("/dist/*filepath", mkStaticHandler(staticDir)) // webapp api routes server.router.GET("/api/version", handler.handleAPIVersion) @@ -160,7 +148,7 @@ func (s *Server) RenderTemplate(w http.ResponseWriter, templateFile, templateNam return template.Execute(w, args) } - return template.ExecuteTemplate(w, templateName, templatePayload{Context: s.templateContext, Contents: args}) + return template.ExecuteTemplate(w, templateName, templatePayload{Contents: args}) } func (s *Server) loadTemplate(templateFile string) (template *template.Template, err error) { @@ -188,17 +176,17 @@ func safelyJoinPath(rootPath, userPath string) string { return filepath.Join(rootPath, path.Clean("/"+userPath)) } -func (s *Server) serveFile(w http.ResponseWriter, fileName string, templateName string, args interface{}) error { - dispositionHeaderVal := fmt.Sprintf("attachment; filename='%s'", fileName) +func mkStaticHandler(staticDir string) httprouter.Handle { + fileServer := http.FileServer(filesonly.FileSystem(staticDir)) - w.Header().Set("Content-Type", "text/yaml") - w.Header().Set("Content-Disposition", dispositionHeaderVal) + return func(w http.ResponseWriter, req *http.Request, p httprouter.Params) { + filepath := p.ByName("filepath") + if filepath == "/index_bundle.js" { + // don't cache the bundle because it references a hashed js file + w.Header().Set("Cache-Control", "no-cache, private, max-age=0") + } - template, err := s.loadTemplate(templateName) - if err != nil { - return err + req.URL.Path = filepath + fileServer.ServeHTTP(w, req) } - - template.Execute(w, args) - return nil } diff --git a/web/templates/app.tmpl.html b/web/templates/app.tmpl.html index 75f84e9b6..27ba15e69 100644 --- a/web/templates/app.tmpl.html +++ b/web/templates/app.tmpl.html @@ -13,10 +13,6 @@ {{ define "script-tags" }} {{ if not .Contents.Error }} - {{ if .Context.WebpackDevServer }} - - {{else}} - - {{end}} + {{end}} {{end}}