diff --git a/.vscode/launch.json b/.vscode/launch.json index b7718125c..d3f98a675 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "request": "launch", "runtimeExecutable": "${execPath}", "args": [ - "--disable-extensions", + // "--disable-extensions", "--extensionDevelopmentPath=${workspaceRoot}/packages/vscode-vue" ], "outFiles": [ diff --git a/package.json b/package.json index 6cb827ced..223984572 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,14 @@ }, "devDependencies": { "@types/node": "latest", - "@volar/language-service": "1.6.9", - "typescript": "latest", + "@volar/language-service": "1.7.2", + "typescript": "~5.0.4", "vite": "latest", - "vitest": "latest" + "vitest": "latest", + "vscode-languageserver-protocol": "^3.17.3" }, "optionalDependencies": { "@lerna-lite/cli": "latest", - "@lerna-lite/publish": "latest", - "opencc": "latest" + "@lerna-lite/publish": "latest" } } diff --git a/packages/typescript-vue-plugin/package.json b/packages/typescript-vue-plugin/package.json index 9dd73e0d5..c8f0f2126 100644 --- a/packages/typescript-vue-plugin/package.json +++ b/packages/typescript-vue-plugin/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@vue/language-core": "1.7.8", - "@vue/typescript": "1.7.8" + "@vue/typescript": "1.7.8", + "vscode-uri": "^3.0.7" } } diff --git a/packages/typescript-vue-plugin/src/index.ts b/packages/typescript-vue-plugin/src/index.ts index f7265037d..1ab4c6ca8 100644 --- a/packages/typescript-vue-plugin/src/index.ts +++ b/packages/typescript-vue-plugin/src/index.ts @@ -1,6 +1,9 @@ import * as vue from '@vue/language-core'; import * as vueTs from '@vue/typescript'; import type * as ts from 'typescript/lib/tsserverlibrary'; +import { URI } from 'vscode-uri'; +import type { FileType } from '@volar/language-service'; +import * as fs from 'fs'; const init: ts.server.PluginModuleFactory = (modules) => { const { typescript: ts } = modules; @@ -35,29 +38,60 @@ const init: ts.server.PluginModuleFactory = (modules) => { }; } - const vueTsLsHost: vue.LanguageServiceHost = { - getNewLine: () => info.project.getNewLine(), - useCaseSensitiveFileNames: () => info.project.useCaseSensitiveFileNames(), - readFile: path => info.project.readFile(path), - writeFile: (path, content) => info.project.writeFile(path, content), - fileExists: path => info.project.fileExists(path), - directoryExists: path => info.project.directoryExists(path), - getDirectories: path => info.project.getDirectories(path), - readDirectory: (path, extensions, exclude, include, depth) => info.project.readDirectory(path, extensions, exclude, include, depth), - realpath: info.project.realpath ? path => info.project.realpath!(path) : undefined, + const vueTsLsHost: vue.TypeScriptLanguageHost = { getCompilationSettings: () => info.project.getCompilationSettings(), getCurrentDirectory: () => info.project.getCurrentDirectory(), - getDefaultLibFileName: () => info.project.getDefaultLibFileName(), getProjectVersion: () => info.project.getProjectVersion(), getProjectReferences: () => info.project.getProjectReferences(), getScriptFileNames: () => [ ...info.project.getScriptFileNames(), ...vueFileNames, ], - getScriptVersion: (fileName) => info.project.getScriptVersion(fileName), getScriptSnapshot: (fileName) => info.project.getScriptSnapshot(fileName), }; - const vueTsLs = vueTs.createLanguageService(vueTsLsHost, parsed.vueOptions, ts); + const uriToFileName = (uri: string) => URI.parse(uri).fsPath.replace(/\\/g, '/'); + const fileNameToUri = (fileName: string) => URI.file(fileName).toString(); + const vueTsLs = vueTs.createLanguageService(vueTsLsHost, parsed.vueOptions, ts, { + uriToFileName, + fileNameToUri, + rootUri: URI.parse(fileNameToUri(vueTsLsHost.getCurrentDirectory())), + fs: { + stat(uri) { + if (uri.startsWith('file://')) { + const stats = fs.statSync(uriToFileName(uri), { throwIfNoEntry: false }); + if (stats) { + return { + type: stats.isFile() ? 1 satisfies FileType.File + : stats.isDirectory() ? 2 satisfies FileType.Directory + : stats.isSymbolicLink() ? 64 satisfies FileType.SymbolicLink + : 0 satisfies FileType.Unknown, + ctime: stats.ctimeMs, + mtime: stats.mtimeMs, + size: stats.size, + }; + } + } + }, + readFile(uri, encoding) { + if (uri.startsWith('file://')) { + return fs.readFileSync(uriToFileName(uri), { encoding: encoding as 'utf-8' ?? 'utf-8' }); + } + }, + readDirectory(uri) { + if (uri.startsWith('file://')) { + const dirName = uriToFileName(uri); + const files = fs.existsSync(dirName) ? fs.readdirSync(dirName, { withFileTypes: true }) : []; + return files.map<[string, FileType]>(file => { + return [file.name, file.isFile() ? 1 satisfies FileType.File + : file.isDirectory() ? 2 satisfies FileType.Directory + : file.isSymbolicLink() ? 64 satisfies FileType.SymbolicLink + : 0 satisfies FileType.Unknown]; + }); + } + return []; + }, + }, + }); return new Proxy(info.languageService, { get: (target: any, property: keyof ts.LanguageService) => { diff --git a/packages/vscode-vue/package.json b/packages/vscode-vue/package.json index f351addb7..bc527d5e1 100644 --- a/packages/vscode-vue/package.json +++ b/packages/vscode-vue/package.json @@ -732,7 +732,7 @@ "devDependencies": { "@types/semver": "^7.3.13", "@types/vscode": "1.67.0", - "@volar/vscode": "1.6.9", + "@volar/vscode": "1.7.2", "@vue/language-server": "1.7.8", "esbuild": "0.15.18", "esbuild-plugin-copy": "latest", diff --git a/packages/vscode-vue/src/common.ts b/packages/vscode-vue/src/common.ts index ad0abb777..f3abe1f89 100644 --- a/packages/vscode-vue/src/common.ts +++ b/packages/vscode-vue/src/common.ts @@ -134,7 +134,7 @@ async function doActivate(context: vscode.ExtensionContext, createLc: CreateLang ); for (const client of clients) { - activateServerSys(context, client, undefined); + activateServerSys(client); } async function requestReloadVscode() { diff --git a/packages/vue-component-meta/package.json b/packages/vue-component-meta/package.json index e7ab4f043..fbfb63ff0 100644 --- a/packages/vue-component-meta/package.json +++ b/packages/vue-component-meta/package.json @@ -13,7 +13,7 @@ "directory": "packages/vue-component-meta" }, "dependencies": { - "@volar/typescript": "1.6.9", + "@volar/typescript": "1.7.2", "@vue/language-core": "1.7.8", "typesafe-path": "^0.2.2", "vue-component-type-helpers": "1.7.8" diff --git a/packages/vue-component-meta/src/index.ts b/packages/vue-component-meta/src/index.ts index a1572e8ad..1b014dfd2 100644 --- a/packages/vue-component-meta/src/index.ts +++ b/packages/vue-component-meta/src/index.ts @@ -25,10 +25,12 @@ export function createComponentMetaCheckerByJsonConfig( checkerOptions: MetaCheckerOptions = {}, ts: typeof import('typescript/lib/tsserverlibrary') = require('typescript'), ) { + const rootPath = (root as path.OsPath).replace(/\\/g, '/') as path.PosixPath; return createComponentMetaCheckerWorker( () => vue.createParsedCommandLineByJson(ts, ts.sys, root, json), checkerOptions, - path.join((root as path.OsPath).replace(/\\/g, '/') as path.PosixPath, 'jsconfig.json.global.vue' as path.PosixPath), + rootPath, + path.join(rootPath, 'jsconfig.json.global.vue' as path.PosixPath), ts, ); } @@ -38,10 +40,12 @@ export function createComponentMetaChecker( checkerOptions: MetaCheckerOptions = {}, ts: typeof import('typescript/lib/tsserverlibrary') = require('typescript'), ) { + const tsconfig = (tsconfigPath as path.OsPath).replace(/\\/g, '/') as path.PosixPath; return createComponentMetaCheckerWorker( () => vue.createParsedCommandLine(ts, ts.sys, tsconfigPath), checkerOptions, - (tsconfigPath as path.OsPath).replace(/\\/g, '/') as path.PosixPath + '.global.vue', + path.dirname(tsconfig), + tsconfig + '.global.vue', ts, ); } @@ -49,6 +53,7 @@ export function createComponentMetaChecker( function createComponentMetaCheckerWorker( loadParsedCommandLine: () => vue.ParsedCommandLine, checkerOptions: MetaCheckerOptions, + rootPath: string, globalComponentName: string, ts: typeof import('typescript/lib/tsserverlibrary'), ) { @@ -62,16 +67,12 @@ function createComponentMetaCheckerWorker( let projectVersion = 0; const scriptSnapshots = new Map(); - const scriptVersions = new Map(); - const _host: vue.LanguageServiceHost = { - ...ts.sys, - getProjectVersion: () => projectVersion.toString(), - getDefaultLibFileName: (options) => ts.getDefaultLibFilePath(options), // should use ts.getDefaultLibFilePath not ts.getDefaultLibFileName - useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames, + const _host: vue.TypeScriptLanguageHost = { + getCurrentDirectory: () => rootPath, + getProjectVersion: () => projectVersion, getCompilationSettings: () => parsedCommandLine.options, getScriptFileNames: () => fileNames, getProjectReferences: () => parsedCommandLine.projectReferences, - getScriptVersion: (fileName) => scriptVersions.get(fileName)?.toString() ?? '', getScriptSnapshot: (fileName) => { if (!scriptSnapshots.has(fileName)) { const fileText = ts.sys.readFile(fileName); @@ -88,7 +89,6 @@ function createComponentMetaCheckerWorker( updateFile(fileName: string, text: string) { fileName = (fileName as path.OsPath).replace(/\\/g, '/') as path.PosixPath; scriptSnapshots.set(fileName, ts.ScriptSnapshot.fromString(text)); - scriptVersions.set(fileName, scriptVersions.has(fileName) ? scriptVersions.get(fileName)! + 1 : 1); projectVersion++; }, deleteFile(fileName: string) { @@ -103,14 +103,13 @@ function createComponentMetaCheckerWorker( }, clearCache() { scriptSnapshots.clear(); - scriptVersions.clear(); projectVersion++; }, }; } export function baseCreate( - _host: vue.LanguageServiceHost, + _host: vue.TypeScriptLanguageHost, _vueCompilerOptions: Partial, checkerOptions: MetaCheckerOptions, globalComponentName: string, @@ -119,7 +118,7 @@ export function baseCreate( const vueCompilerOptions = vue.resolveVueCompilerOptions(_vueCompilerOptions); const globalComponentSnapshot = ts.ScriptSnapshot.fromString(''); const metaSnapshots: Record = {}; - const host = new Proxy>({ + const host = new Proxy>({ getScriptFileNames: () => { const names = _host.getScriptFileNames(); return [ @@ -150,33 +149,28 @@ export function baseCreate( } return _host[prop as keyof typeof _host]; }, - }) as vue.LanguageServiceHost; + }) as vue.TypeScriptLanguageHost; const vueLanguages = ts ? vue.createLanguages( host.getCompilationSettings(), vueCompilerOptions, ts, ) : []; - const proxyApis: Partial = checkerOptions.forceUseTs ? { - getScriptKind: (fileName) => { + const core = vue.createLanguageContext(host, vueLanguages); + const tsLs = createLanguageService(core, ts, ts.sys); + + if (checkerOptions.forceUseTs) { + const getScriptKind = tsLs.__internal__.languageServiceHost.getScriptKind; + tsLs.__internal__.languageServiceHost.getScriptKind = (fileName) => { if (fileName.endsWith('.vue.js')) { return ts.ScriptKind.TS; } if (fileName.endsWith('.vue.jsx')) { return ts.ScriptKind.TSX; } - return host.getScriptKind!(fileName); - }, - } : {}; - const proxyHost = new Proxy(host, { - get(target, propKey: keyof ts.LanguageServiceHost) { - if (propKey in proxyApis) { - return proxyApis[propKey]; - } - return target[propKey]; - } - }); - const core = vue.createLanguageContext(proxyHost, vueLanguages); - const tsLs = createLanguageService(core, ts); + return getScriptKind!(fileName); + }; + } + let globalPropNames: string[] | undefined; return { @@ -626,7 +620,7 @@ function createSchemaResolvers( const [virtualFile] = core.virtualFiles.getVirtualFile(fileName); if (virtualFile) { const maps = core.virtualFiles.getMaps(virtualFile); - for (const [source, map] of maps) { + for (const [source, [_, map]] of maps) { const start = map.toSourceOffset(declaration.getStart()); const end = map.toSourceOffset(declaration.getEnd()); if (start && end) { diff --git a/packages/vue-language-core/package.json b/packages/vue-language-core/package.json index c8ce504de..1b473b093 100644 --- a/packages/vue-language-core/package.json +++ b/packages/vue-language-core/package.json @@ -13,8 +13,8 @@ "directory": "packages/vue-language-core" }, "dependencies": { - "@volar/language-core": "1.6.9", - "@volar/source-map": "1.6.9", + "@volar/language-core": "1.7.2", + "@volar/source-map": "1.7.2", "@vue/compiler-dom": "^3.3.0", "@vue/reactivity": "^3.3.0", "@vue/shared": "^3.3.0", diff --git a/packages/vue-language-core/src/generators/template.ts b/packages/vue-language-core/src/generators/template.ts index 78132f5e7..58649caf9 100644 --- a/packages/vue-language-core/src/generators/template.ts +++ b/packages/vue-language-core/src/generators/template.ts @@ -838,7 +838,7 @@ export function generate( 'default', 'template', [slotDir.loc.start.offset, slotDir.loc.start.offset + (slotDir.loc.source.startsWith('#') ? '#'.length : slotDir.loc.source.startsWith('v-slot:') ? 'v-slot:'.length : 0)], - capabilitiesPresets.slotName, + { ...capabilitiesPresets.slotName, completion: false }, ]) ), ['', 'template', (slotDir.arg ?? slotDir).loc.end.offset, capabilitiesPresets.diagnosticOnly], diff --git a/packages/vue-language-core/src/languageModule.ts b/packages/vue-language-core/src/languageModule.ts index 0292c08a5..45d03759f 100644 --- a/packages/vue-language-core/src/languageModule.ts +++ b/packages/vue-language-core/src/languageModule.ts @@ -12,7 +12,7 @@ export function createLanguage( _vueCompilerOptions: Partial = {}, ts: typeof import('typescript/lib/tsserverlibrary') = require('typescript'), codegenStack: boolean = false, -): Language { +) { const vueCompilerOptions = resolveVueCompilerOptions(_vueCompilerOptions); @@ -24,8 +24,7 @@ export function createLanguage( vueCompilerOptions, codegenStack, ); - const sharedTypesSnapshot = ts.ScriptSnapshot.fromString(sharedTypes.getTypesCode(vueCompilerOptions)); - const languageModule: Language = { + const languageModule: Language = { createVirtualFile(fileName, snapshot, languageId) { if ( languageId === 'vue' @@ -37,35 +36,22 @@ export function createLanguage( return new VueFile(fileName, snapshot, vueCompilerOptions, vueLanguagePlugin, ts, codegenStack); } }, - updateVirtualFile(sourceFile: VueFile, snapshot) { + updateVirtualFile(sourceFile, snapshot) { sourceFile.update(snapshot); }, resolveHost(host) { + const sharedTypesSnapshot = ts.ScriptSnapshot.fromString(sharedTypes.getTypesCode(vueCompilerOptions)); + const sharedTypesFileName = path.join(host.getCurrentDirectory(), sharedTypes.baseName); return { ...host, - fileExists(fileName) { - const basename = path.basename(fileName); - if (basename === sharedTypes.baseName) { - return true; - } - return host.fileExists(fileName); - }, getScriptFileNames() { return [ - path.join(host.getCurrentDirectory(), sharedTypes.baseName), + sharedTypesFileName, ...host.getScriptFileNames(), ]; }, - getScriptVersion(fileName) { - const basename = path.basename(fileName); - if (basename === sharedTypes.baseName) { - return ''; - } - return host.getScriptVersion(fileName); - }, getScriptSnapshot(fileName) { - const basename = path.basename(fileName); - if (basename === sharedTypes.baseName) { + if (fileName === sharedTypesFileName) { return sharedTypesSnapshot; } return host.getScriptSnapshot(fileName); diff --git a/packages/vue-language-core/src/utils/ts.ts b/packages/vue-language-core/src/utils/ts.ts index cf87d2982..021974f63 100644 --- a/packages/vue-language-core/src/utils/ts.ts +++ b/packages/vue-language-core/src/utils/ts.ts @@ -13,9 +13,8 @@ export function createParsedCommandLineByJson( json: any, ): ParsedCommandLine { - const tsConfigPath = path.join(rootDir, 'jsconfig.json'); const proxyHost = proxyParseConfigHostForExtendConfigPaths(parseConfigHost); - ts.parseJsonConfigFileContent(json, proxyHost.host, rootDir, {}, tsConfigPath); + ts.parseJsonConfigFileContent(json, proxyHost.host, rootDir, {}, rootDir + '/jsconfig.json'); let vueOptions: Partial = {}; @@ -31,9 +30,9 @@ export function createParsedCommandLineByJson( const parsed = ts.parseJsonConfigFileContent( json, proxyHost.host, - path.dirname(tsConfigPath), + rootDir, {}, - tsConfigPath, + rootDir + '/jsconfig.json', undefined, (vueOptions.extensions ?? ['.vue']).map(extension => ({ extension: extension.slice(1), diff --git a/packages/vue-language-plugin-pug/package.json b/packages/vue-language-plugin-pug/package.json index 7154463f4..30864331a 100644 --- a/packages/vue-language-plugin-pug/package.json +++ b/packages/vue-language-plugin-pug/package.json @@ -16,7 +16,7 @@ "@vue/language-core": "1.7.8" }, "dependencies": { - "@volar/source-map": "1.6.9", - "volar-service-pug": "0.0.4" + "@volar/source-map": "1.7.2", + "volar-service-pug": "0.0.6" } } diff --git a/packages/vue-language-server/package.json b/packages/vue-language-server/package.json index c154f357a..465455fca 100644 --- a/packages/vue-language-server/package.json +++ b/packages/vue-language-server/package.json @@ -16,8 +16,9 @@ "directory": "packages/vue-language-server" }, "dependencies": { - "@volar/language-core": "1.6.9", - "@volar/language-server": "1.6.9", + "@volar/language-core": "1.7.2", + "@volar/language-server": "1.7.2", + "@volar/typescript": "1.7.2", "@vue/language-core": "1.7.8", "@vue/language-service": "1.7.8", "vscode-languageserver-protocol": "^3.17.3", diff --git a/packages/vue-language-server/src/languageServerPlugin.ts b/packages/vue-language-server/src/languageServerPlugin.ts index 11728f76c..f1dab2454 100644 --- a/packages/vue-language-server/src/languageServerPlugin.ts +++ b/packages/vue-language-server/src/languageServerPlugin.ts @@ -8,6 +8,7 @@ import { VueServerInitializationOptions } from './types'; import type * as ts from 'typescript/lib/tsserverlibrary'; import * as componentMeta from 'vue-component-meta'; import { VueCompilerOptions } from '@vue/language-core'; +import { createSys } from '@volar/typescript'; export function createServerPlugin(connection: Connection) { @@ -20,7 +21,7 @@ export function createServerPlugin(connection: Connection) { const ts = modules.typescript; const vueFileExtensions: string[] = ['vue']; - const hostToVueOptions = new WeakMap>(); + const hostToVueOptions = new WeakMap>(); if (initOptions.additionalExtensions) { for (const additionalExtension of initOptions.additionalExtensions) { @@ -31,9 +32,9 @@ export function createServerPlugin(connection: Connection) { return { extraFileExtensions: vueFileExtensions.map(ext => ({ extension: ext, isMixedContent: true, scriptKind: ts.ScriptKind.Deferred })), watchFileExtensions: ['js', 'cjs', 'mjs', 'ts', 'cts', 'mts', 'jsx', 'tsx', 'json', ...vueFileExtensions], - resolveConfig(config, ctx) { + async resolveConfig(config, ctx) { - const vueOptions = getVueCompilerOptions(); + const vueOptions = await getVueCompilerOptions(); const vueLanguageServiceSettings = getVueLanguageServiceSettings(); if (ctx) { @@ -49,20 +50,27 @@ export function createServerPlugin(connection: Connection) { initOptions.codegenStack, ); - function getVueCompilerOptions() { + async function getVueCompilerOptions() { const ts = modules.typescript; - let vueOptions: Partial; - - if (typeof ctx?.project.tsConfig === 'string' && ts) { - vueOptions = vue2.createParsedCommandLine(ts, ctx.sys, ctx.project.tsConfig).vueOptions; - } - else if (typeof ctx?.project.tsConfig === 'object' && ts) { - vueOptions = vue2.createParsedCommandLineByJson(ts, ctx.sys, ctx.host.getCurrentDirectory(), ctx.project.tsConfig).vueOptions; - } - else { - vueOptions = {}; + let vueOptions: Partial = {}; + + if (ts && ctx) { + const sys = createSys(undefined, ts, ctx.env); + let sysVersion: number | undefined; + let newSysVersion = await sys.sync(); + + while (sysVersion !== newSysVersion) { + sysVersion = newSysVersion; + if (typeof ctx?.project.tsConfig === 'string' && ts) { + vueOptions = vue2.createParsedCommandLine(ts, sys, ctx.project.tsConfig).vueOptions; + } + else if (typeof ctx?.project.tsConfig === 'object' && ts) { + vueOptions = vue2.createParsedCommandLineByJson(ts, sys, ctx.host.getCurrentDirectory(), ctx.project.tsConfig).vueOptions; + } + newSysVersion = await sys.sync(); + } } vueOptions.extensions = [ @@ -115,14 +123,14 @@ export function createServerPlugin(connection: Connection) { connection.onRequest(GetConvertAttrCasingEditsRequest.type, async params => { const languageService = await getService(params.textDocument.uri); if (languageService) { - const vueOptions = hostToVueOptions.get(languageService.context.core.host); + const vueOptions = hostToVueOptions.get(languageService.context.host); if (vueOptions) { return nameCasing.convertAttrName(ts, languageService.context, params.textDocument.uri, params.casing); } } }); - const checkers = new WeakMap(); + const checkers = new WeakMap(); connection.onRequest(GetComponentMeta.type, async params => { @@ -130,16 +138,18 @@ export function createServerPlugin(connection: Connection) { if (!languageService) return; - let checker = checkers.get(languageService.context.core.host); + const host = languageService.context.host; + + let checker = checkers.get(host); if (!checker) { checker = componentMeta.baseCreate( - languageService.context.core.host, - hostToVueOptions.get(languageService.context.core.host)!, + host, + hostToVueOptions.get(host)!, {}, - languageService.context.core.host.getCurrentDirectory() + '/tsconfig.json.global.vue', + host.getCurrentDirectory() + '/tsconfig.json.global.vue', ts, ); - checkers.set(languageService.context.core.host, checker); + checkers.set(host, checker); } return checker.getComponentMeta(env.uriToFileName(params.uri)); }); diff --git a/packages/vue-language-service/data/language-blocks/zh-tw.json b/packages/vue-language-service/data/language-blocks/zh-tw.json deleted file mode 100644 index 67c1551da..000000000 --- a/packages/vue-language-service/data/language-blocks/zh-tw.json +++ /dev/null @@ -1,626 +0,0 @@ -{ - "version": 1.1, - "tags": [ - { - "name": "template", - "attributes": [ - { - "name": "src", - "description": { - "kind": "markdown", - "value": "\n如果你更喜歡將 `*.vue` 組件分散到多個文件中,可以為一個語塊使用 `src` 這個 attribute 來導入一個外部文件:\n\n```vue\n\n\n\n```\n\n請注意 `src` 導入和 JS 模塊導入遵循相同的路徑解析規則,這意味著:\n\n- 相對路徑需要以 `./` 開頭\n- 你也可以從 npm 依賴中導入資源\n\n```vue\n\n\n```\n\n注意對不同預處理器的集成會根據你所使用的工具鏈而有所不同,具體細節請查看相應的工具鏈文檔來確認:\n\n- [Vite](https://cn.vitejs.dev/guide/features.html#css-pre-processors)\n- [Vue CLI](https://cli.vuejs.org/zh/guide/css.html#%E9%A2%84%E5%A4%84%E7%90%86%E5%99%A8)\n- [webpack + vue-loader](https://vue-loader.vuejs.org/zh/guide/pre-processors.html#%E4%BD%BF%E7%94%A8%E9%A2%84%E5%A4%84%E7%90%86%E5%99%A8)\n" - }, - "values": [ - { - "name": "html" - }, - { - "name": "pug" - } - ], - "references": [ - { - "name": "en", - "url": "https://vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "zh-cn", - "url": "https://cn.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "ja", - "url": "https://ja.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "ua", - "url": "https://ua.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "fr", - "url": "https://fr.vuejs.org/api/sfc-spec.html#pre-processors" - }, - { - "name": "ko", - "url": "https://ko.vuejs.org/api/sfc-spec.html#pre-processors" - } - ] - } - ], - "description": { - "kind": "markdown", - "value": "\n- 每個 `*.vue` 文件最多可以包含一個頂層 `