Skip to content

Commit 5bd4ddd

Browse files
committed
feat: Final step to 100% tree-shakeability
1 parent 3acdfb3 commit 5bd4ddd

File tree

11 files changed

+98
-43
lines changed

11 files changed

+98
-43
lines changed

package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+6-6
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,14 @@
3434
"types": "./dist/win32/consts.d.ts",
3535
"default": "./dist/win32/consts.js"
3636
},
37-
"./consts/*": {
38-
"types": "./dist/win32/consts/*.d.ts",
39-
"default": "./dist/win32/consts/*.js"
40-
},
4137
"./rollup-plugin": {
42-
"types": "./dist/rollup/plugin.d.ts",
43-
"default": "./dist/rollup/plugin.js"
38+
"types": "./dist/rollup/index.d.ts",
39+
"default": "./dist/rollup/index.js"
4440
}
4541
},
42+
"imports": {
43+
"#consts-base": "./dist/win32/consts"
44+
},
4645
"engines": {
4746
"node": ">= 18.19.0 || >= 20.6.0"
4847
},
@@ -67,6 +66,7 @@
6766
"@rollup/plugin-node-resolve": "^15.2.3",
6867
"@rollup/plugin-typescript": "^11.1.6",
6968
"@types/node": "^22.5.1",
69+
"magic-string": "^0.30.11",
7070
"rimraf": "^6.0.1",
7171
"rollup": "^4.21.2",
7272
"rollup-plugin-node-externals": "^7.1.3",

rollup.demos.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
/*
2-
* This config only bundles the demos. The library itself is published unbundled.
2+
* This config only bundles the demos and serves as an example of how to bundle the lib with your code.
3+
* The library itself is published unbundled.
34
*/
45
// @ts-check
56
import { defineConfig } from 'rollup'
67
import { nodeExternals } from 'rollup-plugin-node-externals'
78
import { nodeResolve } from '@rollup/plugin-node-resolve'
89
import commonjs from '@rollup/plugin-commonjs'
910
import typescript from '@rollup/plugin-typescript'
10-
import { koffi } from './dist/rollup/plugin.js'
11+
import { libwin32 } from 'libwin32/rollup-plugin'
1112

1213
// Use distinct configs to prevent Rollup from code-splitting the library.
1314
export default [
@@ -38,10 +39,11 @@ function makeConfig(which) {
3839
typescript({
3940
rootDir: `source/demos`,
4041
outDir: `demos/${which}`,
42+
target: 'ES2023',
4143
declaration: false,
4244
declarationMap: false
4345
}),
44-
koffi()
46+
libwin32()
4547
]
4648
})
4749
}

source/demos/messagebox.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { MessageBox } from 'libwin32'
2-
import { MB_ } from 'libwin32/consts/MB'
2+
import { MB_ } from 'libwin32/consts'
33

