diff --git a/jest.config.js b/jest.config.js index cc7eddadc5..7cea379547 100644 --- a/jest.config.js +++ b/jest.config.js @@ -135,7 +135,14 @@ const jestConfig = { configureProject('upward-js', 'Upward JS', () => ({ testEnvironment: 'node' })), - configureProject('venia-concept', 'Venia Storefront', testVenia), + configureProject('venia-concept', 'Venia Storefront', inPackage => { + const veniaConceptConfig = testVenia(inPackage); + veniaConceptConfig.setupFiles = [ + ...veniaConceptConfig.setupFilesAfterEnv, + inPackage('scripts/fetch-mock.js') + ]; + return veniaConceptConfig; + }), configureProject('venia-ui', 'Venia UI', testVenia), // Test any root CI scripts as well, to ensure stable CI behavior. configureProject('scripts', 'CI Scripts', () => ({ @@ -175,7 +182,10 @@ const jestConfig = { '__fixtures__', '__helpers__', '__snapshots__' - ] + ], + globals: { + STORE_NAME: 'Venia' + } }; if (process.env.npm_lifecycle_event === 'test:ci') { diff --git a/packages/pwa-buildpack/lib/WebpackTools/PWADevServer.js b/packages/pwa-buildpack/lib/WebpackTools/PWADevServer.js index 8fb052d5da..03a42a64b2 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/PWADevServer.js +++ b/packages/pwa-buildpack/lib/WebpackTools/PWADevServer.js @@ -55,7 +55,6 @@ const PWADevServer = { contentBase: false, // UpwardDevServerPlugin serves static files compress: true, hot: true, - writeToDisk: true, watchOptions: { // polling is CPU intensive - provide the option to turn it on if needed poll: !!parseInt(devServer.watchOptionsUsePolling) || false @@ -237,7 +236,7 @@ const PWADevServer = { new UpwardDevServerPlugin( webpackDevServerOptions, process.env, - path.resolve(webpackConfig.output.path, upwardPath) + path.resolve(webpackConfig.context, upwardPath) ) ); diff --git a/packages/venia-concept/package.json b/packages/venia-concept/package.json index 59b69ceb5b..8c0216a26a 100644 --- a/packages/venia-concept/package.json +++ b/packages/venia-concept/package.json @@ -87,9 +87,11 @@ "graphql-cli": "~3.0.11", "graphql-cli-validate-magento-pwa-queries": "~1.0.0", "graphql-tag": "~2.10.1", + "html-webpack-plugin": "~3.2.0", "informed": "~2.1.13", "lodash.over": "~4.7.0", "memoize-one": "~5.0.0", + "node-fetch": "~2.6.0", "prettier": "~1.16.4", "prop-types": "~15.7.2", "react": "~16.9.0", diff --git a/packages/venia-concept/scripts/fetch-mock.js b/packages/venia-concept/scripts/fetch-mock.js new file mode 100644 index 0000000000..c158ca4267 --- /dev/null +++ b/packages/venia-concept/scripts/fetch-mock.js @@ -0,0 +1 @@ +global.fetch = require('jest-fetch-mock'); diff --git a/packages/venia-concept/src/MediaBackendURLFetcherPlugin.js b/packages/venia-concept/src/MediaBackendURLFetcherPlugin.js new file mode 100644 index 0000000000..877b71abe3 --- /dev/null +++ b/packages/venia-concept/src/MediaBackendURLFetcherPlugin.js @@ -0,0 +1,40 @@ +const fetch = require('node-fetch'); + +const getMediaURL = () => + fetch(new URL('graphql', process.env.MAGENTO_BACKEND_URL).toString(), { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + query: 'query { storeConfig { secure_base_media_url } }' + }) + }) + .then(result => result.json()) + .then(json => json.data.storeConfig.secure_base_media_url) + .catch(err => { + console.error(err); + return ''; + }); + +/** + * Webpack plugin that makes GraphQL call to get the Media + * Backend URL which is needed by WebpackHTMLPlugin to be + * replace in template.html. This is done by fetching the URL + * from GraphQL and placing it in global.MAGENTO_MEDIA_BACKEND_URL + * for the WebpackHTMLPlugin to pickup. + */ +class MediaBackendURLFetcherPlugin { + apply(compiler) { + compiler.hooks.emit.tapPromise( + 'MediaBackendURLFetcherPlugin', + () => + new Promise(resolve => { + getMediaURL().then(url => { + global.MAGENTO_MEDIA_BACKEND_URL = url; + resolve(); + }); + }) + ); + } +} + +module.exports = MediaBackendURLFetcherPlugin; diff --git a/packages/venia-concept/src/__tests__/MediaBackendURLFetcherPlugin.spec.js b/packages/venia-concept/src/__tests__/MediaBackendURLFetcherPlugin.spec.js new file mode 100644 index 0000000000..98b4004cc0 --- /dev/null +++ b/packages/venia-concept/src/__tests__/MediaBackendURLFetcherPlugin.spec.js @@ -0,0 +1,53 @@ +const MediaBackendURLFetcherPlugin = require('../MediaBackendURLFetcherPlugin'); + +const tapPromise = jest.fn(); + +const compiler = { + hooks: { + emit: { + tapPromise + } + } +}; + +beforeEach(() => { + process.env.MAGENTO_BACKEND_URL = + 'https://venia-cicd-lrov2hi-mfwmkrjfqvbjk.us-4.magentosite.cloud/'; + fetch.resetMocks(); +}); + +test('The plugin prototype should have apply function in its Prototype', () => { + expect( + MediaBackendURLFetcherPlugin.prototype.hasOwnProperty('apply') + ).toBeTruthy(); +}); + +test('tapPromise function should be called on compiler hooks', () => { + const mediaBackendURLFetcher = new MediaBackendURLFetcherPlugin(); + mediaBackendURLFetcher.apply(compiler); + expect(tapPromise.mock.calls[0][0]).toBe('MediaBackendURLFetcherPlugin'); + expect(tapPromise.mock.calls[0][1] instanceof Function).toBeTruthy(); +}); + +test('second argument to tapPromise should return a Promise which when resolved should set global.MAGENTO_MEDIA_BACKEND_URL to a URL', () => { + const expectedMediaURL = + 'https://venia-cicd-lrov2hi-mfwmkrjfqvbjk.us-4.magentosite.cloud/media/'; + fetch.mockResponseOnce( + JSON.stringify({ + data: { + storeConfig: { + secure_base_media_url: expectedMediaURL + } + } + }) + ); + + expect(global.MAGENTO_MEDIA_BACKEND_URL).toBe(undefined); + + const mediaBackendURLFetcher = new MediaBackendURLFetcherPlugin(); + mediaBackendURLFetcher.apply(compiler); + + return tapPromise.mock.calls[0][1].call().then(() => { + expect(global.MAGENTO_MEDIA_BACKEND_URL).toBe(expectedMediaURL); + }); +}); diff --git a/packages/venia-concept/template.html b/packages/venia-concept/template.html new file mode 100644 index 0000000000..d218d090c8 --- /dev/null +++ b/packages/venia-concept/template.html @@ -0,0 +1,61 @@ + + + + + + + + + + Home Page - <%= STORE_NAME %> + + + + + + + + + + +
+ + diff --git a/packages/venia-concept/webpack.config.js b/packages/venia-concept/webpack.config.js index 16aea9bfcd..4a35e858c9 100644 --- a/packages/venia-concept/webpack.config.js +++ b/packages/venia-concept/webpack.config.js @@ -1,4 +1,7 @@ const { configureWebpack } = require('@magento/pwa-buildpack'); +const { DefinePlugin } = require('webpack'); +const HTMLWebpackPlugin = require('html-webpack-plugin'); +const MediaBackendUrlFetcherPlugin = require('./src/MediaBackendURLFetcherPlugin'); module.exports = async env => { const config = await configureWebpack({ @@ -36,13 +39,33 @@ module.exports = async env => { env }); - // configureWebpack() returns a regular Webpack configuration object. - // You can customize the build by mutating the object here, as in - // this example: + /** + * configureWebpack() returns a regular Webpack configuration object. + * You can customize the build by mutating the object here, as in + * this example. Since it's a regular Webpack configuration, the object + * supports the `module.noParse` option in Webpack, documented here: + * https://webpack.js.org/configuration/module/#modulenoparse + */ config.module.noParse = [/braintree\-web\-drop\-in/]; - // Since it's a regular Webpack configuration, the object supports the - // `module.noParse` option in Webpack, documented here: - // https://webpack.js.org/configuration/module/#modulenoparse + config.plugins = [ + ...config.plugins, + new DefinePlugin({ + /** + * Make sure to add the same constants to + * the globals object in jest.config.js. + */ + STORE_NAME: JSON.stringify('Venia') + }), + new MediaBackendUrlFetcherPlugin(), + new HTMLWebpackPlugin({ + filename: 'index.html', + template: './template.html', + minify: { + collapseWhitespace: true, + removeComments: true + } + }) + ]; return config; }; diff --git a/packages/venia-ui/lib/RootComponents/Product/product.js b/packages/venia-ui/lib/RootComponents/Product/product.js index 089c8ca83f..a809e963e5 100644 --- a/packages/venia-ui/lib/RootComponents/Product/product.js +++ b/packages/venia-ui/lib/RootComponents/Product/product.js @@ -61,7 +61,7 @@ class Product extends Component { return ( - {`${product.name} - Venia`} + {`${product.name} - ${STORE_NAME}`} { return instance; }; +beforeAll(() => { + global.STORE_NAME = 'Venia'; +}); + afterAll(() => window.location.reload.mockRestore()); test('renders a full page with onlineIndicator and routes', () => { diff --git a/packages/venia-ui/lib/components/App/app.js b/packages/venia-ui/lib/components/App/app.js index 9974fd4113..22bb1d7777 100644 --- a/packages/venia-ui/lib/components/App/app.js +++ b/packages/venia-ui/lib/components/App/app.js @@ -123,7 +123,7 @@ const App = props => { return ( - {'Home Page - Venia'} + {`Home Page - ${STORE_NAME}`}
1.10.0": version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -8465,7 +8482,7 @@ hash.js@^1.0.0, hash.js@^1.0.3: inherits "^2.0.3" minimalistic-assert "^1.0.1" -he@^1.2.0: +he@1.2.x, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -8565,6 +8582,19 @@ 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-minifier@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56" @@ -8590,6 +8620,19 @@ html-webpack-plugin@^4.0.0-beta.2: tapable "^1.1.3" util.promisify "1.0.0" +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.3.0, htmlparser2@^3.9.1: version "3.10.1" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" @@ -10404,6 +10447,16 @@ loader-utils@1.2.3, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2. emojis-list "^2.0.0" json5 "^1.0.1" +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" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -10575,7 +10628,7 @@ lodash@4.17.5: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" integrity sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw== -lodash@^4.0.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.16.0, lodash@^4.16.4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@~4.17.11: +lodash@^4.0.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.16.0, lodash@^4.16.4, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@~4.17.11: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== @@ -11284,7 +11337,7 @@ node-fetch@^1.0.1, node-fetch@^1.7.3: encoding "^0.1.11" is-stream "^1.0.1" -node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0: +node-fetch@^2.1.2, node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@~2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== @@ -12060,7 +12113,7 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -param-case@^2.1.0, param-case@^2.1.1: +param-case@2.1.x, param-case@^2.1.0, param-case@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc= @@ -12621,7 +12674,7 @@ pretty-bytes@^5.1.0: resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.3.0.tgz#f2849e27db79fb4d6cfe24764fc4134f165989f2" integrity sha512-hjGrh+P926p4R4WbaB6OckyRtO0F0/lQBiT+0gnxjV+5kjPBrfVBFCsCLbMqVQeydvIoouYTCmmEURiH3R1Bdg== -pretty-error@^2.1.1: +pretty-error@^2.0.2, pretty-error@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= @@ -13706,7 +13759,7 @@ regjsparser@^0.6.0: dependencies: jsesc "~0.5.0" -relateurl@^0.2.7: +relateurl@0.2.x, relateurl@^0.2.7: version "0.2.7" resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= @@ -15437,6 +15490,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +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" @@ -15549,6 +15607,14 @@ ua-parser-js@^0.7.18: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.20.tgz#7527178b82f6a62a0f243d1f94fd30e3e3c21098" integrity sha512-8OaIKfzL5cpx8eCMAhhvTlft8GYF8b2eQr6JkCyVdrgjcytyOmPCXrqXFcUnhonRpLlh5yxEZVohm6mzaowUOw== +uglify-js@3.4.x: + version "3.4.10" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f" + integrity sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw== + dependencies: + commander "~2.19.0" + source-map "~0.6.1" + uglify-js@^3.1.4, uglify-js@^3.5.1: version "3.6.0" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.0.tgz#704681345c53a8b2079fb6cec294b05ead242ff5"