diff --git a/packages/pwa-buildpack/envVarDefinitions.json b/packages/pwa-buildpack/envVarDefinitions.json index 430fba6332..864d2d36c9 100644 --- a/packages/pwa-buildpack/envVarDefinitions.json +++ b/packages/pwa-buildpack/envVarDefinitions.json @@ -196,6 +196,23 @@ } ] }, + { + "name": "Custom HTTPS certificates", + "variables": [ + { + "name": "CUSTOM_HTTPS_KEY", + "type": "str", + "desc": "Absolute path to the custom HTTPS certificate key file.", + "default": "" + }, + { + "name": "CUSTOM_HTTPS_CERT", + "type": "str", + "desc": "Absolute path to the custom HTTPS certificate cert file.", + "default": "" + } + ] + }, { "name": "Express compression settings", "variables": [ diff --git a/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/createDotEnvFile.spec.js.snap b/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/createDotEnvFile.spec.js.snap index 36f8344578..fbbc0c6a1a 100644 --- a/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/createDotEnvFile.spec.js.snap +++ b/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/createDotEnvFile.spec.js.snap @@ -167,6 +167,18 @@ BUILDBUS_DEPS_ADDITIONAL= # ################################################################################ +#### Custom HTTPS certificates ################################################# +# +# Absolute path to the custom HTTPS certificate key file. +# - Default when not set: +CUSTOM_HTTPS_KEY= +# +# Absolute path to the custom HTTPS certificate cert file. +# - Default when not set: +CUSTOM_HTTPS_CERT= +# +################################################################################ + #### Express compression settings ############################################## # # Specify if express server compression needs to be enabled. Defaults to false if not provided. diff --git a/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/serve.spec.js.snap b/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/serve.spec.js.snap index 07b75b375b..5451cc1bd0 100644 --- a/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/serve.spec.js.snap +++ b/packages/pwa-buildpack/lib/Utilities/__tests__/__snapshots__/serve.spec.js.snap @@ -115,6 +115,44 @@ UPWARD server running.", } `; +exports[`should throw a warning if key and cert paths are provided but cert doesnt exist 1`] = ` +Object { + "error": Array [], + "info": Array [ + "PORT is set in environment: 1234", + "NODE_ENV=production, will not attempt to use custom host or port", + "Launching UPWARD server +", + ], + "success": Array [ + " +UPWARD server running.", + ], + "warn": Array [ + "Custom key and cert paths provided but files not found, creating HTTP server.", + ], +} +`; + +exports[`should throw a warning if key and cert paths are provided but key doesnt exist 1`] = ` +Object { + "error": Array [], + "info": Array [ + "PORT is set in environment: 1234", + "NODE_ENV=production, will not attempt to use custom host or port", + "Launching UPWARD server +", + ], + "success": Array [ + " +UPWARD server running.", + ], + "warn": Array [ + "Custom key and cert paths provided but files not found, creating HTTP server.", + ], +} +`; + exports[`should throw error if unable to load env 1`] = `[Error: Can not load environment config!]`; exports[`should throw error if unable to load env 2`] = ` @@ -154,6 +192,24 @@ UPWARD server running.", } `; +exports[`should use custom https if key and cert paths provided from the env 1`] = ` +Object { + "error": Array [], + "info": Array [ + "Custom key and cert paths provided, creating HTTPS server.", + "PORT is set in environment: 1234", + "NODE_ENV=production, will not attempt to use custom host or port", + "Launching UPWARD server +", + ], + "success": Array [ + " +UPWARD server running.", + ], + "warn": Array [], +} +`; + exports[`should use env.PORT if provided 1`] = ` Object { "error": Array [], diff --git a/packages/pwa-buildpack/lib/Utilities/__tests__/serve.spec.js b/packages/pwa-buildpack/lib/Utilities/__tests__/serve.spec.js index 8a3c1a8f50..e09fd0d5cb 100644 --- a/packages/pwa-buildpack/lib/Utilities/__tests__/serve.spec.js +++ b/packages/pwa-buildpack/lib/Utilities/__tests__/serve.spec.js @@ -1,3 +1,6 @@ +jest.mock('fs'); +const { existsSync, readFileSync } = require('fs'); + const { createUpwardServer } = require('@magento/upward-js'); const compression = require('compression'); const serve = require('../serve'); @@ -67,6 +70,7 @@ const getImageServiceConfig = jest.fn().mockReturnValue({}); const getCustomOriginConfig = jest.fn().mockReturnValue({ enabled: false }); +const getcustomHttpsConfig = jest.fn().mockReturnValue({}); const getSectionData = sectionName => { if (sectionName === 'stagingServer') { @@ -79,6 +83,8 @@ const getSectionData = sectionName => { return getImageServiceConfig(); } else if (sectionName === 'customOrigin') { return getCustomOriginConfig(); + } else if (sectionName === 'customHttps') { + return getcustomHttpsConfig(); } else { return {}; } @@ -115,6 +121,11 @@ beforeEach(() => { ); }); +afterEach(() => { + existsSync.mockReset(); + readFileSync.mockReset(); +}); + test('should create upward server', async () => { const server = await serve('pwa-buildpack'); @@ -284,3 +295,92 @@ test('should log error if configureHost throws error', async () => { expect(logs).toMatchSnapshot(); }); + +test('should use custom https if key and cert paths provided from the env', async () => { + const key = 'path/to/key'; + const cert = 'path/to/cert'; + getcustomHttpsConfig.mockReturnValueOnce({ + key, + cert + }); + // mock that files exist + existsSync.mockReturnValue(true); + readFileSync + .mockReturnValueOnce('key_for_https') + .mockReturnValueOnce('cert_for_https'); + await serve('pwa-buildpack'); + + // check if files exist + expect(existsSync).toBeCalledTimes(2); + expect(existsSync.mock.calls[0][0]).toBe(key); + expect(existsSync.mock.calls[1][0]).toBe(cert); + + // check if both files are also read + expect(readFileSync).toBeCalledTimes(2); + expect(readFileSync.mock.calls[0]).toEqual([key, 'utf8']); + expect(readFileSync.mock.calls[1]).toEqual([cert, 'utf8']); + + // upward server should now have custom key and cert + expect(createUpwardServer.mock.calls[0][0].https).toEqual({ + key: 'key_for_https', + cert: 'cert_for_https' + }); + // logs info that creating HTTPS server with custom cert and key + expect(logs).toMatchSnapshot(); +}); + +test('should throw a warning if key and cert paths are provided but key doesnt exist', async () => { + const key = 'path/to/key'; + const cert = 'path/to/cert'; + getcustomHttpsConfig.mockReturnValueOnce({ + key, + cert + }); + existsSync.mockReturnValueOnce(false); + + await serve('pwa-buildpack'); + + // will be called only once and fail on the if statement + expect(existsSync).toBeCalledTimes(1); + + // should not try to read files + expect(readFileSync).not.toBeCalled(); + expect(logs.warn.length).toBe(1); + expect(logs).toMatchSnapshot(); + expect(createUpwardServer.mock.calls[0][0].https).toBe(undefined); +}); + +test('should throw a warning if key and cert paths are provided but cert doesnt exist', async () => { + const key = 'path/to/key'; + const cert = 'path/to/cert'; + getcustomHttpsConfig.mockReturnValueOnce({ + key, + cert + }); + existsSync.mockReturnValueOnce(true).mockReturnValueOnce(false); + + await serve('pwa-buildpack'); + + // will be called twice and fail on the if statement + expect(existsSync).toBeCalledTimes(2); + + // should not try to read files + expect(readFileSync).not.toBeCalled(); + expect(logs.warn.length).toBe(1); + expect(logs).toMatchSnapshot(); + expect(createUpwardServer.mock.calls[0][0].https).toBe(undefined); +}); + +test('should not try to read custom key and cert if one of them is missing', async () => { + const key = 'path/to/key'; + getcustomHttpsConfig.mockReturnValueOnce({ + key, + cert: undefined + }); + + await serve('pwa-buildpack'); + + expect(existsSync).not.toBeCalled(); + expect(readFileSync).not.toBeCalled(); + expect(createUpwardServer.mock.calls[0][0].https).toBe(undefined); +}); diff --git a/packages/pwa-buildpack/lib/Utilities/serve.js b/packages/pwa-buildpack/lib/Utilities/serve.js index 95961d3e09..4ce005aa97 100644 --- a/packages/pwa-buildpack/lib/Utilities/serve.js +++ b/packages/pwa-buildpack/lib/Utilities/serve.js @@ -1,5 +1,6 @@ const loadEnvironment = require('../Utilities/loadEnvironment'); const path = require('path'); +const { existsSync, readFileSync } = require('fs'); const compression = require('compression'); module.exports = async function serve(dirname) { @@ -12,6 +13,7 @@ module.exports = async function serve(dirname) { const prettyLogger = require('../util/pretty-logger'); const addImgOptMiddleware = require('./addImgOptMiddleware'); const stagingServerSettings = config.section('stagingServer'); + const customHttpsSettings = config.section('customHttps'); process.chdir(path.join(dirname, 'dist')); @@ -41,6 +43,24 @@ module.exports = async function serve(dirname) { } ); + if (customHttpsSettings.key && customHttpsSettings.cert) { + const { key, cert } = customHttpsSettings; + if (existsSync(key) && existsSync(cert)) { + prettyLogger.info( + 'Custom key and cert paths provided, creating HTTPS server.' + ); + const ssl = { + key: readFileSync(key, 'utf8'), + cert: readFileSync(cert, 'utf8') + }; + upwardServerOptions.https = ssl; + } else { + prettyLogger.warn( + 'Custom key and cert paths provided but files not found, creating HTTP server.' + ); + } + } + let envPort; /** * null and undefined are represented as strings in the env @@ -86,8 +106,10 @@ module.exports = async function serve(dirname) { }) ); upwardServerOptions.host = hostname; - upwardServerOptions.https = ssl; upwardServerOptions.port = envPort || ports.staging || 0; + if (!upwardServerOptions.https) { + upwardServerOptions.https = ssl; + } } catch (e) { prettyLogger.error( 'Could not configure or access custom host. Using loopback...', diff --git a/packages/pwa-buildpack/lib/WebpackTools/__tests__/getModuleRules.spec.js b/packages/pwa-buildpack/lib/WebpackTools/__tests__/getModuleRules.spec.js new file mode 100644 index 0000000000..3538f6e0b5 --- /dev/null +++ b/packages/pwa-buildpack/lib/WebpackTools/__tests__/getModuleRules.spec.js @@ -0,0 +1,70 @@ +const getModuleRules = require('../configureWebpack/getModuleRules'); + +describe('css', async () => { + const helper = { + mode: 'development', + paths: { + src: 'unit_test' + }, + hasFlag: () => [], + babelRootMode: 'unit_test', + transformRequests: { + babel: {}, + source: {} + } + }; + const isStyleLoaderRule = rule => { + return rule.loader === 'style-loader'; + }; + const injectTypeIs = value => { + return rule => rule.options.injectType === value; + }; + const injectTypeIsStyleTag = injectTypeIs('styleTag'); + const injectTypeIsSingletonStyleTag = injectTypeIs('singletonStyleTag'); + + test('style loader inject type is "styleTag" in development', async () => { + // Arrange. + + // Act. + const rules = await getModuleRules(helper); + + // Assert. + // Promise.all returns an array of result objects. + const cssRule = rules[2]; + + const { oneOf } = cssRule; + oneOf.forEach(rule => { + const { use } = rule; + const allInjectTypesAreCorrect = use + .filter(isStyleLoaderRule) + .every(injectTypeIsStyleTag); + + expect(allInjectTypesAreCorrect).toBe(true); + }); + }); + + test('style loader inject type is "singletonStyleTag" not in development', async () => { + // Arrange. + const myHelper = { + ...helper, + mode: 'production' + }; + + // Act. + const rules = await getModuleRules(myHelper); + + // Assert. + // Promise.all returns an array of result objects. + const cssRule = rules[2]; + + const { oneOf } = cssRule; + oneOf.forEach(rule => { + const { use } = rule; + const allInjectTypesAreCorrect = use + .filter(isStyleLoaderRule) + .every(injectTypeIsSingletonStyleTag); + + expect(allInjectTypesAreCorrect).toBe(true); + }); + }); +}); diff --git a/packages/pwa-buildpack/lib/WebpackTools/configureWebpack/getModuleRules.js b/packages/pwa-buildpack/lib/WebpackTools/configureWebpack/getModuleRules.js index 6cadeb3432..0c6bf6ab1f 100644 --- a/packages/pwa-buildpack/lib/WebpackTools/configureWebpack/getModuleRules.js +++ b/packages/pwa-buildpack/lib/WebpackTools/configureWebpack/getModuleRules.js @@ -100,13 +100,21 @@ getModuleRules.js = async ({ * @returns Rule object for Webpack `module` configuration which parses * CSS files */ -getModuleRules.css = async ({ paths, hasFlag }) => ({ +getModuleRules.css = async ({ mode, paths, hasFlag }) => ({ test: /\.css$/, oneOf: [ { test: [paths.src, ...hasFlag('cssModules')], use: [ - 'style-loader', + { + loader: 'style-loader', + options: { + injectType: + mode === 'development' + ? 'styleTag' + : 'singletonStyleTag' + } + }, { loader: 'css-loader', options: { @@ -119,7 +127,15 @@ getModuleRules.css = async ({ paths, hasFlag }) => ({ { include: /node_modules/, use: [ - 'style-loader', + { + loader: 'style-loader', + options: { + injectType: + mode === 'development' + ? 'styleTag' + : 'singletonStyleTag' + } + }, { loader: 'css-loader', options: { diff --git a/packages/venia-concept/package.json b/packages/venia-concept/package.json index d9291c722d..5df04019cb 100644 --- a/packages/venia-concept/package.json +++ b/packages/venia-concept/package.json @@ -113,7 +113,7 @@ "redux-actions": "~2.6.4", "redux-thunk": "~2.3.0", "rimraf": "~2.6.3", - "style-loader": "~0.23.1", + "style-loader": "~2.0.0", "terser-webpack-plugin": "~1.2.3", "uuid": "~3.3.2", "webpack": "~4.46.0", diff --git a/packages/venia-ui/lib/RootComponents/CMS/cms.css b/packages/venia-ui/lib/RootComponents/CMS/cms.css index 95f6cb7414..1035653ead 100644 --- a/packages/venia-ui/lib/RootComponents/CMS/cms.css +++ b/packages/venia-ui/lib/RootComponents/CMS/cms.css @@ -9,6 +9,10 @@ padding: 0.5rem; } +.heading { + line-height: 1.25em; +} + .layout_default { padding: 0; } diff --git a/pwa-devdocs/package.json b/pwa-devdocs/package.json index 19a8ce5ba0..e6670eef87 100644 --- a/pwa-devdocs/package.json +++ b/pwa-devdocs/package.json @@ -89,7 +89,7 @@ "remark-preset-lint-markdown-style-guide": "^2.1.3", "sass": "^1.26.3", "sass-loader": "^7.3.1", - "style-loader": "^0.23.1", + "style-loader": "~2.0.0", "to-vfile": "^5.0.3", "vfile-reporter": "^5.1.2", "webpack": "~4.44.1", diff --git a/pwa-devdocs/src/technologies/basic-concepts/css-modules/index.md b/pwa-devdocs/src/technologies/basic-concepts/css-modules/index.md index 7d9e7dbc22..f33950cf06 100644 --- a/pwa-devdocs/src/technologies/basic-concepts/css-modules/index.md +++ b/pwa-devdocs/src/technologies/basic-concepts/css-modules/index.md @@ -36,7 +36,15 @@ For more information on reusable components and code sharing in front end develo { test: /\.css$/, use: [ - 'style-loader', + { + loader: 'style-loader', + options: { + injectType: + mode === 'development' + ? 'styleTag' + : 'singletonStyleTag' + } + }, { loader: 'css-loader', options: { @@ -49,6 +57,12 @@ For more information on reusable components and code sharing in front end develo }, ``` +The following is an explanation of the `style-loader` configuration: + +`injectType` + +: Allows to setup how styles will be injected into the DOM. + The following is an explanation of each `css-loader` configuration: `importLoaders` diff --git a/pwa-devdocs/src/tutorials/intercept-a-target/modify-talon-results/index.md b/pwa-devdocs/src/tutorials/intercept-a-target/modify-talon-results/index.md index 781683b75a..072639fea5 100644 --- a/pwa-devdocs/src/tutorials/intercept-a-target/modify-talon-results/index.md +++ b/pwa-devdocs/src/tutorials/intercept-a-target/modify-talon-results/index.md @@ -51,7 +51,7 @@ Edit the `packages.json` file so it looks like the following: "@magento/peregrine": "~7.0.0", "@magento/pwa-buildpack": "~6.0.0", "@magento/venia-ui": "~4.0.0", - "apollo-client": "2.6.4", + "@apollo/client": "~3.1.2", "graphql-tag": "~2.10.1", "react": "~17.0.1", "webpack": "~4.38.0" @@ -127,7 +127,7 @@ Inside the `useProductCategoriesList.js` file, add the following content: ```js import { useMemo } from "react"; -import { useQuery } from "@apollo/react-hooks"; +import { useQuery } from "@apollo/client"; import gql from "graphql-tag"; const GET_PRODUCT_CATEGORIES = gql` diff --git a/yarn.lock b/yarn.lock index 1ec57bea1a..2f5873e249 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16345,13 +16345,13 @@ style-loader@^1.2.1: loader-utils "^2.0.0" schema-utils "^2.7.0" -style-loader@~0.23.1: - version "0.23.1" - resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.23.1.tgz#cb9154606f3e771ab6c4ab637026a1049174d925" - integrity sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg== +style-loader@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" + integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== dependencies: - loader-utils "^1.1.0" - schema-utils "^1.0.0" + loader-utils "^2.0.0" + schema-utils "^3.0.0" sudo-prompt@^8.2.0: version "8.2.5"