@remix-run/assets 0.0.0 → 0.2.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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +325 -2
  3. package/dist/assets.d.ts +3 -0
  4. package/dist/assets.d.ts.map +1 -0
  5. package/dist/assets.js +1 -0
  6. package/dist/lib/access.d.ts +10 -0
  7. package/dist/lib/access.d.ts.map +1 -0
  8. package/dist/lib/access.js +14 -0
  9. package/dist/lib/asset-server.d.ts +139 -0
  10. package/dist/lib/asset-server.d.ts.map +1 -0
  11. package/dist/lib/asset-server.js +338 -0
  12. package/dist/lib/compilation-error.d.ts +33 -0
  13. package/dist/lib/compilation-error.d.ts.map +1 -0
  14. package/dist/lib/compilation-error.js +32 -0
  15. package/dist/lib/file-matcher.d.ts +6 -0
  16. package/dist/lib/file-matcher.d.ts.map +1 -0
  17. package/dist/lib/file-matcher.js +44 -0
  18. package/dist/lib/fingerprint.d.ts +12 -0
  19. package/dist/lib/fingerprint.d.ts.map +1 -0
  20. package/dist/lib/fingerprint.js +49 -0
  21. package/dist/lib/module-store.d.ts +41 -0
  22. package/dist/lib/module-store.d.ts.map +1 -0
  23. package/dist/lib/module-store.js +230 -0
  24. package/dist/lib/paths.d.ts +8 -0
  25. package/dist/lib/paths.d.ts.map +1 -0
  26. package/dist/lib/paths.js +50 -0
  27. package/dist/lib/routes.d.ts +13 -0
  28. package/dist/lib/routes.d.ts.map +1 -0
  29. package/dist/lib/routes.js +94 -0
  30. package/dist/lib/scripts/cjs-check.d.ts +3 -0
  31. package/dist/lib/scripts/cjs-check.d.ts.map +1 -0
  32. package/dist/lib/scripts/cjs-check.js +398 -0
  33. package/dist/lib/scripts/compiler.d.ts +62 -0
  34. package/dist/lib/scripts/compiler.d.ts.map +1 -0
  35. package/dist/lib/scripts/compiler.js +439 -0
  36. package/dist/lib/scripts/emit.d.ts +25 -0
  37. package/dist/lib/scripts/emit.d.ts.map +1 -0
  38. package/dist/lib/scripts/emit.js +63 -0
  39. package/dist/lib/scripts/resolve.d.ts +50 -0
  40. package/dist/lib/scripts/resolve.d.ts.map +1 -0
  41. package/dist/lib/scripts/resolve.js +236 -0
  42. package/dist/lib/scripts/transform.d.ts +64 -0
  43. package/dist/lib/scripts/transform.d.ts.map +1 -0
  44. package/dist/lib/scripts/transform.js +373 -0
  45. package/dist/lib/source-maps.d.ts +4 -0
  46. package/dist/lib/source-maps.d.ts.map +1 -0
  47. package/dist/lib/source-maps.js +62 -0
  48. package/dist/lib/styles/compiler.d.ts +52 -0
  49. package/dist/lib/styles/compiler.d.ts.map +1 -0
  50. package/dist/lib/styles/compiler.js +272 -0
  51. package/dist/lib/styles/emit.d.ts +25 -0
  52. package/dist/lib/styles/emit.d.ts.map +1 -0
  53. package/dist/lib/styles/emit.js +78 -0
  54. package/dist/lib/styles/resolve.d.ts +48 -0
  55. package/dist/lib/styles/resolve.d.ts.map +1 -0
  56. package/dist/lib/styles/resolve.js +188 -0
  57. package/dist/lib/styles/transform.d.ts +47 -0
  58. package/dist/lib/styles/transform.d.ts.map +1 -0
  59. package/dist/lib/styles/transform.js +131 -0
  60. package/dist/lib/target.d.ts +21 -0
  61. package/dist/lib/target.d.ts.map +1 -0
  62. package/dist/lib/target.js +127 -0
  63. package/dist/lib/watch.d.ts +22 -0
  64. package/dist/lib/watch.d.ts.map +1 -0
  65. package/dist/lib/watch.js +96 -0
  66. package/package.json +55 -12
  67. package/src/assets.ts +2 -0
  68. package/src/lib/access.ts +24 -0
  69. package/src/lib/asset-server.ts +537 -0
  70. package/src/lib/compilation-error.ts +61 -0
  71. package/src/lib/file-matcher.ts +63 -0
  72. package/src/lib/fingerprint.ts +65 -0
  73. package/src/lib/module-store.ts +340 -0
  74. package/src/lib/paths.ts +66 -0
  75. package/src/lib/routes.ts +164 -0
  76. package/src/lib/scripts/cjs-check.ts +476 -0
  77. package/src/lib/scripts/compiler.ts +640 -0
  78. package/src/lib/scripts/emit.ts +122 -0
  79. package/src/lib/scripts/resolve.ts +433 -0
  80. package/src/lib/scripts/transform.ts +609 -0
  81. package/src/lib/source-maps.ts +75 -0
  82. package/src/lib/styles/compiler.ts +400 -0
  83. package/src/lib/styles/emit.ts +137 -0
  84. package/src/lib/styles/resolve.ts +316 -0
  85. package/src/lib/styles/transform.ts +226 -0
  86. package/src/lib/target.ts +196 -0
  87. package/src/lib/watch.ts +136 -0
