diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..5ef4131 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,23 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Jest All", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "args": ["--runInBand"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + }, + { + "type": "node", + "request": "launch", + "name": "Jest Current File", + "program": "${workspaceFolder}/node_modules/jest/bin/jest", + "args": ["${relativeFile}"], + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen" + } + ] +} \ No newline at end of file diff --git a/build-index.js b/build-index.js index d2376ac..7c08b5b 100755 --- a/build-index.js +++ b/build-index.js @@ -34,8 +34,8 @@ var fs = require("fs-extra"); var packageJson = require("./package.json"); var deps = { - "dependencies": ["babel-runtime", "babel-polyfill", "html-webpack-plugin", "prop-types", "react", "react-dom", "react-redux", "react-router", "react-router-dom", "redux", "redux-saga", "webpack", "node-sass", "history"], - "devDependencies": ["babel-core", "babel-eslint", "babel-jest", "babel-loader", "babel-plugin-transform-async-to-generator", "babel-plugin-transform-class-properties", "babel-plugin-transform-es2015-modules-umd", "babel-plugin-transform-object-rest-spread", "babel-plugin-transform-runtime", "babel-preset-env", "babel-preset-react", "bundlesize", "compression-webpack-plugin", "css-loader", "enzyme", "enzyme-adapter-react-16", "eslint-config-mcrowder65", "jest", "fetch-mock", "style-loader", "postcss-loader", "postcss-flexbugs-fixes", "sass-loader", "react-hot-loader", "webpack-dev-server", "identity-obj-proxy", "webpack-bundle-analyzer"] + "dependencies": ["babel-runtime", "babel-polyfill", "html-webpack-plugin", "prop-types", "express", "react", "react-dom", "react-redux", "react-router", "react-router-dom", "redux", "redux-saga", "webpack", "node-sass", "history", "isomorphic-fetch"], + "devDependencies": ["babel-core", "babel-eslint", "babel-jest", "babel-loader", "babel-plugin-transform-async-to-generator", "babel-plugin-transform-class-properties", "babel-plugin-transform-es2015-modules-umd", "babel-plugin-transform-object-rest-spread", "babel-plugin-transform-runtime", "babel-preset-env", "babel-preset-react", "bundlesize", "compression-webpack-plugin", "css-loader", "enzyme", "enzyme-adapter-react-16", "eslint-config-mcrowder65", "jest", "fetch-mock", "style-loader", "shortid", "postcss-loader", "postcss-flexbugs-fixes", "sass-loader", "react-hot-loader", "webpack-dev-server", "identity-obj-proxy", "webpack-bundle-analyzer"] }; var executeFunction = function executeFunction(func, loadingText) { var spinner = void 0; @@ -124,7 +124,8 @@ var cli = function cli() { linter: "./node_modules/.bin/eslint src --ext .js,.jsx && ./node_modules/.bin/eslint test --ext .js,.jsx", webpack: "export NODE_ENV=production && ./node_modules/.bin/webpack -p --progress", bundlesize: "bundlesize", - "analyze-bundle": "export ANALYZE_BUNDLE=true && npm run webpack" + "analyze-bundle": "export ANALYZE_BUNDLE=true && npm run webpack", + "server-watch": "NODE_ENV=development && babel-watch src/server/index.js" }), jest: (0, _extends4.default)({}, pkgJson.jest, { setupTestFrameworkScriptFile: "/test/client/config.js", @@ -132,8 +133,8 @@ var cli = function cli() { "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/__mocks__/file-mock.js", "\\.(css|scss|less)$": "identity-obj-proxy" }, - collectCoverageFrom: ["src/client/**/*.{js*}", "!src/client/browser-history.js", "!src/client/app.js", "!src/client/router.js", "!src/client/actions/sagas/config.js", "!src/client/actions/sagas/index.js"], - modulePaths: ["src/client/"], + collectCoverageFrom: ["src/**/*.{js*}", "!src/client/browser-history.js", "!src/client/app.js", "!src/client/router.js", "!src/client/actions/sagas/config.js", "!src/client/actions/sagas/index.js"], + modulePaths: ["src/"], coverageReporters: ["html"] }) @@ -227,7 +228,7 @@ var cli = function cli() { while (1) { switch (_context3.prev = _context3.next) { case 0: - files = ["webpack.config.js", ".babelrc", "src/client/actions/sagas/config.js", "src/client/actions/sagas/index.js", "src/client/actions/sagas/ping-server.js", "src/client/actions/sagas/types.js", "src/client/actions/index.js", "src/client/actions/types.js", "src/client/components/home.js", "src/client/reducers/index.js", "src/client/reducers/initial-state.js", "src/client/styles/base.scss", "src/client/app.js", "src/client/browser-history.js", "src/client/index.html", "src/client/router.js", "test/client/__mocks__/file-mock.js", "test/client/actions/sagas/ping-server.spec.js", "test/client/actions/index.spec.js", "test/client/config.js", "test/client/reducers/index.spec.js"]; + files = ["webpack.config.js", ".babelrc", "src/client/actions/sagas/config.js", "src/client/actions/sagas/index.js", "src/client/actions/sagas/ping-server.js", "src/client/actions/sagas/types.js", "src/client/actions/index.js", "src/client/actions/types.js", "src/client/components/home.js", "src/client/reducers/index.js", "src/client/reducers/initial-state.js", "src/client/styles/base.scss", "src/client/app.js", "src/client/browser-history.js", "src/client/index.html", "src/client/router.js", "src/server/index.js", "src/shared/constants.js", "src/shared/fetch-wrapper.js", "test/client/__mocks__/file-mock.js", "test/client/actions/sagas/ping-server.spec.js", "test/client/actions/index.spec.js", "test/client/config.js", "test/client/reducers/index.spec.js", "test/server/index.spec.js", "test/shared/fetch-wrapper.spec.js"]; _iteratorNormalCompletion = true; _didIteratorError = false; _iteratorError = undefined; diff --git a/index-tests/force.spec.js b/index-tests/force.spec.js index aa8889c..1b623ed 100644 --- a/index-tests/force.spec.js +++ b/index-tests/force.spec.js @@ -3,7 +3,7 @@ import { executeBashFunction, cli } from "./utils.js"; test("When force exists, it should reject", async () => { try { const folder = "force"; - await executeBashFunction(`mkdir ${folder}`); + await executeBashFunction(`rm -rf ${folder} && mkdir ${folder}`); process.argv.push(folder); await cli(); } catch (e) { diff --git a/index-tests/forcef.spec.js b/index-tests/forcef.spec.js index 58ceab2..aac48ba 100644 --- a/index-tests/forcef.spec.js +++ b/index-tests/forcef.spec.js @@ -2,7 +2,7 @@ import { executeBashFunction, cli, doesFileExist } from "./utils.js"; test("When forcef exists and you pass -f, it should delete it and work!", async () => { const folder = "forcef"; - await executeBashFunction(`mkdir ${folder}`); + await executeBashFunction(`rm -rf ${folder} && mkdir ${folder}`); process.argv.push(folder); process.argv.push("-f"); process.argv.push("-s"); diff --git a/index.js b/index.js index 9aa3f24..53c5766 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ const deps = { "babel-polyfill", "html-webpack-plugin", "prop-types", + "express", "react", "react-dom", "react-redux", @@ -21,7 +22,8 @@ const deps = { "redux-saga", "webpack", "node-sass", - "history" + "history", + "isomorphic-fetch" ], "devDependencies": [ "babel-core", @@ -44,6 +46,7 @@ const deps = { "jest", "fetch-mock", "style-loader", + "shortid", "postcss-loader", "postcss-flexbugs-fixes", "sass-loader", @@ -89,7 +92,6 @@ const createFolder = folder => { return executeFunction(callback => fs.mkdir(folder, callback), `Creating ${folder}`); }; - const cli = () => { return new Promise((outerResolve, outerReject) => { @@ -160,7 +162,8 @@ const cli = () => { linter: "./node_modules/.bin/eslint src --ext .js,.jsx && ./node_modules/.bin/eslint test --ext .js,.jsx", webpack: "export NODE_ENV=production && ./node_modules/.bin/webpack -p --progress", bundlesize: "bundlesize", - "analyze-bundle": "export ANALYZE_BUNDLE=true && npm run webpack" + "analyze-bundle": "export ANALYZE_BUNDLE=true && npm run webpack", + "server-watch": "NODE_ENV=development && babel-watch src/server/index.js" }, jest: { ...pkgJson.jest, @@ -170,14 +173,14 @@ const cli = () => { "\\.(css|scss|less)$": "identity-obj-proxy" }, collectCoverageFrom: [ - "src/client/**/*.{js*}", + "src/**/*.{js*}", "!src/client/browser-history.js", "!src/client/app.js", "!src/client/router.js", "!src/client/actions/sagas/config.js", "!src/client/actions/sagas/index.js" ], - modulePaths: ["src/client/"], + modulePaths: ["src/"], coverageReporters: ["html"] } @@ -236,11 +239,16 @@ npm-debug.log`; "src/client/browser-history.js", "src/client/index.html", "src/client/router.js", + "src/server/index.js", + "src/shared/constants.js", + "src/shared/fetch-wrapper.js", "test/client/__mocks__/file-mock.js", "test/client/actions/sagas/ping-server.spec.js", "test/client/actions/index.spec.js", "test/client/config.js", - "test/client/reducers/index.spec.js" + "test/client/reducers/index.spec.js", + "test/server/index.spec.js", + "test/shared/fetch-wrapper.spec.js" ]; for (const f of files) { try { diff --git a/package-lock.json b/package-lock.json index 522dbc4..e4dbe4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "create-react-matt", - "version": "0.0.87", + "version": "0.0.88", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -11494,6 +11494,12 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", "dev": true }, + "shortid": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/shortid/-/shortid-2.2.8.tgz", + "integrity": "sha1-AzsRfWoul1gE9vCWnb59PQs1UTE=", + "dev": true + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", diff --git a/package.json b/package.json index 9dff897..5e84444 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "create-react-matt", - "version": "0.0.87", + "version": "0.0.88", "description": "React, Redux, Webpack, Babel, Jest, and code coverage all provided for you", "main": "main-index.js", "repository": { @@ -21,7 +21,8 @@ "jest": "npm run pre-jest && jest --coverage --silent && npm run pre-jest", "bundlesize": "bundlesize", "coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls", - "prepublishOnly": "npm run build" + "prepublishOnly": "npm run build", + "server-watch": "NODE_ENV=development && babel-watch src/server/index.js" }, "bundlesize": [ { @@ -53,7 +54,7 @@ ], "jest": { "collectCoverageFrom": [ - "src/client/**/*.{js*}", + "src/**/*.{js*}", "index.js", "!src/client/browser-history.js", "!src/client/app.js", @@ -71,7 +72,7 @@ "lcov" ], "modulePaths": [ - "src/client/" + "src/" ], "globals": { "localStorage": {} @@ -121,6 +122,7 @@ "history": "4.7.2", "html-webpack-plugin": "3.2.0", "identity-obj-proxy": "3.0.0", + "isomorphic-fetch": "2.2.1", "jest": "23.1.0", "node-sass": "4.9.0", "path": "0.12.7", @@ -137,6 +139,7 @@ "redux": "4.0.0", "redux-saga": "0.16.0", "sass-loader": "7.0.3", + "shortid": "^2.2.8", "style-loader": "0.21.0", "webpack": "3.12.0", "webpack-bundle-analyzer": "2.13.1", diff --git a/src/client/actions/sagas/config.js b/src/client/actions/sagas/config.js index b52d783..61c5ed0 100644 --- a/src/client/actions/sagas/config.js +++ b/src/client/actions/sagas/config.js @@ -1,5 +1,6 @@ +import { pingServer } from "client/actions/sagas/ping-server"; import * as sagaTypes from "./types"; -import { pingServer } from "./ping-server"; + // The second argument of your value object can be method, // where you can override takeEvery and do takeLatest instead const sagaConfig = { diff --git a/src/client/actions/sagas/ping-server.js b/src/client/actions/sagas/ping-server.js index 3409f98..59b53f5 100644 --- a/src/client/actions/sagas/ping-server.js +++ b/src/client/actions/sagas/ping-server.js @@ -1,14 +1,15 @@ import { call, put } from "redux-saga/effects"; import { setPing } from "../index"; +import { fetchGet } from "../../../shared/fetch-wrapper"; +import { HTTP_RESPONSE_TYPES } from "../../../shared/constants"; -export const apiCall = async () => { +const url = process.env.NODE_ENV === "production" ? "" : "http://localhost:3000"; +export const apiCall = () => { // this is up to you whether or not you want to implement this server... - const res = await fetch("http://localhost:3000/ping", { method: "GET" }); - return res.text(); + return fetchGet({ url: `${url}/ping`, headers: { "Content-Type": HTTP_RESPONSE_TYPES.PLAIN, Accept: HTTP_RESPONSE_TYPES.PLAIN } }); }; -// eslint-disable-next-line export function* pingServer() { try { const resp = yield call(apiCall); diff --git a/src/client/router.js b/src/client/router.js index 56d7ec3..b78f27c 100644 --- a/src/client/router.js +++ b/src/client/router.js @@ -9,6 +9,7 @@ const Router = () => (
+
diff --git a/src/server/index.js b/src/server/index.js index af971c6..d3340f7 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -3,7 +3,18 @@ import bodyParser from "body-parser"; import path from "path"; const app = express(); - +if (process.env.NODE_ENV === "development") { + // eslint-disable-next-line no-console + console.log("CORs enabled"); + app.use((req, res, next) => { + res.header("Access-Control-Allow-Origin", "*"); + res.header( + "Access-Control-Allow-Headers", + "Origin, X-Requested-With, Content-Type, Accept" + ); + next(); + }); +} app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.get("*.js", (req, res, next) => { @@ -12,6 +23,16 @@ app.get("*.js", (req, res, next) => { res.set("Content-Encoding", "gzip"); next(); }); +app.get("/ping", (req, res) => { + res.send(`The server says hello`); +}); app.use(express.static(path.resolve(__dirname, "../..", "build"))); +app.get("*", (req, res) => { + res.sendFile(path.join(__dirname, "../../build", "index.html")); +}); + // eslint-disable-next-line no-console -app.listen(3000, () => console.log(`server started on port 3000`)); \ No newline at end of file +const server = app.listen(3000, () => + console.log(`server started on port 3000`) +); +export default server; diff --git a/src/shared/constants.js b/src/shared/constants.js new file mode 100644 index 0000000..6735e16 --- /dev/null +++ b/src/shared/constants.js @@ -0,0 +1,5 @@ +export const HTTP_RESPONSE_TYPES = { + JSON: "application/json", + HTML: "text/html", + PLAIN: "text/plain" +}; \ No newline at end of file diff --git a/src/shared/fetch-wrapper.js b/src/shared/fetch-wrapper.js new file mode 100644 index 0000000..24b2c1f --- /dev/null +++ b/src/shared/fetch-wrapper.js @@ -0,0 +1,58 @@ +import "isomorphic-fetch"; +import { HTTP_RESPONSE_TYPES } from "./constants"; + +const resolveResponse = (response, responseType) => { + if (responseType.indexOf(HTTP_RESPONSE_TYPES.JSON) !== -1) { + return response.json(); + } else if (responseType.indexOf(HTTP_RESPONSE_TYPES.HTML) !== -1) { + return response.text(); + } else if (responseType.indexOf(HTTP_RESPONSE_TYPES.PLAIN) !== -1) { + return response.text(); + } else { + throw Error("Response type not supported yet!"); + } +}; + +const initialHeaders = { "Content-Type": HTTP_RESPONSE_TYPES.JSON, Accept: HTTP_RESPONSE_TYPES.JSON }; + +const fetchWrapper = async ({ + url, + method, + body, + headers, +}) => { + // do this here to get Content-Type to not be overridden if you didn't want to. + const actualHeaders = { ...initialHeaders, ...headers }; + + const response = await fetch(url, { + method, + headers: actualHeaders, + body + }); + const responseType = response.headers.get("content-type"); + if (!responseType) { + throw Error("Response type was not defined"); + } + const resolvedResponse = await resolveResponse(response, responseType); + if (!response.ok) { + throw Error(JSON.stringify(resolvedResponse)); + } + return resolvedResponse; +}; + + +export const fetchGet = ({ url, headers }) => { + return fetchWrapper({ url, method: "GET", headers }); +}; + +export const fetchPost = ({ url, body, headers }) => { + return fetchWrapper({ url, method: "POST", body, headers }); +}; + +export const fetchPut = ({ url, body, headers }) => { + return fetchWrapper({ url, method: "PUT", body, headers }); +}; + +export const fetchDelete = ({ url, body, headers }) => { + return fetchWrapper({ url, method: "DELETE", body, headers }); +}; diff --git a/test/client/actions/sagas/ping-server.spec.js b/test/client/actions/sagas/ping-server.spec.js index 20e753d..3bab24a 100644 --- a/test/client/actions/sagas/ping-server.spec.js +++ b/test/client/actions/sagas/ping-server.spec.js @@ -1,8 +1,8 @@ import { call, put } from "redux-saga/effects"; import fetchMock from "fetch-mock"; -import { setPing, } from "actions/index"; -import { apiCall, pingServer } from "actions/sagas/ping-server"; +import { setPing, } from "client/actions/index"; +import { apiCall, pingServer } from "client/actions/sagas/ping-server"; describe("pingServer", () => { @@ -22,9 +22,20 @@ describe("pingServer", () => { }); }); describe("apiCall", () => { + test.skip("api call with server", async () => { + let server; + try { + const response = "The server says hello"; + server = require("server/index"); + const result = await apiCall(); + expect(result).toEqual(response); + } finally { + server.default.close(); + } + }); test("api call", async () => { const response = "hello"; - fetchMock.mock("http://localhost:3000/ping", response); + fetchMock.get("http://localhost:3000/ping", { body: response, sendAsJson: false, headers: { "Content-Type": "text/html" } }); const result = await apiCall(); expect(result).toEqual(response); diff --git a/test/client/components/home.spec.js b/test/client/components/home.spec.js index e606720..3d14ff6 100644 --- a/test/client/components/home.spec.js +++ b/test/client/components/home.spec.js @@ -5,10 +5,10 @@ import { createStore, applyMiddleware, compose } from "redux"; import { Provider } from "react-redux"; import fetchMock from "fetch-mock"; -import initialState from "reducers/initial-state"; -import rootReducer from "reducers/index"; -import Home from "components/home"; -import sagaActions from "actions/sagas"; +import initialState from "client/reducers/initial-state"; +import rootReducer from "client/reducers/index"; +import Home from "client/components/home"; +import sagaActions from "client/actions/sagas"; const generateEvent = value => ({ target: { value } }); describe("test/client/components/home.spec.js", () => { @@ -43,7 +43,7 @@ describe("full suite around", () => { home.find("#input").props().onChange(generateEvent("hello")); expect(store.getState().input).toEqual("hello"); const response = "hello"; - fetchMock.mock("http://localhost:3000/ping", response); + fetchMock.get("http://localhost:3000/ping", { body: response, sendAsJson: false, headers: { "Content-Type": "text/html" } }); home.find("#ping-server").props().onClick(); await delay(1000); home.update(); diff --git a/test/client/reducers/index.spec.js b/test/client/reducers/index.spec.js index 119a7b6..a542865 100644 --- a/test/client/reducers/index.spec.js +++ b/test/client/reducers/index.spec.js @@ -1,6 +1,6 @@ -import rootReducer from "reducers/index"; -import initialState from "reducers/initial-state"; -import { setInput, setPing } from "actions"; +import rootReducer from "client/reducers/index"; +import initialState from "client/reducers/initial-state"; +import { setInput, setPing } from "client/actions"; describe("input", () => { test("When not passing in a state, it should default to initialState.input", () => { diff --git a/test/server/index.spec.js b/test/server/index.spec.js new file mode 100644 index 0000000..233379d --- /dev/null +++ b/test/server/index.spec.js @@ -0,0 +1,8 @@ +import { fetchGet } from "shared/fetch-wrapper"; + +test("hitting /ping should return 'The server says hello'", async () => { + const server = require("server/index"); + const result = await fetchGet({ url: "http://localhost:3000/ping" }); + expect(result).toEqual("The server says hello"); + server.default.close(); +}); diff --git a/test/shared/fetch-wrapper.spec.js b/test/shared/fetch-wrapper.spec.js new file mode 100644 index 0000000..5412178 --- /dev/null +++ b/test/shared/fetch-wrapper.spec.js @@ -0,0 +1,82 @@ +import fetchMock from "fetch-mock"; +import shortId from "shortid"; + +import { + fetchDelete, + fetchGet, + fetchPost, + fetchPut +} from "../../src/shared/fetch-wrapper"; +import { HTTP_RESPONSE_TYPES } from "../../src/shared/constants"; + +const restore = () => fetchMock.restore(); +describe("edge cases", () => { + afterEach(restore); + + test("When not giving back a response type, it should throw!", async () => { + expect.assertions(1); + try { + const url = shortId.generate(); + fetchMock.put(url, { body: {}, headers: { "content-type": undefined }, sendAsJson: false }); + await fetchPut({ url }); + } catch (e) { + expect(e.message).toEqual("Response type was not defined"); + } + }); + test(`When giving a random content-type, (unique id), + it should throw since it's not handled`, async () => { + expect.assertions(1); + try { + const url = shortId.generate(); + fetchMock.get(url, { body: {}, headers: { "content-type": shortId.generate() } }); + await fetchGet({ url }); + } catch (e) { + expect(e.message).toEqual("Response type not supported yet!"); + } + }); + test(`When giving back a 500 response code, it should throw`, async () => { + expect.assertions(1); + try { + const url = shortId.generate(); + fetchMock.post(url, { body: {}, status: 500 }); + await fetchPost({ url }); + } catch (e) { + expect(e).toBeTruthy(); + } + }); +}); +describe("All the different response types", () => { + afterEach(restore); + test(`content-type: ${HTTP_RESPONSE_TYPES.JSON} should resolve to a json`, async () => { + const body = { hello: "I am a string in a json" }; + const url = shortId.generate(); + fetchMock.post(url, { + body, + headers: { "content-type": HTTP_RESPONSE_TYPES.JSON } + }); + const result = await fetchPost({ url }); + expect(result).toEqual(body); + }); + test(`content-type ${HTTP_RESPONSE_TYPES.HTML} should resolve to a string`, async () => { + const body = "I am a string "; + const url = shortId.generate(); + fetchMock.put(url, { + body, + headers: { "content-type": HTTP_RESPONSE_TYPES.HTML } + }); + const result = await fetchPut({ url }); + expect(result).toEqual(body); + }); + test(`content-type ${HTTP_RESPONSE_TYPES.PLAIN} should resolve to a string`, async () => { + const body = "I am a string "; + const url = shortId.generate(); + fetchMock.delete(url, { + body, + headers: { "content-type": HTTP_RESPONSE_TYPES.PLAIN } + }); + const result = await fetchDelete({ url }); + expect(result).toEqual(body); + }); + +}); + diff --git a/webpack.config.js b/webpack.config.js index b81e913..12f72df 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,7 +5,7 @@ const CompressionPlugin = require("compression-webpack-plugin"); const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin; const isProd = process.env.NODE_ENV === "production"; -const sourcePath = path.join(__dirname, "./src/client"); +const sourcePath = path.join(__dirname, "src"); const webpackConfig = { cache: !isProd, devtool: isProd ? "" : "eval-cheap-module-source-map",