Skip to content

Commit

Permalink
feat: support sourcemap to .vue files (#243)
Browse files Browse the repository at this point in the history
* wip: effective .vue source map

* wip: process sources' paths

* chore: update example
  • Loading branch information
qmhc committed Jul 14, 2023
1 parent e53c815 commit 7445046
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 12 deletions.
10 changes: 5 additions & 5 deletions examples/vue/components/TypeProps.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<template>
<div>{{ color }}</div>
</template>

<script setup lang="ts">
type Props = {
export type Props = {
color: 'blue' | 'red' | 'purple',
array?: { foo: number }[]
}
Expand All @@ -17,7 +21,3 @@ interface Events {
defineEmits<Events>()
</script>

<template>
<div>{{ color }}</div>
</template>
13 changes: 10 additions & 3 deletions examples/vue/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,19 @@ export default defineConfig({
plugins: [
dts({
copyDtsFiles: true,
outDir: ['dist', 'types'],
outDir: [
'dist',
'types'
// 'types/inner'
],
// include: ['src/index.ts'],
exclude: ['src/ignore'],
staticImport: true,
rollupTypes: true,
insertTypesEntry: true
// rollupTypes: true,
insertTypesEntry: true,
compilerOptions: {
declarationMap: true
}
}),
vue(),
vueJsx()
Expand Down
37 changes: 35 additions & 2 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,7 @@ export function dtsPlugin(options: PluginOptions = {}): import('vite').Plugin {
Array.from(outputFiles.entries()),
async ([path, content]) => {
const isMapFile = path.endsWith('.map')
const baseDir = dirname(path)

if (!isMapFile && content) {
content = clearPureImport ? removePureImport(content) : content
Expand All @@ -498,6 +499,19 @@ export function dtsPlugin(options: PluginOptions = {}): import('vite').Plugin {
)
content = cleanVueFileName ? content.replace(/['"](.+)\.vue['"]/g, '"$1"') : content

if (isMapFile) {
try {
const sourceMap: { sources: string[] } = JSON.parse(content)

sourceMap.sources = sourceMap.sources.map(source => {
return normalizePath(relative(dirname(path), resolve(baseDir, source)))
})
content = JSON.stringify(sourceMap)
} catch (e) {
logger.warn(`${logPrefix} ${yellow('Processing source map fail:')} ${path}`)
}
}

await writeOutput(path, content, outDir)
}
)
Expand Down Expand Up @@ -624,8 +638,27 @@ export function dtsPlugin(options: PluginOptions = {}): import('vite').Plugin {
const relativePath = relative(outDir, wroteFile)

await Promise.all(
extraOutDirs.map(async outDir => {
await writeOutput(resolve(outDir, relativePath), content, outDir, false)
extraOutDirs.map(async targetOutDir => {
const path = resolve(targetOutDir, relativePath)

if (wroteFile.endsWith('.map')) {
const relativeOutDir = relative(outDir, targetOutDir)

if (relativeOutDir) {
try {
const sourceMap: { sources: string[] } = JSON.parse(content)

sourceMap.sources = sourceMap.sources.map(source => {
return normalizePath(relative(relativeOutDir, source))
})
content = JSON.stringify(sourceMap)
} catch (e) {
logger.warn(`${logPrefix} ${yellow('Processing source map fail:')} ${path}`)
}
}
}

await writeOutput(path, content, targetOutDir, false)
})
)
})
Expand Down
38 changes: 36 additions & 2 deletions src/resolvers/vue.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { base64VLQEncode } from '../utils'

import type { Resolver } from '../types'

interface SourceMap {
sources: string[],
mappings: string
}

const vueRE = /\.vue$/

export function VueResolver(): Resolver {
Expand All @@ -8,7 +15,7 @@ export function VueResolver(): Resolver {
supports(id) {
return vueRE.test(id)
},
transform({ id, program, service }) {
transform({ id, code, program, service }) {
const sourceFile =
program.getSourceFile(id) ||
program.getSourceFile(id + '.ts') ||
Expand All @@ -18,12 +25,39 @@ export function VueResolver(): Resolver {

if (!sourceFile) return []

return service.getEmitOutput(sourceFile.fileName, true).outputFiles.map(file => {
const outputs = service.getEmitOutput(sourceFile.fileName, true).outputFiles.map(file => {
return {
path: file.name,
content: file.text
}
})

if (!program.getCompilerOptions().declarationMap) return outputs

const [beforeScript] = code.split(/\s*<script.*>/)
const beforeLines = beforeScript.split('\n').length

for (const output of outputs) {
if (output.path.endsWith('.map')) {
try {
const sourceMap: SourceMap = JSON.parse(output.content)

sourceMap.sources = sourceMap.sources.map(source =>
source.replace(/\.vue\.ts$/, '.vue')
)

if (beforeScript && beforeScript !== code && beforeLines) {
sourceMap.mappings = `${base64VLQEncode([0, 0, beforeLines, 0])};${
sourceMap.mappings
}`
}

output.content = JSON.stringify(sourceMap)
} catch (e) {}
}
}

return outputs
}
}
}
45 changes: 45 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,51 @@ export function getTsConfig(
return tsConfig
}

/**
* @see https://github.com/mozilla/source-map/blob/master/lib/base64-vlq.js
*/

const BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('')

function base64Encode(number: number) {
if (number >= 0 && number < BASE64_ALPHABET.length) {
return BASE64_ALPHABET[number]
}

throw new TypeError('Base64 integer must be between 0 and 63: ' + number)
}

const VLQ_BASE_SHIFT = 5
const VLQ_BASE = 1 << VLQ_BASE_SHIFT
const VLQ_BASE_MASK = VLQ_BASE - 1
const VLQ_CONTINUATION_BIT = VLQ_BASE

function toVLQSigned(number: number) {
return number < 0 ? (-number << 1) + 1 : (number << 1) + 0
}

export function base64VLQEncode(numbers: number[]) {
let encoded = ''

for (const number of numbers) {
let vlq = toVLQSigned(number)
let digit: number

do {
digit = vlq & VLQ_BASE_MASK
vlq >>>= VLQ_BASE_SHIFT
if (vlq > 0) {
// There are still more digits in this value, so we must make sure the
// continuation bit is marked.
digit |= VLQ_CONTINUATION_BIT
}
encoded += base64Encode(digit)
} while (vlq > 0)
}

return encoded
}

const pkgPathCache = new Map<string, string | undefined>()

export function tryGetPkgPath(beginPath: string) {
Expand Down
43 changes: 43 additions & 0 deletions tests/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { normalize, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'

import {
base64VLQEncode,
ensureAbsolute,
ensureArray,
isNativeObj,
Expand Down Expand Up @@ -106,4 +107,46 @@ describe('utils tests', () => {
)
).toBe(n('/project/src'))
})

it('test: base64VLQEncode', () => {
// prettier-ignore
const snapshots = [
'/P', '9P', '7P', '5P', '3P', '1P', 'zP', 'xP', 'vP', 'tP', 'rP', 'pP', 'nP', 'lP', 'jP', 'hP',
'/O', '9O', '7O', '5O', '3O', '1O', 'zO', 'xO', 'vO', 'tO', 'rO', 'pO', 'nO', 'lO', 'jO', 'hO',
'/N', '9N', '7N', '5N', '3N', '1N', 'zN', 'xN', 'vN', 'tN', 'rN', 'pN', 'nN', 'lN', 'jN', 'hN',
'/M', '9M', '7M', '5M', '3M', '1M', 'zM', 'xM', 'vM', 'tM', 'rM', 'pM', 'nM', 'lM', 'jM', 'hM',
'/L', '9L', '7L', '5L', '3L', '1L', 'zL', 'xL', 'vL', 'tL', 'rL', 'pL', 'nL', 'lL', 'jL', 'hL',
'/K', '9K', '7K', '5K', '3K', '1K', 'zK', 'xK', 'vK', 'tK', 'rK', 'pK', 'nK', 'lK', 'jK', 'hK',
'/J', '9J', '7J', '5J', '3J', '1J', 'zJ', 'xJ', 'vJ', 'tJ', 'rJ', 'pJ', 'nJ', 'lJ', 'jJ', 'hJ',
'/I', '9I', '7I', '5I', '3I', '1I', 'zI', 'xI', 'vI', 'tI', 'rI', 'pI', 'nI', 'lI', 'jI', 'hI',
'/H', '9H', '7H', '5H', '3H', '1H', 'zH', 'xH', 'vH', 'tH', 'rH', 'pH', 'nH', 'lH', 'jH', 'hH',
'/G', '9G', '7G', '5G', '3G', '1G', 'zG', 'xG', 'vG', 'tG', 'rG', 'pG', 'nG', 'lG', 'jG', 'hG',
'/F', '9F', '7F', '5F', '3F', '1F', 'zF', 'xF', 'vF', 'tF', 'rF', 'pF', 'nF', 'lF', 'jF', 'hF',
'/E', '9E', '7E', '5E', '3E', '1E', 'zE', 'xE', 'vE', 'tE', 'rE', 'pE', 'nE', 'lE', 'jE', 'hE',
'/D', '9D', '7D', '5D', '3D', '1D', 'zD', 'xD', 'vD', 'tD', 'rD', 'pD', 'nD', 'lD', 'jD', 'hD',
'/C', '9C', '7C', '5C', '3C', '1C', 'zC', 'xC', 'vC', 'tC', 'rC', 'pC', 'nC', 'lC', 'jC', 'hC',
'/B', '9B', '7B', '5B', '3B', '1B', 'zB', 'xB', 'vB', 'tB', 'rB', 'pB', 'nB', 'lB', 'jB', 'hB',
'f', 'd', 'b', 'Z', 'X', 'V', 'T', 'R', 'P', 'N', 'L', 'J', 'H', 'F', 'D', 'A', 'C', 'E', 'G',
'I', 'K', 'M', 'O', 'Q', 'S', 'U', 'W', 'Y', 'a', 'c', 'e', 'gB', 'iB', 'kB', 'mB', 'oB', 'qB',
'sB', 'uB', 'wB', 'yB', '0B', '2B', '4B', '6B', '8B', '+B', 'gC', 'iC', 'kC', 'mC', 'oC', 'qC',
'sC', 'uC', 'wC', 'yC', '0C', '2C', '4C', '6C', '8C', '+C', 'gD', 'iD', 'kD', 'mD', 'oD', 'qD',
'sD', 'uD', 'wD', 'yD', '0D', '2D', '4D', '6D', '8D', '+D', 'gE', 'iE', 'kE', 'mE', 'oE', 'qE',
'sE', 'uE', 'wE', 'yE', '0E', '2E', '4E', '6E', '8E', '+E', 'gF', 'iF', 'kF', 'mF', 'oF', 'qF',
'sF', 'uF', 'wF', 'yF', '0F', '2F', '4F', '6F', '8F', '+F', 'gG', 'iG', 'kG', 'mG', 'oG', 'qG',
'sG', 'uG', 'wG', 'yG', '0G', '2G', '4G', '6G', '8G', '+G', 'gH', 'iH', 'kH', 'mH', 'oH', 'qH',
'sH', 'uH', 'wH', 'yH', '0H', '2H', '4H', '6H', '8H', '+H', 'gI', 'iI', 'kI', 'mI', 'oI', 'qI',
'sI', 'uI', 'wI', 'yI', '0I', '2I', '4I', '6I', '8I', '+I', 'gJ', 'iJ', 'kJ', 'mJ', 'oJ', 'qJ',
'sJ', 'uJ', 'wJ', 'yJ', '0J', '2J', '4J', '6J', '8J', '+J', 'gK', 'iK', 'kK', 'mK', 'oK', 'qK',
'sK', 'uK', 'wK', 'yK', '0K', '2K', '4K', '6K', '8K', '+K', 'gL', 'iL', 'kL', 'mL', 'oL', 'qL',
'sL', 'uL', 'wL', 'yL', '0L', '2L', '4L', '6L', '8L', '+L', 'gM', 'iM', 'kM', 'mM', 'oM', 'qM',
'sM', 'uM', 'wM', 'yM', '0M', '2M', '4M', '6M', '8M', '+M', 'gN', 'iN', 'kN', 'mN', 'oN', 'qN',
'sN', 'uN', 'wN', 'yN', '0N', '2N', '4N', '6N', '8N', '+N', 'gO', 'iO', 'kO', 'mO', 'oO', 'qO',
'sO', 'uO', 'wO', 'yO', '0O', '2O', '4O', '6O', '8O', '+O', 'gP', 'iP', 'kP', 'mP', 'oP', 'qP',
'sP', 'uP', 'wP', 'yP', '0P', '2P', '4P', '6P', '8P', '+P'
]

for (let i = 0, len = snapshots.length; i < len; ++i) {
expect(base64VLQEncode([i - 255])).toEqual(snapshots[i])
}
})
})

0 comments on commit 7445046

Please sign in to comment.