@remix-run/assets 0.2.0 → 0.4.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 +241 -28
- package/dist/assets.d.ts +1 -0
- package/dist/assets.d.ts.map +1 -1
- package/dist/assets.js +1 -0
- package/dist/lib/access.d.ts +1 -0
- package/dist/lib/access.d.ts.map +1 -1
- package/dist/lib/access.js +10 -1
- package/dist/lib/asset-server.d.ts +33 -7
- package/dist/lib/asset-server.d.ts.map +1 -1
- package/dist/lib/asset-server.js +195 -23
- package/dist/lib/compilation-error.d.ts +1 -1
- package/dist/lib/compilation-error.d.ts.map +1 -1
- package/dist/lib/files/compiler.d.ts +71 -0
- package/dist/lib/files/compiler.d.ts.map +1 -0
- package/dist/lib/files/compiler.js +552 -0
- package/dist/lib/files/config.d.ts +101 -0
- package/dist/lib/files/config.d.ts.map +1 -0
- package/dist/lib/files/config.js +219 -0
- package/dist/lib/files/store.d.ts +31 -0
- package/dist/lib/files/store.d.ts.map +1 -0
- package/dist/lib/files/store.js +63 -0
- package/dist/lib/fingerprint.d.ts +3 -3
- package/dist/lib/fingerprint.d.ts.map +1 -1
- package/dist/lib/fingerprint.js +5 -5
- package/dist/lib/injected-packages.d.ts +11 -0
- package/dist/lib/injected-packages.d.ts.map +1 -0
- package/dist/lib/injected-packages.js +86 -0
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.d.ts.map +1 -1
- package/dist/lib/paths.js +12 -0
- package/dist/lib/routes.d.ts +5 -7
- package/dist/lib/routes.d.ts.map +1 -1
- package/dist/lib/routes.js +41 -36
- package/dist/lib/scripts/compiler.d.ts +1 -0
- package/dist/lib/scripts/compiler.d.ts.map +1 -1
- package/dist/lib/scripts/compiler.js +30 -5
- package/dist/lib/scripts/resolve.d.ts.map +1 -1
- package/dist/lib/scripts/resolve.js +57 -10
- package/dist/lib/scripts/transform.d.ts.map +1 -1
- package/dist/lib/scripts/transform.js +79 -3
- package/dist/lib/styles/compiler.d.ts +4 -0
- package/dist/lib/styles/compiler.d.ts.map +1 -1
- package/dist/lib/styles/compiler.js +2 -0
- package/dist/lib/styles/emit.d.ts +3 -0
- package/dist/lib/styles/emit.d.ts.map +1 -1
- package/dist/lib/styles/emit.js +36 -1
- package/dist/lib/styles/resolve.d.ts +8 -1
- package/dist/lib/styles/resolve.d.ts.map +1 -1
- package/dist/lib/styles/resolve.js +85 -32
- package/dist/lib/watch.d.ts +2 -0
- package/dist/lib/watch.d.ts.map +1 -1
- package/dist/lib/watch.js +25 -8
- package/package.json +11 -5
- package/src/assets.ts +1 -0
- package/src/lib/access.ts +10 -1
- package/src/lib/asset-server.ts +297 -34
- package/src/lib/compilation-error.ts +9 -0
- package/src/lib/files/compiler.ts +885 -0
- package/src/lib/files/config.ts +479 -0
- package/src/lib/files/store.ts +109 -0
- package/src/lib/fingerprint.ts +8 -7
- package/src/lib/injected-packages.ts +117 -0
- package/src/lib/paths.ts +28 -0
- package/src/lib/routes.ts +67 -55
- package/src/lib/scripts/compiler.ts +43 -5
- package/src/lib/scripts/resolve.ts +89 -11
- package/src/lib/scripts/transform.ts +105 -3
- package/src/lib/styles/compiler.ts +9 -0
- package/src/lib/styles/emit.ts +75 -1
- package/src/lib/styles/resolve.ts +132 -35
- package/src/lib/watch.ts +34 -12
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import * as fs from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
|
|
4
|
+
import { getFilePathDirectory, normalizeFilePath } from './paths.ts'
|
|
5
|
+
|
|
6
|
+
type ResolvedInjectedPackage = {
|
|
7
|
+
packageJsonPath: string
|
|
8
|
+
packageRoot: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const injectedPackageNames = ['@oxc-project/runtime'] as const
|
|
12
|
+
const injectedPackagesBasePath = '/__@remix/injected'
|
|
13
|
+
|
|
14
|
+
const resolvedInjectedPackages = new Map<string, ResolvedInjectedPackage>()
|
|
15
|
+
|
|
16
|
+
export function isInjectedPackageFilePath(filePath: string): boolean {
|
|
17
|
+
let normalizedFilePath = normalizeFilePath(filePath)
|
|
18
|
+
|
|
19
|
+
for (let packageName of injectedPackageNames) {
|
|
20
|
+
let packageRoot = getResolvedInjectedPackage(packageName).packageRoot
|
|
21
|
+
if (normalizedFilePath === packageRoot || normalizedFilePath.startsWith(`${packageRoot}/`)) {
|
|
22
|
+
return true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return false
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getInjectedPackageRouteConfigs(): {
|
|
30
|
+
fileMap: Record<string, string>
|
|
31
|
+
rootDir: string
|
|
32
|
+
}[] {
|
|
33
|
+
return injectedPackageNames.map((packageName) => {
|
|
34
|
+
let { packageRoot } = getResolvedInjectedPackage(packageName)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
fileMap: {
|
|
38
|
+
[getInjectedPackageRoutePattern(packageName)]: `${packageName}/*path`,
|
|
39
|
+
},
|
|
40
|
+
rootDir: getInjectedPackageRouteRoot(packageRoot, packageName),
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function getInjectedPackageNameForSpecifier(specifier: string): string | null {
|
|
46
|
+
for (let packageName of injectedPackageNames) {
|
|
47
|
+
if (specifier === packageName || specifier.startsWith(`${packageName}/`)) {
|
|
48
|
+
return packageName
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return null
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function mayContainInjectedPackageSpecifier(sourceText: string): boolean {
|
|
56
|
+
return injectedPackageNames.some((packageName) => sourceText.includes(packageName))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function maskAuthoredInjectedPackageSpecifier(specifier: string): string | null {
|
|
60
|
+
let packageName = getInjectedPackageNameForSpecifier(specifier)
|
|
61
|
+
if (!packageName) return null
|
|
62
|
+
|
|
63
|
+
let maskedPackageName = getMaskedInjectedPackageName(packageName)
|
|
64
|
+
return `${maskedPackageName}${specifier.slice(packageName.length)}`
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function restoreAuthoredInjectedPackageSpecifier(specifier: string): string | null {
|
|
68
|
+
for (let packageName of injectedPackageNames) {
|
|
69
|
+
let maskedPackageName = getMaskedInjectedPackageName(packageName)
|
|
70
|
+
if (specifier === maskedPackageName) {
|
|
71
|
+
return packageName
|
|
72
|
+
}
|
|
73
|
+
if (specifier.startsWith(`${maskedPackageName}/`)) {
|
|
74
|
+
return `${packageName}${specifier.slice(maskedPackageName.length)}`
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getMaskedInjectedPackageName(packageName: string): string {
|
|
82
|
+
return `~${packageName.slice(1)}`
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getInjectedPackageImporterPath(): string {
|
|
86
|
+
return normalizeFilePath(fileURLToPath(import.meta.url))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getResolvedInjectedPackage(packageName: string): ResolvedInjectedPackage {
|
|
90
|
+
let existing = resolvedInjectedPackages.get(packageName)
|
|
91
|
+
if (existing) return existing
|
|
92
|
+
|
|
93
|
+
let packageJsonUrl = import.meta.resolve(`${packageName}/package.json`)
|
|
94
|
+
let packageJsonPath = normalizeFilePath(fs.realpathSync(fileURLToPath(packageJsonUrl)))
|
|
95
|
+
|
|
96
|
+
let resolvedInjectedPackage = {
|
|
97
|
+
packageJsonPath,
|
|
98
|
+
packageRoot: normalizeFilePath(fs.realpathSync(getFilePathDirectory(packageJsonPath))),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
resolvedInjectedPackages.set(packageName, resolvedInjectedPackage)
|
|
102
|
+
return resolvedInjectedPackage
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getInjectedPackageRoutePattern(packageName: string): string {
|
|
106
|
+
return `${injectedPackagesBasePath}/${packageName}/*path`
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getInjectedPackageRouteRoot(packageRoot: string, packageName: string): string {
|
|
110
|
+
let routeRoot = packageRoot
|
|
111
|
+
|
|
112
|
+
for (let _segment of packageName.split('/')) {
|
|
113
|
+
routeRoot = getFilePathDirectory(routeRoot)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return routeRoot
|
|
117
|
+
}
|
package/src/lib/paths.ts
CHANGED
|
@@ -57,6 +57,34 @@ export function getFilePathDirectory(filePath: string): string {
|
|
|
57
57
|
return path.posix.dirname(normalizeWindowsPath(filePath))
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export function getRelativeFilePath(fromPath: string, toPath: string): string {
|
|
61
|
+
let normalizedFromPath = normalizeFilePath(fromPath)
|
|
62
|
+
let normalizedToPath = normalizeFilePath(toPath)
|
|
63
|
+
|
|
64
|
+
if (normalizedFromPath.startsWith('//') || normalizedToPath.startsWith('//')) {
|
|
65
|
+
return normalizeWindowsPath(
|
|
66
|
+
path.win32.relative(
|
|
67
|
+
normalizedFromPath.replace(/\//g, '\\'),
|
|
68
|
+
normalizedToPath.replace(/\//g, '\\'),
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
windowsDriveLetterRE.test(normalizedFromPath) ||
|
|
75
|
+
windowsDriveLetterRE.test(normalizedToPath)
|
|
76
|
+
) {
|
|
77
|
+
return normalizeWindowsPath(
|
|
78
|
+
path.win32.relative(
|
|
79
|
+
normalizedFromPath.replace(/\//g, '\\'),
|
|
80
|
+
normalizedToPath.replace(/\//g, '\\'),
|
|
81
|
+
),
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return path.posix.relative(normalizedFromPath, normalizedToPath)
|
|
86
|
+
}
|
|
87
|
+
|
|
60
88
|
export function getFilePathBaseName(filePath: string): string {
|
|
61
89
|
return path.posix.basename(normalizeWindowsPath(filePath))
|
|
62
90
|
}
|
package/src/lib/routes.ts
CHANGED
|
@@ -1,22 +1,31 @@
|
|
|
1
|
-
import * as path from 'node:path'
|
|
2
1
|
import { RoutePattern } from '@remix-run/route-pattern'
|
|
2
|
+
import { createHref } from '@remix-run/route-pattern/href'
|
|
3
|
+
import { createMatcher, type Matcher } from '@remix-run/route-pattern/match'
|
|
3
4
|
|
|
4
5
|
import {
|
|
6
|
+
getRelativeFilePath,
|
|
5
7
|
isAbsoluteFilePath,
|
|
6
8
|
normalizeFilePath,
|
|
7
9
|
normalizePathname,
|
|
8
10
|
resolveFilePath,
|
|
9
11
|
} from './paths.ts'
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
interface AssetRouteDefinition {
|
|
12
14
|
urlPattern: string
|
|
13
15
|
filePattern: string
|
|
14
16
|
}
|
|
15
17
|
|
|
18
|
+
interface RouteConfig {
|
|
19
|
+
fileMap: Readonly<Record<string, string>>
|
|
20
|
+
rootDir: string
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
interface CompiledRoute {
|
|
17
24
|
rootDir: string
|
|
18
25
|
urlPattern: RoutePattern
|
|
26
|
+
urlMatcher: Matcher
|
|
19
27
|
filePattern: RoutePattern
|
|
28
|
+
fileMatcher: Matcher
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
export interface CompiledRoutes {
|
|
@@ -34,21 +43,26 @@ function normalizeFilePattern(pattern: string): string {
|
|
|
34
43
|
return normalizePathname(pattern)
|
|
35
44
|
}
|
|
36
45
|
|
|
37
|
-
export function compileRoutes(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (Object.keys(
|
|
46
|
+
export function compileRoutes(
|
|
47
|
+
basePath: string,
|
|
48
|
+
routeConfigs: readonly RouteConfig[],
|
|
49
|
+
): CompiledRoutes {
|
|
50
|
+
if (routeConfigs.every((routeConfig) => Object.keys(routeConfig.fileMap).length === 0)) {
|
|
42
51
|
throw new Error('createAssetServer() requires at least one configured fileMap entry.')
|
|
43
52
|
}
|
|
44
53
|
|
|
45
|
-
let compiledRoutes =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
54
|
+
let compiledRoutes = routeConfigs.flatMap((routeConfig) =>
|
|
55
|
+
Object.entries(routeConfig.fileMap).map(([urlPattern, filePattern]) =>
|
|
56
|
+
compileRoute(
|
|
57
|
+
{
|
|
58
|
+
filePattern,
|
|
59
|
+
urlPattern,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
basePath,
|
|
63
|
+
rootDir: routeConfig.rootDir,
|
|
64
|
+
},
|
|
65
|
+
),
|
|
52
66
|
),
|
|
53
67
|
)
|
|
54
68
|
|
|
@@ -57,9 +71,9 @@ export function compileRoutes(options: {
|
|
|
57
71
|
let normalizedPathname = normalizePathname(pathname)
|
|
58
72
|
|
|
59
73
|
for (let route of compiledRoutes) {
|
|
60
|
-
let match = route.
|
|
74
|
+
let match = route.urlMatcher.match(`http://remix.run${normalizedPathname}`)
|
|
61
75
|
if (!match) continue
|
|
62
|
-
let relativeFilePath = route.filePattern
|
|
76
|
+
let relativeFilePath = createHref(route.filePattern, match.params).replace(/^\/+/, '')
|
|
63
77
|
return resolveFilePath(route.rootDir, relativeFilePath)
|
|
64
78
|
}
|
|
65
79
|
|
|
@@ -69,11 +83,10 @@ export function compileRoutes(options: {
|
|
|
69
83
|
let normalizedFilePath = normalizeFilePath(filePath)
|
|
70
84
|
|
|
71
85
|
for (let route of compiledRoutes) {
|
|
72
|
-
let relativeFilePath = getRelativeFilePath(
|
|
73
|
-
|
|
74
|
-
let match = route.filePattern.ast.pathname.match(relativeFilePath)
|
|
86
|
+
let relativeFilePath = getRelativeFilePath(route.rootDir, normalizedFilePath)
|
|
87
|
+
let match = route.fileMatcher.match(`http://remix.run/${relativeFilePath}`)
|
|
75
88
|
if (!match) continue
|
|
76
|
-
return normalizePathname(route.urlPattern
|
|
89
|
+
return normalizePathname(createHref(route.urlPattern, match.params))
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
return null
|
|
@@ -84,14 +97,19 @@ export function compileRoutes(options: {
|
|
|
84
97
|
function compileRoute(
|
|
85
98
|
route: AssetRouteDefinition,
|
|
86
99
|
options: {
|
|
100
|
+
basePath: string
|
|
87
101
|
rootDir: string
|
|
88
102
|
},
|
|
89
103
|
): CompiledRoute {
|
|
90
|
-
let
|
|
104
|
+
let basePath = normalizePathname(options.basePath).replace(/\/+$/, '') || '/'
|
|
105
|
+
let relativeUrlPattern = normalizePathname(route.urlPattern)
|
|
106
|
+
let urlPatternSource = normalizePathname(
|
|
107
|
+
`${basePath.replace(/\/+$/, '')}/${relativeUrlPattern.replace(/^\/+/, '')}`,
|
|
108
|
+
)
|
|
91
109
|
let filePatternSource = normalizeFilePattern(route.filePattern)
|
|
92
110
|
|
|
93
|
-
let urlPattern =
|
|
94
|
-
let filePattern =
|
|
111
|
+
let urlPattern = RoutePattern.parse(urlPatternSource)
|
|
112
|
+
let filePattern = RoutePattern.parse(filePatternSource)
|
|
95
113
|
|
|
96
114
|
validateNoUnnamedWildcards(urlPattern, 'URL')
|
|
97
115
|
validateNoUnnamedWildcards(filePattern, 'File')
|
|
@@ -100,42 +118,30 @@ function compileRoute(
|
|
|
100
118
|
return {
|
|
101
119
|
rootDir: normalizeFilePath(options.rootDir).replace(/\/+$/, ''),
|
|
102
120
|
urlPattern,
|
|
121
|
+
urlMatcher: createMatcher(urlPattern),
|
|
103
122
|
filePattern,
|
|
123
|
+
fileMatcher: createMatcher(stripDotSegments(filePatternSource)),
|
|
104
124
|
}
|
|
105
125
|
}
|
|
106
126
|
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
return path.posix.relative(rootDir, filePath)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function getPathnameParams(
|
|
113
|
-
pattern: RoutePattern,
|
|
114
|
-
match: Array<{ name: string; type: ':' | '*'; value: string }>,
|
|
115
|
-
): Record<string, string | undefined> {
|
|
116
|
-
let params: Record<string, string | undefined> = {}
|
|
117
|
-
|
|
118
|
-
for (let param of pattern.ast.pathname.params) {
|
|
119
|
-
if (param.name === '*') continue
|
|
120
|
-
params[param.name] = undefined
|
|
121
|
-
}
|
|
127
|
+
function stripDotSegments(pattern: string): string {
|
|
128
|
+
let segments: string[] = []
|
|
122
129
|
|
|
123
|
-
for (let
|
|
124
|
-
if (
|
|
125
|
-
|
|
130
|
+
for (let segment of pattern.split('/')) {
|
|
131
|
+
if (segment === '' || segment === '.') continue
|
|
132
|
+
if (segment === '..') {
|
|
133
|
+
segments.pop()
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
segments.push(segment)
|
|
126
137
|
}
|
|
127
138
|
|
|
128
|
-
return
|
|
139
|
+
return segments.join('/')
|
|
129
140
|
}
|
|
130
141
|
|
|
131
142
|
function validateRoutePatterns(urlPattern: RoutePattern, filePattern: RoutePattern): void {
|
|
132
|
-
let urlParams = urlPattern
|
|
133
|
-
|
|
134
|
-
)
|
|
135
|
-
let fileParams = filePattern.ast.pathname.params.map(
|
|
136
|
-
(param: { name: string; type: ':' | '*' }) => `${param.type}:${param.name}`,
|
|
137
|
-
)
|
|
138
|
-
|
|
143
|
+
let urlParams = getPathnameParams(urlPattern)
|
|
144
|
+
let fileParams = getPathnameParams(filePattern)
|
|
139
145
|
if (urlParams.length !== fileParams.length) {
|
|
140
146
|
throw new Error(
|
|
141
147
|
`Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`,
|
|
@@ -143,7 +149,9 @@ function validateRoutePatterns(urlPattern: RoutePattern, filePattern: RoutePatte
|
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
for (let i = 0; i < urlParams.length; i++) {
|
|
146
|
-
|
|
152
|
+
let urlParam = urlParams[i]
|
|
153
|
+
let fileParam = fileParams[i]
|
|
154
|
+
if (urlParam.type !== fileParam.type || urlParam.name !== fileParam.name) {
|
|
147
155
|
throw new Error(
|
|
148
156
|
`Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`,
|
|
149
157
|
)
|
|
@@ -152,13 +160,17 @@ function validateRoutePatterns(urlPattern: RoutePattern, filePattern: RoutePatte
|
|
|
152
160
|
}
|
|
153
161
|
|
|
154
162
|
function validateNoUnnamedWildcards(pattern: RoutePattern, label: string): void {
|
|
155
|
-
if (
|
|
156
|
-
pattern.ast.pathname.params.some(
|
|
157
|
-
(param: { name: string; type: ':' | '*' }) => param.type === '*' && param.name === '*',
|
|
158
|
-
)
|
|
159
|
-
) {
|
|
163
|
+
if (pattern.pathname.tokens.some((token) => token.type === '*' && token.name === '*')) {
|
|
160
164
|
throw new Error(
|
|
161
165
|
`${label} route patterns must use named wildcards for reversible mapping.\nPattern: ${pattern}`,
|
|
162
166
|
)
|
|
163
167
|
}
|
|
164
168
|
}
|
|
169
|
+
|
|
170
|
+
type PathnameParam = Extract<RoutePattern['pathname']['tokens'][number], { type: ':' | '*' }>
|
|
171
|
+
|
|
172
|
+
function getPathnameParams(pattern: RoutePattern): Array<PathnameParam> {
|
|
173
|
+
return pattern.pathname.tokens.filter(
|
|
174
|
+
(token): token is PathnameParam => token.type === ':' || token.type === '*',
|
|
175
|
+
)
|
|
176
|
+
}
|
|
@@ -66,6 +66,7 @@ type ScriptCompilerOptions = {
|
|
|
66
66
|
external: string[]
|
|
67
67
|
fingerprintAssets: boolean
|
|
68
68
|
isAllowed(absolutePath: string): boolean
|
|
69
|
+
isDenied(absolutePath: string): boolean
|
|
69
70
|
minify: boolean
|
|
70
71
|
onWatchDirectoriesChange?: (delta: { add: string[]; remove: string[] }) => void
|
|
71
72
|
rootDir: string
|
|
@@ -117,8 +118,14 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
117
118
|
extensionAlias: resolverExtensionAlias,
|
|
118
119
|
extensions: resolverExtensions,
|
|
119
120
|
mainFields: ['browser', 'module', 'main'],
|
|
121
|
+
symlinks: false,
|
|
120
122
|
tsconfig: 'auto',
|
|
121
123
|
})
|
|
124
|
+
let resolveModulePathOptions = {
|
|
125
|
+
isAllowed: resolvedOptions.isAllowed,
|
|
126
|
+
isDenied: resolvedOptions.isDenied,
|
|
127
|
+
routes: resolvedOptions.routes,
|
|
128
|
+
}
|
|
122
129
|
let resolveInFlightByCacheKey = new Map<string, Promise<ResolvedModule>>()
|
|
123
130
|
let emitInFlightByCacheKey = new Map<string, Promise<EmittedModule>>()
|
|
124
131
|
|
|
@@ -138,7 +145,9 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
138
145
|
let resolveArgs: ResolveArgs = {
|
|
139
146
|
isAllowed: resolvedOptions.isAllowed,
|
|
140
147
|
isWatchIgnored,
|
|
141
|
-
resolveModulePath
|
|
148
|
+
resolveModulePath(absolutePath) {
|
|
149
|
+
return resolveModulePath(absolutePath, resolveModulePathOptions)
|
|
150
|
+
},
|
|
142
151
|
resolverFactory,
|
|
143
152
|
routes: resolvedOptions.routes,
|
|
144
153
|
}
|
|
@@ -253,7 +262,7 @@ export function createScriptCompiler(options: ScriptCompilerOptions): ScriptComp
|
|
|
253
262
|
}
|
|
254
263
|
|
|
255
264
|
function resolveServedScriptOrThrow(absolutePath: string): ResolveModuleResult {
|
|
256
|
-
let resolvedModule = resolveModulePath(absolutePath)
|
|
265
|
+
let resolvedModule = resolveModulePath(absolutePath, resolveModulePathOptions)
|
|
257
266
|
if (!resolvedModule) {
|
|
258
267
|
throw createAssetServerCompilationError(`File not found: ${absolutePath}`, {
|
|
259
268
|
code: 'FILE_NOT_FOUND',
|
|
@@ -548,11 +557,19 @@ function shouldClearResolverCacheForFileEvent(filePath: string, event: ModuleWat
|
|
|
548
557
|
return event !== 'change' || isPackageJsonPath(filePath) || isTsconfigPath(filePath)
|
|
549
558
|
}
|
|
550
559
|
|
|
551
|
-
function resolveModulePath(
|
|
560
|
+
function resolveModulePath(
|
|
561
|
+
absolutePath: string,
|
|
562
|
+
options: {
|
|
563
|
+
isAllowed(absolutePath: string): boolean
|
|
564
|
+
isDenied(absolutePath: string): boolean
|
|
565
|
+
routes: CompiledRoutes
|
|
566
|
+
},
|
|
567
|
+
): ResolveModuleResult | null {
|
|
568
|
+
let candidateIdentityPath = normalizeFilePath(absolutePath)
|
|
552
569
|
let resolvedPath: string
|
|
553
570
|
|
|
554
571
|
try {
|
|
555
|
-
resolvedPath = normalizeFilePath(fs.realpathSync(
|
|
572
|
+
resolvedPath = normalizeFilePath(fs.realpathSync(candidateIdentityPath))
|
|
556
573
|
} catch (error) {
|
|
557
574
|
if (isNoEntityError(error)) return null
|
|
558
575
|
throw error
|
|
@@ -563,11 +580,32 @@ function resolveModulePath(absolutePath: string): ResolveModuleResult | null {
|
|
|
563
580
|
}
|
|
564
581
|
|
|
565
582
|
return {
|
|
566
|
-
identityPath: resolvedPath,
|
|
583
|
+
identityPath: getModuleIdentityPath(candidateIdentityPath, resolvedPath, options),
|
|
567
584
|
resolvedPath,
|
|
568
585
|
}
|
|
569
586
|
}
|
|
570
587
|
|
|
588
|
+
function getModuleIdentityPath(
|
|
589
|
+
candidateIdentityPath: string,
|
|
590
|
+
resolvedPath: string,
|
|
591
|
+
options: {
|
|
592
|
+
isAllowed(absolutePath: string): boolean
|
|
593
|
+
isDenied(absolutePath: string): boolean
|
|
594
|
+
routes: CompiledRoutes
|
|
595
|
+
},
|
|
596
|
+
): string {
|
|
597
|
+
if (candidateIdentityPath === resolvedPath) return resolvedPath
|
|
598
|
+
if (!containsNodeModulesPathSegment(candidateIdentityPath)) return resolvedPath
|
|
599
|
+
if (!options.routes.toUrlPathname(candidateIdentityPath)) return resolvedPath
|
|
600
|
+
if (!options.isAllowed(candidateIdentityPath)) return resolvedPath
|
|
601
|
+
if (options.isDenied(resolvedPath)) return resolvedPath
|
|
602
|
+
return candidateIdentityPath
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function containsNodeModulesPathSegment(filePath: string): boolean {
|
|
606
|
+
return filePath.split('/').includes('node_modules')
|
|
607
|
+
}
|
|
608
|
+
|
|
571
609
|
function resolveActualPath(identityPath: string): string | null {
|
|
572
610
|
try {
|
|
573
611
|
return normalizeFilePath(fs.realpathSync(identityPath))
|
|
@@ -7,6 +7,11 @@ import {
|
|
|
7
7
|
isAssetServerCompilationError,
|
|
8
8
|
} from '../compilation-error.ts'
|
|
9
9
|
import type { AssetServerCompilationError } from '../compilation-error.ts'
|
|
10
|
+
import {
|
|
11
|
+
getInjectedPackageNameForSpecifier,
|
|
12
|
+
getInjectedPackageImporterPath,
|
|
13
|
+
restoreAuthoredInjectedPackageSpecifier,
|
|
14
|
+
} from '../injected-packages.ts'
|
|
10
15
|
import type { ModuleRecord, ModuleTracking } from '../module-store.ts'
|
|
11
16
|
import { normalizeFilePath } from '../paths.ts'
|
|
12
17
|
import type { CompiledRoutes } from '../routes.ts'
|
|
@@ -81,6 +86,11 @@ type ResolvedSpec = {
|
|
|
81
86
|
specifier: string
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
type NormalizedSpecifierResolution = {
|
|
90
|
+
importerPath: string
|
|
91
|
+
specifier: string
|
|
92
|
+
}
|
|
93
|
+
|
|
84
94
|
export async function resolveModule(
|
|
85
95
|
record: ScriptRecord,
|
|
86
96
|
transformed: TransformedModule,
|
|
@@ -95,7 +105,7 @@ export async function resolveModule(
|
|
|
95
105
|
transformed.unresolvedImports.length > 0
|
|
96
106
|
? await batchResolveSpecifiers(
|
|
97
107
|
getUniqueSpecifiers(transformed.unresolvedImports),
|
|
98
|
-
transformed.
|
|
108
|
+
transformed.identityPath,
|
|
99
109
|
args.resolverFactory,
|
|
100
110
|
)
|
|
101
111
|
: new Map<string, ResolvedSpec>()
|
|
@@ -109,9 +119,10 @@ export async function resolveModule(
|
|
|
109
119
|
let deps = new Set<string>()
|
|
110
120
|
|
|
111
121
|
for (let unresolved of transformed.unresolvedImports) {
|
|
122
|
+
let displaySpecifier = getDisplayImportSpecifier(unresolved.specifier)
|
|
112
123
|
let trackedResolution = getTrackedRelativeImportResolution(
|
|
113
124
|
transformed.importerDir,
|
|
114
|
-
|
|
125
|
+
displaySpecifier,
|
|
115
126
|
args.isWatchIgnored,
|
|
116
127
|
)
|
|
117
128
|
|
|
@@ -119,7 +130,7 @@ export async function resolveModule(
|
|
|
119
130
|
if (!resolvedSpec?.absolutePath) {
|
|
120
131
|
return failResolve(
|
|
121
132
|
createAssetServerCompilationError(
|
|
122
|
-
`Failed to resolve import "${
|
|
133
|
+
`Failed to resolve import "${displaySpecifier}" in ${transformed.resolvedPath}. ` +
|
|
123
134
|
`Ensure it resolves to a file within the configured asset server fileMap, or mark it as external.`,
|
|
124
135
|
{
|
|
125
136
|
code: 'IMPORT_RESOLUTION_FAILED',
|
|
@@ -136,7 +147,7 @@ export async function resolveModule(
|
|
|
136
147
|
if (!resolvedImport) {
|
|
137
148
|
return failResolve(
|
|
138
149
|
createAssetServerCompilationError(
|
|
139
|
-
`Import "${
|
|
150
|
+
`Import "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedSpec.absolutePath}", is not a supported script file. ` +
|
|
140
151
|
`Supported extensions are ${supportedScriptExtensions.join(', ')}.`,
|
|
141
152
|
{
|
|
142
153
|
code: 'IMPORT_NOT_SUPPORTED',
|
|
@@ -152,7 +163,7 @@ export async function resolveModule(
|
|
|
152
163
|
if (!args.isAllowed(resolvedImport.identityPath)) {
|
|
153
164
|
return failResolve(
|
|
154
165
|
createAssetServerCompilationError(
|
|
155
|
-
`Import "${
|
|
166
|
+
`Import "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is not allowed by the asset server allow/deny configuration. ` +
|
|
156
167
|
`Add a matching allow rule for this file path, remove a conflicting deny rule for this file path, or mark this import as external.`,
|
|
157
168
|
{
|
|
158
169
|
code: 'IMPORT_NOT_ALLOWED',
|
|
@@ -169,7 +180,7 @@ export async function resolveModule(
|
|
|
169
180
|
if (!stableUrlPathname) {
|
|
170
181
|
return failResolve(
|
|
171
182
|
createAssetServerCompilationError(
|
|
172
|
-
`Import "${
|
|
183
|
+
`Import "${displaySpecifier}" in ${transformed.resolvedPath}, resolved to "${resolvedImport.identityPath}", is outside all configured fileMap entries. ` +
|
|
173
184
|
`Add a matching fileMap entry for this file path, or mark this import as external.`,
|
|
174
185
|
{
|
|
175
186
|
code: 'IMPORT_OUTSIDE_FILE_MAP',
|
|
@@ -185,8 +196,10 @@ export async function resolveModule(
|
|
|
185
196
|
deps.add(resolvedImport.identityPath)
|
|
186
197
|
|
|
187
198
|
if (transformed.packageSpecifiers.includes(unresolved.specifier)) {
|
|
188
|
-
let packageJsonPath =
|
|
189
|
-
resolvedSpec.packageJsonPath
|
|
199
|
+
let packageJsonPath = resolvePackageJsonPath(
|
|
200
|
+
resolvedSpec.packageJsonPath,
|
|
201
|
+
resolvedImport.resolvedPath,
|
|
202
|
+
)
|
|
190
203
|
if (packageJsonPath && !args.isWatchIgnored(packageJsonPath)) {
|
|
191
204
|
trackedFiles.add(packageJsonPath)
|
|
192
205
|
}
|
|
@@ -224,6 +237,25 @@ export async function resolveModule(
|
|
|
224
237
|
}
|
|
225
238
|
}
|
|
226
239
|
|
|
240
|
+
function resolvePackageJsonPath(
|
|
241
|
+
packageJsonPath: string | null,
|
|
242
|
+
resolvedPath: string,
|
|
243
|
+
): string | null {
|
|
244
|
+
return (
|
|
245
|
+
(packageJsonPath ? resolveExistingPath(packageJsonPath) : null) ??
|
|
246
|
+
findNearestPackageJsonPath(resolvedPath)
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function resolveExistingPath(filePath: string): string | null {
|
|
251
|
+
try {
|
|
252
|
+
return normalizeFilePath(fs.realpathSync(filePath))
|
|
253
|
+
} catch (error) {
|
|
254
|
+
if (isNoEntityError(error)) return null
|
|
255
|
+
throw error
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
227
259
|
function findNearestPackageJsonPath(filePath: string): string | null {
|
|
228
260
|
let directory = path.dirname(filePath)
|
|
229
261
|
|
|
@@ -239,6 +271,17 @@ function findNearestPackageJsonPath(filePath: string): string | null {
|
|
|
239
271
|
}
|
|
240
272
|
}
|
|
241
273
|
|
|
274
|
+
function isNoEntityError(
|
|
275
|
+
error: unknown,
|
|
276
|
+
): error is NodeJS.ErrnoException & { code: 'ENOENT' | 'ENOTDIR' } {
|
|
277
|
+
return (
|
|
278
|
+
error instanceof Error &&
|
|
279
|
+
'code' in error &&
|
|
280
|
+
((error as NodeJS.ErrnoException).code === 'ENOENT' ||
|
|
281
|
+
(error as NodeJS.ErrnoException).code === 'ENOTDIR')
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
|
|
242
285
|
function isRelativeImportSpecifier(specifier: string): boolean {
|
|
243
286
|
return specifier.startsWith('./') || specifier.startsWith('../')
|
|
244
287
|
}
|
|
@@ -323,11 +366,17 @@ async function batchResolveSpecifiers(
|
|
|
323
366
|
|
|
324
367
|
try {
|
|
325
368
|
for (let specifier of specifiers) {
|
|
326
|
-
let
|
|
369
|
+
let normalizedResolution = normalizeSpecifierResolution(specifier, importerPath)
|
|
370
|
+
let resolutionResult = await resolverFactory.resolveFileAsync(
|
|
371
|
+
normalizedResolution.importerPath,
|
|
372
|
+
normalizedResolution.specifier,
|
|
373
|
+
)
|
|
327
374
|
if (resolutionResult.error) {
|
|
328
375
|
throw createAssetServerCompilationError(
|
|
329
|
-
|
|
330
|
-
`
|
|
376
|
+
normalizedResolution.importerPath === getInjectedPackageImporterPath()
|
|
377
|
+
? `Failed to resolve injected import "${specifier}" from asset server.`
|
|
378
|
+
: `Failed to resolve import "${normalizedResolution.specifier}" in ${normalizedResolution.importerPath}. ` +
|
|
379
|
+
`Ensure it resolves to a file within the configured asset server fileMap, or mark it as external.`,
|
|
331
380
|
{
|
|
332
381
|
code: 'IMPORT_RESOLUTION_FAILED',
|
|
333
382
|
},
|
|
@@ -370,6 +419,35 @@ function formatUnknownError(error: unknown): string {
|
|
|
370
419
|
return error instanceof Error ? error.message : String(error)
|
|
371
420
|
}
|
|
372
421
|
|
|
422
|
+
function normalizeSpecifierResolution(
|
|
423
|
+
specifier: string,
|
|
424
|
+
importerPath: string,
|
|
425
|
+
): NormalizedSpecifierResolution {
|
|
426
|
+
let authoredInjectedPackageSpecifier = restoreAuthoredInjectedPackageSpecifier(specifier)
|
|
427
|
+
if (authoredInjectedPackageSpecifier) {
|
|
428
|
+
return {
|
|
429
|
+
importerPath,
|
|
430
|
+
specifier: authoredInjectedPackageSpecifier,
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (getInjectedPackageNameForSpecifier(specifier)) {
|
|
435
|
+
return {
|
|
436
|
+
importerPath: getInjectedPackageImporterPath(),
|
|
437
|
+
specifier,
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return {
|
|
442
|
+
importerPath,
|
|
443
|
+
specifier,
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function getDisplayImportSpecifier(specifier: string): string {
|
|
448
|
+
return restoreAuthoredInjectedPackageSpecifier(specifier) ?? specifier
|
|
449
|
+
}
|
|
450
|
+
|
|
373
451
|
function failResolve(
|
|
374
452
|
error: unknown,
|
|
375
453
|
trackedFiles: ReadonlySet<string>,
|