@remix-run/assets 0.4.4 → 0.6.0
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 +331 -72
- package/dist/assets.d.ts +4 -1
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +2 -2
- package/dist/lib/access.d.ts +35 -4
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +332 -11
- package/dist/lib/asset-server.d.ts +138 -11
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +281 -30
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/file-matcher.js +1 -1
- package/dist/lib/files/compiler.d.ts.map +1 -1
- package/dist/lib/files/compiler.js +9 -8
- package/dist/lib/files/config.js +1 -1
- package/dist/lib/hmr.d.ts +37 -0
- package/dist/lib/hmr.d.ts.map +1 -0
- package/dist/lib/hmr.js +357 -0
- package/dist/lib/injected-packages.d.ts +3 -2
- package/dist/lib/injected-packages.d.ts.map +1 -1
- package/dist/lib/injected-packages.js +43 -17
- package/dist/lib/inspection.d.ts +39 -0
- package/dist/lib/inspection.d.ts.map +1 -0
- package/dist/lib/inspection.js +160 -0
- package/dist/lib/loaders.d.ts +57 -0
- package/dist/lib/loaders.d.ts.map +1 -0
- package/dist/lib/loaders.js +1 -0
- package/dist/lib/module-store.d.ts +24 -0
- package/dist/lib/module-store.d.ts.map +1 -1
- package/dist/lib/module-store.js +136 -11
- package/dist/lib/routes.d.ts +11 -3
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +124 -80
- package/dist/lib/scripts/compiler.d.ts +24 -1
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +183 -29
- package/dist/lib/scripts/conditions.d.ts +2 -0
- package/dist/lib/scripts/conditions.d.ts.map +1 -0
- package/dist/lib/scripts/conditions.js +1 -0
- package/dist/lib/scripts/emit.d.ts +3 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -1
- package/dist/lib/scripts/emit.js +24 -5
- package/dist/lib/scripts/resolve.d.ts +4 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +75 -10
- package/dist/lib/scripts/transform.d.ts +9 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +222 -18
- package/dist/lib/source-maps.js +1 -1
- package/dist/lib/styles/compiler.d.ts +14 -1
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +78 -14
- package/dist/lib/styles/emit.js +4 -4
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +16 -15
- package/dist/lib/styles/transform.js +5 -5
- package/dist/lib/target.d.ts +1 -1
- package/dist/lib/target.d.ts.map +1 -1
- package/dist/lib/watch.js +1 -1
- package/dist/types/hmr.d.ts +36 -0
- package/package.json +16 -11
- package/src/assets.ts +4 -1
- package/src/lib/access.ts +445 -11
- package/src/lib/asset-server.ts +466 -27
- package/src/lib/compilation-error.ts +4 -3
- package/src/lib/files/compiler.ts +9 -5
- package/src/lib/hmr.ts +397 -0
- package/src/lib/injected-packages.ts +52 -16
- package/src/lib/inspection.ts +229 -0
- package/src/lib/loaders.ts +80 -0
- package/src/lib/module-store.ts +190 -9
- package/src/lib/routes.ts +158 -126
- package/src/lib/scripts/compiler.ts +248 -24
- package/src/lib/scripts/conditions.ts +1 -0
- package/src/lib/scripts/emit.ts +50 -5
- package/src/lib/scripts/resolve.ts +129 -7
- package/src/lib/scripts/transform.ts +290 -19
- package/src/lib/styles/compiler.ts +108 -8
- package/src/lib/styles/emit.ts +1 -1
- package/src/lib/styles/resolve.ts +19 -15
- package/src/lib/styles/transform.ts +2 -2
- package/src/types/hmr.d.ts +36 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import * as fsPromises from 'node:fs/promises'
|
|
3
|
+
import * as path from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
import type { AccessPolicy, AssetAccessDetails } from './access.ts'
|
|
7
|
+
import { parseFingerprintSuffix } from './fingerprint.ts'
|
|
8
|
+
import { getInjectedPackageRoots } from './injected-packages.ts'
|
|
9
|
+
import { isAbsoluteFilePath, normalizeFilePath, resolveFilePath } from './paths.ts'
|
|
10
|
+
import type { AssetRouteMatch, CompiledRoutes } from './routes.ts'
|
|
11
|
+
import { supportedScriptExtensions } from './scripts/resolve.ts'
|
|
12
|
+
import { isStyleFilePath } from './styles/compiler.ts'
|
|
13
|
+
|
|
14
|
+
const scriptExtensions = new Set<string>(supportedScriptExtensions)
|
|
15
|
+
const globSyntaxPattern = /[*?[\]{}()!+@]/
|
|
16
|
+
|
|
17
|
+
/** How the asset server handles an inspected file. */
|
|
18
|
+
export type AssetKind = 'file' | 'script' | 'style' | 'unsupported'
|
|
19
|
+
|
|
20
|
+
/** Browser-reachability result for an inspected asset. */
|
|
21
|
+
export type AssetStatus =
|
|
22
|
+
| 'denied'
|
|
23
|
+
| 'missing'
|
|
24
|
+
| 'not-allowed'
|
|
25
|
+
| 'reachable'
|
|
26
|
+
| 'unmapped'
|
|
27
|
+
| 'unsupported'
|
|
28
|
+
|
|
29
|
+
/** Diagnostic information about a configured asset URL or file path. */
|
|
30
|
+
export interface AssetDetails {
|
|
31
|
+
/** Access-control decision and the rules responsible for it. */
|
|
32
|
+
access?: AssetAccessDetails
|
|
33
|
+
/** Absolute mapped file path. */
|
|
34
|
+
filePath?: string
|
|
35
|
+
/** Configured filesystem mount root that matched the asset. */
|
|
36
|
+
fileRoot?: string
|
|
37
|
+
/** Browser-reachability result. */
|
|
38
|
+
status: AssetStatus
|
|
39
|
+
/** How the asset server handles the file. */
|
|
40
|
+
type?: AssetKind
|
|
41
|
+
/** Stable public URL pathname for the asset. */
|
|
42
|
+
url?: string
|
|
43
|
+
/** Public mount root that matched the asset. */
|
|
44
|
+
urlRoot?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface AssetInspectorOptions {
|
|
48
|
+
accessPolicy: AccessPolicy
|
|
49
|
+
allowFiles: readonly string[]
|
|
50
|
+
fileExtensions: readonly string[]
|
|
51
|
+
rootDir: string
|
|
52
|
+
routes: CompiledRoutes
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface AssetInspector {
|
|
56
|
+
/** Returns diagnostic information for a public URL or file path. */
|
|
57
|
+
getAssetDetails(input: string): Promise<AssetDetails>
|
|
58
|
+
/** Returns every file currently reachable through the configured asset server. */
|
|
59
|
+
getAssets(): Promise<AssetDetails[]>
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createAssetInspector(options: AssetInspectorOptions): AssetInspector {
|
|
63
|
+
let fileExtensions = new Set(options.fileExtensions.map((extension) => extension.toLowerCase()))
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
async getAssetDetails(input) {
|
|
67
|
+
let routeMatch = await resolveInput(input, options)
|
|
68
|
+
if (routeMatch === null) return { status: 'unmapped' }
|
|
69
|
+
return inspectRouteMatch(routeMatch, options, fileExtensions)
|
|
70
|
+
},
|
|
71
|
+
async getAssets() {
|
|
72
|
+
let filePaths = await discoverFilePaths(options)
|
|
73
|
+
let assets: AssetDetails[] = []
|
|
74
|
+
|
|
75
|
+
for (let filePath of filePaths) {
|
|
76
|
+
let routeMatch = options.routes.matchFilePath(filePath)
|
|
77
|
+
if (routeMatch === null) continue
|
|
78
|
+
|
|
79
|
+
let details = await inspectRouteMatch(routeMatch, options, fileExtensions)
|
|
80
|
+
if (details.status === 'reachable') assets.push(details)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
assets.sort((left, right) => {
|
|
84
|
+
let urlOrder = (left.url ?? '').localeCompare(right.url ?? '')
|
|
85
|
+
return urlOrder === 0 ? (left.filePath ?? '').localeCompare(right.filePath ?? '') : urlOrder
|
|
86
|
+
})
|
|
87
|
+
return assets
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function resolveInput(
|
|
93
|
+
input: string,
|
|
94
|
+
options: Pick<AssetInspectorOptions, 'rootDir' | 'routes'>,
|
|
95
|
+
): Promise<AssetRouteMatch | null> {
|
|
96
|
+
if (input.startsWith('file://')) {
|
|
97
|
+
return options.routes.matchFilePath(fileURLToPath(input))
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (/^[A-Za-z][A-Za-z\d+.-]*:\/\//.test(input)) {
|
|
101
|
+
let pathname = parseFingerprintSuffix(new URL(input).pathname).pathname
|
|
102
|
+
return options.routes.matchUrlPathname(pathname)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let filePath = resolveFilePath(options.rootDir, input)
|
|
106
|
+
if (!input.startsWith('/') || isAbsoluteFilePath(input)) {
|
|
107
|
+
if (await pathExists(filePath)) return options.routes.matchFilePath(filePath)
|
|
108
|
+
if (!input.startsWith('/')) return options.routes.matchFilePath(filePath)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
let pathname = parseFingerprintSuffix(new URL(input, 'http://remix.run').pathname).pathname
|
|
112
|
+
return options.routes.matchUrlPathname(pathname)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function inspectRouteMatch(
|
|
116
|
+
routeMatch: AssetRouteMatch,
|
|
117
|
+
options: Pick<AssetInspectorOptions, 'accessPolicy' | 'rootDir'>,
|
|
118
|
+
fileExtensions: ReadonlySet<string>,
|
|
119
|
+
): Promise<AssetDetails> {
|
|
120
|
+
let exists = await pathExists(routeMatch.filePath)
|
|
121
|
+
let identityPath = exists ? fs.realpathSync(routeMatch.filePath) : routeMatch.filePath
|
|
122
|
+
let normalizedIdentityPath = normalizeFilePath(identityPath)
|
|
123
|
+
let access = options.accessPolicy.inspect(normalizedIdentityPath)
|
|
124
|
+
|
|
125
|
+
let type = getAssetKind(routeMatch.filePath, fileExtensions)
|
|
126
|
+
let details = {
|
|
127
|
+
access,
|
|
128
|
+
filePath: routeMatch.filePath,
|
|
129
|
+
fileRoot: routeMatch.fileRoot,
|
|
130
|
+
type,
|
|
131
|
+
url: routeMatch.urlPathname,
|
|
132
|
+
urlRoot: routeMatch.urlRoot,
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!exists) return { ...details, status: 'missing' }
|
|
136
|
+
if (!access.allowed) {
|
|
137
|
+
return { ...details, status: access.deniedBy === undefined ? 'not-allowed' : 'denied' }
|
|
138
|
+
}
|
|
139
|
+
if (type === 'unsupported') return { ...details, status: 'unsupported' }
|
|
140
|
+
return { ...details, status: 'reachable' }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function getAssetKind(filePath: string, fileExtensions: ReadonlySet<string>): AssetKind {
|
|
144
|
+
let extension = path.extname(filePath).toLowerCase()
|
|
145
|
+
if (scriptExtensions.has(extension)) return 'script'
|
|
146
|
+
if (isStyleFilePath(filePath)) return 'style'
|
|
147
|
+
if (fileExtensions.has(extension)) return 'file'
|
|
148
|
+
return 'unsupported'
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function discoverFilePaths(options: AssetInspectorOptions): Promise<string[]> {
|
|
152
|
+
let roots = new Set<string>()
|
|
153
|
+
|
|
154
|
+
for (let pattern of options.allowFiles) {
|
|
155
|
+
roots.add(resolveDiscoveryRoot(options.rootDir, pattern))
|
|
156
|
+
}
|
|
157
|
+
for (let packageRoot of options.accessPolicy.getAllowedPackageRoots()) {
|
|
158
|
+
roots.add(packageRoot)
|
|
159
|
+
}
|
|
160
|
+
for (let packageRoot of getInjectedPackageRoots()) {
|
|
161
|
+
roots.add(packageRoot)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let filePaths = new Set<string>()
|
|
165
|
+
for (let root of roots) {
|
|
166
|
+
await collectFiles(root, filePaths)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return [...filePaths]
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function resolveDiscoveryRoot(rootDir: string, pattern: string): string {
|
|
173
|
+
let dynamicIndex = pattern.search(globSyntaxPattern)
|
|
174
|
+
if (dynamicIndex === -1) return resolveFilePath(rootDir, pattern)
|
|
175
|
+
|
|
176
|
+
let rawStaticPrefix = pattern.slice(0, dynamicIndex)
|
|
177
|
+
let staticPrefix = rawStaticPrefix.replace(/[/\\]+$/, '')
|
|
178
|
+
if (staticPrefix.length === 0) return rootDir
|
|
179
|
+
return resolveFilePath(
|
|
180
|
+
rootDir,
|
|
181
|
+
/[/\\]$/.test(rawStaticPrefix) ? staticPrefix : path.dirname(staticPrefix),
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function collectFiles(root: string, filePaths: Set<string>): Promise<void> {
|
|
186
|
+
let stat
|
|
187
|
+
try {
|
|
188
|
+
stat = await fsPromises.stat(root)
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (isPathNotFoundError(error)) return
|
|
191
|
+
throw error
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (stat.isFile()) {
|
|
195
|
+
filePaths.add(normalizeFilePath(root))
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
if (!stat.isDirectory()) return
|
|
199
|
+
|
|
200
|
+
let entries = await fsPromises.readdir(root, { withFileTypes: true })
|
|
201
|
+
for (let entry of entries) {
|
|
202
|
+
let entryPath = path.join(root, entry.name)
|
|
203
|
+
if (entry.isDirectory()) {
|
|
204
|
+
await collectFiles(entryPath, filePaths)
|
|
205
|
+
} else if (entry.isFile()) {
|
|
206
|
+
filePaths.add(normalizeFilePath(entryPath))
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function pathExists(filePath: string): Promise<boolean> {
|
|
212
|
+
try {
|
|
213
|
+
await fsPromises.access(filePath)
|
|
214
|
+
return true
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (isPathNotFoundError(error)) return false
|
|
217
|
+
throw error
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function isPathNotFoundError(
|
|
222
|
+
error: unknown,
|
|
223
|
+
): error is NodeJS.ErrnoException & { code: 'ENOENT' | 'ENOTDIR' } {
|
|
224
|
+
return (
|
|
225
|
+
error instanceof Error &&
|
|
226
|
+
'code' in error &&
|
|
227
|
+
(error.code === 'ENOENT' || error.code === 'ENOTDIR')
|
|
228
|
+
)
|
|
229
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
type ModuleFloat16Array = typeof globalThis extends {
|
|
2
|
+
Float16Array: { prototype: infer array }
|
|
3
|
+
}
|
|
4
|
+
? array
|
|
5
|
+
: never
|
|
6
|
+
|
|
7
|
+
type ModuleTypedArray =
|
|
8
|
+
| Uint8Array
|
|
9
|
+
| Uint8ClampedArray
|
|
10
|
+
| Uint16Array
|
|
11
|
+
| Uint32Array
|
|
12
|
+
| Int8Array
|
|
13
|
+
| Int16Array
|
|
14
|
+
| Int32Array
|
|
15
|
+
| BigUint64Array
|
|
16
|
+
| BigInt64Array
|
|
17
|
+
| ModuleFloat16Array
|
|
18
|
+
| Float32Array
|
|
19
|
+
| Float64Array
|
|
20
|
+
|
|
21
|
+
type ModuleLoadSource = string | ArrayBuffer | ModuleTypedArray
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Synchronously loads a module or delegates to the next configured loader.
|
|
25
|
+
*
|
|
26
|
+
* Loaders follow Node's synchronous `load` hook signature and chaining contract. The asset server
|
|
27
|
+
* passes them JavaScript after its TypeScript/JavaScript transform and before HMR analysis and
|
|
28
|
+
* minification. Each loader must delegate to `nextLoad` or return `shortCircuit: true`.
|
|
29
|
+
*
|
|
30
|
+
* @param url Resolved `file:` URL for the module being compiled.
|
|
31
|
+
* @param context Format, conditions, attributes, and public asset URL for this load.
|
|
32
|
+
* @param nextLoad Next loader in the chain, ending with the asset server's compiled JavaScript.
|
|
33
|
+
* @returns A Node-compatible load result containing ES module source.
|
|
34
|
+
*/
|
|
35
|
+
export type ModuleLoader = (
|
|
36
|
+
url: string,
|
|
37
|
+
context: ModuleLoadContext,
|
|
38
|
+
nextLoad: NextModuleLoader,
|
|
39
|
+
) => ModuleLoadResult
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Continues loading through the remaining loader chain.
|
|
43
|
+
*
|
|
44
|
+
* Omitted context fields retain their current values.
|
|
45
|
+
*
|
|
46
|
+
* @param url Current module URL. Loaders cannot change this URL.
|
|
47
|
+
* @param context Context fields to override for the remainder of this load.
|
|
48
|
+
* @returns The source produced by the remaining loaders or the asset compiler.
|
|
49
|
+
*/
|
|
50
|
+
export type NextModuleLoader = (
|
|
51
|
+
url: string,
|
|
52
|
+
context?: Partial<ModuleLoadContext>,
|
|
53
|
+
) => ModuleLoadResult
|
|
54
|
+
|
|
55
|
+
/** State passed through a module loader chain. */
|
|
56
|
+
export interface ModuleLoadContext {
|
|
57
|
+
/** Package export conditions used while compiling this module. */
|
|
58
|
+
conditions: string[]
|
|
59
|
+
/** Current module format. Asset-server loaders must ultimately return `'module'`. */
|
|
60
|
+
format: string | null | undefined
|
|
61
|
+
/** Import attributes supplied by the previous loader. Authored import attributes are unsupported. */
|
|
62
|
+
importAttributes: Record<string, string | undefined>
|
|
63
|
+
/**
|
|
64
|
+
* Stable public URL path used to request this module from the asset server.
|
|
65
|
+
*
|
|
66
|
+
* Unlike Node's standard load context, this field is provided so loaders can use the browser
|
|
67
|
+
* module identity rather than its private filesystem URL.
|
|
68
|
+
*/
|
|
69
|
+
moduleUrl?: string
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Source returned by a module loader. */
|
|
73
|
+
export interface ModuleLoadResult {
|
|
74
|
+
/** Loaded module format. Must be `'module'` for asset-server scripts. */
|
|
75
|
+
format: string | null | undefined
|
|
76
|
+
/** Whether this result intentionally ends the loader chain without calling `nextLoad`. */
|
|
77
|
+
shortCircuit?: boolean
|
|
78
|
+
/** Compiled JavaScript source, optionally rewritten by the loader. */
|
|
79
|
+
source?: ModuleLoadSource
|
|
80
|
+
}
|
package/src/lib/module-store.ts
CHANGED
|
@@ -14,11 +14,27 @@ export type FileSnapshot = {
|
|
|
14
14
|
|
|
15
15
|
export type ModuleSnapshot = ReadonlyMap<string, FileSnapshot>
|
|
16
16
|
|
|
17
|
+
type ModuleLinksState = {
|
|
18
|
+
acceptedDependencies: ReadonlySet<string>
|
|
19
|
+
dependencies: ReadonlySet<string>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type MutableModuleLinks = {
|
|
23
|
+
acceptedDependencies: Set<string>
|
|
24
|
+
dependencies: Set<string>
|
|
25
|
+
}
|
|
26
|
+
|
|
17
27
|
type ModuleRecordState<transformed, resolved, emitted> = {
|
|
28
|
+
emittedVersion?: number
|
|
29
|
+
hmrUpdateTimestamp?: number
|
|
18
30
|
identityPath: string
|
|
19
31
|
invalidationVersion: number
|
|
20
32
|
transformed?: transformed
|
|
33
|
+
transformedVersion?: number
|
|
21
34
|
resolved?: resolved
|
|
35
|
+
resolvedVersion?: number
|
|
36
|
+
lastResolved?: resolved
|
|
37
|
+
links: ModuleLinksState
|
|
22
38
|
emitted?: emitted
|
|
23
39
|
emittedSnapshot?: ModuleSnapshot
|
|
24
40
|
staleEmitted?: emitted
|
|
@@ -32,10 +48,16 @@ export type ModuleRecord<transformed, resolved, emitted> = Readonly<
|
|
|
32
48
|
>
|
|
33
49
|
|
|
34
50
|
type MutableModuleRecord<transformed, resolved, emitted> = {
|
|
51
|
+
emittedVersion?: number
|
|
52
|
+
hmrUpdateTimestamp?: number
|
|
35
53
|
identityPath: string
|
|
36
54
|
invalidationVersion: number
|
|
37
55
|
transformed?: transformed
|
|
56
|
+
transformedVersion?: number
|
|
38
57
|
resolved?: resolved
|
|
58
|
+
resolvedVersion?: number
|
|
59
|
+
lastResolved?: resolved
|
|
60
|
+
links: MutableModuleLinks
|
|
39
61
|
emitted?: emitted
|
|
40
62
|
emittedSnapshot?: ModuleSnapshot
|
|
41
63
|
staleEmitted?: emitted
|
|
@@ -46,6 +68,14 @@ type MutableModuleRecord<transformed, resolved, emitted> = {
|
|
|
46
68
|
|
|
47
69
|
export type ModuleStore<transformed, resolved, emitted> = {
|
|
48
70
|
get(identityPath: string): ModuleRecord<transformed, resolved, emitted>
|
|
71
|
+
getAcceptedImporters(identityPath: string): ReadonlySet<string>
|
|
72
|
+
getHmrUpdateTimestamp(identityPath: string): number | undefined
|
|
73
|
+
getImporters(identityPath: string): ReadonlySet<string>
|
|
74
|
+
getLastResolved(identityPath: string): resolved | undefined
|
|
75
|
+
isEmittedFresh(record: ModuleRecord<transformed, resolved, emitted>): boolean
|
|
76
|
+
isResolvedFresh(record: ModuleRecord<transformed, resolved, emitted>): boolean
|
|
77
|
+
isTransformedFresh(record: ModuleRecord<transformed, resolved, emitted>): boolean
|
|
78
|
+
setHmrUpdateTimestamp(identityPath: string, timestamp: number): void
|
|
49
79
|
clearTransformed(identityPath: string, tracking: readonly ModuleTracking[]): void
|
|
50
80
|
setTransformed(
|
|
51
81
|
identityPath: string,
|
|
@@ -61,12 +91,19 @@ export type ModuleStore<transformed, resolved, emitted> = {
|
|
|
61
91
|
|
|
62
92
|
export function createModuleStore<transformed, resolved, emitted>(
|
|
63
93
|
options: {
|
|
94
|
+
getAcceptedDependencies?: (resolved: resolved) => readonly string[]
|
|
95
|
+
getDependencies?: (resolved: resolved) => readonly string[]
|
|
64
96
|
onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
97
|
+
onWatchFilesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
65
98
|
} = {},
|
|
66
99
|
): ModuleStore<transformed, resolved, emitted> {
|
|
67
100
|
let recordsByIdentityPath = new Map<string, MutableModuleRecord<transformed, resolved, emitted>>()
|
|
101
|
+
let importersByDepPath = new Map<string, Set<string>>()
|
|
102
|
+
let acceptedImportersByDepPath = new Map<string, Set<string>>()
|
|
68
103
|
let recordsByTrackedFile = new Map<string, Set<string>>()
|
|
69
104
|
let watchDirectoryRefCountByPath = new Map<string, number>()
|
|
105
|
+
let watchFileRefCountByPath = new Map<string, number>()
|
|
106
|
+
let emptyImporters = new Set<string>()
|
|
70
107
|
|
|
71
108
|
return {
|
|
72
109
|
get(identityPath) {
|
|
@@ -76,17 +113,55 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
76
113
|
let record: MutableModuleRecord<transformed, resolved, emitted> = {
|
|
77
114
|
identityPath,
|
|
78
115
|
invalidationVersion: 0,
|
|
116
|
+
links: createEmptyLinks(),
|
|
79
117
|
trackedFiles: new Set(),
|
|
80
118
|
trackedDirectories: new Set(),
|
|
81
119
|
}
|
|
82
120
|
recordsByIdentityPath.set(identityPath, record)
|
|
83
121
|
return record
|
|
84
122
|
},
|
|
123
|
+
|
|
124
|
+
getAcceptedImporters(identityPath) {
|
|
125
|
+
return acceptedImportersByDepPath.get(identityPath) ?? emptyImporters
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
getHmrUpdateTimestamp(identityPath) {
|
|
129
|
+
return recordsByIdentityPath.get(identityPath)?.hmrUpdateTimestamp
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
getImporters(identityPath) {
|
|
133
|
+
return importersByDepPath.get(identityPath) ?? emptyImporters
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
getLastResolved(identityPath) {
|
|
137
|
+
return recordsByIdentityPath.get(identityPath)?.lastResolved
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
isEmittedFresh(record) {
|
|
141
|
+
return record.emittedVersion === record.invalidationVersion
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
isResolvedFresh(record) {
|
|
145
|
+
return record.resolvedVersion === record.invalidationVersion
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
isTransformedFresh(record) {
|
|
149
|
+
return record.transformedVersion === record.invalidationVersion
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
setHmrUpdateTimestamp(identityPath, timestamp) {
|
|
153
|
+
let record = getOrCreateMutableRecord(identityPath)
|
|
154
|
+
record.hmrUpdateTimestamp = timestamp
|
|
155
|
+
},
|
|
156
|
+
|
|
85
157
|
clearTransformed(identityPath, tracking) {
|
|
86
158
|
let record = getOrCreateMutableRecord(identityPath)
|
|
87
159
|
record.transformed = undefined
|
|
160
|
+
record.transformedVersion = undefined
|
|
88
161
|
record.resolved = undefined
|
|
162
|
+
record.resolvedVersion = undefined
|
|
89
163
|
record.emitted = undefined
|
|
164
|
+
record.emittedVersion = undefined
|
|
90
165
|
record.emittedSnapshot = undefined
|
|
91
166
|
record.staleEmitted = undefined
|
|
92
167
|
record.staleEmittedSnapshot = undefined
|
|
@@ -96,8 +171,11 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
96
171
|
setTransformed(identityPath, transformed, tracking) {
|
|
97
172
|
let record = getOrCreateMutableRecord(identityPath)
|
|
98
173
|
record.transformed = transformed
|
|
174
|
+
record.transformedVersion = record.invalidationVersion
|
|
99
175
|
record.resolved = undefined
|
|
176
|
+
record.resolvedVersion = undefined
|
|
100
177
|
record.emitted = undefined
|
|
178
|
+
record.emittedVersion = undefined
|
|
101
179
|
record.emittedSnapshot = undefined
|
|
102
180
|
record.staleEmitted = undefined
|
|
103
181
|
record.staleEmittedSnapshot = undefined
|
|
@@ -106,18 +184,26 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
106
184
|
|
|
107
185
|
setResolved(identityPath, resolved, tracking) {
|
|
108
186
|
let record = getOrCreateMutableRecord(identityPath)
|
|
187
|
+
removeResolvedIndexes(record)
|
|
109
188
|
record.resolved = resolved
|
|
189
|
+
record.resolvedVersion = record.invalidationVersion
|
|
190
|
+
record.lastResolved = resolved
|
|
191
|
+
record.links = createLinks(resolved)
|
|
110
192
|
record.emitted = undefined
|
|
193
|
+
record.emittedVersion = undefined
|
|
111
194
|
record.emittedSnapshot = undefined
|
|
112
195
|
record.staleEmitted = undefined
|
|
113
196
|
record.staleEmittedSnapshot = undefined
|
|
114
197
|
setTracking(record, tracking)
|
|
198
|
+
addResolvedIndexes(record)
|
|
115
199
|
},
|
|
116
200
|
|
|
117
201
|
clearResolved(identityPath, tracking) {
|
|
118
202
|
let record = getOrCreateMutableRecord(identityPath)
|
|
119
203
|
record.resolved = undefined
|
|
204
|
+
record.resolvedVersion = undefined
|
|
120
205
|
record.emitted = undefined
|
|
206
|
+
record.emittedVersion = undefined
|
|
121
207
|
record.emittedSnapshot = undefined
|
|
122
208
|
record.staleEmitted = undefined
|
|
123
209
|
record.staleEmittedSnapshot = undefined
|
|
@@ -127,6 +213,7 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
127
213
|
setEmitted(identityPath, emitted, snapshot) {
|
|
128
214
|
let record = getOrCreateMutableRecord(identityPath)
|
|
129
215
|
record.emitted = emitted
|
|
216
|
+
record.emittedVersion = record.invalidationVersion
|
|
130
217
|
record.emittedSnapshot = snapshot ?? undefined
|
|
131
218
|
record.staleEmitted = undefined
|
|
132
219
|
record.staleEmittedSnapshot = undefined
|
|
@@ -145,12 +232,21 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
145
232
|
|
|
146
233
|
for (let identityPath of affected) {
|
|
147
234
|
let record = recordsByIdentityPath.get(identityPath)
|
|
148
|
-
if (record)
|
|
235
|
+
if (record) {
|
|
236
|
+
if (event === 'change') {
|
|
237
|
+
invalidateContent(record, { retainStale: true })
|
|
238
|
+
} else {
|
|
239
|
+
invalidateGraph(record)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
149
242
|
}
|
|
150
243
|
|
|
151
244
|
if (event === 'unlink') {
|
|
152
245
|
let deletedRecord = recordsByIdentityPath.get(filePath)
|
|
153
246
|
if (deletedRecord) {
|
|
247
|
+
if (!affected.has(filePath)) {
|
|
248
|
+
invalidateGraph(deletedRecord)
|
|
249
|
+
}
|
|
154
250
|
clearTracking(deletedRecord)
|
|
155
251
|
}
|
|
156
252
|
}
|
|
@@ -158,7 +254,7 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
158
254
|
|
|
159
255
|
invalidateAll() {
|
|
160
256
|
for (let record of recordsByIdentityPath.values()) {
|
|
161
|
-
|
|
257
|
+
invalidateGraph(record)
|
|
162
258
|
}
|
|
163
259
|
},
|
|
164
260
|
}
|
|
@@ -172,6 +268,7 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
172
268
|
let record: MutableModuleRecord<transformed, resolved, emitted> = {
|
|
173
269
|
identityPath,
|
|
174
270
|
invalidationVersion: 0,
|
|
271
|
+
links: createEmptyLinks(),
|
|
175
272
|
trackedFiles: new Set(),
|
|
176
273
|
trackedDirectories: new Set(),
|
|
177
274
|
}
|
|
@@ -179,7 +276,7 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
179
276
|
return record
|
|
180
277
|
}
|
|
181
278
|
|
|
182
|
-
function
|
|
279
|
+
function invalidateContent(
|
|
183
280
|
record: MutableModuleRecord<transformed, resolved, emitted>,
|
|
184
281
|
options: { retainStale: boolean },
|
|
185
282
|
) {
|
|
@@ -194,18 +291,51 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
194
291
|
record.staleEmittedSnapshot = undefined
|
|
195
292
|
}
|
|
196
293
|
|
|
197
|
-
record.emitted = undefined
|
|
198
|
-
record.emittedSnapshot = undefined
|
|
199
|
-
record.resolved = undefined
|
|
200
|
-
record.transformed = undefined
|
|
201
294
|
record.invalidationVersion += 1
|
|
202
295
|
}
|
|
203
296
|
|
|
297
|
+
function invalidateGraph(record: MutableModuleRecord<transformed, resolved, emitted>) {
|
|
298
|
+
record.hmrUpdateTimestamp = undefined
|
|
299
|
+
invalidateContent(record, { retainStale: false })
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function createLinks(resolved: resolved): MutableModuleLinks {
|
|
303
|
+
return {
|
|
304
|
+
acceptedDependencies: new Set(options.getAcceptedDependencies?.(resolved) ?? []),
|
|
305
|
+
dependencies: new Set(options.getDependencies?.(resolved) ?? []),
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function createEmptyLinks(): MutableModuleLinks {
|
|
310
|
+
return {
|
|
311
|
+
acceptedDependencies: new Set(),
|
|
312
|
+
dependencies: new Set(),
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function addResolvedIndexes(record: MutableModuleRecord<transformed, resolved, emitted>): void {
|
|
317
|
+
for (let depPath of record.links.dependencies) {
|
|
318
|
+
addToIndexedSet(importersByDepPath, depPath, record.identityPath)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
for (let depPath of record.links.acceptedDependencies) {
|
|
322
|
+
addToIndexedSet(acceptedImportersByDepPath, depPath, record.identityPath)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function removeResolvedIndexes(
|
|
327
|
+
record: MutableModuleRecord<transformed, resolved, emitted>,
|
|
328
|
+
): void {
|
|
329
|
+
removeFromIndexedSets(importersByDepPath, record.identityPath)
|
|
330
|
+
removeFromIndexedSets(acceptedImportersByDepPath, record.identityPath)
|
|
331
|
+
}
|
|
332
|
+
|
|
204
333
|
function setTracking(
|
|
205
334
|
record: MutableModuleRecord<transformed, resolved, emitted>,
|
|
206
335
|
tracking: readonly ModuleTracking[],
|
|
207
336
|
) {
|
|
208
337
|
let previousWatchedDirectories = getWatchedDirectories(record)
|
|
338
|
+
let previousWatchedFiles = new Set(record.trackedFiles)
|
|
209
339
|
removeIndexes(record)
|
|
210
340
|
|
|
211
341
|
let normalizedTracking = mergeTracking(tracking)
|
|
@@ -217,8 +347,13 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
217
347
|
}
|
|
218
348
|
|
|
219
349
|
let nextWatchedDirectories = getWatchedDirectories(record)
|
|
220
|
-
let
|
|
221
|
-
|
|
350
|
+
let directoryDelta = updateWatchDirectoryRefCounts(
|
|
351
|
+
previousWatchedDirectories,
|
|
352
|
+
nextWatchedDirectories,
|
|
353
|
+
)
|
|
354
|
+
let fileDelta = updateWatchFileRefCounts(previousWatchedFiles, record.trackedFiles)
|
|
355
|
+
emitWatchDirectoryDelta(directoryDelta)
|
|
356
|
+
emitWatchFileDelta(fileDelta)
|
|
222
357
|
}
|
|
223
358
|
|
|
224
359
|
function clearTracking(record: MutableModuleRecord<transformed, resolved, emitted>) {
|
|
@@ -262,11 +397,48 @@ export function createModuleStore<transformed, resolved, emitted>(
|
|
|
262
397
|
return { add, remove }
|
|
263
398
|
}
|
|
264
399
|
|
|
400
|
+
function updateWatchFileRefCounts(
|
|
401
|
+
previousWatchedFiles: ReadonlySet<string>,
|
|
402
|
+
nextWatchedFiles: ReadonlySet<string>,
|
|
403
|
+
): { add: string[]; remove: string[] } {
|
|
404
|
+
let add: string[] = []
|
|
405
|
+
let remove: string[] = []
|
|
406
|
+
|
|
407
|
+
for (let file of previousWatchedFiles) {
|
|
408
|
+
if (nextWatchedFiles.has(file)) continue
|
|
409
|
+
let previousCount = watchFileRefCountByPath.get(file)
|
|
410
|
+
if (!previousCount) continue
|
|
411
|
+
if (previousCount === 1) {
|
|
412
|
+
watchFileRefCountByPath.delete(file)
|
|
413
|
+
remove.push(file)
|
|
414
|
+
} else {
|
|
415
|
+
watchFileRefCountByPath.set(file, previousCount - 1)
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
for (let file of nextWatchedFiles) {
|
|
420
|
+
if (previousWatchedFiles.has(file)) continue
|
|
421
|
+
let previousCount = watchFileRefCountByPath.get(file) ?? 0
|
|
422
|
+
watchFileRefCountByPath.set(file, previousCount + 1)
|
|
423
|
+
if (previousCount === 0) {
|
|
424
|
+
add.push(file)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return { add, remove }
|
|
429
|
+
}
|
|
430
|
+
|
|
265
431
|
function emitWatchDirectoryDelta(delta: { add: string[]; remove: string[] }): void {
|
|
266
432
|
if (!options.onWatchDirectoriesChange) return
|
|
267
433
|
if (delta.add.length === 0 && delta.remove.length === 0) return
|
|
268
434
|
options.onWatchDirectoriesChange(delta)
|
|
269
435
|
}
|
|
436
|
+
|
|
437
|
+
function emitWatchFileDelta(delta: { add: string[]; remove: string[] }): void {
|
|
438
|
+
if (!options.onWatchFilesChange) return
|
|
439
|
+
if (delta.add.length === 0 && delta.remove.length === 0) return
|
|
440
|
+
options.onWatchFilesChange(delta)
|
|
441
|
+
}
|
|
270
442
|
}
|
|
271
443
|
|
|
272
444
|
function addToIndexedSet(map: Map<string, Set<string>>, key: string, value: string) {
|
|
@@ -284,6 +456,15 @@ function removeFromIndexedSet(map: Map<string, Set<string>>, key: string, value:
|
|
|
284
456
|
}
|
|
285
457
|
}
|
|
286
458
|
|
|
459
|
+
function removeFromIndexedSets(map: Map<string, Set<string>>, value: string): void {
|
|
460
|
+
for (let [key, values] of map) {
|
|
461
|
+
values.delete(value)
|
|
462
|
+
if (values.size === 0) {
|
|
463
|
+
map.delete(key)
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
287
468
|
function matchesTrackedDirectory(
|
|
288
469
|
trackedDirectories: ReadonlySet<string>,
|
|
289
470
|
filePath: string,
|