Skip to content

Commit

Permalink
Merge pull request #130 from ktsn/fix-vue-src
Browse files Browse the repository at this point in the history
fix(vue): resolve src attribute on the script block on Vue files
  • Loading branch information
johnnyreilly authored Jul 26, 2018
2 parents 06a5917 + 706298d commit 6a83c7d
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 54 deletions.
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,10 @@
"ts-loader": "4.3.0",
"tslint": "^5.0.0",
"typescript": "^2.6.2",
"vue": "^2.5.9",
"vue": "^2.5.16",
"vue-class-component": "^6.1.1",
"vue-loader": "^15.2.4",
"vue-template-compiler": "^2.5.9",
"vue-template-compiler": "^2.5.16",
"webpack": "^4.0.0"
},
"peerDependencies": {
Expand All @@ -92,7 +92,6 @@
"lodash.startswith": "^4.2.1",
"minimatch": "^3.0.4",
"resolve": "^1.5.0",
"tapable": "^1.0.0",
"vue-parser": "^1.1.5"
"tapable": "^1.0.0"
}
}
102 changes: 73 additions & 29 deletions src/VueProgram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ import path = require('path');
import ts = require('typescript');
import FilesRegister = require('./FilesRegister');
import FilesWatcher = require('./FilesWatcher');
import vueParser = require('vue-parser');
// tslint:disable-next-line
import vueCompiler = require('vue-template-compiler');

interface ResolvedScript {
scriptKind: ts.ScriptKind;
content: string;
}

