UNPKG

11.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const fs = require("fs");
4const path = require("path");
5const resolution_1 = require("./resolution");
6class VueProgram {
7 static loadProgramConfig(typescript, configFile, compilerOptions) {
8 const extraExtensions = ['vue'];
9 const parseConfigHost = {
10 fileExists: typescript.sys.fileExists,
11 readFile: typescript.sys.readFile,
12 useCaseSensitiveFileNames: typescript.sys.useCaseSensitiveFileNames,
13 readDirectory: (rootDir, extensions, excludes, includes, depth) => {
14 return typescript.sys.readDirectory(rootDir, extensions.concat(extraExtensions), excludes, includes, depth);
15 }
16 };
17 const tsconfig = typescript.readConfigFile(configFile, typescript.sys.readFile).config;
18 tsconfig.compilerOptions = tsconfig.compilerOptions || {};
19 tsconfig.compilerOptions = Object.assign({}, tsconfig.compilerOptions, compilerOptions);
20 const parsed = typescript.parseJsonConfigFileContent(tsconfig, parseConfigHost, path.dirname(configFile));
21 parsed.options.allowNonTsExtensions = true;
22 return parsed;
23 }
24 /**
25 * Search for default wildcard or wildcard from options, we only search for that in tsconfig CompilerOptions.paths.
26 * The path is resolved with thie given substitution and includes the CompilerOptions.baseUrl (if given).
27 * If no paths given in tsconfig, then the default substitution is '[tsconfig directory]/src'.
28 * (This is a fast, simplified inspiration of what's described here: https://github.com/Microsoft/TypeScript/issues/5039)
29 */
30 static resolveNonTsModuleName(moduleName, containingFile, basedir, options) {
31 const baseUrl = options.baseUrl ? options.baseUrl : basedir;
32 const discardedSymbols = ['.', '..', '/'];
33 const wildcards = [];
34 if (options.paths) {
35 Object.keys(options.paths).forEach(key => {
36 const pathSymbol = key[0];
37 if (discardedSymbols.indexOf(pathSymbol) < 0 &&
38 wildcards.indexOf(pathSymbol) < 0) {
39 wildcards.push(pathSymbol);
40 }
41 });
42 }
43 else {
44 wildcards.push('@');
45 }
46 const isRelative = !path.isAbsolute(moduleName);
47 let correctWildcard;
48 wildcards.forEach(wildcard => {
49 if (moduleName.substr(0, 2) === `${wildcard}/`) {
50 correctWildcard = wildcard;
51 }
52 });
53 if (correctWildcard) {
54 const pattern = options.paths
55 ? options.paths[`${correctWildcard}/*`]
56 : undefined;
57 const substitution = pattern
58 ? options.paths[`${correctWildcard}/*`][0].replace('*', '')
59 : 'src';
60 moduleName = path.resolve(baseUrl, substitution, moduleName.substr(2));
61 }
62 else if (isRelative) {
63 moduleName = path.resolve(path.dirname(containingFile), moduleName);
64 }
65 return moduleName;
66 }
67 static isVue(filePath) {
68 return path.extname(filePath) === '.vue';
69 }
70 static createProgram(typescript, programConfig, basedir, files, watcher, oldProgram, userResolveModuleName, userResolveTypeReferenceDirective) {
71 const host = typescript.createCompilerHost(programConfig.options);
72 const realGetSourceFile = host.getSourceFile;
73 const { resolveModuleName, resolveTypeReferenceDirective } = resolution_1.makeResolutionFunctions(userResolveModuleName, userResolveTypeReferenceDirective);
74 host.resolveModuleNames = (moduleNames, containingFile) => {
75 return moduleNames.map(moduleName => {
76 return resolveModuleName(typescript, moduleName, containingFile, programConfig.options, host).resolvedModule;
77 });
78 };
79 host.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => {
80 return typeDirectiveNames.map(typeDirectiveName => {
81 return resolveTypeReferenceDirective(typescript, typeDirectiveName, containingFile, programConfig.options, host).resolvedTypeReferenceDirective;
82 });
83 };
84 // We need a host that can parse Vue SFCs (single file components).
85 host.getSourceFile = (filePath, languageVersion, onError) => {
86 // first check if watcher is watching file - if not - check it's mtime
87 if (!watcher.isWatchingFile(filePath)) {
88 try {
89 const stats = fs.statSync(filePath);
90 files.setMtime(filePath, stats.mtime.valueOf());
91 }
92 catch (e) {
93 // probably file does not exists
94 files.remove(filePath);
95 }
96 }
97 // get source file only if there is no source in files register
98 if (!files.has(filePath) || !files.getData(filePath).source) {
99 files.mutateData(filePath, data => {
100 data.source = realGetSourceFile(filePath, languageVersion, onError);
101 });
102 }
103 let source = files.getData(filePath).source;
104 // get typescript contents from Vue file
105 if (source && VueProgram.isVue(filePath)) {
106 const resolved = VueProgram.resolveScriptBlock(typescript, source.text);
107 source = typescript.createSourceFile(filePath, resolved.content, languageVersion, true, resolved.scriptKind);
108 }
109 return source;
110 };
111 // We need a host with special module resolution for Vue files.
112 host.resolveModuleNames = (moduleNames, containingFile) => {
113 const resolvedModules = [];
114 for (const moduleName of moduleNames) {
115 // Try to use standard resolution.
116 const { resolvedModule } = typescript.resolveModuleName(moduleName, containingFile, programConfig.options, {
117 fileExists(fileName) {
118 if (fileName.endsWith('.vue.ts')) {
119 return (host.fileExists(fileName.slice(0, -3)) ||
120 host.fileExists(fileName));
121 }
122 else {
123 return host.fileExists(fileName);
124 }
125 },
126 readFile(fileName) {
127 // This implementation is not necessary. Just for consistent behavior.
128 if (fileName.endsWith('.vue.ts') && !host.fileExists(fileName)) {
129 return host.readFile(fileName.slice(0, -3));
130 }
131 else {
132 return host.readFile(fileName);
133 }
134 }
135 });
136 if (resolvedModule) {
137 if (resolvedModule.resolvedFileName.endsWith('.vue.ts') &&
138 !host.fileExists(resolvedModule.resolvedFileName)) {
139 resolvedModule.resolvedFileName = resolvedModule.resolvedFileName.slice(0, -3);
140 }
141 resolvedModules.push(resolvedModule);
142 }
143 else {
144 // For non-ts extensions.
145 const absolutePath = VueProgram.resolveNonTsModuleName(moduleName, containingFile, basedir, programConfig.options);
146 if (VueProgram.isVue(moduleName)) {
147 resolvedModules.push({
148 resolvedFileName: absolutePath,
149 extension: '.ts'
150 });
151 }
152 else {
153 resolvedModules.push({
154 // If the file does exist, return an empty string (because we assume user has provided a ".d.ts" file for it).
155 resolvedFileName: host.fileExists(absolutePath)
156 ? ''
157 : absolutePath,
158 extension: '.ts'
159 });
160 }
161 }
162 }
163 return resolvedModules;
164 };
165 return typescript.createProgram(programConfig.fileNames, programConfig.options, host, oldProgram // re-use old program
166 );
167 }
168 static getScriptKindByLang(typescript, lang) {
169 if (lang === 'ts') {
170 return typescript.ScriptKind.TS;
171 }
172 else if (lang === 'tsx') {
173 return typescript.ScriptKind.TSX;
174 }
175 else if (lang === 'jsx') {
176 return typescript.ScriptKind.JSX;
177 }
178 else {
179 // when lang is "js" or no lang specified
180 return typescript.ScriptKind.JS;
181 }
182 }
183 static resolveScriptBlock(typescript, content) {
184 // We need to import vue-template-compiler lazily because it cannot be included it
185 // as direct dependency because it is an optional dependency of fork-ts-checker-webpack-plugin.
186 // Since its version must not mismatch with user-installed Vue.js,
187 // we should let the users install vue-template-compiler by themselves.
188 let parser;
189 try {
190 // tslint:disable-next-line
191 parser = require('vue-template-compiler');
192 }
193 catch (err) {
194 throw new Error('When you use `vue` option, make sure to install `vue-template-compiler`.');
195 }
196 const { script } = parser.parseComponent(content, {
197 pad: 'space'
198 });
199 // No <script> block
200 if (!script) {
201 return {
202 scriptKind: typescript.ScriptKind.JS,
203 content: '/* tslint:disable */\nexport default {};\n'
204 };
205 }
206 const scriptKind = VueProgram.getScriptKindByLang(typescript, script.lang);
207 // There is src attribute
208 if (script.attrs.src) {
209 // import path cannot be end with '.ts[x]'
210 const src = script.attrs.src.replace(/\.tsx?$/i, '');
211 return {
212 scriptKind,
213 // For now, ignore the error when the src file is not found
214 // since it will produce incorrect code location.
215 // It's not a large problem since it's handled on webpack side.
216 content: '/* tslint:disable */\n' +
217 '// @ts-ignore\n' +
218 `export { default } from '${src}';\n` +
219 '// @ts-ignore\n' +
220 `export * from '${src}';\n`
221 };
222 }
223 // Pad blank lines to retain diagnostics location
224 // We need to prepend `//` for each line to avoid
225 // false positive of no-consecutive-blank-lines TSLint rule
226 const offset = content.slice(0, script.start).split(/\r?\n/g).length;
227 const paddedContent = Array(offset).join('//\n') + script.content.slice(script.start);
228 return {
229 scriptKind,
230 content: paddedContent
231 };
232 }
233}
234exports.VueProgram = VueProgram;
235//# sourceMappingURL=VueProgram.js.map
\No newline at end of file