diff --git a/README.md b/README.md index c96565a..28a8501 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ Now you're all set. From now on, whenever you want to preview your application locally, just run: -1. `npm run build`: This will run `next build` to build your Next.js app and `next-on-netlify` to prepare your Next.js app for compatibility with Netlify +1. `npx next-on-netlify watch`: This will run `next build` to build your Next.js app and `next-on-netlify` to prepare your Next.js app for compatibility with Netlify. Any source code changes will trigger another build. 1. `netlify dev`: This will emulate Netlify on your computer and let you preview your app on `http://localhost:8888`. *Note:* diff --git a/index.js b/index.js index d6eed2d..0e104d5 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,10 @@ const { normalize } = require("path"); +const debounceFn = require("debounce-fn"); +const chokidar = require("chokidar"); +const execa = require("execa"); + +const { logTitle } = require("./lib/helpers/logger"); + const prepareFolders = require("./lib/steps/prepareFolders"); const copyPublicFiles = require("./lib/steps/copyPublicFiles"); const copyNextAssets = require("./lib/steps/copyNextAssets"); @@ -9,21 +15,10 @@ const setupHeaders = require("./lib/steps/setupHeaders"); const { NETLIFY_PUBLISH_PATH, NETLIFY_FUNCTIONS_PATH, + SRC_FILES, } = require("./lib/config"); -/** options param: - * { - * functionsDir: string to path - * publishDir: string to path - * } - */ - -const nextOnNetlify = (options = {}) => { - const functionsPath = normalize( - options.functionsDir || NETLIFY_FUNCTIONS_PATH - ); - const publishPath = normalize(options.publishDir || NETLIFY_PUBLISH_PATH); - +const build = (functionsPath, publishPath) => { const trackNextOnNetlifyFiles = prepareFolders({ functionsPath, publishPath, @@ -42,6 +37,47 @@ const nextOnNetlify = (options = {}) => { setupHeaders(publishPath); trackNextOnNetlifyFiles(); + + logTitle("✅ Success! All done!"); +}; + +const watch = (functionsPath, publishPath) => { + logTitle(`👀 Watching source code for changes`); + + const runBuild = debounceFn( + () => { + try { + execa.sync("next", ["build"], { stdio: "inherit" }); + build(functionsPath, publishPath); + } catch (e) { + console.log(e); + } + }, + { + wait: 3000, + } + ); + + chokidar.watch(SRC_FILES).on("all", runBuild); +}; + +/** options param: + * { + * functionsDir: string to path + * publishDir: string to path + * watch: { directory: string to path } + * } + */ + +const nextOnNetlify = (options = {}) => { + const functionsPath = normalize( + options.functionsDir || NETLIFY_FUNCTIONS_PATH + ); + const publishPath = normalize(options.publishDir || NETLIFY_PUBLISH_PATH); + + options.watch + ? watch(functionsPath, publishPath) + : build(functionsPath, publishPath); }; module.exports = nextOnNetlify; diff --git a/lib/config.js b/lib/config.js index 4edd65e..cca7c72 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,5 +1,6 @@ const { join } = require("path"); const getNextDistDir = require("./helpers/getNextDistDir"); +const getNextSrcDirs = require("./helpers/getNextSrcDir"); // This is where next-on-netlify will place all static files. // The publish key in netlify.toml should point to this folder. @@ -17,7 +18,9 @@ const PUBLIC_PATH = join(".", "public/"); const NEXT_CONFIG_PATH = join(".", "next.config.js"); // This is the folder that NextJS builds to; default is .next -const NEXT_DIST_DIR = getNextDistDir({ nextConfigPath: NEXT_CONFIG_PATH }); +const NEXT_DIST_DIR = getNextDistDir(); + +const NEXT_SRC_DIRS = getNextSrcDirs(); // This is the folder with templates for Netlify Functions const TEMPLATES_DIR = join(__dirname, "templates"); @@ -35,6 +38,14 @@ const CUSTOM_HEADERS_PATH = join(".", "_headers"); // creating the next/image redirect const NEXT_IMAGE_FUNCTION_NAME = "next_image"; +const SRC_FILES = [ + PUBLIC_PATH, + NEXT_CONFIG_PATH, + CUSTOM_REDIRECTS_PATH, + CUSTOM_HEADERS_PATH, + ...NEXT_SRC_DIRS, +]; + module.exports = { NETLIFY_PUBLISH_PATH, NETLIFY_FUNCTIONS_PATH, @@ -46,4 +57,5 @@ module.exports = { CUSTOM_REDIRECTS_PATH, CUSTOM_HEADERS_PATH, NEXT_IMAGE_FUNCTION_NAME, + SRC_FILES, }; diff --git a/lib/helpers/getNextDistDir.js b/lib/helpers/getNextDistDir.js index 23215b6..0d23770 100644 --- a/lib/helpers/getNextDistDir.js +++ b/lib/helpers/getNextDistDir.js @@ -2,7 +2,7 @@ const { join } = require("path"); const getNextConfig = require("./getNextConfig"); -const getNextDistDir = ({ nextConfigPath }) => { +const getNextDistDir = () => { const nextConfig = getNextConfig(); return join(".", nextConfig.distDir); diff --git a/lib/helpers/getNextSrcDir.js b/lib/helpers/getNextSrcDir.js new file mode 100644 index 0000000..99bde4e --- /dev/null +++ b/lib/helpers/getNextSrcDir.js @@ -0,0 +1,7 @@ +const { join } = require("path"); + +const getNextSrcDirs = () => { + return ["pages", "src", "public", "styles"].map((dir) => join(".", dir)); +}; + +module.exports = getNextSrcDirs; diff --git a/next-on-netlify.js b/next-on-netlify.js index 1bc0cbb..90dfbcb 100755 --- a/next-on-netlify.js +++ b/next-on-netlify.js @@ -1,17 +1,26 @@ #!/usr/bin/env node const { program } = require("commander"); -program - .option( - "--max-log-lines [number]", - "lines of build output to show for each section", - 50 - ) - .parse(process.argv); - const nextOnNetlify = require("./index"); -const { logTitle } = require("./lib/helpers/logger"); -nextOnNetlify(); +program.option( + "--max-log-lines [number]", + "lines of build output to show for each section", + 50 +); + +program + .command("watch") + .description("re-runs next-on-netlify on changes") + .action(() => { + nextOnNetlify({ watch: true }); + }); + +program + .command("build", { isDefault: true }) + .description("runs next-on-netlify") + .action(() => { + nextOnNetlify(); + }); -logTitle("✅ Success! All done!"); +program.parse(process.argv); diff --git a/package-lock.json b/package-lock.json index efa6203..a2fd4f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4877,7 +4877,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -5508,8 +5507,7 @@ "binary-extensions": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", - "dev": true + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" }, "bindings": { "version": "1.5.0", @@ -6134,19 +6132,26 @@ "dev": true }, "chokidar": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", - "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", - "dev": true, + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", - "readdirp": "~3.3.0" + "readdirp": "~3.5.0" + }, + "dependencies": { + "fsevents": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "optional": true + } } }, "chownr": { @@ -7510,6 +7515,21 @@ "time-zone": "^1.0.0" } }, + "debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "requires": { + "mimic-fn": "^3.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==" + } + } + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -9747,7 +9767,6 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -10757,7 +10776,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -10843,8 +10861,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -10867,7 +10884,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -15251,8 +15267,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "npm-bundled": { "version": "1.1.1", @@ -16889,12 +16904,11 @@ } }, "readdirp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", - "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", - "dev": true, + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", "requires": { - "picomatch": "^2.0.7" + "picomatch": "^2.2.1" } }, "redeyed": { diff --git a/package.json b/package.json index b8110b2..6afe47b 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,9 @@ }, "dependencies": { "@sls-next/lambda-at-edge": "^1.5.2", + "chokidar": "^3.5.1", "commander": "^6.0.0", + "debounce-fn": "^4.0.0", "fs-extra": "^9.0.1", "jimp": "^0.16.1" },