1
1
const fs = require ( 'fs' ) ;
2
- const { pbjs } = require ( 'protobufjs/cli' ) ;
2
+ const { pbjs, pbts } = require ( 'protobufjs/cli' ) ;
3
3
const protobuf = require ( 'protobufjs' ) ;
4
4
const tmp = require ( 'tmp-promise' ) ;
5
5
const validateOptions = require ( 'schema-utils' ) . validate ;
@@ -12,17 +12,67 @@ const schema = {
12
12
properties : {
13
13
json : {
14
14
type : 'boolean' ,
15
+ default : false ,
15
16
} ,
16
17
paths : {
17
18
type : 'array' ,
18
19
} ,
19
20
pbjsArgs : {
20
21
type : 'array' ,
22
+ default : [ ] ,
23
+ } ,
24
+ pbts : {
25
+ oneOf : [
26
+ {
27
+ type : 'boolean' ,
28
+ } ,
29
+ {
30
+ type : 'object' ,
31
+ properties : {
32
+ args : {
33
+ type : 'array' ,
34
+ default : [ ] ,
35
+ } ,
36
+ } ,
37
+ additionalProperties : false ,
38
+ } ,
39
+ ] ,
40
+ default : false ,
21
41
} ,
22
42
} ,
43
+
44
+ // pbts config is only applicable if the pbjs target is
45
+ // `static-module`, i.e. if the `json` flag is false. We enforce
46
+ // this at the schema level; see
47
+ // https://json-schema.org/understanding-json-schema/reference/conditionals.html#implication.
48
+ anyOf : [
49
+ {
50
+ properties : {
51
+ json : { const : true } ,
52
+ pbts : { const : false } ,
53
+ } ,
54
+ } ,
55
+ {
56
+ not : {
57
+ properties : { json : { const : true } } ,
58
+ } ,
59
+ } ,
60
+ ] ,
23
61
additionalProperties : false ,
24
62
} ;
25
63
64
+ /**
65
+ * Shared type for the validated options object, with no missing
66
+ * properties (i.e. the user-provided object merged with default
67
+ * values).
68
+ *
69
+ * @typedef {{ args: string[] } } PbtsOptions
70
+ * @typedef {{
71
+ * json: boolean, paths: string[], pbjsArgs: string[],
72
+ * pbts: boolean | PbtsOptions
73
+ * }} LoaderOptions
74
+ */
75
+
26
76
/**
27
77
* We're supporting multiple webpack versions, so there are several
28
78
* different possible structures for the `this` context in our loader
@@ -31,12 +81,47 @@ const schema = {
31
81
* The `never` generic in the v5 context sets the return type of
32
82
* `getOptions`. Since we're using the deprecated `loader-utils`
33
83
* method of fetching options, this should be fine; however, if we
34
- * drop support for older webpack versions, we'll want to define a
35
- * stricter type for the options object .
84
+ * drop support for older webpack versions, we'll want to switch to
85
+ * using `getOptions` .
36
86
*
37
87
* @typedef { import('webpack').LoaderContext<never> | import('webpack4').loader.LoaderContext | import('webpack3').loader.LoaderContext | import('webpack2').loader.LoaderContext } LoaderContext
38
88
*/
39
89
90
+ /** @type { (resourcePath: string, pbtsOptions: true | PbtsOptions, compiledContent: string, callback: NonNullable<ReturnType<LoaderContext['async']>>) => any } */
91
+ const execPbts = ( resourcePath , pbtsOptions , compiledContent , callback ) => {
92
+ /** @type PbtsOptions */
93
+ const normalizedOptions = {
94
+ args : [ ] ,
95
+ ...( pbtsOptions === true ? { } : pbtsOptions ) ,
96
+ } ;
97
+
98
+ // pbts CLI only supports streaming from stdin without a lot of
99
+ // duplicated logic, so we need to use a tmp file. :(
100
+ tmp
101
+ . file ( { postfix : '.js' } )
102
+ . then (
103
+ ( o ) =>
104
+ new Promise ( ( resolve , reject ) => {
105
+ fs . write ( o . fd , compiledContent , ( err ) => {
106
+ if ( err ) {
107
+ reject ( err ) ;
108
+ } else {
109
+ resolve ( o . path ) ;
110
+ }
111
+ } ) ;
112
+ } )
113
+ )
114
+ . then ( ( compiledFilename ) => {
115
+ const declarationFilename = `${ resourcePath } .d.ts` ;
116
+ const pbtsArgs = [ '-o' , declarationFilename ]
117
+ . concat ( normalizedOptions . args )
118
+ . concat ( [ compiledFilename ] ) ;
119
+ pbts . main ( pbtsArgs , ( err ) => {
120
+ callback ( err , compiledContent ) ;
121
+ } ) ;
122
+ } ) ;
123
+ } ;
124
+
40
125
/** @type { (this: LoaderContext, source: string) => any } */
41
126
module . exports = function protobufJsLoader ( source ) {
42
127
const callback = this . async ( ) ;
@@ -64,17 +149,25 @@ module.exports = function protobufJsLoader(source) {
64
149
return undefined ;
65
150
} ) ( ) ;
66
151
67
- /** @type { { json: boolean, paths: string[], pbjsArgs: string[] } } */
152
+ /** @type LoaderOptions */
68
153
const options = {
69
154
json : false ,
70
155
71
156
// Default to the paths given to the compiler.
72
157
paths : defaultPaths || [ ] ,
73
158
74
159
pbjsArgs : [ ] ,
160
+
161
+ pbts : false ,
162
+
75
163
...getOptions ( this ) ,
76
164
} ;
77
- validateOptions ( schema , options , { name : 'protobufjs-loader' } ) ;
165
+ try {
166
+ validateOptions ( schema , options , { name : 'protobufjs-loader' } ) ;
167
+ } catch ( err ) {
168
+ callback ( err instanceof Error ? err : new Error ( `${ err } ` ) , undefined ) ;
169
+ return ;
170
+ }
78
171
79
172
/** @type { string } */
80
173
let filename ;
@@ -161,7 +254,11 @@ module.exports = function protobufJsLoader(source) {
161
254
callback ( depErr ) ;
162
255
} )
163
256
. then ( ( ) => {
164
- callback ( err , result ) ;
257
+ if ( ! options . pbts || err ) {
258
+ callback ( err , result ) ;
259
+ } else {
260
+ execPbts ( self . resourcePath , options . pbts , result || '' , callback ) ;
261
+ }
165
262
} ) ;
166
263
} ) ;
167
264
} ) ;
0 commit comments