@@ -0,0 +1,316 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+
4
+ import {
5
+ createAssetServerCompilationError,
6
+ isAssetServerCompilationError,
7
+ } from '../compilation-error.ts'
8
+ import { normalizeFilePath } from '../paths.ts'
9
+ import type { AssetServerCompilationError } from '../compilation-error.ts'
10
+ import type { ModuleRecord, ModuleTracking } from '../module-store.ts'
11
+ import type { CompiledRoutes } from '../routes.ts'
12
+ import type { EmittedStyle } from './emit.ts'
13
+ import type { TransformedStyle } from './transform.ts'
14
+
15
+ type StyleRecord = ModuleRecord<TransformedStyle, ResolvedStyle, EmittedStyle>
16
+
17
+ type ResolvedDependency =
18
+ | {
19
+ kind: 'external'
20
+ placeholder: string
21
+ replacement: string
22
+ }
23
+ | {
24
+ depPath: string
25
+ kind: 'local'
26
+ placeholder: string
27
+ suffix: string
28
+ }
29
+
30
+ export type ResolvedStyle = {
31
+ dependencies: ResolvedDependency[]
32
+ deps: string[]
33
+ fingerprint: string | null
34
+ identityPath: string
35
+ rawCode: string
36
+ resolvedPath: string
37
+ sourceMap: string | null
38
+ stableUrlPathname: string
39
+ trackedFiles: string[]
40
+ }
41
+
42
+ export type ResolveArgs = {
43
+ isAllowed(absolutePath: string): boolean
44
+ isWatchIgnored(filePath: string): boolean
45
+ routes: CompiledRoutes
46
+ }
47
+
48
+ type ResolveResult = {
49
+ tracking: ModuleTracking
50
+ } & (
51
+ | {
52
+ ok: true
53
+ value: ResolvedStyle
54
+ }
55
+ | {
56
+ error: AssetServerCompilationError
57
+ ok: false
58
+ }
59
+ )
60
+
61
+ export async function resolveStyle(
62
+ record: StyleRecord,
63
+ transformed: TransformedStyle,
64
+ args: ResolveArgs,
65
+ ): Promise<ResolveResult> {
66
+ let trackedFiles = new Set(transformed.trackedFiles)
67
+ let dependencies: ResolvedDependency[] = []
68
+ let deps = new Set<string>()
69
+
70
+ for (let unresolved of transformed.unresolvedDependencies) {
71
+ let trackedFile =
72
+ unresolved.type === 'import'
73
+ ? getTrackedImportFilePath(unresolved.url, transformed.resolvedPath)
74
+ : null
75
+ if (trackedFile && !args.isWatchIgnored(trackedFile)) {
76
+ trackedFiles.add(trackedFile)
77
+ }
78
+
79
+ try {
80
+ let resolved =
81
+ unresolved.type === 'import'
82
+ ? resolveImportDependency(
83
+ unresolved.url,
84
+ transformed.resolvedPath,
85
+ unresolved.placeholder,
86
+ args,
87
+ )
88
+ : resolveUrlDependency(unresolved.url, unresolved.placeholder)
89
+
90
+ dependencies.push(resolved)
91
+
92
+ if (resolved.kind === 'local') {
93
+ if (!args.isWatchIgnored(resolved.depPath)) {
94
+ trackedFiles.add(resolved.depPath)
95
+ }
96
+ deps.add(resolved.depPath)
97
+ }
98
+ } catch (error) {
99
+ return failResolve(error, trackedFiles, transformed.resolvedPath)
100
+ }
101
+ }
102
+
103
+ return {
104
+ ok: true,
105
+ tracking: {
106
+ trackedFiles: [...trackedFiles],
107
+ },
108
+ value: {
109
+ dependencies,
110
+ deps: [...deps],
111
+ fingerprint: transformed.fingerprint,
112
+ identityPath: record.identityPath,
113
+ rawCode: transformed.rawCode,
114
+ resolvedPath: transformed.resolvedPath,
115
+ sourceMap: transformed.sourceMap,
116
+ stableUrlPathname: transformed.stableUrlPathname,
117
+ trackedFiles: [...trackedFiles],
118
+ },
119
+ }
120
+ }
121
+
122
+ export function resolveServedStyleOrThrow(
123
+ filePath: string,
124
+ args: ResolveArgs,
125
+ ): {
126
+ identityPath: string
127
+ stableUrlPathname: string
128
+ } {
129
+ let identityPath = resolveExistingFilePath(filePath)
130
+ if (!identityPath) {
131
+ throw createAssetServerCompilationError(`File not found: ${filePath}`, {
132
+ code: 'FILE_NOT_FOUND',
133
+ })
134
+ }
135
+
136
+ if (!isStyleFilePath(identityPath)) {
137
+ throw createAssetServerCompilationError(`File not found: ${identityPath}`, {
138
+ code: 'FILE_NOT_FOUND',
139
+ })
140
+ }
141
+
142
+ if (!args.isAllowed(identityPath)) {
143
+ throw createAssetServerCompilationError(`File is not allowed: ${identityPath}`, {
144
+ code: 'FILE_NOT_ALLOWED',
145
+ })
146
+ }
147
+
148
+ let stableUrlPathname = args.routes.toUrlPathname(identityPath)
149
+ if (!stableUrlPathname) {
150
+ throw createAssetServerCompilationError(
151
+ `File ${identityPath} is outside all configured fileMap entries.`,
152
+ {
153
+ code: 'FILE_OUTSIDE_FILE_MAP',
154
+ },
155
+ )
156
+ }
157
+
158
+ return { identityPath, stableUrlPathname }
159
+ }
160
+
161
+ function resolveImportDependency(
162
+ url: string,
163
+ importerPath: string,
164
+ placeholder: string,
165
+ args: ResolveArgs,
166
+ ): ResolvedDependency {
167
+ if (isExternalUrl(url)) {
168
+ return {
169
+ kind: 'external',
170
+ placeholder,
171
+ replacement: url,
172
+ }
173
+ }
174
+
175
+ let { pathname, suffix } = splitUrlSuffix(url)
176
+ if (pathname.length === 0 || pathname === '#') {
177
+ return {
178
+ kind: 'external',
179
+ placeholder,
180
+ replacement: url,
181
+ }
182
+ }
183
+
184
+ if (pathname.startsWith('/')) {
185
+ return {
186
+ kind: 'external',
187
+ placeholder,
188
+ replacement: url,
189
+ }
190
+ }
191
+
192
+ let resolvedFilePath = normalizeFilePath(path.resolve(path.dirname(importerPath), pathname))
193
+ let identityPath = resolveExistingFilePath(resolvedFilePath)
194
+ if (!identityPath || !isStyleFilePath(identityPath)) {
195
+ throw createAssetServerCompilationError(
196
+ `Failed to resolve import "${url}" in ${importerPath}.`,
197
+ {
198
+ code: 'IMPORT_RESOLUTION_FAILED',
199
+ },
200
+ )
201
+ }
202
+
203
+ if (!args.isAllowed(identityPath)) {
204
+ throw createAssetServerCompilationError(
205
+ `Import "${url}" in ${importerPath}, resolved to "${identityPath}", is not allowed by the asset server allow/deny configuration. ` +
206
+ `Add a matching allow rule for this file path, remove a conflicting deny rule for this file path, or mark this import as external.`,
207
+ {
208
+ code: 'IMPORT_NOT_ALLOWED',
209
+ },
210
+ )
211
+ }
212
+
213
+ if (!args.routes.toUrlPathname(identityPath)) {
214
+ throw createAssetServerCompilationError(
215
+ `Import "${url}" in ${importerPath}, resolved to "${identityPath}", is outside all configured fileMap entries. ` +
216
+ `Add a matching fileMap entry for this file path, or mark this import as external.`,
217
+ {
218
+ code: 'IMPORT_OUTSIDE_FILE_MAP',
219
+ },
220
+ )
221
+ }
222
+
223
+ return {
224
+ depPath: identityPath,
225
+ kind: 'local',
226
+ placeholder,
227
+ suffix,
228
+ }
229
+ }
230
+
231
+ function resolveUrlDependency(url: string, placeholder: string): ResolvedDependency {
232
+ return {
233
+ kind: 'external',
234
+ placeholder,
235
+ replacement: url,
236
+ }
237
+ }
238
+
239
+ function resolveExistingFilePath(filePath: string): string | null {
240
+ try {
241
+ return normalizeFilePath(fs.realpathSync(filePath))
242
+ } catch (error) {
243
+ if (isNoEntityError(error)) return null
244
+ throw error
245
+ }
246
+ }
247
+
248
+ function splitUrlSuffix(url: string): {
249
+ pathname: string
250
+ suffix: string
251
+ } {
252
+ let queryIndex = url.indexOf('?')
253
+ let hashIndex = url.indexOf('#')
254
+ let endIndex = [queryIndex, hashIndex].filter((index) => index >= 0).sort((a, b) => a - b)[0]
255
+
256
+ if (endIndex == null) {
257
+ return {
258
+ pathname: url,
259
+ suffix: '',
260
+ }
261
+ }
262
+
263
+ return {
264
+ pathname: url.slice(0, endIndex),
265
+ suffix: url.slice(endIndex),
266
+ }
267
+ }
268
+
269
+ function getTrackedImportFilePath(specifier: string, importerPath: string): string | null {
270
+ let { pathname } = splitUrlSuffix(specifier)
271
+ if (pathname.startsWith('./') || pathname.startsWith('../')) {
272
+ return normalizeFilePath(path.resolve(path.dirname(importerPath), pathname))
273
+ }
274
+
275
+ return null
276
+ }
277
+
278
+ function isStyleFilePath(filePath: string): boolean {
279
+ return path.extname(filePath).toLowerCase() === '.css'
280
+ }
281
+
282
+ function isExternalUrl(url: string): boolean {
283
+ return url.startsWith('#') || url.startsWith('//') || /^[A-Za-z][A-Za-z\d+.-]*:/.test(url)
284
+ }
285
+
286
+ function isNoEntityError(error: unknown): error is NodeJS.ErrnoException & { code: 'ENOENT' } {
287
+ return (
288
+ error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT'
289
+ )
290
+ }
291
+
292
+ function toResolveError(error: unknown, importerPath: string): AssetServerCompilationError {
293
+ if (isAssetServerCompilationError(error)) return error
294
+
295
+ return createAssetServerCompilationError(
296
+ `Failed to resolve imports in ${importerPath}. ${error instanceof Error ? error.message : String(error)}`,
297
+ {
298
+ cause: error,
299
+ code: 'IMPORT_RESOLUTION_FAILED',
300
+ },
301
+ )
302
+ }
303
+
304
+ function failResolve(
305
+ error: unknown,
306
+ trackedFiles: ReadonlySet<string>,
307
+ importerPath: string,
308
+ ): ResolveResult {
309
+ return {
310
+ ok: false,
311
+ error: toResolveError(error, importerPath),
312
+ tracking: {
313
+ trackedFiles: [...trackedFiles],
314
+ },
315
+ }
316
+ }
@@ -0,0 +1,226 @@
1
+ import * as fs from 'node:fs/promises'
2
+ import { transform } from 'lightningcss'
3
+
4
+ import {
5
+ createAssetServerCompilationError,
6
+ isAssetServerCompilationError,
7
+ } from '../compilation-error.ts'
8
+ import { generateFingerprint } from '../fingerprint.ts'
9
+ import type { ModuleTracking } from '../module-store.ts'
10
+ import { rewriteSourceMapSources, stringifySourceMap } from '../source-maps.ts'
11
+ import type { AssetServerCompilationError } from '../compilation-error.ts'
12
+ import type { CompiledRoutes } from '../routes.ts'
13
+ import type { ResolvedStyleTarget } from '../target.ts'
14
+
15
+ type TransformedStyleDependency =
16
+ | {
17
+ placeholder: string
18
+ type: 'import'
19
+ url: string
20
+ }
21
+ | {
22
+ placeholder: string
23
+ type: 'url'
24
+ url: string
25
+ }
26
+
27
+ export type TransformedStyle = {
28
+ fingerprint: string | null
29
+ identityPath: string
30
+ rawCode: string
31
+ resolvedPath: string
32
+ sourceMap: string | null
33
+ stableUrlPathname: string
34
+ trackedFiles: string[]
35
+ unresolvedDependencies: TransformedStyleDependency[]
36
+ }
37
+
38
+ type TransformResult = {
39
+ tracking: ModuleTracking
40
+ } & (
41
+ | {
42
+ ok: true
43
+ value: TransformedStyle
44
+ }
45
+ | {
46
+ error: AssetServerCompilationError
47
+ ok: false
48
+ }
49
+ )
50
+
51
+ export type TransformArgs = {
52
+ buildId: string | null
53
+ isWatchIgnored(filePath: string): boolean
54
+ minify: boolean
55
+ routes: CompiledRoutes
56
+ sourceMaps: 'external' | 'inline' | null
57
+ sourceMapSourcePaths: 'absolute' | 'url'
58
+ targets: ResolvedStyleTarget | null
59
+ }
60
+
61
+ type TransformRecord = {
62
+ identityPath: string
63
+ }
64
+
65
+ export async function transformStyle(
66
+ record: TransformRecord,
67
+ args: TransformArgs,
68
+ ): Promise<TransformResult> {
69
+ let resolvedPath = record.identityPath
70
+ let trackedFiles = args.isWatchIgnored(resolvedPath) ? [] : [resolvedPath]
71
+ let rawBytes: Uint8Array
72
+
73
+ try {
74
+ rawBytes = new Uint8Array(await fs.readFile(resolvedPath))
75
+ } catch (error) {
76
+ if (isNoEntityError(error)) {
77
+ return {
78
+ ok: false,
79
+ error: createAssetServerCompilationError(`File not found: ${resolvedPath}`, {
80
+ cause: error,
81
+ code: 'FILE_NOT_FOUND',
82
+ }),
83
+ tracking: {
84
+ trackedFiles,
85
+ },
86
+ }
87
+ }
88
+
89
+ return {
90
+ ok: false,
91
+ error: toTransformFailedError(error, resolvedPath),
92
+ tracking: {
93
+ trackedFiles,
94
+ },
95
+ }
96
+ }
97
+
98
+ try {
99
+ let stableUrlPathname = args.routes.toUrlPathname(record.identityPath)
100
+ if (!stableUrlPathname) {
101
+ throw createAssetServerCompilationError(
102
+ `File ${record.identityPath} is outside all configured fileMap entries.`,
103
+ {
104
+ code: 'FILE_OUTSIDE_FILE_MAP',
105
+ },
106
+ )
107
+ }
108
+
109
+ let transformResult = runLightningTransform(resolvedPath, rawBytes, {
110
+ minify: args.minify,
111
+ sourceMap: args.sourceMaps != null,
112
+ targets: args.targets,
113
+ })
114
+ let sourceText = Buffer.from(rawBytes).toString('utf8')
115
+ let sourceMap = stringifySourceMap(transformResult.map)
116
+ sourceMap = sourceMap
117
+ ? rewriteSourceMapSources(
118
+ sourceMap,
119
+ resolvedPath,
120
+ stableUrlPathname,
121
+ args.sourceMapSourcePaths,
122
+ sourceText,
123
+ )
124
+ : null
125
+
126
+ let unresolvedDependencies: TransformedStyleDependency[] = []
127
+ for (let dependency of transformResult.dependencies ?? []) {
128
+ if (dependency.type === 'import') {
129
+ unresolvedDependencies.push({
130
+ placeholder: dependency.placeholder,
131
+ type: 'import',
132
+ url: dependency.url,
133
+ })
134
+ continue
135
+ }
136
+
137
+ if (dependency.type === 'url') {
138
+ unresolvedDependencies.push({
139
+ placeholder: dependency.placeholder,
140
+ type: 'url',
141
+ url: dependency.url,
142
+ })
143
+ }
144
+ }
145
+
146
+ return {
147
+ ok: true,
148
+ tracking: {
149
+ trackedFiles,
150
+ },
151
+ value: {
152
+ fingerprint:
153
+ args.buildId === null
154
+ ? null
155
+ : await generateFingerprint({
156
+ buildId: args.buildId,
157
+ content: sourceText,
158
+ }),
159
+ identityPath: record.identityPath,
160
+ rawCode: Buffer.from(transformResult.code).toString('utf8'),
161
+ resolvedPath,
162
+ sourceMap,
163
+ stableUrlPathname,
164
+ trackedFiles,
165
+ unresolvedDependencies,
166
+ },
167
+ }
168
+ } catch (error) {
169
+ return {
170
+ ok: false,
171
+ error: toTransformFailedError(error, resolvedPath),
172
+ tracking: {
173
+ trackedFiles,
174
+ },
175
+ }
176
+ }
177
+ }
178
+
179
+ function runLightningTransform(
180
+ identityPath: string,
181
+ code: Uint8Array,
182
+ options: {
183
+ minify: boolean
184
+ sourceMap: boolean
185
+ targets: ResolvedStyleTarget | null
186
+ },
187
+ ) {
188
+ try {
189
+ return transform({
190
+ analyzeDependencies: {
191
+ preserveImports: true,
192
+ },
193
+ code,
194
+ filename: identityPath,
195
+ minify: options.minify,
196
+ sourceMap: options.sourceMap,
197
+ targets: options.targets ?? undefined,
198
+ })
199
+ } catch (error) {
200
+ throw createAssetServerCompilationError(
201
+ `Failed to transform style ${identityPath}. ${error instanceof Error ? error.message : String(error)}`,
202
+ {
203
+ cause: error,
204
+ code: 'TRANSFORM_FAILED',
205
+ },
206
+ )
207
+ }
208
+ }
209
+
210
+ function toTransformFailedError(error: unknown, resolvedPath: string): AssetServerCompilationError {
211
+ if (isAssetServerCompilationError(error)) return error
212
+
213
+ return createAssetServerCompilationError(
214
+ `Failed to transform style ${resolvedPath}. ${error instanceof Error ? error.message : String(error)}`,
215
+ {
216
+ cause: error,
217
+ code: 'TRANSFORM_FAILED',
218
+ },
219
+ )
220
+ }
221
+
222
+ function isNoEntityError(error: unknown): error is NodeJS.ErrnoException & { code: 'ENOENT' } {
223
+ return (
224
+ error instanceof Error && 'code' in error && (error as NodeJS.ErrnoException).code === 'ENOENT'
225
+ )
226
+ }