Skip to content

Commit 74ef12d

Browse files
committed
Support for Vue Multi File Components and mixing of TypeScript/JavaScript
1 parent 6e6e63a commit 74ef12d

File tree

3 files changed

+153
-49
lines changed

3 files changed

+153
-49
lines changed

package-lock.json

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
"homepage": "https://github.com/ttskp/module-structure-lang-vue#readme",
2121
"extensions": {
2222
"module-structure:language": {
23-
"vue": "./dist/vue-dependency-provider"
23+
"vue": "./dist/vue-dependency-provider",
24+
"ts": "./dist/vue-dependency-provider",
25+
"js": "./dist/vue-dependency-provider"
2426
}
2527
},
2628
"devDependencies": {
@@ -35,6 +37,7 @@
3537
"@vue/component-compiler-utils": "^3.1.2",
3638
"enhanced-resolve": "^4.1.1",
3739
"konan": "^1.2.1",
40+
"preconditions": "^2.2.3",
3841
"vue": "^2.6.11",
3942
"vue-template-compiler": "^2.6.11"
4043
},

src/vue-dependency-provider.ts

+135-48
Original file line numberDiff line numberDiff line change
@@ -3,59 +3,146 @@ import {readFileSync} from 'fs';
33
import {dirname, join, relative} from 'path';
44
import * as konan from 'konan';
55
import * as enhancedResolve from 'enhanced-resolve';
6-
const {parse} = require('@vue/component-compiler-utils');
76
import configLoader from './configLoader';
87

