From cdc4c5f75b732c098168281b5d0b1a9e2f4c10eb Mon Sep 17 00:00:00 2001 From: Hung Viet Nguyen Date: Mon, 4 Jul 2022 01:08:56 +0700 Subject: [PATCH] feat: use Vite for Jest Preview Server instead of DIY --- cli/server/previewServer.js | 298 ++++++++++++++++-------------------- cli/server/vite.config.js | 9 ++ package-lock.json | 50 +++--- package.json | 4 +- 4 files changed, 161 insertions(+), 200 deletions(-) create mode 100644 cli/server/vite.config.js diff --git a/cli/server/previewServer.js b/cli/server/previewServer.js index 2d9c733d..f065ea0c 100755 --- a/cli/server/previewServer.js +++ b/cli/server/previewServer.js @@ -5,185 +5,151 @@ const fs = require('fs'); const connect = require('connect'); const sirv = require('sirv'); const app = connect(); -const chokidar = require('chokidar'); const { openBrowser } = require('./browser'); -const { WebSocketServer } = require('ws'); - -const port = process.env.PORT || 3336; -// TODO: Can we reuse `port`, I think Vite they can do that -// https://github.com/vitejs/vite/blob/50a876537cc7b934ec5c1d11171b5ce02e3891a8/packages/vite/src/node/server/ws.ts#L97 -// TODO: Increase port by 1 is not a good strategy, we should check if it's also available -const wsPort = Number(port) + 1; - -const CACHE_DIRECTORY = './node_modules/.cache/jest-preview'; -const INDEX_BASENAME = 'index.html'; -const INDEX_PATH = path.join(CACHE_DIRECTORY, INDEX_BASENAME); -const PUBLIC_CONFIG_BASENAME = 'cache-public.config'; -const PUBLIC_CONFIG_PATH = path.join(CACHE_DIRECTORY, PUBLIC_CONFIG_BASENAME); -const FAV_ICON_PATH = './node_modules/jest-preview/cli/server/favicon.ico'; - -// Always set default public folder to `public` if not specified -let publicFolder = 'public'; - -if (fs.existsSync(PUBLIC_CONFIG_PATH)) { - publicFolder = fs.readFileSync(PUBLIC_CONFIG_PATH, 'utf8').trim(); -} -const wss = new WebSocketServer({ port: wsPort }); +const { createServer: createViteServer } = require('vite'); -wss.on('connection', function connection(ws) { - ws.on('message', function message(data) { - console.log('received: %s', data); - try { - const dataJSON = JSON.parse(data); - if (dataJSON.type === 'publicFolder') { - publicFolder = dataJSON.payload; - } - } catch (error) { - console.error(error); - } +async function createServer() { + const vite = await createViteServer({ + server: { middlewareMode: 'ssr' }, + configFile: path.resolve(__dirname, './vite.config.js'), }); -}); - -const watcher = chokidar.watch([INDEX_PATH, PUBLIC_CONFIG_PATH], { - // ignored: ['**/node_modules/**', '**/.git/**'], - ignoreInitial: true, - ignorePermissionErrors: true, - disableGlobbing: true, -}); - -function handleFileChange(filePath) { - const basename = path.basename(filePath); - // TODO: Check if this is the root cause for issue on linux - if (basename === INDEX_BASENAME) { - wss.clients.forEach((client) => { - if (client.readyState === 1) { - client.send(JSON.stringify({ type: 'reload' })); - } - }); - } - if (basename === PUBLIC_CONFIG_BASENAME) { - publicFolder = fs.readFileSync(PUBLIC_CONFIG_PATH, 'utf8').trim(); - } -} + const port = process.env.PORT || 3336; -watcher - .on('change', handleFileChange) - .on('add', handleFileChange) - .on('unlink', handleFileChange); - -/** - * - * @param {string} string - * @param {string} word - * @param {string} injectWord - * @returns string - */ - -function injectToString(string, word, injectWord) { - const breakPosition = string.indexOf(word) + word.length; - return ( - string.slice(0, breakPosition) + injectWord + string.slice(breakPosition) - ); -} + const CACHE_DIRECTORY = './node_modules/.cache/jest-preview'; + const INDEX_BASENAME = 'index.html'; + const INDEX_PATH = path.join(CACHE_DIRECTORY, INDEX_BASENAME); + const PUBLIC_CONFIG_BASENAME = 'cache-public.config'; + const PUBLIC_CONFIG_PATH = path.join(CACHE_DIRECTORY, PUBLIC_CONFIG_BASENAME); + const FAV_ICON_PATH = './node_modules/jest-preview/cli/server/favicon.ico'; -function injectToHead(html, content) { - return injectToString(html, '', content); -} + // Always set default public folder to `public` if not specified + let publicFolder = 'public'; -app.use((req, res, next) => { - // Learn from https://github.com/vitejs/vite/blob/2b7dad1ea1d78d7977e0569fcca4c585b4014e85/packages/vite/src/node/server/middlewares/static.ts#L38 - const serve = sirv('.', { - dev: true, - etag: true, - }); - // Do not serve index - if (req.url === '/') { - return next(); + if (fs.existsSync(PUBLIC_CONFIG_PATH)) { + publicFolder = fs.readFileSync(PUBLIC_CONFIG_PATH, 'utf8').trim(); } - // Check if req.url is existed, if not, look up in public directory - const filePath = path.join('.', req.url); - if (!fs.existsSync(filePath)) { - const newPath = path.join(publicFolder, req.url); - if (fs.existsSync(newPath)) { - req.url = newPath; - } else { - // Cannot find the file, warns user about it - // Likely user has old Jest cached code transformations. - // Or just a bug in their source code - console.log('[WARN] File not found: ', req.url); - console.log(`[WARN] Please check if ${req.url} is existed.`); - console.log( - `[WARN] If it is existed, likely you forget to setup the code transformation, or you haven't flushed the old cache yet. Try to run "./node_modules/.bin/jest --clearCache" to clear the cache.\n`, - ); - // TODO: To send those warning to browser as an overlay/ toast, the idea is similar to https://www.npmjs.com/package/vite-plugin-checker - // TODO: Known issue: in development, we can't find `favicon.ico` yet. So it will yell in the Preview Server logs - } + function injectToString(string, word, injectWord) { + const breakPosition = string.indexOf(word) + word.length; + return ( + string.slice(0, breakPosition) + injectWord + string.slice(breakPosition) + ); } - serve(req, res, next); -}); - -app.use('/', (req, res) => { - const reloadScriptContent = fs - .readFileSync(path.join(__dirname, './ws-client.js'), 'utf-8') - .replace(/\$PORT/g, wsPort); - - if (!fs.existsSync(INDEX_PATH)) { - // Make it looks nice - return res.end(` - - - - Jest Preview Dashboard - - -No preview found.
-Please add following lines to your test:

