Skip to content

Commit da0a786

Browse files
authored
feat(rsc): show warning for non optimized cjs (#635)
1 parent 0f40c3b commit da0a786

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

packages/plugin-rsc/e2e/basic.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,28 @@ test.describe('build-default', () => {
4646
defineTest(f)
4747
})
4848

49+
test.describe('dev-non-optimized-cjs', () => {
50+
test.beforeAll(async () => {
51+
// remove explicitly added optimizeDeps.include
52+
const editor = f.createEditor('vite.config.ts')
53+
editor.edit((s) =>
54+
s.replace(
55+
`'@vitejs/test-dep-transitive-cjs > use-sync-external-store/shim/index.js',`,
56+
``,
57+
),
58+
)
59+
})
60+
61+
const f = useFixture({ root: 'examples/basic', mode: 'dev' })
62+
63+
test('show warning', async ({ page }) => {
64+
await page.goto(f.url())
65+
expect(f.proc().stderr()).toContain(
66+
`[vite-rsc] found non-optimized CJS dependency in 'ssr' environment.`,
67+
)
68+
})
69+
})
70+
4971
function defineTest(f: Fixture) {
5072
test('basic', async ({ page }) => {
5173
using _ = expectNoPageError(page)

packages/plugin-rsc/e2e/fixture.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ function runCli(options: { command: string; label?: string } & SpawnOptions) {
1010
const [name, ...args] = options.command.split(' ')
1111
const child = x(name!, args, { nodeOptions: options }).process!
1212
const label = `[${options.label ?? 'cli'}]`
13+
let stdout = ''
14+
let stderr = ''
1315
child.stdout!.on('data', (data) => {
16+
stdout += stripVTControlCharacters(String(data))
1417
if (process.env.TEST_DEBUG) {
1518
console.log(styleText('cyan', label), data.toString())
1619
}
1720
})
1821
child.stderr!.on('data', (data) => {
22+
stderr += stripVTControlCharacters(String(data))
1923
console.log(styleText('magenta', label), data.toString())
2024
})
2125
const done = new Promise<void>((resolve) => {
@@ -48,7 +52,14 @@ function runCli(options: { command: string; label?: string } & SpawnOptions) {
4852
}
4953
}
5054

51-
return { proc: child, done, findPort, kill }
55+
return {
56+
proc: child,
57+
done,
58+
findPort,
59+
kill,
60+
stdout: () => stdout,
61+
stderr: () => stderr,
62+
}
5263
}
5364

5465
export type Fixture = ReturnType<typeof useFixture>
@@ -64,12 +75,13 @@ export function useFixture(options: {
6475
let baseURL!: string
6576

6677
const cwd = path.resolve(options.root)
78+
let proc!: ReturnType<typeof runCli>
6779

6880
// TODO: `beforeAll` is called again on any test failure.
6981
// https://playwright.dev/docs/test-retries
7082
test.beforeAll(async () => {
7183
if (options.mode === 'dev') {
72-
const proc = runCli({
84+
proc = runCli({
7385
command: options.command ?? `pnpm dev`,
7486
label: `${options.root}:dev`,
7587
cwd,
@@ -144,6 +156,7 @@ export function useFixture(options: {
144156
root: cwd,
145157
url: (url: string = './') => new URL(url, baseURL).href,
146158
createEditor,
159+
proc: () => proc,
147160
}
148161
}
149162

packages/plugin-rsc/examples/basic/vite.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,6 @@ export default { fetch: handler };
151151
},
152152
ssr: {
153153
optimizeDeps: {
154-
// TODO: this should be somehow auto inferred or at least show a warning
155-
// to guide users to `optimizeDeps.include`
156154
include: [
157155
'@vitejs/test-dep-transitive-cjs > use-sync-external-store/shim/index.js',
158156
],

packages/plugin-rsc/src/plugin.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import {
2020
normalizePath,
2121
parseAstAsync,
2222
} from 'vite'
23-
import { crawlFrameworkPkgs } from 'vitefu'
23+
import { crawlFrameworkPkgs, findClosestPkgJsonPath } from 'vitefu'
2424
import vitePluginRscCore from './core/plugin'
2525
import {
2626
type TransformWrapExportFilter,
@@ -838,9 +838,42 @@ globalThis.AsyncLocalStorage = __viteRscAyncHooks.AsyncLocalStorage;
838838
? [validateImportPlugin()]
839839
: []),
840840
scanBuildStripPlugin(),
841+
detectNonOptimizedCjsPlugin(),
841842
]
842843
}
843844

845+
function detectNonOptimizedCjsPlugin(): Plugin {
846+
return {
847+
name: 'rsc:detect-non-optimized-cjs',
848+
apply: 'serve',
849+
async transform(code, id) {
850+
if (
851+
id.includes('/node_modules/') &&
852+
!id.startsWith(this.environment.config.cacheDir) &&
853+
/\b(require|exports)\b/.test(code)
854+
) {
855+
id = parseIdQuery(id).filename
856+
let isEsm = id.endsWith('.mjs')
857+
if (id.endsWith('.js')) {
858+
const pkgJsonPath = await findClosestPkgJsonPath(path.dirname(id))
859+
if (pkgJsonPath) {
860+
const pkgJson = JSON.parse(
861+
fs.readFileSync(pkgJsonPath, 'utf-8'),
862+
) as { type?: string }
863+
isEsm = pkgJson.type === 'module'
864+
}
865+
}
866+
if (!isEsm) {
867+
this.warn(
868+
`[vite-rsc] found non-optimized CJS dependency in '${this.environment.name}' environment. ` +
869+
`It is recommended to manually add the dependency to 'environments.${this.environment.name}.optimizeDeps.include'.`,
870+
)
871+
}
872+
}
873+
},
874+
}
875+
}
876+
844877
function scanBuildStripPlugin(): Plugin {
845878
return {
846879
name: 'rsc:scan-strip',
@@ -1900,7 +1933,7 @@ function evalValue<T = any>(rawValue: string): T {
19001933
// https://github.com/vitejs/vite-plugin-vue/blob/06931b1ea2b9299267374cb8eb4db27c0626774a/packages/plugin-vue/src/utils/query.ts#L13
19011934
function parseIdQuery(id: string) {
19021935
if (!id.includes('?')) return { filename: id, query: {} }
1903-
const [filename, rawQuery] = id.split(`?`, 2)
1936+
const [filename, rawQuery] = id.split(`?`, 2) as [string, string]
19041937
const query = Object.fromEntries(new URLSearchParams(rawQuery))
19051938
return { filename, query }
19061939
}

0 commit comments

Comments
 (0)