@@ -3,59 +3,146 @@ import {readFileSync} from 'fs';
3
3
import { dirname , join , relative } from 'path' ;
4
4
import * as konan from 'konan' ;
5
5
import * as enhancedResolve from 'enhanced-resolve' ;
6
- const { parse} = require ( '@vue/component-compiler-utils' ) ;
7
6
import configLoader from './configLoader' ;
8
7
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 = / i m p o r t (?: [ " ' \s ] * ( [ \w * { \s * } \n , ] + ) f r o m \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
+
9
75
export class VueDependencyProvider {
10
76
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
+ }
57
144
}
58
145
59
146
module . exports = function ( ) {
60
- return new VueDependencyProvider ( ) ;
147
+ return new VueDependencyProvider ( ) ;
61
148
} ;
0 commit comments