diff --git a/package.json b/package.json index b1a71983a3ab..e6702902c0e0 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "fetch:supporters": "node src/utilities/fetch-supporters.js", "fetch:starter-kits": "node src/utilities/fetch-starter-kits.js", "prebuild": "npm run clean", - "build": "run-s fetch content && cross-env NODE_ENV=production webpack --config webpack.prod.js", + "build": "run-s fetch content && cross-env NODE_ENV=production webpack --config webpack.ssg.js && cross-env NODE_ENV=production webpack --config webpack.prod.js", "postbuild": "npm run sitemap", "test": "npm run lint", "lint": "run-s lint:*", @@ -109,6 +109,7 @@ "modularscale-sass": "^3.0.3", "node-sass": "^4.5.3", "npm-run-all": "^4.1.1", + "offline-plugin": "^5.0.7", "optimize-css-assets-webpack-plugin": "^5.0.1", "postcss-loader": "^2.1.3", "redirect-webpack-plugin": "^0.1.1", diff --git a/src/components/Site/Site.jsx b/src/components/Site/Site.jsx index 419b98ebec4d..4cbe5033f3fc 100644 --- a/src/components/Site/Site.jsx +++ b/src/components/Site/Site.jsx @@ -31,6 +31,11 @@ import './Site.scss'; // Load Content Tree import Content from '../../_content.json'; +// call offline plugin so it can build +if (isClient) { + require('offline-plugin/runtime').install(); +} + class Site extends React.Component { state = { mobileSidebarOpen: false @@ -77,6 +82,7 @@ class Site extends React.Component { + } /> {pages.map(page => ( item.endsWith(endsWith)) + : filesInDist; +}; \ No newline at end of file diff --git a/webpack.common.js b/webpack.common.js index ea7e608010f2..956e6ef4317d 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -148,6 +148,7 @@ module.exports = (env = {}) => ({ output: { path: path.resolve(__dirname, './dist'), publicPath: '/', - filename: '[name].bundle.js' + filename: '[name].bundle.js', + chunkFilename: '[name].[chunkhash].chunk.js' } }); diff --git a/webpack.prod.js b/webpack.prod.js index f0c82f6de796..6fb7a169122d 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -1,27 +1,22 @@ // Import External Dependencies const merge = require('webpack-merge'); -const SSGPlugin = require('static-site-generator-webpack-plugin'); -const RedirectWebpackPlugin = require('redirect-webpack-plugin'); -const CopyWebpackPlugin = require('copy-webpack-plugin'); -const flattenContentTree = require('./src/utilities/flatten-content-tree'); -const contentTree = require('./src/_content.json'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const TerserJSPlugin = require('terser-webpack-plugin'); +const OfflinePlugin = require('offline-plugin'); // Load Common Configuration const common = require('./webpack.common.js'); -// content tree to path array -const paths = [ - ...flattenContentTree(contentTree), - '/vote', - '/organization', - '/starter-kits' -]; +// find css files for sw +const cssFiles = require('./src/utilities/find-files-in-dist')('.css'); +// find favicons +const favicons = require('./src/utilities/find-files-in-dist')('.ico'); -// Prod only config -const prod = { +// fall back all urls to app shell + +module.exports = env => merge(common(env), { mode: 'production', + target: 'web', optimization: { minimizer: [ new TerserJSPlugin({}), @@ -29,83 +24,16 @@ const prod = { ] }, plugins: [ - new CopyWebpackPlugin([ - { - from: './assets/PWA', - to: './' - }, - { - from: './assets/icon-square-small-slack.png', - to: './assets/' - }, - { - from: './assets/icon-square-big.svg', - to: './assets/' - }, - 'CNAME' - ]) + new OfflinePlugin({ + autoUpdate: true, + publicPath: '/', + appShell: '/app-shell/', + // make sure to cache homepage and app shell as app shell for the rest of the pages. + externals: ['/app-shell/', '/', '/manifest.json', ...cssFiles, ...favicons], + excludes: [], + AppCache: { + publicPath: '/' + } + }) ] -}; - -// Export both SSG and SPA configurations -module.exports = env => [ - merge(common(env), prod, { - target: 'node', - entry: { - index: './server.jsx' - }, - plugins: [ - new SSGPlugin({ - globals: { - window: {} - }, - paths, - locals: { - content: contentTree - } - }), - new RedirectWebpackPlugin({ - redirects: { - 'support': '/contribute/', - 'writers-guide': '/contribute/writers-guide/', - 'get-started': '/guides/getting-started/', - 'get-started/install-webpack': '/guides/installation/', - 'get-started/why-webpack': '/guides/why-webpack/', - 'pluginsapi': '/api/plugins/', - 'pluginsapi/compiler': '/api/compiler-hooks/', - 'pluginsapi/template': '/api/template/', - 'api/passing-a-config': '/configuration/configuration-types/', - 'api/plugins/compiler': '/api/compiler-hooks/', - 'api/plugins/compilation': '/api/compilation/', - 'api/plugins/module-factories': '/api/module-methods/', - 'api/plugins/parser': '/api/parser/', - 'api/plugins/tapable': '/api/tapable/', - 'api/plugins/template': '/api/template/', - 'api/plugins/resolver': '/api/resolver/', - 'development': '/contribute/', - 'development/plugin-patterns': '/contribute/plugin-patterns/', - 'development/release-process': '/contribute/release-process/', - 'development/how-to-write-a-loader': '/contribute/writing-a-loader/', - 'development/how-to-write-a-plugin': '/contribute/writing-a-plugin/', - 'guides/code-splitting-import': '/guides/code-splitting/', - 'guides/code-splitting-require': '/guides/code-splitting/', - 'guides/code-splitting-async': '/guides/code-splitting/', - 'guides/code-splitting-css': '/guides/code-splitting/', - 'guides/code-splitting-libraries': '/guides/code-splitting/', - 'guides/why-webpack': '/comparison/', - 'guides/production-build': '/guides/production/', - 'migrating': '/migrate/3/', - 'plugins/no-emit-on-errors-plugin': '/configuration/optimization/#optimization-noemitonerrors', - 'concepts/mode': '/configuration/mode' - } - }) - ], - output: { - filename: 'server.[name].js', - libraryTarget: 'umd' - } - }), - merge(common(env), prod, { - target: 'web' - }) -]; +}); diff --git a/webpack.ssg.js b/webpack.ssg.js new file mode 100644 index 000000000000..373b62ecf5bd --- /dev/null +++ b/webpack.ssg.js @@ -0,0 +1,95 @@ +// Import External Dependencies +const merge = require('webpack-merge'); +const SSGPlugin = require('static-site-generator-webpack-plugin'); +const RedirectWebpackPlugin = require('redirect-webpack-plugin'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const flattenContentTree = require('./src/utilities/flatten-content-tree'); +const contentTree = require('./src/_content.json'); + +// Load Common Configuration +const common = require('./webpack.common.js'); + +// content tree to path array +const paths = [ + ...flattenContentTree(contentTree), + '/vote', + '/organization', + '/starter-kits', + '/app-shell' +]; + +module.exports = env => merge(common(env), { + mode: 'production', + target: 'node', + entry: { + index: './server.jsx' + }, + output: { + filename: 'server.[name].js', + libraryTarget: 'umd' + }, + optimization: { + splitChunks: false + }, + plugins: [ + new SSGPlugin({ + globals: { + window: {} + }, + paths, + locals: { + content: contentTree + } + }), + new RedirectWebpackPlugin({ + redirects: { + 'support': '/contribute/', + 'writers-guide': '/contribute/writers-guide/', + 'get-started': '/guides/getting-started/', + 'get-started/install-webpack': '/guides/installation/', + 'get-started/why-webpack': '/guides/why-webpack/', + 'pluginsapi': '/api/plugins/', + 'pluginsapi/compiler': '/api/compiler-hooks/', + 'pluginsapi/template': '/api/template/', + 'api/passing-a-config': '/configuration/configuration-types/', + 'api/plugins/compiler': '/api/compiler-hooks/', + 'api/plugins/compilation': '/api/compilation/', + 'api/plugins/module-factories': '/api/module-methods/', + 'api/plugins/parser': '/api/parser/', + 'api/plugins/tapable': '/api/tapable/', + 'api/plugins/template': '/api/template/', + 'api/plugins/resolver': '/api/resolver/', + 'development': '/contribute/', + 'development/plugin-patterns': '/contribute/plugin-patterns/', + 'development/release-process': '/contribute/release-process/', + 'development/how-to-write-a-loader': '/contribute/writing-a-loader/', + 'development/how-to-write-a-plugin': '/contribute/writing-a-plugin/', + 'guides/code-splitting-import': '/guides/code-splitting/', + 'guides/code-splitting-require': '/guides/code-splitting/', + 'guides/code-splitting-async': '/guides/code-splitting/', + 'guides/code-splitting-css': '/guides/code-splitting/', + 'guides/code-splitting-libraries': '/guides/code-splitting/', + 'guides/why-webpack': '/comparison/', + 'guides/production-build': '/guides/production/', + 'migrating': '/migrate/3/', + 'plugins/no-emit-on-errors-plugin': '/configuration/optimization/#optimization-noemitonerrors', + 'concepts/mode': '/configuration/mode' + } + }), + new CopyWebpackPlugin([ + { + from: './assets/PWA', + to: './' + }, + { + from: './assets/icon-square-small-slack.png', + to: './assets/' + }, + { + from: './assets/icon-square-big.svg', + to: './assets/' + }, + 'CNAME' + ]) + ] + }); diff --git a/yarn.lock b/yarn.lock index 825f292eb416..45fc677618f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3057,16 +3057,16 @@ deep-equal@^1.0.0, deep-equal@^1.0.1: resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= +deep-extend@^0.5.1, deep-extend@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f" + integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w== + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-extend@~0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f" - integrity sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w== - deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -3434,6 +3434,11 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= +ejs@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" + integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== + electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.124, electron-to-chromium@^1.3.30, electron-to-chromium@^1.3.47: version "1.3.125" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.125.tgz#dbde0e95e64ebe322db0eca764d951f885a5aff2" @@ -6374,7 +6379,7 @@ loader-runner@^2.3.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-utils@^0.2.10, loader-utils@^0.2.16: +loader-utils@0.2.x, loader-utils@^0.2.10, 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= @@ -7071,7 +7076,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2, minimatch@~3.0.4: +"minimatch@2 || 3", minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2, minimatch@~3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -7666,6 +7671,17 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +offline-plugin@^5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/offline-plugin/-/offline-plugin-5.0.7.tgz#26936ad1a7699f4d67e0a095a258972a4ccf1788" + integrity sha512-ArMFt4QFjK0wg8B5+R/6tt65u6Dk+Pkx4PAcW5O7mgIF3ywMepaQqFOQgfZD4ybanuGwuJihxUwMRgkzd+YGYw== + dependencies: + deep-extend "^0.5.1" + ejs "^2.3.4" + loader-utils "0.2.x" + minimatch "^3.0.3" + slash "^1.0.0" + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"