Skip to content

Commit

Permalink
feat: compatible with backend integration apps
Browse files Browse the repository at this point in the history
  • Loading branch information
fi3ework committed Jan 15, 2023
1 parent cb0ebe5 commit 1c1d4b5
Show file tree
Hide file tree
Showing 21 changed files with 964 additions and 38 deletions.
21 changes: 16 additions & 5 deletions packages/vite-plugin-checker/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@ import fs from 'fs'
import { createRequire } from 'module'
const _require = createRequire(import.meta.url)

export const RUNTIME_CLIENT_RUNTIME_PATH = '/@vite-plugin-checker-runtime'
export const RUNTIME_CLIENT_ENTRY_PATH = '/@vite-plugin-checker-runtime-entry'

export const composePreambleCode = (base = '/', config: Record<string, any>) => `
import { inject } from "${base}${RUNTIME_CLIENT_RUNTIME_PATH.slice(1)}";
inject({
overlayConfig: ${JSON.stringify(config)},
base: "${base}",
});
`

// #region
// NOTE: sync modification with packages/runtime/src/ws.js
export const RUNTIME_PUBLIC_PATH = '/@vite-plugin-checker-runtime'
export const RUNTIME_FILE_PATH = import.meta.url.endsWith('.ts')
? _require.resolve('../@runtime/main.js')
: _require.resolve('../../@runtime/main.js')
export const WS_CHECKER_ERROR_EVENT = 'vite-plugin-checker:error'
export const WS_CHECKER_RECONNECT_EVENT = 'vite-plugin-checker:reconnect'
// #endregion

