Skip to content

Commit b065d70

Browse files
committed
resolve src attribute of .vue file
1 parent 06a5917 commit b065d70

File tree

7 files changed

+302
-54
lines changed

7 files changed

+302
-54
lines changed

package.json

+3-4
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,10 @@
7171
"ts-loader": "4.3.0",
7272
"tslint": "^5.0.0",
7373
"typescript": "^2.6.2",
74-
"vue": "^2.5.9",
74+
"vue": "^2.5.16",
7575
"vue-class-component": "^6.1.1",
7676
"vue-loader": "^15.2.4",
77-
"vue-template-compiler": "^2.5.9",
77+
"vue-template-compiler": "^2.5.16",
7878
"webpack": "^4.0.0"
7979
},
8080
"peerDependencies": {
@@ -92,7 +92,6 @@
9292
"lodash.startswith": "^4.2.1",
9393
"minimatch": "^3.0.4",
9494
"resolve": "^1.5.0",
95-
"tapable": "^1.0.0",
96-
"vue-parser": "^1.1.5"
95+
"tapable": "^1.0.0"
9796
}
9897
}

src/VueProgram.ts

+63-29
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import path = require('path');
33
import ts = require('typescript');
44
import FilesRegister = require('./FilesRegister');
55
import FilesWatcher = require('./FilesWatcher');
6-
import vueParser = require('vue-parser');
6+
// tslint:disable-next-line
7+
import vueCompiler = require('vue-template-compiler');
8+
9+
interface ResolvedScript {
10+
scriptKind: ts.ScriptKind;
11+
content: string;
12+
}
713

