diff --git a/Dockerfile b/Dockerfile index fdbd4c0a..62d348c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ COPY server . RUN cp .env.sample .env COPY --from=client-builder /app/build public -COPY --from=client-builder /app/build/index.html views +COPY --from=client-builder /app/build/index.html views/index.ejs VOLUME /app/public/user-avatars VOLUME /app/public/project-background-images diff --git a/client/config-overrides.js b/client/config-overrides.js new file mode 100644 index 00000000..53ef3908 --- /dev/null +++ b/client/config-overrides.js @@ -0,0 +1,56 @@ +const fs = require('fs'); +const path = require('path'); + +const BASE_URL_PLACEHOLDER = 'BASE_URL_PLACEHOLDER'; + +const replaceInFile = (file, search, replace) => { + fs.readFile(file, 'utf8', (readError, data) => { + if (readError) { + throw new Error(`${readError}`); + } + const res = data.replaceAll(search, replace); + fs.writeFile(file, res, 'utf8', (writeError) => { + if (writeError) { + throw new Error(`${writeError}`); + } + }); + }); +}; + +const replaceBaseUrl = (compiler) => { + compiler.hooks.assetEmitted.tap('ReplaceBaseUrlPlaceholder', (file, info) => { + if (info.content.indexOf(BASE_URL_PLACEHOLDER) >= 0) { + if (/\.css$/.exec(info.targetPath)) { + // For CSS 'url(...)' import we can use relative import + const relPath = path.relative(path.dirname(info.targetPath), info.outputPath); + replaceInFile(info.targetPath, BASE_URL_PLACEHOLDER, `${relPath}/`); + } else if (/\.js$/.exec(info.targetPath)) { + // For JS 'import ... from "some-asset"' we can get the variable injected in the window object + // eslint-disable-next-line no-template-curly-in-string + replaceInFile(info.targetPath, `"${BASE_URL_PLACEHOLDER}"`, '`${window.BASE_URL}/`'); + } else if (/index\.html$/.exec(info.targetPath)) { + // For the main html file, we set a placeholder for sails to inject the correct value as runtime + replaceInFile(info.targetPath, BASE_URL_PLACEHOLDER, '<%= BASE_URL %>'); + } + } + }); +}; + +module.exports = function override(config, env) { + if (env === 'production') { + const plugins = config.plugins.map((plugin) => { + if (plugin.constructor.name === 'InterpolateHtmlPlugin') { + const newPlugin = plugin; + newPlugin.replacements.PUBLIC_URL = BASE_URL_PLACEHOLDER; + return newPlugin; + } + return plugin; + }); + return { + ...config, + output: { ...config.output, publicPath: BASE_URL_PLACEHOLDER }, + plugins: [...plugins, { apply: replaceBaseUrl }], + }; + } + return config; +}; diff --git a/client/package-lock.json b/client/package-lock.json index 915cfd50..f69a43cd 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -63,6 +63,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "jest-enzyme": "^7.1.2", "prettier": "2.7.1", + "react-app-rewired": "^2.2.1", "react-test-renderer": "^17.0.2" } }, @@ -18665,6 +18666,30 @@ "node": ">=14" } }, + "node_modules/react-app-rewired": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.2.1.tgz", + "integrity": "sha512-uFQWTErXeLDrMzOJHKp0h8P1z0LV9HzPGsJ6adOtGlA/B9WfT6Shh4j2tLTTGlXOfiVx6w6iWpp7SOC5pvk+gA==", + "dev": true, + "dependencies": { + "semver": "^5.6.0" + }, + "bin": { + "react-app-rewired": "bin/index.js" + }, + "peerDependencies": { + "react-scripts": ">=2.1.3" + } + }, + "node_modules/react-app-rewired/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/react-beautiful-dnd": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz", @@ -38717,6 +38742,23 @@ "whatwg-fetch": "^3.6.2" } }, + "react-app-rewired": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.2.1.tgz", + "integrity": "sha512-uFQWTErXeLDrMzOJHKp0h8P1z0LV9HzPGsJ6adOtGlA/B9WfT6Shh4j2tLTTGlXOfiVx6w6iWpp7SOC5pvk+gA==", + "dev": true, + "requires": { + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "react-beautiful-dnd": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz", diff --git a/client/package.json b/client/package.json index f4f05ae7..d562336a 100755 --- a/client/package.json +++ b/client/package.json @@ -2,11 +2,11 @@ "name": "planka-client", "private": true, "scripts": { - "build": "react-scripts build", + "build": "react-app-rewired build", "eject": "react-scripts eject", - "lint": "eslint --ext js,jsx src", - "start": "react-scripts start", - "test": "react-scripts test" + "lint": "eslint --ext js,jsx src config-overrides.js", + "start": "react-app-rewired start", + "test": "react-app-rewired test" }, "browserslist": { "production": [ @@ -120,6 +120,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "jest-enzyme": "^7.1.2", "prettier": "2.7.1", + "react-app-rewired": "^2.2.1", "react-test-renderer": "^17.0.2" } } diff --git a/client/public/index.html b/client/public/index.html index e8955e7e..fc7327a7 100755 --- a/client/public/index.html +++ b/client/public/index.html @@ -26,6 +26,7 @@ -->