Skip to content

Commit

Permalink
feat: provide deploy-url as runtime property (intershop#630)
Browse files Browse the repository at this point in the history
  • Loading branch information
dhhyi authored and jometzner committed Jul 30, 2021
1 parent b3bb4c5 commit 03911e1
Show file tree
Hide file tree
Showing 12 changed files with 149 additions and 28 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ RUN node schematics/customization/service-worker ${serviceWorker} || true
COPY templates/webpack/* /workspace/templates/webpack/
ARG testing=false
ENV TESTING=${testing}
RUN npm run ng -- build -c ${configuration}
RUN npm run ng -- build -c ${configuration} --deploy-url=DEPLOY_URL_PLACEHOLDER
# synchronize-marker:pwa-docker-build:end

# ^ this part above is copied to Dockerfile_noSSR and should be kept in sync
Expand Down
5 changes: 4 additions & 1 deletion Dockerfile_noSSR
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ RUN node schematics/customization/service-worker ${serviceWorker} || true
COPY templates/webpack/* /workspace/templates/webpack/
ARG testing=false
ENV TESTING=${testing}
RUN npm run ng -- build -c ${configuration}
RUN npm run ng -- build -c ${configuration} --deploy-url=DEPLOY_URL_PLACEHOLDER
# synchronize-marker:pwa-docker-build:end

# ^ this part above is copied from the standard Dockerfile and should be kept in sync

ARG deployUrl=/
RUN npx ts-node scripts/set-deploy-url ${deployUrl}

FROM nginx:alpine
COPY templates/nginx.conf /etc/nginx/nginx.conf
COPY --from=buildstep /workspace/dist/browser /usr/share/nginx/html
Expand Down
26 changes: 22 additions & 4 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,32 @@ services:
- TRUST_ICM=true
# - PROMETHEUS=on
# - MULTI_SITE_LOCALE_MAP={"en_US":"/en","de_DE":"/de","fr_FR":"/fr"}

# <CDN-Example>
# add 127.0.0.1 mypwa.net to your hosts file and
# uncomment the following lines
#
# - DEPLOY_URL=http://mypwa.net:4222
# cdn:
# build:
# context: .
# dockerfile: Dockerfile_noSSR
# args:
# configuration: production
# serviceWorker: 'false'
# deployUrl: http://mypwa.net:4222
# ports:
# - '4222:4200'
#
# </CDN-Example>

nginx:
build: nginx
depends_on:
- pwa
ports:
- '4200:80'
# - '9113:9113'
environment:
UPSTREAM_PWA: 'http://pwa:4200'
# DEBUG: 1
Expand Down Expand Up @@ -51,7 +73,3 @@ services:
channel: inSPIRED-inTRONICS_Business-Site
features: quoting,businessCustomerRegistration,advancedVariationHandling,quickorder,orderTemplates,punchout,recently
theme: 'blue|688dc3'
ports:
- '4200:80'
# - '9113:9113'
2 changes: 1 addition & 1 deletion e2e/test-universal.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ universalTest 8 "${PWA_BASE_URL}/home" "intershop-pwa-state"
universalTest 9 "${PWA_BASE_URL}/home" "&q;baseURL&q;:"
universalTest 10 "${PWA_BASE_URL}/home" "<ish-content-include includeid=.include.homepage.content.pagelet2-Include"
universalTest 11 "${PWA_BASE_URL}/home" "<link rel=.canonical. href=.${PWA_CANONICAL_BASE_URL}/home.>"
universalTest 12 "${PWA_BASE_URL}/home" "<meta property=.og:image. content=.${PWA_CANONICAL_BASE_URL}[^>]*og-image-default"
universalTest 12 "${PWA_BASE_URL}/home" "<meta property=.og:image. content=./assets/img/og-image-default"
universalTest 13 "${PWA_BASE_URL}/home" "<title>inTRONICS Home | Intershop PWA</title>"
universalTest 14 "${PWA_BASE_URL}/sku6997041" "<link rel=.canonical. href=.${PWA_CANONICAL_BASE_URL}/Notebooks/Asus-Eee-PC-1008P-Karim-Rashid-sku6997041-catComputers.1835.151.>"
universalTest 15 "${PWA_BASE_URL}/sku6997041" "<meta property=.og:image. content=[^>]*6997041"
Expand Down
26 changes: 26 additions & 0 deletions scripts/set-deploy-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// tslint:disable: ish-ordered-imports ban-specific-imports no-console
import * as fs from 'fs';
import * as glob from 'glob';
import * as path from 'path';
import { setDeployUrlInFile } from '../src/ssr/deploy-url';

if (process.argv.length < 3) {
console.error('required argument deployUrl missing');
process.exit(1);
}

let deployUrl = process.argv[2];

if (!deployUrl.endsWith('/')) {
deployUrl += '/';
}

glob.sync('dist/browser/*.{js,css,html}').forEach(file => {
console.log(`setting deployUrl "${deployUrl}" in`, file);

const input = fs.readFileSync(file, { encoding: 'utf-8' });

const output = setDeployUrlInFile(deployUrl, path.basename(file), input);

fs.writeFileSync(file, output);
});
41 changes: 32 additions & 9 deletions server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ import { join } from 'path';
import * as robots from 'express-robots-txt';
import * as fs from 'fs';
import * as proxy from 'express-http-proxy';
// tslint:disable-next-line: ban-specific-imports
import { AppServerModule, ICM_WEB_URL, HYBRID_MAPPING_TABLE, environment, APP_BASE_HREF } from './src/main.server';
import { ngExpressEngine } from '@nguniversal/express-engine';
import { getDeployURLFromEnv, setDeployUrlInFile } from './src/ssr/deploy-url';

const PORT = process.env.PORT || 4200;

const DEPLOY_URL = getDeployURLFromEnv();

const DIST_FOLDER = join(process.cwd(), 'dist');

// uncomment this block to prevent ssr issues with third-party libraries regarding window, document, HTMLElement and navigator
Expand All @@ -28,8 +30,8 @@ global['HTMLElement'] = win.HTMLElement;
global['navigator'] = win.navigator;
// tslint:enable:no-string-literal
*/

// The Express app is exported so that it can be used by serverless Functions.
// not-dead-code
export function app() {
const logging = /on|1|true|yes/.test(process.env.LOGGING?.toLowerCase());

Expand Down Expand Up @@ -61,7 +63,7 @@ export function app() {
path: '/',
};

const req = https.request(options, res => {
const req = https.request(options, (res: { socket: { authorized: boolean } }) => {
console.log('Certificate for', ICM_BASE_URL, 'authorized:', res.socket.authorized);
});

Expand All @@ -84,7 +86,7 @@ export function app() {
'HOSTNAME_MISMATCH',
];

req.on('error', e => {
req.on('error', (e: { code: string }) => {
if (certErrorCodes.includes(e.code)) {
console.log(
e.code,
Expand Down Expand Up @@ -119,7 +121,7 @@ export function app() {
if (logging) {
server.use(
require('morgan')('tiny', {
skip: req => req.originalUrl.startsWith('/INTERSHOP/static'),
skip: (req: express.Request) => req.originalUrl.startsWith('/INTERSHOP/static'),
})
);
}
Expand Down Expand Up @@ -149,7 +151,18 @@ export function app() {
);
}

// Serve static files from /browser
// Serve static files from browser folder
server.get(/\/.*\.(js|css)$/, (req, res) => {
const path = req.originalUrl.substring(1);
fs.readFile(join(DIST_FOLDER, 'browser', path), { encoding: 'utf-8' }, (err, data) => {
if (err) {
res.sendStatus(404);
} else {
res.set('Content-Type', (path.endsWith('css') ? 'text/css' : 'application/javascript') + '; charset=UTF-8');
res.send(setDeployUrlInFile(DEPLOY_URL, path, data));
}
});
});
server.get(
'*.*',
express.static(join(DIST_FOLDER, 'browser'), {
Expand All @@ -161,14 +174,22 @@ export function app() {
// file should be re-checked more frequently -> 5m
res.set('Cache-Control', 'public, max-age=300');
}
// add cors headers for required resources
if (
DEPLOY_URL.startsWith('http') &&
['manifest.webmanifest', 'woff2', 'woff', 'json'].some(match => path.endsWith(match))
) {
res.set('access-control-allow-origin', '*');
}
},
})
);

const icmProxy = proxy(ICM_BASE_URL, {
// preserve original path
proxyReqPathResolver: req => req.originalUrl,
proxyReqOptDecorator: options => {
proxyReqPathResolver: (req: express.Request) => req.originalUrl,
// tslint:disable-next-line: no-any
proxyReqOptDecorator: (options: any) => {
if (process.env.TRUST_ICM) {
// https://github.com/villadora/express-http-proxy#q-how-to-ignore-self-signed-certificates-
options.rejectUnauthorized = false;
Expand Down Expand Up @@ -227,7 +248,9 @@ export function app() {
}
newHtml = newHtml.replace(/<base href="[^>]*>/, `<base href="${baseHref}" />`);

res.status(res.statusCode).send(newHtml || html);
newHtml = setDeployUrlInFile(DEPLOY_URL, req.originalUrl, newHtml);

res.status(res.statusCode).send(newHtml);
} else {
res.status(500).send(err.message);
}
Expand Down
12 changes: 6 additions & 6 deletions src/app/extensions/seo/store/seo/seo.effects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ export class SeoEffects {
switchMap(() =>
race([
// PRODUCT PAGE
this.productPage$.pipe(map(product => this.baseURL(true) + generateProductUrl(product).substr(1))),
this.productPage$.pipe(map(product => this.baseURL + generateProductUrl(product).substr(1))),
// CATEGORY / FAMILY PAGE
this.categoryPage$.pipe(map(category => this.baseURL(true) + generateCategoryUrl(category).substr(1))),
this.categoryPage$.pipe(map(category => this.baseURL + generateCategoryUrl(category).substr(1))),
// DEFAULT
this.appRef.isStable.pipe(whenTruthy(), mapTo(this.doc.URL.replace(/[;?].*/g, ''))),
])
Expand Down Expand Up @@ -123,7 +123,7 @@ export class SeoEffects {
description: 'seo.defaults.description',
robots: 'index, follow',
'og:type': 'website',
'og:image': `${this.baseURL(false)}assets/img/og-image-default.jpg`.replace('http:', 'https:'),
'og:image': '/assets/img/og-image-default.jpg',
...attributes,
})),
distinctUntilChanged(isEqual),
Expand Down Expand Up @@ -180,12 +180,12 @@ export class SeoEffects {
{ dispatch: false }
);

private baseURL(includeBaseHref: boolean) {
private get baseURL() {
let url: string;
if (this.request) {
url = `${this.request.protocol}://${this.request.get('host')}${includeBaseHref ? this.baseHref : ''}`;
url = `${this.request.protocol}://${this.request.get('host')}${this.baseHref}`;
} else {
url = includeBaseHref ? this.doc.baseURI : this.doc.baseURI.replace(new RegExp(`${this.baseHref}$`), '');
url = this.doc.baseURI;
}
return url.endsWith('/') ? url : url + '/';
}
Expand Down
33 changes: 33 additions & 0 deletions src/ssr/deploy-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export function getDeployURLFromEnv(): string {
const envDeployUrl = process.env.DEPLOY_URL || '/';

return `${envDeployUrl}${envDeployUrl.endsWith('/') ? '' : '/'}`;
}

export function setDeployUrlInFile(deployUrl: string, path: string, input: string): string {
if (input) {
if (path.startsWith('runtime') && path.endsWith('.js')) {
return input.replace(/DEPLOY_URL_PLACEHOLDER/g, deployUrl);
}

let newInput = input;

const cssRegex = /url\((?!http)\/?(assets.*?|[a-zA-Z].*?woff2?)\)/g;
if (cssRegex.test(newInput)) {
newInput = newInput.replace(cssRegex, (...args) => `url(${deployUrl}${args[1]})`);
}

const assetsRegex = /"\/?(assets[^"]*\.(\w{2,5}|webmanifest)|[a-z0-9]+\.css)"/g;
if (assetsRegex.test(newInput)) {
newInput = newInput.replace(assetsRegex, (...args) => `"${deployUrl}${args[1]}"`);
}

const javascriptRegex = /"(DEPLOY_URL_PLACEHOLDER|\/)?((runtime|vendor|main|polyfills|styles)[^"]*\.(js|css))"/g;
if (javascriptRegex.test(newInput)) {
newInput = newInput.replace(javascriptRegex, (...args) => `"${deployUrl}${args[2]}"`);
}

return newInput;
}
return input;
}
8 changes: 5 additions & 3 deletions src/typings.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
/* SystemJS module definition */
declare var module: NodeModule;

interface NodeModule {
id: string;
}
declare module 'express-http-proxy';

declare module 'express-robots-txt';

interface NodeModule {}

declare var PRODUCTION_MODE: boolean;

Expand Down
19 changes: 17 additions & 2 deletions templates/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ events {
}

http {
include /etc/nginx/mime.types;
include mime.types;
default_type application/octet-stream;

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
Expand All @@ -26,17 +26,32 @@ http {
keepalive_timeout 65;

gzip on;
gzip_types text/plain text/xml text/css
text/javascript application/javascript;

server {
listen 4200;
listen [::]:4200;
server_name localhost;

location / {
location ~* /.*index.html {
root /usr/share/nginx/html;
try_files $uri $uri/ /index.html;
}

location / {
root /usr/share/nginx/html;

if ($request_method = OPTIONS) {
return 204;
}

add_header Access-Control-Allow-Origin *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Expose-Headers Content-Length;
add_header Access-Control-Allow-Headers Range;
}

error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.all.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
"outDir": "./node_modules/.out-tsc/all",
"types": ["jest", "jest-extended", "node"]
},
"include": ["src/**/*.ts", "projects/**/*.ts"]
"include": ["src/**/*.ts", "projects/**/*.ts", "server.ts", "src/**/*.d.ts"]
}
1 change: 1 addition & 0 deletions tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@
],
"ignoredFiles": [
"server.ts$",
"src/ssr/.*.ts",
"src/[^/]*.ts$",
".*.spec.ts$",
"tslint-rules/",
Expand Down

0 comments on commit 03911e1

Please sign in to comment.