Skip to content

Commit ede6c13

Browse files
committed
feat: enhance QR code plugin with web environment support and middleware integration
1 parent 6411f84 commit ede6c13

File tree

18 files changed

+199
-115
lines changed

18 files changed

+199
-115
lines changed

.changeset/crazy-jeans-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@lynx-js/rspeedy": patch
3+
---
4+
5+
fix: error `lynx.getJSModule` is not a function on web platform

.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/core/src/plugins/dev.plugin.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import color from 'picocolors'
1212
import type { Dev } from '../config/dev/index.js'
1313
import type { Server } from '../config/server/index.js'
1414
import { debug } from '../debug.js'
15+
import { isLynx } from '../utils/is-lynx.js'
1516
import { ProvidePlugin } from '../webpack/ProvidePlugin.js'
1617

1718
export function pluginDev(
@@ -149,7 +150,7 @@ export function pluginDev(
149150
.end()
150151
.plugin('lynx.hmr.provide')
151152
.use(ProvidePlugin, [
152-
{
153+
isLynx(environment) ? {
153154
WebSocket: [
154155
options?.client?.websocketTransport ?? require.resolve('@lynx-js/websocket'),
155156
'default',
@@ -163,6 +164,16 @@ export function pluginDev(
163164
),
164165
'default'
165166
],
167+
} : {
168+
__webpack_dev_server_client__: [
169+
require.resolve(
170+
'./client/hmr/WebSocketClient.js',
171+
{
172+
paths: [rspeedyDir],
173+
},
174+
),
175+
'default'
176+
],
166177
}
167178
])
168179
.end()

packages/rspeedy/core/test/plugins/dev.plugin.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,27 @@ describe('Plugins - Dev', () => {
9999
)
100100
})
101101

102+
test('no Websocket class injected for web', async () => {
103+
const rsbuild = await createStubRspeedy({
104+
environments: {
105+
web: {},
106+
},
107+
})
108+
109+
await rsbuild.unwrapConfig()
110+
111+
const { ProvidePlugin } = await import('../../src/webpack/ProvidePlugin.js')
112+
113+
expect(vi.isMockFunction(ProvidePlugin)).toBe(true)
114+
expect(vi.mocked(ProvidePlugin)).toBeCalled()
115+
expect(ProvidePlugin).toBeCalledWith({
116+
__webpack_dev_server_client__: [
117+
require.resolve('../../client/hmr/WebSocketClient.js'),
118+
'default',
119+
],
120+
})
121+
})
122+
102123
test('not inject entry and provide variables in production', async () => {
103124
vi.stubEnv('NODE_ENV', 'production')
104125
const rsbuild = await createStubRspeedy({})

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: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function generateDevUrls(
1212
entry: string,
1313
schemaFn: CustomizedSchemaFn,
1414
port: number,
15-
): Record<string, string> {
15+
): { lynx: Record<string, string>, web: string } {
1616
const { dev: { assetPrefix } } = api.getNormalizedConfig()
1717
const { config } = api.useExposed<ExposedAPI>(
1818
Symbol.for('rspeedy.api'),
@@ -35,15 +35,35 @@ export default function generateDevUrls(
3535
name = filename
3636
}
3737

38+
// <port> is supported in `dev.assetPrefix`, we should replace it with the real port
39+
let base: string
40+
try {
41+
base = new URL(
42+
'',
43+
assetPrefix.replaceAll('<port>', String(port)),
44+
).toString()
45+
} catch {
46+
base = `http://localhost:${port}/`
47+
}
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+
base,
4352
).toString(),
4453
)
4554

46-
return typeof customSchema === 'string'
55+
const outputPathname = new URL(
56+
name.replace('[name]', entry).replace('[platform]', 'web'),
57+
base,
58+
).pathname
59+
const web = new URL(
60+
`/web?casename=${outputPathname}`,
61+
base,
62+
).toString()
63+
64+
const lynx = typeof customSchema === 'string'
4765
? { default: customSchema }
4866
: customSchema
67+
68+
return { lynx, web }
4969
}

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

Lines changed: 25 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,29 @@ 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+
const allEntries = [
154+
...new Set([
155+
...lynxEntries,
156+
...webEntries,
157+
]),
158+
]
159+
if (allEntries.length === 0) {
160+
// Not lynx or web environment, skip print
152161
return
153162
}
154163

155164
const unregister = await registerConsoleShortcuts(
156165
{
157-
entries,
166+
entries: {
167+
all: allEntries,
168+
lynx: lynxEntries,
169+
web: webEntries,
170+
},
158171
api,
159172
port,
160173
schema,

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

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,18 @@ const gExistingShortcuts = new WeakSet<Options>()
1313

1414
interface Options {
1515
api: RsbuildPluginAPI
16-
entries: string[]
16+
entries: {
17+
all: string[]
18+
lynx: string[]
19+
web: string[]
20+
}
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?: ((lynx?: string, web?: string) => Promise<void>) | undefined
2428
}
2529

2630
export async function registerConsoleShortcuts(
@@ -32,22 +36,29 @@ export async function registerConsoleShortcuts(
3236
import('./showQRCode.js'),
3337
])
3438

35-
const currentEntry = options.entries[0]!
39+
// keep the default entry to be lynx explorer entry if exists
40+
const currentEntry = (options.entries.lynx[0] ?? options.entries.web[0])!
3641
const devUrls = generateDevUrls(
3742
options.api,
3843
currentEntry,
3944
options.schema,
4045
options.port,
4146
)
4247

43-
const value: string | symbol = Object.values(devUrls)[0]!
44-
await options.onPrint?.(value)
45-
showQRCode(value)
48+
const hasLynxEntry = options.entries.lynx.includes(currentEntry)
49+
const hasWebEntry = options.entries.web.includes(currentEntry)
50+
const lynxUrl = hasLynxEntry ? Object.values(devUrls.lynx)[0] : undefined
51+
const webUrl = hasWebEntry ? devUrls.web : undefined
52+
await options.onPrint?.(
53+
lynxUrl,
54+
webUrl,
55+
)
56+
showQRCode(lynxUrl, webUrl)
4657

4758
gExistingShortcuts.add(options)
4859

4960
// We should not `await` on this since it would block the NodeJS main thread.
50-
void loop(options, value, devUrls)
61+
void loop(options, { lynx: lynxUrl, web: webUrl }, devUrls, currentEntry)
5162

5263
function off() {
5364
gExistingShortcuts.delete(options)
@@ -57,8 +68,9 @@ export async function registerConsoleShortcuts(
5768

5869
async function loop(
5970
options: Options,
60-
value: string | symbol,
61-
devUrls: Record<string, string>,
71+
value: { lynx: string | symbol | undefined, web: string | undefined },
72+
devUrls: { lynx: Record<string, string | symbol>, web: string },
73+
currentEntry: string,
6274
) {
6375
const [
6476
{ autocomplete, select, selectKey, isCancel, cancel },
@@ -70,10 +82,11 @@ async function loop(
7082

7183
const selectFn = (length: number) => length > 5 ? autocomplete : select
7284

73-
let currentEntry = options.entries[0]!
74-
let currentSchema = Object.keys(devUrls)[0]!
85+
let currentSchema = Object.keys(devUrls.lynx)[0]!
7586

76-
while (!isCancel(value)) {
87+
while (
88+
!(value.lynx && isCancel(value.lynx) || value.web && isCancel(value.web))
89+
) {
7790
const name = await selectKey({
7891
message: 'Usage',
7992
options: [
@@ -95,18 +108,21 @@ async function loop(
95108
break
96109
}
97110
if (name === 'r') {
98-
const selection = await selectFn(options.entries.length)({
111+
const selection = await selectFn(options.entries.all.length)({
99112
message: 'Select entry',
100-
options: options.entries.map(entry => ({
101-
value: entry,
102-
label: entry,
103-
hint: generateDevUrls(
113+
options: options.entries.all.map(entry => {
114+
const newDevUrls = generateDevUrls(
104115
options.api,
105116
entry,
106117
options.schema,
107118
options.port,
108-
)[currentSchema]!,
109-
})),
119+
)
120+
return {
121+
value: entry,
122+
label: entry,
123+
hint: (newDevUrls.lynx[currentSchema] ?? newDevUrls.web),
124+
}
125+
}),
110126
initialValue: currentEntry,
111127
})
112128
if (isCancel(selection)) {
@@ -121,9 +137,9 @@ async function loop(
121137
options.schema,
122138
options.port,
123139
)
124-
const selection = await selectFn(Object.keys(devUrls).length)({
140+
const selection = await selectFn(Object.keys(devUrls.lynx).length)({
125141
message: 'Select schema',
126-
options: Object.entries(devUrls).map(([name, url]) => ({
142+
options: Object.entries(devUrls.lynx).map(([name, url]) => ({
127143
value: name,
128144
label: name,
129145
hint: url,
@@ -138,8 +154,8 @@ async function loop(
138154
} else if (options.customShortcuts?.[name]) {
139155
await options.customShortcuts[name].action?.()
140156
}
141-
await options.onPrint?.(value)
142-
showQRCode(value)
157+
await options.onPrint?.(value.lynx as string | undefined, value.web)
158+
showQRCode(value.lynx as string, value.web)
143159
}
144160

145161
// If the `options` is not deleted from `gExistingShortcuts`, means that this is an explicitly
@@ -151,13 +167,17 @@ async function loop(
151167

152168
return
153169

154-
function getCurrentUrl(): string {
155-
return generateDevUrls(
170+
function getCurrentUrl(): {
171+
lynx: string | undefined
172+
web: string | undefined
173+
} {
174+
const { lynx, web } = generateDevUrls(
156175
options.api,
157176
currentEntry,
158177
options.schema,
159178
options.port,
160-
)[currentSchema]!
179+
)
180+
return { lynx: lynx[currentSchema], web }
161181
}
162182

163183
function exit(code?: number) {

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ 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(lynxUrl?: string, webUrl?: string): void {
9+
if (lynxUrl) {
10+
log.info(color.green('Scan with Lynx'))
11+
log.success(renderUnicodeCompact(lynxUrl))
12+
log.success('Lynx Explorer: ' + lynxUrl)
13+
}
14+
if (webUrl) {
15+
log.success('Lynx Web Platform: ' + webUrl)
16+
}
1217
}

0 commit comments

Comments
 (0)