Skip to content

Commit 3eba98d

Browse files
committed
feat: add support for Lynx Web Platform preview and middleware integration
1 parent a35a245 commit 3eba98d

File tree

12 files changed

+167
-66
lines changed

12 files changed

+167
-66
lines changed

.changeset/tricky-lamps-battle.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
"@lynx-js/qrcode-rsbuild-plugin": patch
3+
---
4+
5+
feat: support web platform preview
6+
7+
use rspeedy settings to enable Lynx Web Platform preview
8+
9+
```js
10+
// rspeedy configurations
11+
environments:{
12+
web:{},
13+
lynx:{}
14+
}
15+
```

examples/react/lynx.config.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,8 @@ export default defineConfig({
1717
performance: {
1818
profile: enableBundleAnalysis,
1919
},
20+
environments: {
21+
web: {},
22+
lynx: {},
23+
},
2024
});

packages/rspeedy/plugin-qrcode/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@
4747
"build": "rslib build",
4848
"test": "pnpm -w run test --project rspeedy/qrcode"
4949
},
50+
"dependencies": {
51+
"@lynx-js/web-rsbuild-server-middleware": "workspace:*"
52+
},
5053
"devDependencies": {
5154
"@clack/prompts": "1.0.0-alpha.5",
5255
"@lynx-js/rspeedy": "workspace:*",

packages/rspeedy/plugin-qrcode/src/generateDevUrls.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@ import type { ExposedAPI } from '@lynx-js/rspeedy'
77

88
import type { CustomizedSchemaFn } from './index.js'
99

10-
export default function generateDevUrls(
10+
function generateFileNameBase(
1111
api: RsbuildPluginAPI,
12-
entry: string,
13-
schemaFn: CustomizedSchemaFn,
1412
port: number,
15-
): Record<string, string> {
13+
): { name: string, assetPublicPath: string } {
1614
const { dev: { assetPrefix } } = api.getNormalizedConfig()
1715
const { config } = api.useExposed<ExposedAPI>(
1816
Symbol.for('rspeedy.api'),
@@ -34,16 +32,51 @@ export default function generateDevUrls(
3432
} else {
3533
name = filename
3634
}
35+
// <port> is supported in `dev.assetPrefix`, we should replace it with the real port
36+
const assetPublicPath = assetPrefix.replaceAll('<port>', String(port))
37+
return { name, assetPublicPath }
38+
}
39+
40+
export function generateExplorerDevUrls(
41+
api: RsbuildPluginAPI,
42+
entry: string,
43+
schemaFn: CustomizedSchemaFn,
44+
port: number,
45+
): Record<string, string> {
46+
const { name, assetPublicPath } = generateFileNameBase(api, port)
3747

3848
const customSchema = schemaFn(
3949
new URL(
4050
name.replace('[name]', entry).replace('[platform]', 'lynx'),
41-
// <port> is supported in `dev.assetPrefix`, we should replace it with the real port
42-
assetPrefix.replaceAll('<port>', String(port)),
51+
assetPublicPath,
4352
).toString(),
4453
)
4554

4655
return typeof customSchema === 'string'
4756
? { default: customSchema }
4857
: customSchema
4958
}
59+
60+
export function generateWebDevUrls(
61+
api: RsbuildPluginAPI,
62+
webEntries: string[],
63+
port: number,
64+
): Record<string, string> {
65+
const { name, assetPublicPath } = generateFileNameBase(api, port)
66+
return Object.fromEntries(
67+
webEntries.map(entry => {
68+
const base = URL.parse(assetPublicPath)
69+
? assetPublicPath
70+
: `http://localhost:${port}/`
71+
const pathname = new URL(
72+
name.replace('[name]', entry).replace('[platform]', 'web'),
73+
base,
74+
).pathname
75+
const url = new URL(`/web?casename=${pathname}`, base).toString()
76+
return [
77+
entry,
78+
url,
79+
]
80+
}),
81+
)
82+
}

packages/rspeedy/plugin-qrcode/src/index.ts

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
import type { EnvironmentContext, RsbuildPlugin } from '@rsbuild/core'
1212

13+
import { createWebVirtualFilesMiddleware } from '@lynx-js/web-rsbuild-server-middleware'
14+
1315
import { registerConsoleShortcuts } from './shortcuts.js'
1416

1517
/**
@@ -101,8 +103,13 @@ export function pluginQRCode(
101103
name: 'lynx:rsbuild:qrcode',
102104
pre: ['lynx:rsbuild:api'],
103105
setup(api) {
106+
api.onBeforeStartDevServer(({ environments, server }) => {
107+
if (environments['web']) {
108+
server.middlewares.use(createWebVirtualFilesMiddleware('/web'))
109+
}
110+
})
104111
api.onAfterStartProdServer(async ({ environments, port }) => {
105-
await main(environments['lynx'], port)
112+
await main(environments, port)
106113
})
107114

108115
let printedQRCode = false
@@ -122,7 +129,7 @@ export function pluginQRCode(
122129

123130
printedQRCode = true
124131

125-
await main(environments['lynx'], api.context.devServer.port)
132+
await main(environments, api.context.devServer.port)
126133
})
127134

128135
api.modifyRsbuildConfig((config) => {
@@ -138,23 +145,20 @@ export function pluginQRCode(
138145
})
139146

140147
async function main(
141-
environmentContext: EnvironmentContext | undefined,
148+
environments: Record<string, EnvironmentContext>,
142149
port: number,
143150
) {
144-
if (!environmentContext) {
145-
// Not lynx environment, skip print QRCode
146-
return
147-
}
148-
149-
const entries = Object.keys(environmentContext.entry)
150-
151-
if (entries.length === 0) {
151+
const lynxEntries = Object.keys(environments['lynx']?.entry ?? {})
152+
const webEntries = Object.keys(environments['web']?.entry ?? {})
153+
if (lynxEntries.length === 0 && webEntries.length === 0) {
154+
// Not lynx or web environment, skip print
152155
return
153156
}
154157

155158
const unregister = await registerConsoleShortcuts(
156159
{
157-
entries,
160+
entries: lynxEntries,
161+
webEntries,
158162
api,
159163
port,
160164
schema,

packages/rspeedy/plugin-qrcode/src/shortcuts.ts

Lines changed: 49 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import type { RsbuildPluginAPI } from '@rsbuild/core'
55

66
import type { ExposedAPI } from '@lynx-js/rspeedy'
77

8-
import generateDevUrls from './generateDevUrls.js'
8+
import {
9+
generateExplorerDevUrls,
10+
generateWebDevUrls,
11+
} from './generateDevUrls.js'
912

1013
import type { CustomizedSchemaFn } from './index.js'
1114

@@ -14,13 +17,16 @@ const gExistingShortcuts = new WeakSet<Options>()
1417
interface Options {
1518
api: RsbuildPluginAPI
1619
entries: string[]
20+
webEntries: string[]
1721
schema: CustomizedSchemaFn
1822
port: number
1923
customShortcuts?: Record<
2024
string,
2125
{ value: string, label: string, hint?: string, action?(): Promise<void> }
2226
>
23-
onPrint?: ((url: string) => Promise<void>) | undefined
27+
onPrint?:
28+
| ((lynx?: string, web?: Record<string, string>) => Promise<void>)
29+
| undefined
2430
}
2531

2632
export async function registerConsoleShortcuts(
@@ -32,22 +38,35 @@ export async function registerConsoleShortcuts(
3238
import('./showQRCode.js'),
3339
])
3440

35-
const currentEntry = options.entries[0]!
36-
const devUrls = generateDevUrls(
41+
// keep the default entry to be lynx explorer entry if exists
42+
const currentEntry = options.entries[0]
43+
const devUrls = currentEntry
44+
? generateExplorerDevUrls(
45+
options.api,
46+
currentEntry,
47+
options.schema,
48+
options.port,
49+
)
50+
: undefined
51+
52+
const value: string | symbol | undefined = devUrls
53+
? Object.values(devUrls)[0]
54+
: undefined
55+
const webUrls = generateWebDevUrls(
3756
options.api,
38-
currentEntry,
39-
options.schema,
57+
options.webEntries,
4058
options.port,
4159
)
42-
43-
const value: string | symbol = Object.values(devUrls)[0]!
44-
await options.onPrint?.(value)
45-
showQRCode(value)
60+
await options.onPrint?.(
61+
value,
62+
webUrls,
63+
)
64+
showQRCode(value, webUrls)
4665

4766
gExistingShortcuts.add(options)
4867

4968
// We should not `await` on this since it would block the NodeJS main thread.
50-
void loop(options, value, devUrls)
69+
void loop(options, value, devUrls, webUrls)
5170

5271
function off() {
5372
gExistingShortcuts.delete(options)
@@ -57,8 +76,9 @@ export async function registerConsoleShortcuts(
5776

5877
async function loop(
5978
options: Options,
60-
value: string | symbol,
61-
devUrls: Record<string, string>,
79+
value: string | symbol | undefined,
80+
devUrls: Record<string, string> | undefined,
81+
webUrls: Record<string, string>,
6282
) {
6383
const [
6484
{ autocomplete, select, selectKey, isCancel, cancel },
@@ -70,8 +90,8 @@ async function loop(
7090

7191
const selectFn = (length: number) => length > 5 ? autocomplete : select
7292

73-
let currentEntry = options.entries[0]!
74-
let currentSchema = Object.keys(devUrls)[0]!
93+
let currentEntry = options.entries[0]
94+
let currentSchema = devUrls ? Object.keys(devUrls)[0]! : undefined
7595

7696
while (!isCancel(value)) {
7797
const name = await selectKey({
@@ -94,18 +114,18 @@ async function loop(
94114
) {
95115
break
96116
}
97-
if (name === 'r') {
117+
if (name === 'r' && currentSchema) {
98118
const selection = await selectFn(options.entries.length)({
99119
message: 'Select entry',
100120
options: options.entries.map(entry => ({
101121
value: entry,
102122
label: entry,
103-
hint: generateDevUrls(
123+
hint: generateExplorerDevUrls(
104124
options.api,
105125
entry,
106126
options.schema,
107127
options.port,
108-
)[currentSchema]!,
128+
)[currentSchema!]!,
109129
})),
110130
initialValue: currentEntry,
111131
})
@@ -114,8 +134,8 @@ async function loop(
114134
}
115135
currentEntry = selection
116136
value = getCurrentUrl()
117-
} else if (name === 'a') {
118-
const devUrls = generateDevUrls(
137+
} else if (name === 'a' && currentEntry) {
138+
const devUrls = generateExplorerDevUrls(
119139
options.api,
120140
currentEntry,
121141
options.schema,
@@ -139,7 +159,7 @@ async function loop(
139159
await options.customShortcuts[name].action?.()
140160
}
141161
await options.onPrint?.(value)
142-
showQRCode(value)
162+
showQRCode(value, webUrls)
143163
}
144164

145165
// If the `options` is not deleted from `gExistingShortcuts`, means that this is an explicitly
@@ -152,12 +172,14 @@ async function loop(
152172
return
153173

154174
function getCurrentUrl(): string {
155-
return generateDevUrls(
156-
options.api,
157-
currentEntry,
158-
options.schema,
159-
options.port,
160-
)[currentSchema]!
175+
return (currentEntry && currentSchema)
176+
? generateExplorerDevUrls(
177+
options.api,
178+
currentEntry,
179+
options.schema,
180+
options.port,
181+
)[currentSchema]!
182+
: ''
161183
}
162184

163185
function exit(code?: number) {

packages/rspeedy/plugin-qrcode/src/showQRCode.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@ import { log } from '@clack/prompts'
55
import color from 'picocolors'
66
import { renderUnicodeCompact } from 'uqr'
77

8-
export default function showQRCode(url: string): void {
9-
log.info(color.green('Scan with Lynx'))
10-
log.success(renderUnicodeCompact(url))
11-
log.success(url)
8+
export default function showQRCode(
9+
lynxUrl?: string,
10+
webUrls?: Record<string, string>,
11+
): void {
12+
if (lynxUrl) {
13+
log.info(color.green('Scan with Lynx'))
14+
log.success(renderUnicodeCompact(lynxUrl))
15+
log.success('Lynx Explorer: ' + lynxUrl)
16+
}
17+
if (webUrls) {
18+
for (const [name, webUrl] of Object.entries(webUrls)) {
19+
log.success(`Web Preview for ${name}: ` + webUrl)
20+
}
21+
}
1222
}

0 commit comments

Comments
 (0)