class VueProgram {
static loadProgramConfig(configFile: string) {
Expand Down Expand Up @@ -85,19 +91,6 @@ class VueProgram {
const host = ts.createCompilerHost(programConfig.options);
const realGetSourceFile = host.getSourceFile;

const getScriptKind = (lang: string) => {
if (lang === "ts") {
return ts.ScriptKind.TS;
} else if (lang === "tsx") {
return ts.ScriptKind.TSX;
} else if (lang === "jsx") {
return ts.ScriptKind.JSX;
} else {
// when lang is "js" or no lang specified
return ts.ScriptKind.JS;
}
}

// We need a host that can parse Vue SFCs (single file components).
host.getSourceFile = (filePath, languageVersion, onError) => {
// first check if watcher is watching file - if not - check it's mtime
Expand All @@ -123,21 +116,8 @@ class VueProgram {

// get typescript contents from Vue file
if (source && VueProgram.isVue(filePath)) {
let parsed: string;
let kind: ts.ScriptKind;
for (const lang of ['ts', 'tsx', 'js', 'jsx']) {
parsed = vueParser.parse(source.text, 'script', { lang: [lang], emptyExport: false });
if (parsed) {
kind = getScriptKind(lang);
break;
}
}
if (!parsed) {
// when script tag has no lang, or no script tag given
parsed = vueParser.parse(source.text, 'script');
kind = ts.ScriptKind.JS;
}
source = ts.createSourceFile(filePath, parsed, languageVersion, true, kind);
const resolved = VueProgram.resolveScriptBlock(source.text);
source = ts.createSourceFile(filePath, resolved.content, languageVersion, true, resolved.scriptKind);
}

return source;
Expand Down Expand Up @@ -201,6 +181,70 @@ class VueProgram {
oldProgram // re-use old program
);
}

private static getScriptKindByLang(lang: string) {
if (lang === "ts") {
return ts.ScriptKind.TS;
} else if (lang === "tsx") {
return ts.ScriptKind.TSX;
} else if (lang === "jsx") {
return ts.ScriptKind.JSX;
} else {
// when lang is "js" or no lang specified
return ts.ScriptKind.JS;
}
}

private static resolveScriptBlock(content: string): ResolvedScript {
// We need to import vue-template-compiler lazily because it cannot be included it
// as direct dependency because it is an optional dependency of fork-ts-checker-webpack-plugin.
// Since its version must not mismatch with user-installed Vue.js,
// we should let the users install vue-template-compiler by themselves.
let parser: typeof vueCompiler;
try {
// tslint:disable-next-line
parser = require('vue-template-compiler');
} catch (err) {
throw new Error('When you use `vue` option, make sure to install `vue-template-compiler`.');
}

const { script } = parser.parseComponent(content, {
pad: 'line'
});

// No <script> block
if (!script) {
return {
scriptKind: ts.ScriptKind.JS,
content: '/* tslint:disable */\nexport default {};\n'
};
}

const scriptKind = VueProgram.getScriptKindByLang(script.lang);

// There is src attribute
if (script.attrs.src) {
// import path cannot be end with '.ts[x]'
const src = script.attrs.src.replace(/\.tsx?$/i, '');
return {
scriptKind,

// For now, ignore the error when the src file is not found
// since it will produce incorrect code location.
// It's not a large problem since it's handled on webpack side.
content: '/* tslint:disable */\n'
+ '// @ts-ignore\n'
+ `export { default } from '${src}';\n`
+ '// @ts-ignore\n'
+ `export * from '${src}';\n`
};
}

return {
scriptKind,
content: script.content
};
}
}

export = VueProgram;
2 changes: 1 addition & 1 deletion src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"suppressImplicitAnyIndexErrors": true,
"strictNullChecks": false,
"lib": [
"es5", "es2015.core"
"es5", "es2015.core", "dom"
],
"module": "commonjs",
"moduleResolution": "node",
Expand Down
227 changes: 227 additions & 0 deletions src/types/vue-template-compiler.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/**
* This declaration is copied from https://github.com/vuejs/vue/pull/7918
* which may included vue-template-compiler v2.6.0.
*/
declare module 'vue-template-compiler' {
import Vue, { VNode } from "vue"

/*
* Template compilation options / results
*/
interface CompilerOptions {
modules?: ModuleOptions[];
directives?: Record<string, DirectiveFunction>;
preserveWhitespace?: boolean;
}

interface CompiledResult {
ast: ASTElement | undefined;
render: string;
staticRenderFns: string[];
errors: string[];
tips: string[];
}

interface CompiledResultFunctions {
render: () => VNode;
staticRenderFns: (() => VNode)[];
}

interface ModuleOptions {
preTransformNode: (el: ASTElement) => ASTElement | undefined;
transformNode: (el: ASTElement) => ASTElement | undefined;
postTransformNode: (el: ASTElement) => void;
genData: (el: ASTElement) => string;
transformCode?: (el: ASTElement, code: string) => string;
staticKeys?: string[];
}

type DirectiveFunction = (node: ASTElement, directiveMeta: ASTDirective) => void;

/*
* AST Types
*/

/**
* - 0: FALSE - whole sub tree un-optimizable
* - 1: FULL - whole sub tree optimizable
* - 2: SELF - self optimizable but has some un-optimizable children
* - 3: CHILDREN - self un-optimizable but have fully optimizable children
* - 4: PARTIAL - self un-optimizable with some un-optimizable children
*/
export type SSROptimizability = 0 | 1 | 2 | 3 | 4

export interface ASTModifiers {
[key: string]: boolean;
}

export interface ASTIfCondition {
exp: string | undefined;
block: ASTElement;
}

export interface ASTElementHandler {
value: string;
params?: any[];
modifiers: ASTModifiers | undefined;
}

export interface ASTElementHandlers {
[key: string]: ASTElementHandler | ASTElementHandler[];
}

export interface ASTDirective {
name: string;
rawName: string;
value: string;
arg: string | undefined;
modifiers: ASTModifiers | undefined;
}

export type ASTNode = ASTElement | ASTText | ASTExpression;

export interface ASTElement {
type: 1;
tag: string;
attrsList: { name: string; value: any }[];
attrsMap: Record<string, any>;
parent: ASTElement | undefined;
children: ASTNode[];

processed?: true;

static?: boolean;
staticRoot?: boolean;
staticInFor?: boolean;
staticProcessed?: boolean;
hasBindings?: boolean;

text?: string;
attrs?: { name: string; value: any }[];
props?: { name: string; value: string }[];
plain?: boolean;
pre?: true;
ns?: string;

component?: string;
inlineTemplate?: true;
transitionMode?: string | null;
slotName?: string;
slotTarget?: string;
slotScope?: string;
scopedSlots?: Record<string, ASTElement>;

ref?: string;
refInFor?: boolean;

if?: string;
ifProcessed?: boolean;
elseif?: string;
else?: true;
ifConditions?: ASTIfCondition[];

for?: string;
forProcessed?: boolean;
key?: string;
alias?: string;
iterator1?: string;
iterator2?: string;

staticClass?: string;
classBinding?: string;
staticStyle?: string;
styleBinding?: string;
events?: ASTElementHandlers;
nativeEvents?: ASTElementHandlers;

transition?: string | true;
transitionOnAppear?: boolean;

model?: {
value: string;
callback: string;
expression: string;
};

directives?: ASTDirective[];

forbidden?: true;
once?: true;
onceProcessed?: boolean;
wrapData?: (code: string) => string;
wrapListeners?: (code: string) => string;

// 2.4 ssr optimization
ssrOptimizability?: SSROptimizability;

// weex specific
appendAsTree?: boolean;
}

export interface ASTExpression {
type: 2;
expression: string;
text: string;
tokens: (string | Record<string, any>)[];
static?: boolean;
// 2.4 ssr optimization
ssrOptimizability?: SSROptimizability;
}

export interface ASTText {
type: 3;
text: string;
static?: boolean;
isComment?: boolean;
// 2.4 ssr optimization
ssrOptimizability?: SSROptimizability;
}

/*
* SFC parser related types
*/
interface SFCParserOptions {
pad?: true | 'line' | 'space';
}

export interface SFCBlock {
type: string;
content: string;
attrs: Record<string, string>;
start?: number;
end?: number;
lang?: string;
src?: string;
scoped?: boolean;
module?: string | boolean;
}

export interface SFCDescriptor {
template: SFCBlock | undefined;
script: SFCBlock | undefined;
styles: SFCBlock[];
customBlocks: SFCBlock[];
}

/*
* Exposed functions
*/
export function compile(
template: string,
options?: CompilerOptions
): CompiledResult;

export function compileToFunctions(template: string): CompiledResultFunctions;

export function ssrCompile(
template: string,
options?: CompilerOptions
): CompiledResult;

export function ssrCompileToFunctions(template: string): CompiledResultFunctions;

export function parseComponent(
file: string,
options?: SFCParserOptions
): SFCDescriptor;
}
Loading

0 comments on commit 6a83c7d

Please sign in to comment.