8+
const {parse} = require('@vue/component-compiler-utils');
9+
import fs = require("fs");
10+
import path = require("path");
11+
12+
const preconditions = require("preconditions").instance();
13+
const checkArgument = preconditions.checkArgument;
14+
15+
/**
16+
* Simple implementation to allow processing of TypeScript/ES6 modules - necessary because
17+
* TypeScript compiler output cannot be used since it removes even "important" imports.
18+
*/
19+
class ImportsProvider {
20+
private static readonly COMMENT_REGEXP = /(\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*+\/)|(\/\/.*)/g;
21+
private static readonly IMPORT_REGEXP = /import(?:["'\s]*([\w*{\s*}\n, ]+)from\s*)?["'\s]*([@\w\/\._-]+)["'\s]*;?;/g;
22+
23+
24+
public getImportsFromFile(modulePath: string): Array<string> {
25+
checkArgument(fs.existsSync(modulePath) && fs.statSync(modulePath).isFile());
26+
let moduleAsString = fs.readFileSync(modulePath, "utf-8");
27+
28+
return this.getImportsFromString(moduleAsString);
29+
}
30+
31+
public getImportsFromString(moduleAsString: string): Array<string> {
32+
const moduleAsStringWithoutComments = this.removeComments(moduleAsString);
33+
return this.findImportSources(moduleAsStringWithoutComments);
34+
}
35+
36+
private removeComments(str: string): string {
37+
return this.replaceAll(str, ImportsProvider.COMMENT_REGEXP, "");
38+
}
39+
40+
private replaceAll(str: string, searchValue: RegExp, replaceValue: string): string {
41+
let length = str.length;
42+
str = str.replace(searchValue, replaceValue);
43+
return str.length === length ? str : this.replaceAll(str, searchValue, replaceValue);
44+
}
45+
46+
private findImportSources(moduleString: string): Array<string> {
47+
let matches = ImportsProvider.match(moduleString, ImportsProvider.IMPORT_REGEXP);
48+
return matches
49+
.filter(match => match.length === 3)
50+
.map(match => {
51+
const path = match[2];
52+
return path.endsWith(".vue") || path.endsWith(".ts")
53+
? path
54+
: path + ".ts";
55+
});
56+
}
57+
58+
private static match(str: string, regExp: RegExp): Array<RegExpExecArray> {
59+
let match: RegExpExecArray;
60+
let matches: Array<RegExpExecArray> = [];
61+
62+
while ((match = regExp.exec(str)) !== null) {
63+
// This is necessary to avoid infinite loops with zero-width matches
64+
if (match.index === regExp.lastIndex) {
65+
regExp.lastIndex++;
66+
}
67+
68+
matches.push(match);
69+
}
70+
71+
return matches;
72+
}
73+
}
74+
975
export class VueDependencyProvider {
1076

11-
private readonly myResolver;
12-
13-
constructor() {
14-
this.myResolver = enhancedResolve.create.sync({
15-
extensions: ['.js'],
16-
alias: VueDependencyProvider.loadAlias(),
17-
modules: []
18-
});
19-
}
20-
21-
private static loadAlias(): any {
22-
const config = configLoader();
23-
if(!config['module-structure-lang-vue'] || !config['module-structure-lang-vue'].webpackConfig) {
24-
return [];
25-
}
26-
const webpackConfigPath = join(process.cwd(), config['module-structure-lang-vue'].webpackConfig);
27-
const webpackConfig = require(webpackConfigPath);
28-
if(!webpackConfig.resolve || !webpackConfig.resolve.alias) {
29-
return []
30-
}
31-
return webpackConfig.resolve.alias;
32-
}
33-
34-
public getDependencies(modulePath: string, rootDir: string): Array<string> {
35-
const source = readFileSync(modulePath, 'utf8');
36-
const {script} = parse({source, compiler, needMap: false, sourceRoot: rootDir});
37-
if(!script) {
38-
return [];
39-
}
40-
const {content} = script;
41-
42-
const {strings: imports} = konan(content);
43-
return imports
44-
.map(p => this.resolve(p, modulePath))
45-
.filter(p => p !== undefined);
46-
}
47-
48-
private resolve(request, path) {
49-
try{
50-
const moduleDirectory = dirname(path);
51-
const absPath = this.myResolver(undefined, moduleDirectory, request);
52-
return relative(moduleDirectory, absPath);
53-
} catch {
54-
return undefined;
55-
}
56-
}
77+
private readonly myResolver;
78+
private readonly importsProvider = new ImportsProvider();
79+
80+
constructor() {
81+
this.myResolver = enhancedResolve.create.sync({
82+
extensions: ['.js'],
83+
alias: VueDependencyProvider.loadAlias(),
84+
modules: []
85+
});
86+
}
87+
88+
private static loadAlias(): any {
89+
const config = configLoader();
90+
if (!config['module-structure-lang-vue'] || !config['module-structure-lang-vue'].webpackConfig) {
91+
return [];
92+
}
93+
const webpackConfigPath = join(process.cwd(), config['module-structure-lang-vue'].webpackConfig);
94+
const webpackConfig = require(webpackConfigPath);
95+
if (!webpackConfig.resolve || !webpackConfig.resolve.alias) {
96+
return []
97+
}
98+
return webpackConfig.resolve.alias;
99+
}
100+
101+
public getDependencies(modulePath: string, rootDir: string): Array<string> {
102+
const fileExtension = path.extname(modulePath);
103+
if (fileExtension === ".ts" || fileExtension === ".js") {
104+
return this.importsProvider.getImportsFromFile(modulePath);
105+
} else if (fileExtension === ".vue") {
106+
return this.getImportsFromVueFile(modulePath, rootDir);
107+
} else {
108+
throw Error(`Unhandled file extension ${fileExtension}`);
109+
}
110+
}
111+
112+
private getImportsFromVueFile(modulePath: string, rootDir: string) {
113+
const source = readFileSync(modulePath, 'utf8');
114+
const {script} = parse({source, compiler, needMap: false, sourceRoot: rootDir});
115+
if (!script) {
116+
return [];
117+
}
118+
119+
if (script.lang === "ts" || script.lang === "js") {
120+
let imports = new Array<string>();
121+
if (script.src) {
122+
imports.push(script.src);
123+
}
124+
imports.concat(this.importsProvider.getImportsFromString(script.content));
125+
return imports;
126+
}
127+
128+
const {content} = script;
129+
const {strings: imports} = konan(content);
130+
return imports
131+
.map(p => this.resolve(p, modulePath))
132+
.filter(p => p !== undefined);
133+
}
134+
135+
private resolve(request, path) {
136+
try {
137+
const moduleDirectory = dirname(path);
138+
const absPath = this.myResolver(undefined, moduleDirectory, request);
139+
return relative(moduleDirectory, absPath);
140+
} catch {
141+
return undefined;
142+
}
143+
}
57144
}
58145

59146
module.exports = function () {
60-
return new VueDependencyProvider();
147+
return new VueDependencyProvider();
61148
};

0 commit comments

Comments
 (0)