export const runtimeCode = `${fs.readFileSync(RUNTIME_FILE_PATH, 'utf-8')};`
export const runtimeSourceFilePath = import.meta.url.endsWith('.ts')
? // for development only, maybe should use NODE_ENV to distinguish
_require.resolve('../@runtime/main.js')
: _require.resolve('../../@runtime/main.js')
export const runtimeCode = `${fs.readFileSync(runtimeSourceFilePath, 'utf-8')};`
36 changes: 17 additions & 19 deletions packages/vite-plugin-checker/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import npmRunPath from 'npm-run-path'
import path from 'path'
import type { ConfigEnv, Plugin, ResolvedConfig } from 'vite'
import { Checker } from './Checker.js'
import { RUNTIME_PUBLIC_PATH, runtimeCode, WS_CHECKER_RECONNECT_EVENT } from './client/index.js'
import {
RUNTIME_CLIENT_RUNTIME_PATH,
RUNTIME_CLIENT_ENTRY_PATH,
runtimeCode,
composePreambleCode,
WS_CHECKER_RECONNECT_EVENT,
} from './client/index.js'
import {
ACTION_TYPES,
BuildCheckBinStr,
Expand Down Expand Up @@ -50,7 +56,6 @@ export function checker(userConfig: UserPluginConfig): Plugin {
const enableOverlay = userConfig?.overlay !== false
const enableTerminal = userConfig?.terminal !== false
const overlayConfig = typeof userConfig?.overlay === 'object' ? userConfig?.overlay : {}
let resolvedRuntimePath = RUNTIME_PUBLIC_PATH
let checkers: ServeAndBuildChecker[] = []
let isProduction = true
let skipRuntime = false
Expand Down Expand Up @@ -82,7 +87,6 @@ export function checker(userConfig: UserPluginConfig): Plugin {
},
configResolved(config) {
resolvedConfig = config
resolvedRuntimePath = config.base + RUNTIME_PUBLIC_PATH.slice(1)
isProduction = config.isProduction
skipRuntime ||= isProduction || config.command === 'build'
},
Expand All @@ -95,20 +99,14 @@ export function checker(userConfig: UserPluginConfig): Plugin {
}
},
resolveId(id) {
if (id === RUNTIME_PUBLIC_PATH) {
if (id === RUNTIME_CLIENT_RUNTIME_PATH || id === RUNTIME_CLIENT_ENTRY_PATH) {
return id
}
return
},
load(id) {
if (id === RUNTIME_PUBLIC_PATH) {
return runtimeCode
}

return
},
transform(code, id, options) {
if (id === RUNTIME_PUBLIC_PATH) {
load(id) {
if (id === RUNTIME_CLIENT_RUNTIME_PATH) {
if (!resolvedConfig) return

const devBase = resolvedConfig.base
Expand All @@ -130,27 +128,27 @@ export function checker(userConfig: UserPluginConfig): Plugin {
hmrBase = path.posix.join(hmrBase, hmrConfig.path)
}

return code
return runtimeCode
.replace(/__HMR_PROTOCOL__/g, JSON.stringify(protocol))
.replace(/__HMR_HOSTNAME__/g, JSON.stringify(host))
.replace(/__HMR_PORT__/g, JSON.stringify(port))
.replace(/__HMR_BASE__/g, JSON.stringify(hmrBase))
// #endregion
}

return null
if (id === RUNTIME_CLIENT_ENTRY_PATH) {
return composePreambleCode(resolvedConfig!.base, overlayConfig)
}

return
},
transformIndexHtml() {
if (!skipRuntime) {
return [
{
tag: 'script',
attrs: { type: 'module' },
children: `import { inject } from "${resolvedRuntimePath}";
inject({
overlayConfig: ${JSON.stringify(overlayConfig)},
base: "${resolvedConfig?.base}",
});`,
children: composePreambleCode(resolvedConfig!.base, overlayConfig),
},
]
}
Expand Down
24 changes: 24 additions & 0 deletions playground/backend-integration/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Vitest Snapshot v1

exports[`backend-integration > serve > get initial error 1`] = `"[{\\"checkerId\\":\\"TypeScript\\",\\"frame\\":\\" 4 |/n 5 | function App() {/n > 6 | const [count, setCount] = useState<string>(0)/n | ^/n 7 |/n 8 | return (/n 9 | <div className=/\\"App/\\">\\",\\"id\\":\\"<PROJECT_ROOT>/playground/backend-integration/src/App.tsx\\",\\"level\\":1,\\"loc\\":{\\"column\\":46,\\"file\\":\\"<PROJECT_ROOT>/playground/backend-integration/src/App.tsx\\",\\"line\\":6},\\"message\\":\\"Argument of type 'number' is not assignable to parameter of type 'string | (() => string)'.\\",\\"stack\\":\\"\\"}]"`;

exports[`backend-integration > serve > get initial error 2`] = `
"
ERROR(TypeScript) Argument of type 'number' is not assignable to parameter of type 'string | (() => string)'.
FILE <PROJECT_ROOT>/playground/backend-integration/src/App.tsx:6:46
4 |
5 | function App() {
> 6 | const [count, setCount] = useState<string>(0)
| ^
7 |
8 | return (
9 | <div className=\\"App\\">
[TypeScript] Found 1 error. Watching for file changes."
`;
42 changes: 42 additions & 0 deletions playground/backend-integration/__test__/serve.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// this is automatically detected by playground/vitestSetup.ts and will replace
// the default e2e test serve behavior

import path from 'node:path'
// import kill from 'kill-port'
// import { hmrPorts, ports } from '~utils'
import { fileURLToPath } from 'url'

const isTest = process.env.VITEST
const __dirname = path.dirname(fileURLToPath(import.meta.url))

export async function serve() {
// await kill(port)
const rootDir = path.resolve(__dirname, '../')

const { createServer, port } = await import(path.resolve(rootDir, 'server.js'))
const { app, viteDevServer } = await createServer(rootDir, port)

return new Promise((resolve, reject) => {
try {
const server = app.listen(port, () => {
resolve({
// for test teardown
async close() {
await new Promise((resolve) => {
server.close(resolve)
})
if (viteDevServer) {
await viteDevServer.close()
}
},
viteDevServer,
port,
})
})
} catch (e) {
reject(e)
}
})
}

// serve()
30 changes: 30 additions & 0 deletions playground/backend-integration/__test__/test.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import stringify from 'fast-json-stable-stringify'
import { describe, expect, it } from 'vitest'

import {
diagnostics,
expectStderrContains,
isBuild,
isServe,
log,
sleepForServerReady,
stripedLog,
} from '../../testUtils'

describe('backend-integration', () => {
describe.runIf(isServe)('serve', () => {
it('get initial error', async () => {
await sleepForServerReady()

expect(stringify(diagnostics)).toMatchSnapshot()
expect(stripedLog).toMatchSnapshot()
})
})

describe.runIf(isBuild)('build', () => {
it('should fail', async () => {
const expectedMsg = `TS2345: Argument of type 'number' is not assignable to parameter of type 'string | (() => string)'`
expectStderrContains(log, expectedMsg)
})
})
})
13 changes: 13 additions & 0 deletions playground/backend-integration/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
</head>
<body>
<h1>Title on static server</h1>
<div id="root"></div>
<!-- VITE_CLIENT_SLOT -->
</body>
</html>
26 changes: 26 additions & 0 deletions playground/backend-integration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@playground/backend-integration",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "node server.js",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^17.0.0",
"react-dom": "^17.0.0"
},
"devDependencies": {
"@types/express": "^4.17.15",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@vitejs/plugin-react": "^2.0.0",
"express": "^4.18.2",
"http-proxy-middleware": "^2.0.6",
"typescript": "~4.5.5",
"vite": "^3.0.4",
"vite-plugin-checker": "workspace:*"
}
}
1 change: 1 addition & 0 deletions playground/backend-integration/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions playground/backend-integration/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import fs from 'fs'
import path from 'path'
import { fileURLToPath } from 'url'
import express from 'express'
import { promisify } from 'util'
import { createProxyMiddleware } from 'http-proxy-middleware'
import { createServer as createViteServer } from 'vite'
const __dirname = path.dirname(fileURLToPath(import.meta.url))

const rootDir = path.resolve(__dirname)
export const port = 3008

export async function createServer(port) {
const app = express()
const viteDevServer = await createViteServer({ root: rootDir })

let html = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8')

app.get('/', async (req, res) => {
html = html.replace(
'<!-- VITE_CLIENT_SLOT -->',
`
<script type="module">
import RefreshRuntime from 'http://localhost:5173/@react-refresh'
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<script type="module" src="http://localhost:5173/@vite-plugin-checker-runtime-entry"></script>
<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/src/main.tsx"></script>
`
)
res.status(200).set({ 'Content-Type': 'text/html' }).end(html)
})

app.use('/', createProxyMiddleware({ target: 'http://127.0.0.1:5173', changeOrigin: true }))

return { app, viteDevServer }
}

const isTest = process.env.VITEST

if (!isTest) {
createServer().then(({ app, viteDevServer }) => {
viteDevServer.listen()
app.listen(port, () => {
console.log('http://localhost:5173')
})
})
}
41 changes: 41 additions & 0 deletions playground/backend-integration/src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}

.card {
padding: 2em;
}

.read-the-docs {
color: #888;
}
Loading

0 comments on commit 1c1d4b5

Please sign in to comment.