Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ssr): better DX with sourcemaps, breakpoints, error messages #3928

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/vite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
"//": "READ .github/contributing.md to understand what to put under deps vs. devDeps!",
"dependencies": {
"@babel/code-frame": "^7.14.5",
"esbuild": "^0.12.8",
"postcss": "^8.3.4",
"resolve": "^1.20.0",
Expand All @@ -65,6 +66,7 @@
"@rollup/plugin-node-resolve": "13.0.0",
"@rollup/plugin-typescript": "^8.2.1",
"@rollup/pluginutils": "^4.1.0",
"@types/babel__code-frame": "^7.0.2",
"@types/clean-css": "^4.2.4",
"@types/convert-source-map": "^1.5.1",
"@types/debug": "^4.1.5",
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ export async function createServer(
},
ssrFixStacktrace(e) {
if (e.stack) {
e.stack = ssrRewriteStacktrace(e.stack, moduleGraph)
e.stack = ssrRewriteStacktrace(e, moduleGraph)
}
},
listen(port?: number, isRestart?: boolean) {
Expand Down
21 changes: 15 additions & 6 deletions packages/vite/src/node/server/sourcemap.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import { promises as fs } from 'fs'
import path from 'path'
import { ModuleGraph } from './moduleGraph'

export async function injectSourcesContent(
map: { sources: string[]; sourcesContent?: string[]; sourceRoot?: string },
file: string
file: string,
moduleGraph?: ModuleGraph
): Promise<void> {
const sourceRoot = await fs.realpath(
path.resolve(path.dirname(file), map.sourceRoot || '')
)
map.sourcesContent = []
const needsContent = !map.sourcesContent
if (needsContent) {
map.sourcesContent = []
}
await Promise.all(
map.sources.filter(Boolean).map(async (sourcePath, i) => {
map.sourcesContent![i] = await fs.readFile(
path.resolve(sourceRoot, decodeURI(sourcePath)),
'utf-8'
)
const mod = await moduleGraph?.getModuleByUrl(sourcePath)
sourcePath = mod?.file || path.resolve(sourceRoot, decodeURI(sourcePath))
if (moduleGraph) {
map.sources[i] = sourcePath
}
if (needsContent) {
map.sourcesContent![i] = await fs.readFile(sourcePath, 'utf-8')
}
})
)
}
3 changes: 2 additions & 1 deletion packages/vite/src/node/server/transformRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ export async function transformRequest(
return (mod.ssrTransformResult = await ssrTransform(
code,
map as SourceMap,
url
url,
config.isProduction
))
} else {
return (mod.transformResult = {
Expand Down
147 changes: 55 additions & 92 deletions packages/vite/src/node/ssr/__tests__/ssrTransform.spec.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,21 @@
import { traverseHtml } from '../../plugins/html'
import { ssrTransform } from '../ssrTransform'

const transform = (code: string, url?: string, isProduction = false) =>
ssrTransform(code, null, url, isProduction)

test('default import', async () => {
expect(
(
await ssrTransform(
`import foo from 'vue';console.log(foo.bar)`,
null,
null
)
).code
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
console.log(__vite_ssr_import_0__.default.bar)"
`)
expect((await transform(`import foo from 'vue';console.log(foo.bar)`)).code)
.toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
console.log(__vite_ssr_import_0__.default.bar)"
`)
})

test('named import', async () => {
expect(
(
await ssrTransform(
`import { ref } from 'vue';function foo() { return ref(0) }`,
null,
null
await transform(
`import { ref } from 'vue';function foo() { return ref(0) }`
)
).code
).toMatchInlineSnapshot(`
Expand All @@ -34,10 +27,8 @@ test('named import', async () => {
test('namespace import', async () => {
expect(
(
await ssrTransform(
`import * as vue from 'vue';function foo() { return vue.ref(0) }`,
null,
null
await transform(
`import * as vue from 'vue';function foo() { return vue.ref(0) }`
)
).code
).toMatchInlineSnapshot(`
Expand All @@ -47,62 +38,51 @@ test('namespace import', async () => {
})

test('export function declaration', async () => {
expect((await ssrTransform(`export function foo() {}`, null, null)).code)
expect((await transform(`export function foo() {}`)).code)
.toMatchInlineSnapshot(`
"function foo() {}
Object.defineProperty(__vite_ssr_exports__, \\"foo\\", { enumerable: true, configurable: true, get(){ return foo }})"
`)
"function foo() {}
Object.defineProperty(__vite_ssr_exports__, \\"foo\\", { enumerable: true, configurable: true, get(){ return foo }})"
`)
})

test('export class declaration', async () => {
expect((await ssrTransform(`export class foo {}`, null, null)).code)
.toMatchInlineSnapshot(`
expect((await transform(`export class foo {}`)).code).toMatchInlineSnapshot(`
"class foo {}
Object.defineProperty(__vite_ssr_exports__, \\"foo\\", { enumerable: true, configurable: true, get(){ return foo }})"
`)
})

test('export var declaration', async () => {
expect((await ssrTransform(`export const a = 1, b = 2`, null, null)).code)
expect((await transform(`export const a = 1, b = 2`)).code)
.toMatchInlineSnapshot(`
"const a = 1, b = 2
Object.defineProperty(__vite_ssr_exports__, \\"a\\", { enumerable: true, configurable: true, get(){ return a }})
Object.defineProperty(__vite_ssr_exports__, \\"b\\", { enumerable: true, configurable: true, get(){ return b }})"
`)
"const a = 1, b = 2
Object.defineProperty(__vite_ssr_exports__, \\"a\\", { enumerable: true, configurable: true, get(){ return a }})
Object.defineProperty(__vite_ssr_exports__, \\"b\\", { enumerable: true, configurable: true, get(){ return b }})"
`)
})

test('export named', async () => {
expect(
(await ssrTransform(`const a = 1, b = 2; export { a, b as c }`, null, null))
.code
).toMatchInlineSnapshot(`
"const a = 1, b = 2;
Object.defineProperty(__vite_ssr_exports__, \\"a\\", { enumerable: true, configurable: true, get(){ return a }})
Object.defineProperty(__vite_ssr_exports__, \\"c\\", { enumerable: true, configurable: true, get(){ return b }})"
`)
expect((await transform(`const a = 1, b = 2; export { a, b as c }`)).code)
.toMatchInlineSnapshot(`
"const a = 1, b = 2;
Object.defineProperty(__vite_ssr_exports__, \\"a\\", { enumerable: true, configurable: true, get(){ return a }})
Object.defineProperty(__vite_ssr_exports__, \\"c\\", { enumerable: true, configurable: true, get(){ return b }})"
`)
})

test('export named from', async () => {
expect(
(await ssrTransform(`export { ref, computed as c } from 'vue'`, null, null))
.code
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
expect((await transform(`export { ref, computed as c } from 'vue'`)).code)
.toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")

Object.defineProperty(__vite_ssr_exports__, \\"ref\\", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.ref }})
Object.defineProperty(__vite_ssr_exports__, \\"c\\", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.computed }})"
`)
Object.defineProperty(__vite_ssr_exports__, \\"ref\\", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.ref }})
Object.defineProperty(__vite_ssr_exports__, \\"c\\", { enumerable: true, configurable: true, get(){ return __vite_ssr_import_0__.computed }})"
`)
})

test('named exports of imported binding', async () => {
expect(
(
await ssrTransform(
`import {createApp} from 'vue';export {createApp}`,
null,
null
)
).code
(await transform(`import {createApp} from 'vue';export {createApp}`)).code
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")

Expand All @@ -111,45 +91,36 @@ test('named exports of imported binding', async () => {
})

test('export * from', async () => {
expect((await ssrTransform(`export * from 'vue'`, null, null)).code)
.toMatchInlineSnapshot(`
expect((await transform(`export * from 'vue'`)).code).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")

__vite_ssr_exportAll__(__vite_ssr_import_0__)"
`)
})

test('export default', async () => {
expect(
(await ssrTransform(`export default {}`, null, null)).code
).toMatchInlineSnapshot(`"__vite_ssr_exports__.default = {}"`)
expect((await transform(`export default {}`)).code).toMatchInlineSnapshot(
`"__vite_ssr_exports__.default = {}"`
)
})

test('import.meta', async () => {
expect(
(await ssrTransform(`console.log(import.meta.url)`, null, null)).code
(await transform(`console.log(import.meta.url)`)).code
).toMatchInlineSnapshot(`"console.log(__vite_ssr_import_meta__.url)"`)
})

test('dynamic import', async () => {
expect(
(await ssrTransform(`export const i = () => import('./foo')`, null, null))
.code
).toMatchInlineSnapshot(`
"const i = () => __vite_ssr_dynamic_import__('./foo')
Object.defineProperty(__vite_ssr_exports__, \\"i\\", { enumerable: true, configurable: true, get(){ return i }})"
`)
expect((await transform(`export const i = () => import('./foo')`)).code)
.toMatchInlineSnapshot(`
"const i = () => __vite_ssr_dynamic_import__('./foo')
Object.defineProperty(__vite_ssr_exports__, \\"i\\", { enumerable: true, configurable: true, get(){ return i }})"
`)
})

test('do not rewrite method definition', async () => {
expect(
(
await ssrTransform(
`import { fn } from 'vue';class A { fn() { fn() } }`,
null,
null
)
).code
(await transform(`import { fn } from 'vue';class A { fn() { fn() } }`)).code
).toMatchInlineSnapshot(`
"const __vite_ssr_import_0__ = __vite_ssr_import__(\\"vue\\")
class A { fn() { __vite_ssr_import_0__.fn() } }"
Expand All @@ -159,10 +130,8 @@ test('do not rewrite method definition', async () => {
test('do not rewrite catch clause', async () => {
expect(
(
await ssrTransform(
`import {error} from './dependency';try {} catch(error) {}`,
null,
null
await transform(
`import {error} from './dependency';try {} catch(error) {}`
)
).code
).toMatchInlineSnapshot(`
Expand All @@ -175,10 +144,8 @@ test('do not rewrite catch clause', async () => {
test('should declare variable for imported super class', async () => {
expect(
(
await ssrTransform(
`import { Foo } from './dependency';` + `class A extends Foo {}`,
null,
null
await transform(
`import { Foo } from './dependency';` + `class A extends Foo {}`
)
).code
).toMatchInlineSnapshot(`
Expand All @@ -191,12 +158,10 @@ test('should declare variable for imported super class', async () => {
// first class that uses the binding
expect(
(
await ssrTransform(
await transform(
`import { Foo } from './dependency';` +
`export default class A extends Foo {}\n` +
`export class B extends Foo {}`,
null,
null
`export class B extends Foo {}`
)
).code
).toMatchInlineSnapshot(`
Expand All @@ -210,23 +175,21 @@ test('should declare variable for imported super class', async () => {

test('sourcemap source', async () => {
expect(
(await ssrTransform(`export const a = 1`, null, 'input.js')).map.sources
(await transform(`export const a = 1`, 'input.js')).map.sources
).toStrictEqual(['input.js'])
})

test('overwrite bindings', async () => {
expect(
(
await ssrTransform(
await transform(
`import { inject } from 'vue';` +
`const a = { inject }\n` +
`const b = { test: inject }\n` +
`function c() { const { test: inject } = { test: true }; console.log(inject) }\n` +
`const d = inject \n` +
`function f() { console.log(inject) }\n` +
`function e() { const { inject } = { inject: true } }\n`,
null,
null
`function e() { const { inject } = { inject: true } }\n`
)
).code
).toMatchInlineSnapshot(`
Expand Down
Loading