44 * @see https://github.com/flex-development/mkbuild
55 */
66
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'
817import pkg from './package.json' assert { type : 'json' }
918
1019/**
@@ -13,7 +22,173 @@ import pkg from './package.json' assert { type: 'json' }
1322 * @const {Config} config
1423 */
1524const 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+ ] ,
17192 sourcemap : true ,
18193 sourcesContent : false ,
19194 target : 'node' + pkg . engines . node . replace ( / ^ \D + / , '' ) ,
0 commit comments