Skip to content

fix(vue): resolve src attribute on the script block on Vue files #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 26, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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