From 744504652c06bbbe1718edce5729bb7af1431135 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9C=AA=E8=A7=89=E9=9B=A8=E5=A3=B0?= <544022268@qq.com>
Date: Sat, 15 Jul 2023 01:23:52 +0800
Subject: [PATCH] feat: support sourcemap to `.vue` files (#243)
* wip: effective .vue source map
* wip: process sources' paths
* chore: update example
---
examples/vue/components/TypeProps.vue | 10 +++---
examples/vue/vite.config.ts | 13 ++++++--
src/plugin.ts | 37 ++++++++++++++++++++--
src/resolvers/vue.ts | 38 ++++++++++++++++++++--
src/utils.ts | 45 +++++++++++++++++++++++++++
tests/utils.spec.ts | 43 +++++++++++++++++++++++++
6 files changed, 174 insertions(+), 12 deletions(-)
diff --git a/examples/vue/components/TypeProps.vue b/examples/vue/components/TypeProps.vue
index f1f2ca9..bc074a3 100644
--- a/examples/vue/components/TypeProps.vue
+++ b/examples/vue/components/TypeProps.vue
@@ -1,5 +1,9 @@
+
+ {{ color }}
+
+
-
-
- {{ color }}
-
diff --git a/examples/vue/vite.config.ts b/examples/vue/vite.config.ts
index 3f071f1..03ed92e 100644
--- a/examples/vue/vite.config.ts
+++ b/examples/vue/vite.config.ts
@@ -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()
diff --git a/src/plugin.ts b/src/plugin.ts
index f71ffdd..274f1eb 100644
--- a/src/plugin.ts
+++ b/src/plugin.ts
@@ -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
@@ -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)
}
)
@@ -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)
})
)
})
diff --git a/src/resolvers/vue.ts b/src/resolvers/vue.ts
index 75bab63..81ffeee 100644
--- a/src/resolvers/vue.ts
+++ b/src/resolvers/vue.ts
@@ -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 {
@@ -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') ||
@@ -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*/)
+ 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
}
}
}
diff --git a/src/utils.ts b/src/utils.ts
index 48f8338..38173ca 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -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()
export function tryGetPkgPath(beginPath: string) {
diff --git a/tests/utils.spec.ts b/tests/utils.spec.ts
index c5c8b40..74df3ed 100644
--- a/tests/utils.spec.ts
+++ b/tests/utils.spec.ts
@@ -5,6 +5,7 @@ import { normalize, resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import {
+ base64VLQEncode,
ensureAbsolute,
ensureArray,
isNativeObj,
@@ -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])
+ }
+ })
})