4
4
* @see https://github.com/flex-development/mkbuild
5
5
*/
6
6
7
- import { defineBuildConfig , type Config } from '@flex-development/mkbuild'
7
+ import { EXT_DTS_REGEX } from '@flex-development/ext-regex'
8
+ import {
9
+ defineBuildConfig ,
10
+ type Config ,
11
+ type OutputMetadata
12
+ } from '@flex-development/mkbuild'
13
+ import * as mlly from '@flex-development/mlly'
14
+ import pathe from '@flex-development/pathe'
15
+ import type { BuildResult , OutputFile , PluginBuild } from 'esbuild'
16
+ import util from 'node:util'
8
17
import pkg from './package.json' assert { type : 'json' }
9
18
10
19
/**
@@ -13,7 +22,173 @@ import pkg from './package.json' assert { type: 'json' }
13
22
* @const {Config} config
14
23
*/
15
24
const config : Config = defineBuildConfig ( {
16
- entries : [ { } , { format : 'cjs' } ] ,
25
+ entries : [
26
+ { } ,
27
+ {
28
+ format : 'cjs' ,
29
+ plugins : [
30
+ {
31
+ name : 'named-exports' ,
32
+ setup ( { initialOptions, onEnd } : PluginBuild ) : void {
33
+ const { absWorkingDir = process . cwd ( ) , format } = initialOptions
34
+
35
+ // do nothing if format is not commonjs
36
+ if ( format !== 'cjs' ) return void format
37
+
38
+ // add named exports
39
+ return void onEnd (
40
+ async (
41
+ result : BuildResult < { metafile : true ; write : false } >
42
+ ) : Promise < void > => {
43
+ /**
44
+ * Named exports.
45
+ *
46
+ * @const {Set<string>} names
47
+ */
48
+ const names : Set < string > = new Set < string > ( )
49
+
50
+ /**
51
+ * Output file objects.
52
+ *
53
+ * @const {OutputFile[]} outputFiles
54
+ */
55
+ const outputFiles : OutputFile [ ] = [ ]
56
+
57
+ /**
58
+ * Adds named exports to the given `output` file content.
59
+ *
60
+ * @param {string } output - Output file content
61
+ * @param {string[] } exports - Named exports
62
+ * @return {string } Output file content with named exports
63
+ */
64
+ const nameExports = (
65
+ output : string ,
66
+ exports : string [ ]
67
+ ) : string => {
68
+ if ( exports . length > 0 ) {
69
+ // get sourceMappingURL comment
70
+ const [ sourcemap = '' ] = / \/ \/ # .+ \n / . exec ( output ) ?? [ ]
71
+
72
+ /**
73
+ * Output file content.
74
+ *
75
+ * @var {string} text
76
+ */
77
+ let text : string = output . replace ( sourcemap , '' )
78
+
79
+ // add named exports
80
+ for ( const name of exports ) {
81
+ names . add ( name )
82
+ text += `exports.${ name } = module.exports.${ name } ;\n`
83
+ }
84
+
85
+ // alias default export
86
+ text += 'exports = module.exports;\n'
87
+
88
+ // re-add sourceMappingURL comment
89
+ return ( text += sourcemap )
90
+ }
91
+
92
+ return output
93
+ }
94
+
95
+ // add named exports to output file content
96
+ for ( const output of result . outputFiles ) {
97
+ // skip declaration files
98
+ if ( EXT_DTS_REGEX . test ( output . path ) ) {
99
+ outputFiles . push ( output )
100
+ continue
101
+ }
102
+
103
+ // skip interface and type definition files
104
+ if ( / (?: i n t e r f a c e s | t y p e s ) \/ .* $ / . test ( output . path ) ) {
105
+ outputFiles . push ( output )
106
+ continue
107
+ }
108
+
109
+ /**
110
+ * Relative path to output file.
111
+ *
112
+ * **Note**: Relative to {@linkcode absWorkingDir}.
113
+ *
114
+ * @const {string} outfile
115
+ */
116
+ const outfile : string = output . path
117
+ . replace ( absWorkingDir , '' )
118
+ . replace ( / ^ \/ / , '' )
119
+
120
+ /**
121
+ * {@linkcode output } metadata.
122
+ *
123
+ * @const {OutputMetadata} metadata
124
+ */
125
+ const metadata : OutputMetadata =
126
+ result . metafile . outputs [ outfile ] !
127
+
128
+ // skip output files without entry points
129
+ if ( ! metadata . entryPoint ) {
130
+ outputFiles . push ( output )
131
+ continue
132
+ }
133
+
134
+ /**
135
+ * TypeScript source code for current output file.
136
+ *
137
+ * @const {string} code
138
+ */
139
+ const code : string = ( await mlly . getSource (
140
+ pathe . resolve ( absWorkingDir , metadata . entryPoint )
141
+ ) ) as string
142
+
143
+ /**
144
+ * Output file content.
145
+ *
146
+ * @const {string} text
147
+ */
148
+ const text : string = nameExports (
149
+ output . text ,
150
+ mlly
151
+ . findExports ( code )
152
+ . filter ( s => s . syntax === mlly . StatementSyntaxKind . NAMED )
153
+ . flatMap ( statement => statement . exports )
154
+ . map ( name => name . replace ( / ^ d e f a u l t a s / , '' ) )
155
+ . filter ( name => name !== 'default' )
156
+ )
157
+
158
+ // add output file with named exports
159
+ outputFiles . push ( {
160
+ ...output ,
161
+ contents : new util . TextEncoder ( ) . encode ( text ) ,
162
+ text
163
+ } )
164
+ }
165
+
166
+ return void ( result . outputFiles = outputFiles . map ( output => {
167
+ // add named exports to package entry point
168
+ if ( output . path . endsWith ( 'dist/index.cjs' ) ) {
169
+ /**
170
+ * Output file content.
171
+ *
172
+ * @const {string} text
173
+ */
174
+ const text : string = nameExports ( output . text , [ ...names ] )
175
+
176
+ return {
177
+ ...output ,
178
+ contents : new util . TextEncoder ( ) . encode ( text ) ,
179
+ text
180
+ }
181
+ }
182
+
183
+ return output
184
+ } ) )
185
+ }
186
+ )
187
+ }
188
+ }
189
+ ]
190
+ }
191
+ ] ,
17
192
sourcemap : true ,
18
193
sourcesContent : false ,
19
194
target : 'node' + pkg . engines . node . replace ( / ^ \D + / , '' ) ,
0 commit comments