814
class VueProgram {
915
static loadProgramConfig(configFile: string) {
@@ -85,19 +91,6 @@ class VueProgram {
8591
const host = ts.createCompilerHost(programConfig.options);
8692
const realGetSourceFile = host.getSourceFile;
8793

88-
const getScriptKind = (lang: string) => {
89-
if (lang === "ts") {
90-
return ts.ScriptKind.TS;
91-
} else if (lang === "tsx") {
92-
return ts.ScriptKind.TSX;
93-
} else if (lang === "jsx") {
94-
return ts.ScriptKind.JSX;
95-
} else {
96-
// when lang is "js" or no lang specified
97-
return ts.ScriptKind.JS;
98-
}
99-
}
100-
10194
// We need a host that can parse Vue SFCs (single file components).
10295
host.getSourceFile = (filePath, languageVersion, onError) => {
10396
// first check if watcher is watching file - if not - check it's mtime
@@ -123,21 +116,8 @@ class VueProgram {
123116

124117
// get typescript contents from Vue file
125118
if (source && VueProgram.isVue(filePath)) {
126-
let parsed: string;
127-
let kind: ts.ScriptKind;
128-
for (const lang of ['ts', 'tsx', 'js', 'jsx']) {
129-
parsed = vueParser.parse(source.text, 'script', { lang: [lang], emptyExport: false });
130-
if (parsed) {
131-
kind = getScriptKind(lang);
132-
break;
133-
}
134-
}
135-
if (!parsed) {
136-
// when script tag has no lang, or no script tag given
137-
parsed = vueParser.parse(source.text, 'script');
138-
kind = ts.ScriptKind.JS;
139-
}
140-
source = ts.createSourceFile(filePath, parsed, languageVersion, true, kind);
119+
const resolved = VueProgram.resolveScriptBlock(source.text);
120+
source = ts.createSourceFile(filePath, resolved.content, languageVersion, true, resolved.scriptKind);
141121
}
142122

143123
return source;
@@ -201,6 +181,60 @@ class VueProgram {
201181
oldProgram // re-use old program
202182
);
203183
}
184+
185+
private static getScriptKindByLang(lang: string) {
186+
if (lang === "ts") {
187+
return ts.ScriptKind.TS;
188+
} else if (lang === "tsx") {
189+
return ts.ScriptKind.TSX;
190+
} else if (lang === "jsx") {
191+
return ts.ScriptKind.JSX;
192+
} else {
193+
// when lang is "js" or no lang specified
194+
return ts.ScriptKind.JS;
195+
}
196+
}
197+
198+
private static resolveScriptBlock(content: string): ResolvedScript {
199+
// We need to import vue-template-compiler dinamically because cannot include it as direct dependency.
200+
// The reason is that it should not mismatch the versions with user-installed vue-template-compiler
201+
// while it is an optional dependency for fork-ts-checker-webpack-plugin.
202+
// So here we load the compiler and throws when it is not in the user's dependencies.
203+
let parser: typeof vueCompiler;
204+
try {
205+
// tslint:disable-next-line
206+
parser = require('vue-template-compiler');
207+
} catch (err) {
208+
throw new Error('When you use `vue` option, make sure to install `vue-template-compiler`.');
209+
}
210+
211+
const { script } = parser.parseComponent(content, {
212+
pad: 'line'
213+
});
214+
215+
// No <script> block
216+
if (!script) {
217+
return {
218+
scriptKind: ts.ScriptKind.JS,
219+
content: '// tslint:disable\nexport default {};\n',
220+
};
221+
}
222+
223+
const scriptKind = VueProgram.getScriptKindByLang(script.lang);
224+
225+
// There is src attribute
226+
if (script.attrs.src) {
227+
return {
228+
scriptKind,
229+
content: `// tslint:disable\nexport { default } from '${src}';`,
230+
};
231+
}
232+
233+
return {
234+
scriptKind,
235+
content: script.content
236+
};
237+
}
204238
}
205239

206240
export = VueProgram;

src/tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"suppressImplicitAnyIndexErrors": true,
1010
"strictNullChecks": false,
1111
"lib": [
12-
"es5", "es2015.core"
12+
"es5", "es2015.core", "dom"
1313
],
1414
"module": "commonjs",
1515
"moduleResolution": "node",

src/types/vue-template-compiler.d.ts

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/**
2+
* This declaration is copied from https://github.com/vuejs/vue/pull/7918
3+
* which may included vue-template-compiler v2.6.0.
4+
*/
5+
declare module 'vue-template-compiler' {
6+
import Vue, { VNode } from "vue"
7+
8+
/*
9+
* Template compilation options / results
10+
*/
11+
interface CompilerOptions {
12+
modules?: ModuleOptions[];
13+
directives?: Record<string, DirectiveFunction>;
14+
preserveWhitespace?: boolean;
15+
}
16+
17+
interface CompiledResult {
18+
ast: ASTElement | undefined;
19+
render: string;
20+
staticRenderFns: string[];
21+
errors: string[];
22+
tips: string[];
23+
}
24+
25+
interface CompiledResultFunctions {
26+
render: () => VNode;
27+
staticRenderFns: (() => VNode)[];
28+
}
29+
30+
interface ModuleOptions {
31+
preTransformNode: (el: ASTElement) => ASTElement | undefined;
32+
transformNode: (el: ASTElement) => ASTElement | undefined;
33+
postTransformNode: (el: ASTElement) => void;
34+
genData: (el: ASTElement) => string;
35+
transformCode?: (el: ASTElement, code: string) => string;
36+
staticKeys?: string[];
37+
}
38+
39+
type DirectiveFunction = (node: ASTElement, directiveMeta: ASTDirective) => void;
40+
41+
/*
42+
* AST Types
43+
*/
44+
45+
/**
46+
* - 0: FALSE - whole sub tree un-optimizable
47+
* - 1: FULL - whole sub tree optimizable
48+
* - 2: SELF - self optimizable but has some un-optimizable children
49+
* - 3: CHILDREN - self un-optimizable but have fully optimizable children
50+
* - 4: PARTIAL - self un-optimizable with some un-optimizable children
51+
*/
52+
export type SSROptimizability = 0 | 1 | 2 | 3 | 4
53+
54+
export interface ASTModifiers {
55+
[key: string]: boolean;
56+
}
57+
58+
export interface ASTIfCondition {
59+
exp: string | undefined;
60+
block: ASTElement;
61+
}
62+
63+
export interface ASTElementHandler {
64+
value: string;
65+
params?: any[];
66+
modifiers: ASTModifiers | undefined;
67+
}
68+
69+
export interface ASTElementHandlers {
70+
[key: string]: ASTElementHandler | ASTElementHandler[];
71+
}
72+
73+
export interface ASTDirective {
74+
name: string;
75+
rawName: string;
76+
value: string;
77+
arg: string | undefined;
78+
modifiers: ASTModifiers | undefined;
79+
}
80+
81+
export type ASTNode = ASTElement | ASTText | ASTExpression;
82+
83+
export interface ASTElement {
84+
type: 1;
85+
tag: string;
86+
attrsList: { name: string; value: any }[];
87+
attrsMap: Record<string, any>;
88+
parent: ASTElement | undefined;
89+
children: ASTNode[];
90+
91+
processed?: true;
92+
93+
static?: boolean;
94+
staticRoot?: boolean;
95+
staticInFor?: boolean;
96+
staticProcessed?: boolean;
97+
hasBindings?: boolean;
98+
99+
text?: string;
100+
attrs?: { name: string; value: any }[];
101+
props?: { name: string; value: string }[];
102+
plain?: boolean;
103+
pre?: true;
104+
ns?: string;
105+
106+
component?: string;
107+
inlineTemplate?: true;
108+
transitionMode?: string | null;
109+
slotName?: string;
110+
slotTarget?: string;
111+
slotScope?: string;
112+
scopedSlots?: Record<string, ASTElement>;
113+
114+
ref?: string;
115+
refInFor?: boolean;
116+
117+
if?: string;
118+
ifProcessed?: boolean;
119+
elseif?: string;
120+
else?: true;
121+
ifConditions?: ASTIfCondition[];
122+
123+
for?: string;
124+
forProcessed?: boolean;
125+
key?: string;
126+
alias?: string;
127+
iterator1?: string;
128+
iterator2?: string;
129+
130+
staticClass?: string;
131+
classBinding?: string;
132+
staticStyle?: string;
133+
styleBinding?: string;
134+
events?: ASTElementHandlers;
135+
nativeEvents?: ASTElementHandlers;
136+
137+
transition?: string | true;
138+
transitionOnAppear?: boolean;
139+
140+
model?: {
141+
value: string;
142+
callback: string;
143+
expression: string;
144+
};
145+
146+
directives?: ASTDirective[];
147+
148+
forbidden?: true;
149+
once?: true;
150+
onceProcessed?: boolean;
151+
wrapData?: (code: string) => string;
152+
wrapListeners?: (code: string) => string;
153+
154+
// 2.4 ssr optimization
155+
ssrOptimizability?: SSROptimizability;
156+
157+
// weex specific
158+
appendAsTree?: boolean;
159+
}
160+
161+
export interface ASTExpression {
162+
type: 2;
163+
expression: string;
164+
text: string;
165+
tokens: (string | Record<string, any>)[];
166+
static?: boolean;
167+
// 2.4 ssr optimization
168+
ssrOptimizability?: SSROptimizability;
169+
}
170+
171+
export interface ASTText {
172+
type: 3;
173+
text: string;
174+
static?: boolean;
175+
isComment?: boolean;
176+
// 2.4 ssr optimization
177+
ssrOptimizability?: SSROptimizability;
178+
}
179+
180+
/*
181+
* SFC parser related types
182+
*/
183+
interface SFCParserOptions {
184+
pad?: true | 'line' | 'space';
185+
}
186+
187+
export interface SFCBlock {
188+
type: string;
189+
content: string;
190+
attrs: Record<string, string>;
191+
start?: number;
192+
end?: number;
193+
lang?: string;
194+
src?: string;
195+
scoped?: boolean;
196+
module?: string | boolean;
197+
}
198+
199+
export interface SFCDescriptor {
200+
template: SFCBlock | undefined;
201+
script: SFCBlock | undefined;
202+
styles: SFCBlock[];
203+
customBlocks: SFCBlock[];
204+
}
205+
206+
/*
207+
* Exposed functions
208+
*/
209+
export function compile(
210+
template: string,
211+
options?: CompilerOptions
212+
): CompiledResult;
213+
214+
export function compileToFunctions(template: string): CompiledResultFunctions;
215+
216+
export function ssrCompile(
217+
template: string,
218+
options?: CompilerOptions
219+
): CompiledResult;
220+
221+
export function ssrCompileToFunctions(template: string): CompiledResultFunctions;
222+
223+
export function parseComponent(
224+
file: string,
225+
options?: SFCParserOptions
226+
): SFCDescriptor;
227+
}

test/integration/vue.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ describe('[INTEGRATION] vue', function () {
167167
var source = checker.program.getSourceFile(sourceFilePath);
168168
expect(source).to.not.be.undefined;
169169
// remove padding lines
170-
var text = source.text.replace(/^\s*\/\/.*$\r*\n/gm, '');
170+
var text = source.text.replace(/^\s*\/\/.*$\r*\n/gm, '').trim();
171171
expect(text.startsWith('/* OK */')).to.be.true;
172172
});
173173
});

test/unit/VueProgram.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ describe('[UNIT] VueProgram', function () {
4747
var options = {};
4848
var moduleName = '@/test.vue';
4949

50-
resolvedModuleName = VueProgram.resolveNonTsModuleName(moduleName, containingFile, basedir, options);
50+
var resolvedModuleName = VueProgram.resolveNonTsModuleName(moduleName, containingFile, basedir, options);
5151
expect(resolvedModuleName).to.be.equal('/base/dir/src/test.vue');
5252

5353
options.baseUrl = '/baseurl1';

0 commit comments

Comments
 (0)