-
- - import { debug } from 'jest-preview'; -
-
- // Inside your tests -
- debug(); -
-
-
-Then rerun your tests. -
-See an example in the documentation - - -`); + + function injectToHead(html, content) { + return injectToString(html, '', content); } - let indexHtml = fs.readFileSync(INDEX_PATH, 'utf8'); - indexHtml += ``; - indexHtml = injectToHead( - indexHtml, - ` + + app.use(vite.middlewares); + + app.use((req, res, next) => { + // Learn from https://github.com/vitejs/vite/blob/2b7dad1ea1d78d7977e0569fcca4c585b4014e85/packages/vite/src/node/server/middlewares/static.ts#L38 + const serve = sirv('.', { + dev: true, + etag: true, + }); + // Do not serve index + if (req.url === '/') { + return next(); + } + + // Check if req.url is existed, if not, look up in public directory + const filePath = path.join('.', req.url); + if (!fs.existsSync(filePath)) { + const newPath = path.join(publicFolder, req.url); + if (fs.existsSync(newPath)) { + req.url = newPath; + } else { + // Cannot find the file, warns user about it + // Likely user has old Jest cached code transformations. + // Or just a bug in their source code + console.log('[WARN] File not found: ', req.url); + console.log(`[WARN] Please check if ${req.url} is existed.`); + console.log( + `[WARN] If it is existed, likely you forget to setup the code transformation, or you haven't flushed the old cache yet. Try to run "./node_modules/.bin/jest --clearCache" to clear the cache.\n`, + ); + // TODO: To send those warning to browser as an overlay/ toast, the idea is similar to https://www.npmjs.com/package/vite-plugin-checker + // TODO: Known issue: in development, we can't find `favicon.ico` yet. So it will yell in the Preview Server logs + } + } + serve(req, res, next); + }); + + app.use('/', async (req, res) => { + let indexHtml = fs.readFileSync(INDEX_PATH, 'utf8'); + indexHtml = injectToHead( + indexHtml, + ` Jest Preview Dashboard `, - ); - res.end(indexHtml); -}); - -const server = http.createServer(app); - -server.listen(port, () => { - if (fs.existsSync(INDEX_PATH)) { - // Remove old preview - const files = fs.readdirSync(CACHE_DIRECTORY); - files.forEach((file) => { - if (!file.startsWith('cache-')) { - fs.unlinkSync(path.join(CACHE_DIRECTORY, file)); - } - }); - } + ); + indexHtml = injectToHead( + indexHtml, + ` + `, + ); + res.end(indexHtml); + }); + + const server = http.createServer(app); + + server.listen(port, () => { + if (fs.existsSync(INDEX_PATH)) { + // Remove old preview + const files = fs.readdirSync(CACHE_DIRECTORY); + files.forEach((file) => { + if (!file.startsWith('cache-')) { + fs.unlinkSync(path.join(CACHE_DIRECTORY, file)); + } + }); + } + + if (!fs.existsSync(CACHE_DIRECTORY)) { + fs.mkdirSync(CACHE_DIRECTORY, { + recursive: true, + }); + } + fs.writeFileSync( + INDEX_PATH, + ` + + + + Jest Preview Dashboard + + + No preview found.
+ Please add following lines to your test:

+
+ + import { debug } from 'jest-preview'; +
+
+ // Inside your tests +
+ debug(); +
+
+
+ Then rerun your tests. +
+ See an example in the documentation + + `, + ); + + console.log(`Jest Preview Server listening on port ${port}`); + openBrowser(`http://localhost:${port}`); + }); +} - console.log(`Jest Preview Server listening on port ${port}`); - openBrowser(`http://localhost:${port}`); -}); +createServer(); diff --git a/cli/server/vite.config.js b/cli/server/vite.config.js new file mode 100644 index 00000000..12e23fb1 --- /dev/null +++ b/cli/server/vite.config.js @@ -0,0 +1,9 @@ +import { defineConfig, loadEnv } from 'vite'; + +export default defineConfig({ + server: { + watch: { + ignored: ['!**/node_modules/.cache/jest-preview/**'], + }, + }, +}); diff --git a/package-lock.json b/package-lock.json index 48512b84..dc49ca95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "dependencies": { "camelcase": "^6.3.0", "chalk": "^4.1.2", - "chokidar": "^3.5.3", "commander": "^9.2.0", "connect": "^3.7.0", "find-node-modules": "^2.1.3", @@ -20,8 +19,7 @@ "sirv": "^2.0.2", "slash": "^3.0.0", "string-hash": "^1.1.3", - "update-notifier": "^5.1.0", - "ws": "^8.5.0" + "update-notifier": "^5.1.0" }, "bin": { "jest-preview": "cli/index.js" @@ -2138,6 +2136,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2356,6 +2355,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, "engines": { "node": ">=8" } @@ -2594,6 +2594,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "funding": [ { "type": "individual", @@ -3950,6 +3951,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4067,6 +4069,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -4516,6 +4519,7 @@ "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, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -6421,6 +6425,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -7445,6 +7450,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -8810,26 +8816,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", @@ -10479,6 +10465,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -10641,7 +10628,8 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true }, "boxen": { "version": "5.1.2", @@ -10804,6 +10792,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -11754,6 +11743,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "optional": true }, "function-bind": { @@ -11834,6 +11824,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -12178,6 +12169,7 @@ "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" } @@ -13602,7 +13594,8 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true }, "normalize-range": { "version": "0.1.2", @@ -14319,6 +14312,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -15348,12 +15342,6 @@ "typedarray-to-buffer": "^3.1.5" } }, - "ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "requires": {} - }, "xdg-basedir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", diff --git a/package.json b/package.json index b05904d7..ca4f0edb 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "dependencies": { "camelcase": "^6.3.0", "chalk": "^4.1.2", - "chokidar": "^3.5.3", "commander": "^9.2.0", "connect": "^3.7.0", "find-node-modules": "^2.1.3", @@ -66,8 +65,7 @@ "sirv": "^2.0.2", "slash": "^3.0.0", "string-hash": "^1.1.3", - "update-notifier": "^5.1.0", - "ws": "^8.5.0" + "update-notifier": "^5.1.0" }, "devDependencies": { "@emotion/react": "^11.9.0",