@jk2908/cxx 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/build.d.ts +13 -11
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +90 -47
- package/dist/build.js.map +4 -4
- package/dist/next/cxx-loader.cjs +85 -26
- package/dist/next/cxx-loader.cjs.map +6 -6
- package/dist/next/index.d.ts +4 -0
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/index.js +96 -49
- package/dist/next/index.js.map +5 -5
- package/dist/types.d.ts +2 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/vite/index.d.ts +0 -7
- package/dist/vite/index.d.ts.map +1 -1
- package/dist/vite/index.js +133 -94
- package/dist/vite/index.js.map +5 -5
- package/package.json +6 -6
- package/src/build.ts +104 -65
- package/src/next/cxx-loader.js +8 -1
- package/src/next/index.ts +14 -5
- package/src/types.ts +2 -6
- package/src/utils.ts +20 -12
- package/src/vite/index.ts +78 -57
package/src/vite/index.ts
CHANGED
|
@@ -4,8 +4,14 @@ import path from 'node:path'
|
|
|
4
4
|
|
|
5
5
|
import { type Plugin, type ResolvedConfig, type ViteDevServer } from 'vite'
|
|
6
6
|
|
|
7
|
-
import type {
|
|
8
|
-
import {
|
|
7
|
+
import type { PluginConfig } from '../types.js'
|
|
8
|
+
import {
|
|
9
|
+
collect,
|
|
10
|
+
flushTypes,
|
|
11
|
+
isENOENT,
|
|
12
|
+
takeRebuildError,
|
|
13
|
+
writeTypesForFile,
|
|
14
|
+
} from '../build.js'
|
|
9
15
|
import { GENERATED_DIR } from '../config.js'
|
|
10
16
|
import { Logger } from '../logger.js'
|
|
11
17
|
import { createBatcher } from '../utils.js'
|
|
@@ -18,15 +24,23 @@ function normaliseWatchPath(p: string) {
|
|
|
18
24
|
return p.replace(/\\/g, '/')
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
/** Tiny glob matcher for the ignore patterns we support (`**`, `*`, `?`). */
|
|
28
|
+
function globToRegExp(glob: string) {
|
|
29
|
+
const pattern = glob
|
|
30
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
31
|
+
.replace(/\*\*(\/|$)/g, '.*$1')
|
|
32
|
+
.replace(/\*/g, '[^/]*')
|
|
33
|
+
.replace(/\?/g, '[^/]')
|
|
34
|
+
|
|
35
|
+
return new RegExp(`^${pattern}$`)
|
|
36
|
+
}
|
|
25
37
|
|
|
38
|
+
export default function cxx(pluginConfig: PluginConfig = {}) {
|
|
26
39
|
const logger = new Logger()
|
|
27
40
|
|
|
28
41
|
// resolved from Vite's root in `configResolved`, not from `process.cwd()`
|
|
29
42
|
let watchRoot = normaliseWatchPath(process.cwd())
|
|
43
|
+
const ignored = (pluginConfig.watch?.ignore ?? []).map(globToRegExp)
|
|
30
44
|
|
|
31
45
|
function resolveWatchFile(filePath: string) {
|
|
32
46
|
const absolutePath = path.resolve(watchRoot, filePath)
|
|
@@ -43,14 +57,15 @@ export default function cxx(pluginConfig: PluginConfig = {}) {
|
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
59
|
|
|
46
|
-
|
|
60
|
+
/** Returns the canonical, watchable path, or null when the file is irrelevant. */
|
|
47
61
|
function watchedFile(filePath: string): string | null {
|
|
48
62
|
const resolvedPath = resolveWatchFile(filePath)
|
|
49
63
|
|
|
50
64
|
if (
|
|
51
65
|
resolvedPath.startsWith(`${watchRoot}/`) &&
|
|
52
66
|
!resolvedPath.includes(`/${GENERATED_DIR}/`) &&
|
|
53
|
-
ACCEPTED_EXTENSIONS.has(path.extname(resolvedPath))
|
|
67
|
+
ACCEPTED_EXTENSIONS.has(path.extname(resolvedPath)) &&
|
|
68
|
+
!ignored.some(regexp => regexp.test(resolvedPath))
|
|
54
69
|
) {
|
|
55
70
|
return resolvedPath
|
|
56
71
|
}
|
|
@@ -58,47 +73,55 @@ export default function cxx(pluginConfig: PluginConfig = {}) {
|
|
|
58
73
|
return null
|
|
59
74
|
}
|
|
60
75
|
|
|
61
|
-
async function
|
|
62
|
-
const hadTags = buildContext.tagsByFile.has(file)
|
|
63
|
-
|
|
64
|
-
if (event === 'unlink') {
|
|
65
|
-
if (!hadTags) return
|
|
66
|
-
|
|
67
|
-
buildContext.tagsByFile.delete(file)
|
|
68
|
-
await writeTypes(flattenTags(buildContext.tagsByFile))
|
|
69
|
-
logger.info(`updated types from ${file}`)
|
|
70
|
-
|
|
71
|
-
return
|
|
72
|
-
}
|
|
73
|
-
|
|
76
|
+
async function recordFile(file: string) {
|
|
74
77
|
let source: string
|
|
75
78
|
|
|
76
79
|
try {
|
|
77
80
|
source = await fs.readFile(file, 'utf-8')
|
|
78
81
|
} catch (err) {
|
|
79
82
|
if (!isENOENT(err)) throw err
|
|
80
|
-
|
|
83
|
+
await writeTypesForFile(file, new Map())
|
|
84
|
+
return
|
|
85
|
+
}
|
|
81
86
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
const tags = source.includes('cxx')
|
|
88
|
+
? collect(source, file, pluginConfig).tags
|
|
89
|
+
: new Map()
|
|
90
|
+
await writeTypesForFile(file, tags)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Seed the type surface from the source tree, so a fresh checkout has types before any file is
|
|
95
|
+
* transformed or edited.
|
|
96
|
+
*/
|
|
97
|
+
async function scan(dir: string) {
|
|
98
|
+
let entries
|
|
85
99
|
|
|
100
|
+
try {
|
|
101
|
+
entries = await fs.readdir(dir, { withFileTypes: true })
|
|
102
|
+
} catch {
|
|
86
103
|
return
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
|
|
90
|
-
|
|
106
|
+
await Promise.all(
|
|
107
|
+
entries.map(async entry => {
|
|
108
|
+
if (entry.name.startsWith('.') || entry.name === 'node_modules') return
|
|
91
109
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
110
|
+
const full = path.join(dir, entry.name)
|
|
111
|
+
|
|
112
|
+
if (entry.isDirectory()) {
|
|
113
|
+
await scan(full)
|
|
114
|
+
return
|
|
115
|
+
}
|
|
97
116
|
|
|
98
|
-
|
|
117
|
+
if (!ACCEPTED_EXTENSIONS.has(path.extname(entry.name))) return
|
|
99
118
|
|
|
100
|
-
|
|
101
|
-
|
|
119
|
+
const resolved = resolveWatchFile(full)
|
|
120
|
+
if (ignored.some(regexp => regexp.test(resolved))) return
|
|
121
|
+
|
|
122
|
+
await recordFile(resolved)
|
|
123
|
+
}),
|
|
124
|
+
)
|
|
102
125
|
}
|
|
103
126
|
|
|
104
127
|
// collect change events that arrive close together and handle each file once.
|
|
@@ -107,7 +130,13 @@ export default function cxx(pluginConfig: PluginConfig = {}) {
|
|
|
107
130
|
const queue = createBatcher<string, WatchEvent>(75, async batch => {
|
|
108
131
|
for (const [file, event] of batch) {
|
|
109
132
|
try {
|
|
110
|
-
|
|
133
|
+
if (event === 'unlink') {
|
|
134
|
+
await writeTypesForFile(file, new Map())
|
|
135
|
+
} else {
|
|
136
|
+
await recordFile(file)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
logger.info(`updated types from ${file}`)
|
|
111
140
|
} catch (err) {
|
|
112
141
|
logger.error(`failed to update types from ${file}`, err)
|
|
113
142
|
}
|
|
@@ -117,19 +146,6 @@ export default function cxx(pluginConfig: PluginConfig = {}) {
|
|
|
117
146
|
return {
|
|
118
147
|
name: 'cxx',
|
|
119
148
|
enforce: 'pre',
|
|
120
|
-
config() {
|
|
121
|
-
const ignore = pluginConfig.watch?.ignore
|
|
122
|
-
if (!ignore?.length) return null
|
|
123
|
-
|
|
124
|
-
// Vite concatenates `server.watch.ignored`, so only return the additions
|
|
125
|
-
return {
|
|
126
|
-
server: {
|
|
127
|
-
watch: {
|
|
128
|
-
ignored: ignore,
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
}
|
|
132
|
-
},
|
|
133
149
|
configResolved(config: ResolvedConfig) {
|
|
134
150
|
const root = pluginConfig.watch?.root ?? config.root
|
|
135
151
|
|
|
@@ -141,22 +157,19 @@ export default function cxx(pluginConfig: PluginConfig = {}) {
|
|
|
141
157
|
},
|
|
142
158
|
transform(code, id) {
|
|
143
159
|
if (id.startsWith('\0') || id.includes('/node_modules/')) return null
|
|
160
|
+
|
|
144
161
|
// cheap bail-out before running the regex/lightningcss on every module
|
|
145
162
|
if (!code.includes('cxx')) return null
|
|
146
163
|
|
|
147
164
|
const cleanId = id.split('?')[0]
|
|
165
|
+
|
|
148
166
|
if (!ACCEPTED_EXTENSIONS.has(path.extname(cleanId))) return null
|
|
149
167
|
|
|
150
168
|
const { template, tags, map } = collect(code, cleanId, pluginConfig)
|
|
169
|
+
|
|
151
170
|
if (template === code) return null
|
|
152
171
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
if (tags.size) {
|
|
156
|
-
buildContext.tagsByFile.set(resolved, tags)
|
|
157
|
-
} else {
|
|
158
|
-
buildContext.tagsByFile.delete(resolved)
|
|
159
|
-
}
|
|
172
|
+
void writeTypesForFile(resolveWatchFile(cleanId), tags)
|
|
160
173
|
|
|
161
174
|
return {
|
|
162
175
|
code: template,
|
|
@@ -179,9 +192,17 @@ export default function cxx(pluginConfig: PluginConfig = {}) {
|
|
|
179
192
|
const file = watchedFile(p)
|
|
180
193
|
if (file) queue.add(file, 'unlink')
|
|
181
194
|
})
|
|
195
|
+
|
|
196
|
+
void scan(watchRoot)
|
|
197
|
+
.then(() => flushTypes())
|
|
198
|
+
.then(() => {
|
|
199
|
+
const err = takeRebuildError()
|
|
200
|
+
if (err) logger.error('failed to generate types', err)
|
|
201
|
+
})
|
|
202
|
+
.catch(err => logger.error('failed to scan for cxx templates', err))
|
|
182
203
|
},
|
|
183
204
|
async writeBundle() {
|
|
184
|
-
await
|
|
205
|
+
await flushTypes()
|
|
185
206
|
},
|
|
186
207
|
} satisfies Plugin
|
|
187
208
|
}
|