1
1
import webpack from "webpack" ;
2
+ import * as ts from "typescript" ;
3
+ import path from "path" ;
4
+ import fs from "fs" ;
2
5
// TODO: Import from "react-docgen-typescript" directly when
3
6
// https://github.com/styleguidist/react-docgen-typescript/pull/104 is hopefully
4
7
// merged in. Will be considering to make a peer dependency as that point.
@@ -14,6 +17,14 @@ import validateOptions from "./validateOptions";
14
17
import generateDocgenCodeBlock from "./generateDocgenCodeBlock" ;
15
18
import { getOptions } from "loader-utils" ;
16
19
20
+ export interface TSFile {
21
+ text ?: string ;
22
+ version : number ;
23
+ }
24
+
25
+ let languageService : ts . LanguageService | null = null ;
26
+ const files : Map < string , TSFile > = new Map < string , TSFile > ( ) ;
27
+
17
28
export default function loader (
18
29
this : webpack . loader . LoaderContext ,
19
30
source : string ,
@@ -72,13 +83,50 @@ function processResource(
72
83
// Configure parser using settings provided to loader.
73
84
// See: node_modules/react-docgen-typescript/lib/parser.d.ts
74
85
let parser : FileParser = withDefaultConfig ( parserOptions ) ;
86
+
87
+ let compilerOptions : ts . CompilerOptions = {
88
+ allowJs : true ,
89
+ } ;
90
+ let tsConfigFile : ts . ParsedCommandLine | null = null ;
91
+
75
92
if ( options . tsconfigPath ) {
76
93
parser = withCustomConfig ( options . tsconfigPath , parserOptions ) ;
94
+
95
+ tsConfigFile = getTSConfigFile ( options . tsconfigPath ! ) ;
96
+ compilerOptions = tsConfigFile . options ;
97
+
98
+ const filesToLoad = tsConfigFile . fileNames ;
99
+ loadFiles ( filesToLoad ) ;
77
100
} else if ( options . compilerOptions ) {
78
101
parser = withCompilerOptions ( options . compilerOptions , parserOptions ) ;
102
+ compilerOptions = options . compilerOptions ;
103
+ }
104
+
105
+ if ( ! tsConfigFile ) {
106
+ const basePath = path . dirname ( context . context ) ;
107
+ tsConfigFile = getDefaultTSConfigFile ( basePath ) ;
108
+
109
+ const filesToLoad = tsConfigFile . fileNames ;
110
+ loadFiles ( filesToLoad ) ;
79
111
}
80
112
81
- const componentDocs = parser . parse ( context . resourcePath ) ;
113
+ const componentDocs = parser . parseWithProgramProvider (
114
+ context . resourcePath ,
115
+ ( ) => {
116
+ if ( languageService ) {
117
+ return languageService . getProgram ( ) ! ;
118
+ }
119
+
120
+ const servicesHost = createServiceHost ( compilerOptions , files ) ;
121
+
122
+ languageService = ts . createLanguageService (
123
+ servicesHost ,
124
+ ts . createDocumentRegistry ( ) ,
125
+ ) ;
126
+
127
+ return languageService ! . getProgram ( ) ! ;
128
+ } ,
129
+ ) ;
82
130
83
131
// Return amended source code if there is docgen information available.
84
132
if ( componentDocs . length ) {
@@ -94,3 +142,67 @@ function processResource(
94
142
// Return unchanged source code if no docgen information was available.
95
143
return source ;
96
144
}
145
+
146
+ function getTSConfigFile ( tsconfigPath : string ) : ts . ParsedCommandLine {
147
+ const basePath = path . dirname ( tsconfigPath ) ;
148
+ const configFile = ts . readConfigFile ( tsconfigPath , ts . sys . readFile ) ;
149
+ return ts . parseJsonConfigFileContent (
150
+ configFile ! . config ,
151
+ ts . sys ,
152
+ basePath ,
153
+ { } ,
154
+ tsconfigPath ,
155
+ ) ;
156
+ }
157
+
158
+ function getDefaultTSConfigFile ( basePath : string ) : ts . ParsedCommandLine {
159
+ return ts . parseJsonConfigFileContent ( { } , ts . sys , basePath , { } ) ;
160
+ }
161
+
162
+ function loadFiles ( filesToLoad : string [ ] ) : void {
163
+ let normalizedFilePath : string ;
164
+ filesToLoad . forEach ( filePath => {
165
+ normalizedFilePath = path . normalize ( filePath ) ;
166
+ files . set ( normalizedFilePath , {
167
+ text : fs . readFileSync ( normalizedFilePath , "utf-8" ) ,
168
+ version : 0 ,
169
+ } ) ;
170
+ } ) ;
171
+ }
172
+
173
+ function createServiceHost (
174
+ compilerOptions : ts . CompilerOptions ,
175
+ files : Map < string , TSFile > ,
176
+ ) : ts . LanguageServiceHost {
177
+ return {
178
+ getScriptFileNames : ( ) => {
179
+ return [ ...files . keys ( ) ] ;
180
+ } ,
181
+ getScriptVersion : fileName => {
182
+ const file = files . get ( fileName ) ;
183
+ return ( file && file . version . toString ( ) ) || "" ;
184
+ } ,
185
+ getScriptSnapshot : fileName => {
186
+ if ( ! fs . existsSync ( fileName ) ) {
187
+ return undefined ;
188
+ }
189
+
190
+ let file = files . get ( fileName ) ;
191
+
192
+ if ( file === undefined ) {
193
+ const text = fs . readFileSync ( fileName ) . toString ( ) ;
194
+
195
+ file = { version : 0 , text } ;
196
+ files . set ( fileName , file ) ;
197
+ }
198
+
199
+ return ts . ScriptSnapshot . fromString ( file ! . text ! ) ;
200
+ } ,
201
+ getCurrentDirectory : ( ) => process . cwd ( ) ,
202
+ getCompilationSettings : ( ) => compilerOptions ,
203
+ getDefaultLibFileName : options => ts . getDefaultLibFilePath ( options ) ,
204
+ fileExists : ts . sys . fileExists ,
205
+ readFile : ts . sys . readFile ,
206
+ readDirectory : ts . sys . readDirectory ,
207
+ } ;
208
+ }
0 commit comments