44
const result = MessageBox(
55
null,

source/demos/window.ts

+1-12
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,7 @@ import {
66
MessageBox,
77
type HINSTANCE, type WPARAM, type LPARAM, type HWND, type MSG
88
} from 'libwin32'
9-
10-
// Also import some helpfull constants.
11-
// Note that import { CS_, CW_, IDC_, IDI_, MB_, SW_, WM_, WS_ } from 'libwin32/consts' would also work,
12-
// but at the cost of tree-shakeability due to the way TypeScript exports enums.
13-
import { CS_ } from 'libwin32/consts/CS'
14-
import { CW_ } from 'libwin32/consts/CW'
15-
import { IDC_ } from 'libwin32/consts/IDC'
16-
import { IDI_ } from 'libwin32/consts/IDI'
17-
import { MB_ } from 'libwin32/consts/MB'
18-
import { SW_ } from 'libwin32/consts/SW'
19-
import { WM_ } from 'libwin32/consts/WM'
20-
import { WS_, WS_EX_ } from 'libwin32/consts/WS'
9+
import { CS_, CW_, IDC_, IDI_, MB_, SW_, WM_, WS_, WS_EX_ } from 'libwin32/consts'
2110

2211
const windowClass = "NodeApp"
2312
const windowName = "Window Demo!"

source/rollup/enums.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { fileURLToPath } from 'node:url'
2+
import MagicString from 'magic-string'
3+
import type { Plugin } from 'rollup'
4+
5+
/**
6+
* A plugin that rewrites TypeScript's `enum`s into a tree-shakeable
7+
* form --the same as esbuid.
8+
*/
9+
export function enums(): Plugin {
10+
11+
const constsBase = fileURLToPath(import.meta.resolve('#consts-base'))
12+
13+
// https://regex101.com/r/1nyPC3/3
14+
const enumRx = /\b(?<intro>(?:export\s+)?var\s+(?<name>[^;\s]+);?\s*\(function\s*\(\k<name>\)\s*{).*(?<outro>}\)\(\k<name>\s*\|\|\s*\(\k<name>\s*=\s{}\)\);?)/gsmd
15+
16+
return {
17+
name: 'libwin32-tree-skakeable-enums',
18+
19+
transform(code, id) {
20+
if (!id.startsWith(constsBase))
21+
return
22+
23+
let ms: MagicString | undefined
24+
let match: RegExpMatchArray | null
25+
enumRx.lastIndex = 0
26+
while (match = enumRx.exec(code)) {
27+
const varName = match.groups!.name
28+
const indices = match.indices!.groups
29+
30+
ms ??= new MagicString(code)
31+
ms.update(indices!.intro[0], indices!.intro[1], `export var ${varName}= /*@__PURE__*/(${varName} => {`)
32+
ms.update(indices!.outro[0], indices!.outro[1], `${ms.getIndentString()}return ${varName};\n})(${varName} || {});`)
33+
}
34+
35+
if (ms) {
36+
ms.replace(/\/\/#\s*sourceMappingURL=(?:.*).map\s*$/, '')
37+
return {
38+
code: ms.toString(),
39+
map: ms.generateMap()
40+
}
41+
}
42+
43+
return null
44+
}
45+
}
46+
}

source/rollup/index.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { koffi } from './koffi.js'
2+
import { enums } from './enums.js'
3+
4+
export const libwin32 = () => [
5+
koffi(),
6+
enums(),
7+
]

source/rollup/plugin.ts renamed to source/rollup/koffi.ts

+24-18
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,33 @@ import { fileURLToPath } from 'node:url'
44
import path from 'node:path'
55
import type { Plugin } from 'rollup'
66

7+
/**
8+
* A plugin that simplifies bundling Koffi with user apps.
9+
*/
710
export function koffi(): Plugin {
811

9-
// The path to Koffi's .node file.
12+
// The path to the orginal `koffi.node` binary.
1013
const koffiSource = fileURLToPath(new URL(
1114
`./build/koffi/${platform}_${arch}/koffi.node`,
12-
import.meta.resolve('koffi') // Unflagged in Node v20.6.0, v18.19.0
15+
import.meta.resolve('koffi')
1316
))
1417

15-
// The path we'll copy it to.
16-
const koffiDest = `koffi-${platform}-${arch}.node`
18+
// The name under which we'll copy it to.
19+
const koffi_win32_x64 = `koffi-${platform}-${arch}.node`
1720

21+
// Won't do if this is still false at writeBundle() time.
1822
let copy = false
1923

2024
return {
21-
name: 'koffi-cream',
25+
name: 'libwin32-koffi-cream',
2226

2327
async buildStart() {
2428
copy = false
2529

2630
// Make sure Koffi's installed and accessible.
2731
const err = await fs.access(koffiSource, fs.constants.R_OK).catch((err: NodeJS.ErrnoException) => err)
2832
if (err instanceof Error)
29-
this.error({ message: `Cannot access "${koffiSource}": ${err.code}.`, stack: undefined })
33+
this.error({ message: `Cannot access "${koffiSource}": ${err.code}. Did you install koffi as a dependency?`, stack: undefined })
3034
},
3135

3236
buildEnd(error) {
@@ -36,35 +40,37 @@ export function koffi(): Plugin {
3640
async writeBundle(options) {
3741
if (copy) {
3842

39-
// Create all necessary directories.
43+
// Create all the necessary directories.
4044
const outDir = path.join(
4145
options.dir ?? path.dirname(options.file!),
4246
'node_modules', 'koffi'
4347
)
4448
await fs.mkdir(outDir, { recursive: true }).catch(() => {})
4549

46-
// Copy Koffi's native code.
47-
const destFile = path.join(outDir, koffiDest)
48-
let err = await fs.copyFile(koffiSource, destFile).catch((err: NodeJS.ErrnoException) => err)
49-
if (err instanceof Error)
50-
this.error({ message: `Cannot write "${destFile}": ${err.code}.`, stack: undefined })
50+
// Copy Koffi's binary.
51+
const outFile = path.join(outDir, koffi_win32_x64)
52+
let err = await fs.copyFile(koffiSource, outFile).catch((err: NodeJS.ErrnoException) => err)
53+
if (err)
54+
this.error({ message: `Cannot write "${outFile}": ${err.code}.`, stack: undefined })
5155

5256
// Write index.cjs
53-
err = await fs.writeFile(path.join(outDir, 'index.cjs'), [
54-
`module.exports = require('./${koffiDest}');`
55-
].join('\n')).catch((err: NodeJS.ErrnoException) => err)
56-
if (err instanceof Error)
57+
err = await fs.writeFile(
58+
path.join(outDir, 'index.cjs'),
59+
`module.exports = require('./${koffi_win32_x64}');`
60+
).catch((err: NodeJS.ErrnoException) => err)
61+
if (err)
5762
this.error({ message: `Cannot write index.cjs: ${err.code}.`, stack: undefined })
5863

5964
// Write package.json
6065
err = await fs.writeFile(path.join(outDir, 'package.json'), JSON.stringify({
6166
name: 'koffi',
67+
description: `koffi binary for ${platform} ${arch} systems`,
6268
type: 'commonjs',
6369
main: './index.cjs'
6470
}, undefined, 2)).catch((err: NodeJS.ErrnoException) => err)
65-
if (err instanceof Error)
71+
if (err)
6672
this.error({ message: `Cannot write package.json: ${err.code}.`, stack: undefined })
6773
}
68-
},
74+
}
6975
}
7076
}

source/win32/consts/GA.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/** Flags for GetAncestor() */
1+
/**
2+
* Flags for GetAncestor()
3+
*/
24
export enum GA_ {
35
PARENT = 1,
46
ROOT = 2,

source/win32/consts/HWND.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/** Special HWND values. */
1+
/**
2+
* Special HWND values.
3+
*/
24
export enum HWND_ {
35
// PostMessage() and SendMessage()
46
BROADCAST = 0xffff,

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
// Output
1919
"outDir": "dist",
20-
"target": "ES2023",
20+
"target": "ESNext",
2121
"removeComments": false,
2222
"preserveConstEnums": true,
2323
"sourceMap": true,

0 commit comments

Comments
 (0)