@remix-run/assets 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +319 -2
- package/dist/assets.d.ts +3 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.js +1 -0
- package/dist/lib/access.d.ts +10 -0
- package/dist/lib/access.d.ts.map +1 -0
- package/dist/lib/access.js +14 -0
- package/dist/lib/asset-server.d.ts +137 -0
- package/dist/lib/asset-server.d.ts.map +1 -0
- package/dist/lib/asset-server.js +242 -0
- package/dist/lib/compilation-error.d.ts +33 -0
- package/dist/lib/compilation-error.d.ts.map +1 -0
- package/dist/lib/compilation-error.js +32 -0
- package/dist/lib/file-matcher.d.ts +6 -0
- package/dist/lib/file-matcher.d.ts.map +1 -0
- package/dist/lib/file-matcher.js +43 -0
- package/dist/lib/fingerprint.d.ts +12 -0
- package/dist/lib/fingerprint.d.ts.map +1 -0
- package/dist/lib/fingerprint.js +49 -0
- package/dist/lib/paths.d.ts +8 -0
- package/dist/lib/paths.d.ts.map +1 -0
- package/dist/lib/paths.js +50 -0
- package/dist/lib/routes.d.ts +13 -0
- package/dist/lib/routes.d.ts.map +1 -0
- package/dist/lib/routes.js +94 -0
- package/dist/lib/scripts/cjs-check.d.ts +3 -0
- package/dist/lib/scripts/cjs-check.d.ts.map +1 -0
- package/dist/lib/scripts/cjs-check.js +398 -0
- package/dist/lib/scripts/compiler.d.ts +62 -0
- package/dist/lib/scripts/compiler.d.ts.map +1 -0
- package/dist/lib/scripts/compiler.js +435 -0
- package/dist/lib/scripts/emit.d.ts +25 -0
- package/dist/lib/scripts/emit.d.ts.map +1 -0
- package/dist/lib/scripts/emit.js +63 -0
- package/dist/lib/scripts/resolve.d.ts +60 -0
- package/dist/lib/scripts/resolve.d.ts.map +1 -0
- package/dist/lib/scripts/resolve.js +230 -0
- package/dist/lib/scripts/store.d.ts +40 -0
- package/dist/lib/scripts/store.d.ts.map +1 -0
- package/dist/lib/scripts/store.js +228 -0
- package/dist/lib/scripts/transform.d.ts +62 -0
- package/dist/lib/scripts/transform.d.ts.map +1 -0
- package/dist/lib/scripts/transform.js +362 -0
- package/dist/lib/source-maps.d.ts +4 -0
- package/dist/lib/source-maps.d.ts.map +1 -0
- package/dist/lib/source-maps.js +56 -0
- package/dist/lib/watch.d.ts +22 -0
- package/dist/lib/watch.d.ts.map +1 -0
- package/dist/lib/watch.js +96 -0
- package/package.json +50 -12
- package/src/assets.ts +2 -0
- package/src/lib/access.ts +24 -0
- package/src/lib/asset-server.ts +415 -0
- package/src/lib/compilation-error.ts +61 -0
- package/src/lib/file-matcher.ts +62 -0
- package/src/lib/fingerprint.ts +65 -0
- package/src/lib/paths.ts +66 -0
- package/src/lib/routes.ts +164 -0
- package/src/lib/scripts/cjs-check.ts +476 -0
- package/src/lib/scripts/compiler.ts +622 -0
- package/src/lib/scripts/emit.ts +122 -0
- package/src/lib/scripts/resolve.ts +422 -0
- package/src/lib/scripts/store.ts +327 -0
- package/src/lib/scripts/transform.ts +594 -0
- package/src/lib/source-maps.ts +68 -0
- package/src/lib/watch.ts +136 -0
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as fsp from 'node:fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { getTsconfig } from 'get-tsconfig';
|
|
5
|
+
import { minify } from 'oxc-minify';
|
|
6
|
+
import { transform as oxcTransform } from 'oxc-transform';
|
|
7
|
+
import { init as esModuleLexerInit, parse as esModuleLexer } from 'es-module-lexer';
|
|
8
|
+
import { isCommonJS, mayContainCommonJSModuleGlobals } from "./cjs-check.js";
|
|
9
|
+
import { createAssetServerCompilationError, isAssetServerCompilationError, } from "../compilation-error.js";
|
|
10
|
+
import { generateFingerprint } from "../fingerprint.js";
|
|
11
|
+
import { normalizeFilePath } from "../paths.js";
|
|
12
|
+
import { composeSourceMaps, rewriteSourceMapSources, stringifySourceMap } from "../source-maps.js";
|
|
13
|
+
const scriptModuleTypes = [
|
|
14
|
+
{ extension: '.js', lang: 'js' },
|
|
15
|
+
{ extension: '.jsx', lang: 'jsx' },
|
|
16
|
+
{ extension: '.mjs', lang: 'js' },
|
|
17
|
+
{ extension: '.mts', lang: 'ts' },
|
|
18
|
+
{ extension: '.ts', lang: 'ts' },
|
|
19
|
+
{ extension: '.tsx', lang: 'tsx' },
|
|
20
|
+
];
|
|
21
|
+
const sourceLanguageByExtension = new Map(scriptModuleTypes.map(({ extension, lang }) => [extension, lang]));
|
|
22
|
+
const supportedTsconfigTransformCompilerOptions = {
|
|
23
|
+
allowNamespaces: 'allowNamespaces',
|
|
24
|
+
emitDecoratorMetadata: 'emitDecoratorMetadata',
|
|
25
|
+
experimentalDecorators: 'experimentalDecorators',
|
|
26
|
+
jsx: 'jsx',
|
|
27
|
+
jsxFactory: 'jsxFactory',
|
|
28
|
+
jsxFragmentFactory: 'jsxFragmentFactory',
|
|
29
|
+
jsxImportSource: 'jsxImportSource',
|
|
30
|
+
useDefineForClassFields: 'useDefineForClassFields',
|
|
31
|
+
};
|
|
32
|
+
export function createTsconfigTransformOptionsResolver() {
|
|
33
|
+
let fileSystemCache = new Map();
|
|
34
|
+
let transformOptionsByDirectory = new Map();
|
|
35
|
+
return {
|
|
36
|
+
clear() {
|
|
37
|
+
fileSystemCache = new Map();
|
|
38
|
+
transformOptionsByDirectory.clear();
|
|
39
|
+
},
|
|
40
|
+
getTransformOptions(filePath, isWatchIgnored) {
|
|
41
|
+
let directory = path.dirname(filePath);
|
|
42
|
+
let cached = transformOptionsByDirectory.get(directory);
|
|
43
|
+
if (cached)
|
|
44
|
+
return cached;
|
|
45
|
+
let tsconfig = getTsconfig(directory, 'tsconfig.json', fileSystemCache);
|
|
46
|
+
if (!tsconfig) {
|
|
47
|
+
let transformOptions = { trackedFiles: [] };
|
|
48
|
+
transformOptionsByDirectory.set(directory, transformOptions);
|
|
49
|
+
return transformOptions;
|
|
50
|
+
}
|
|
51
|
+
let tsconfigPath = findNearestTsconfigPath(directory);
|
|
52
|
+
let transformOptions = {
|
|
53
|
+
trackedFiles: tsconfigPath && !isWatchIgnored(tsconfigPath) ? [tsconfigPath] : [],
|
|
54
|
+
tsconfigRaw: tsconfig.config,
|
|
55
|
+
};
|
|
56
|
+
transformOptionsByDirectory.set(directory, transformOptions);
|
|
57
|
+
return transformOptions;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export async function transformModule(record, args) {
|
|
62
|
+
let resolvedPath = args.resolveActualPath(record.identityPath);
|
|
63
|
+
if (!resolvedPath) {
|
|
64
|
+
return {
|
|
65
|
+
ok: false,
|
|
66
|
+
error: createAssetServerCompilationError(`Module not found: ${record.identityPath}`, {
|
|
67
|
+
code: 'MODULE_NOT_FOUND',
|
|
68
|
+
}),
|
|
69
|
+
trackedFiles: args.isWatchIgnored(record.identityPath) ? [] : [record.identityPath],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
let transformOptions = args.tsconfigTransformOptionsResolver.getTransformOptions(resolvedPath, args.isWatchIgnored);
|
|
73
|
+
let trackedFiles = [
|
|
74
|
+
...(args.isWatchIgnored(resolvedPath) ? [] : [resolvedPath]),
|
|
75
|
+
...transformOptions.trackedFiles,
|
|
76
|
+
];
|
|
77
|
+
let sourceText;
|
|
78
|
+
try {
|
|
79
|
+
sourceText = await fsp.readFile(resolvedPath, 'utf-8');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
if (isNoEntityError(error)) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
error: createAssetServerCompilationError(`Module not found: ${resolvedPath}`, {
|
|
86
|
+
cause: error,
|
|
87
|
+
code: 'MODULE_NOT_FOUND',
|
|
88
|
+
}),
|
|
89
|
+
trackedFiles,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
error: toTransformFailedError(error, resolvedPath),
|
|
95
|
+
trackedFiles,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
let analysis = await analyzeModuleSource(sourceText, resolvedPath, transformOptions, {
|
|
100
|
+
define: args.define ?? undefined,
|
|
101
|
+
minify: args.minify,
|
|
102
|
+
sourceMaps: args.sourceMaps ?? undefined,
|
|
103
|
+
target: args.target ?? undefined,
|
|
104
|
+
});
|
|
105
|
+
analysis.unresolvedImports = analysis.unresolvedImports.filter((unresolved) => !args.externalSet.has(unresolved.specifier));
|
|
106
|
+
if (mayContainCommonJSModuleGlobals(sourceText) && isCommonJS(analysis.rawCode)) {
|
|
107
|
+
throw createAssetServerCompilationError(`CommonJS module detected: ${resolvedPath}. ` +
|
|
108
|
+
`This module uses CommonJS (require/module.exports) which is not supported. ` +
|
|
109
|
+
`Please use an ESM-compatible module.`, {
|
|
110
|
+
code: 'MODULE_COMMONJS_NOT_SUPPORTED',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
let stableUrlPathname = args.routes.toUrlPathname(record.identityPath);
|
|
114
|
+
if (!stableUrlPathname) {
|
|
115
|
+
throw createAssetServerCompilationError(`Module ${record.identityPath} is outside all configured fileMap entries.`, {
|
|
116
|
+
code: 'MODULE_OUTSIDE_FILE_MAP',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
let sourceMap = analysis.sourceMap
|
|
120
|
+
? rewriteSourceMapSources(analysis.sourceMap, resolvedPath, stableUrlPathname, args.sourceMapSourcePaths)
|
|
121
|
+
: null;
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
value: {
|
|
125
|
+
fingerprint: args.buildId === null
|
|
126
|
+
? null
|
|
127
|
+
: await generateFingerprint({
|
|
128
|
+
buildId: args.buildId,
|
|
129
|
+
content: sourceText,
|
|
130
|
+
}),
|
|
131
|
+
identityPath: record.identityPath,
|
|
132
|
+
importerDir: path.dirname(resolvedPath),
|
|
133
|
+
packageSpecifiers: analysis.unresolvedImports
|
|
134
|
+
.filter((unresolved) => isPackageImportSpecifier(unresolved.specifier))
|
|
135
|
+
.map((unresolved) => unresolved.specifier),
|
|
136
|
+
rawCode: analysis.rawCode,
|
|
137
|
+
resolvedPath,
|
|
138
|
+
sourceMap,
|
|
139
|
+
stableUrlPathname,
|
|
140
|
+
trackedFiles,
|
|
141
|
+
unresolvedImports: analysis.unresolvedImports,
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
return {
|
|
147
|
+
ok: false,
|
|
148
|
+
error: toTransformFailedError(error, resolvedPath),
|
|
149
|
+
trackedFiles,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function findNearestTsconfigPath(directory) {
|
|
154
|
+
let currentDirectory = directory;
|
|
155
|
+
while (true) {
|
|
156
|
+
let tsconfigPath = path.join(currentDirectory, 'tsconfig.json');
|
|
157
|
+
if (fs.existsSync(tsconfigPath)) {
|
|
158
|
+
return normalizeFilePath(tsconfigPath);
|
|
159
|
+
}
|
|
160
|
+
let parentDirectory = path.dirname(currentDirectory);
|
|
161
|
+
if (parentDirectory === currentDirectory)
|
|
162
|
+
return null;
|
|
163
|
+
currentDirectory = parentDirectory;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function isPackageImportSpecifier(specifier) {
|
|
167
|
+
return !specifier.startsWith('./') && !specifier.startsWith('../') && !specifier.startsWith('/');
|
|
168
|
+
}
|
|
169
|
+
async function analyzeModuleSource(sourceText, resolvedPath, transformOptions, options) {
|
|
170
|
+
let transformResult;
|
|
171
|
+
try {
|
|
172
|
+
transformResult = await oxcTransform(resolvedPath, sourceText, getTransformOptions(resolvedPath, transformOptions, options));
|
|
173
|
+
assertNoCompilerErrors(transformResult.errors, resolvedPath, 'transform');
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (isAssetServerCompilationError(error))
|
|
177
|
+
throw error;
|
|
178
|
+
throw createAssetServerCompilationError(`Failed to transform module ${resolvedPath}. ${formatUnknownError(error)}`, {
|
|
179
|
+
cause: error,
|
|
180
|
+
code: 'MODULE_TRANSFORM_FAILED',
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
let rawCode = transformResult.code.trimEnd();
|
|
184
|
+
let sourceMap = stringifySourceMap(transformResult.map);
|
|
185
|
+
if (options.minify) {
|
|
186
|
+
let minifyResult = await minifyModule(rawCode, resolvedPath, options.target, options.sourceMaps);
|
|
187
|
+
rawCode = minifyResult.code.trimEnd();
|
|
188
|
+
let minifyMap = stringifySourceMap(minifyResult.map);
|
|
189
|
+
sourceMap =
|
|
190
|
+
minifyMap == null
|
|
191
|
+
? sourceMap
|
|
192
|
+
: sourceMap == null
|
|
193
|
+
? minifyMap
|
|
194
|
+
: composeSourceMaps(minifyMap, sourceMap);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
rawCode,
|
|
198
|
+
sourceMap,
|
|
199
|
+
unresolvedImports: await getUnresolvedImportsFromLexer(rawCode),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
async function minifyModule(rawCode, resolvedPath, target, sourceMaps) {
|
|
203
|
+
try {
|
|
204
|
+
let result = await minify(resolvedPath, rawCode, {
|
|
205
|
+
compress: target ? { target } : true,
|
|
206
|
+
mangle: true,
|
|
207
|
+
module: true,
|
|
208
|
+
sourcemap: sourceMaps != null,
|
|
209
|
+
});
|
|
210
|
+
assertNoCompilerErrors(result.errors, resolvedPath, 'minify');
|
|
211
|
+
return result;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
if (isAssetServerCompilationError(error))
|
|
215
|
+
throw error;
|
|
216
|
+
throw createAssetServerCompilationError(`Failed to minify module ${resolvedPath}. ${formatUnknownError(error)}`, {
|
|
217
|
+
cause: error,
|
|
218
|
+
code: 'MODULE_TRANSFORM_FAILED',
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function getTransformOptions(resolvedPath, transformOptions, options) {
|
|
223
|
+
let compilerOptions = transformOptions.tsconfigRaw?.compilerOptions;
|
|
224
|
+
let useDefineForClassFields = getBooleanOption(compilerOptions, supportedTsconfigTransformCompilerOptions.useDefineForClassFields);
|
|
225
|
+
let jsxFactory = getStringOption(compilerOptions, supportedTsconfigTransformCompilerOptions.jsxFactory);
|
|
226
|
+
let jsxFragmentFactory = getStringOption(compilerOptions, supportedTsconfigTransformCompilerOptions.jsxFragmentFactory);
|
|
227
|
+
return {
|
|
228
|
+
assumptions: useDefineForClassFields === false
|
|
229
|
+
? {
|
|
230
|
+
setPublicClassFields: true,
|
|
231
|
+
}
|
|
232
|
+
: undefined,
|
|
233
|
+
decorator: getDecoratorOptions(compilerOptions),
|
|
234
|
+
define: options.define,
|
|
235
|
+
jsx: getJsxOptions(resolvedPath, compilerOptions),
|
|
236
|
+
lang: getSourceLanguageForPath(resolvedPath),
|
|
237
|
+
sourceType: 'module',
|
|
238
|
+
sourcemap: options.sourceMaps != null,
|
|
239
|
+
target: options.target,
|
|
240
|
+
typescript: {
|
|
241
|
+
allowNamespaces: getBooleanOption(compilerOptions, supportedTsconfigTransformCompilerOptions.allowNamespaces),
|
|
242
|
+
jsxPragma: jsxFactory,
|
|
243
|
+
jsxPragmaFrag: jsxFragmentFactory,
|
|
244
|
+
removeClassFieldsWithoutInitializer: useDefineForClassFields === false ? true : undefined,
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function getJsxOptions(resolvedPath, compilerOptions) {
|
|
249
|
+
let language = getSourceLanguageForPath(resolvedPath);
|
|
250
|
+
if (language !== 'jsx' && language !== 'tsx')
|
|
251
|
+
return undefined;
|
|
252
|
+
let jsx = getStringOption(compilerOptions, supportedTsconfigTransformCompilerOptions.jsx);
|
|
253
|
+
let importSource = getStringOption(compilerOptions, supportedTsconfigTransformCompilerOptions.jsxImportSource);
|
|
254
|
+
let pragma = getStringOption(compilerOptions, supportedTsconfigTransformCompilerOptions.jsxFactory);
|
|
255
|
+
let pragmaFrag = getStringOption(compilerOptions, supportedTsconfigTransformCompilerOptions.jsxFragmentFactory);
|
|
256
|
+
if (jsx === 'preserve' || jsx === 'react-native') {
|
|
257
|
+
throw createAssetServerCompilationError(`Unsupported tsconfig compilerOptions.jsx = "${jsx}" for ${resolvedPath}. ` +
|
|
258
|
+
`Asset server must compile JSX to browser-runnable JavaScript.`, {
|
|
259
|
+
code: 'MODULE_TRANSFORM_FAILED',
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
if (jsx === 'react-jsx' || jsx === 'react-jsxdev') {
|
|
263
|
+
return {
|
|
264
|
+
development: jsx === 'react-jsxdev',
|
|
265
|
+
importSource,
|
|
266
|
+
runtime: 'automatic',
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
pragma,
|
|
271
|
+
pragmaFrag,
|
|
272
|
+
runtime: 'classic',
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
function getDecoratorOptions(compilerOptions) {
|
|
276
|
+
let legacy = getBooleanOption(compilerOptions, supportedTsconfigTransformCompilerOptions.experimentalDecorators);
|
|
277
|
+
let emitDecoratorMetadata = getBooleanOption(compilerOptions, supportedTsconfigTransformCompilerOptions.emitDecoratorMetadata);
|
|
278
|
+
if (legacy !== true && emitDecoratorMetadata !== true)
|
|
279
|
+
return undefined;
|
|
280
|
+
return {
|
|
281
|
+
emitDecoratorMetadata,
|
|
282
|
+
legacy,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function getBooleanOption(compilerOptions, key) {
|
|
286
|
+
let value = compilerOptions?.[key];
|
|
287
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
288
|
+
}
|
|
289
|
+
function getStringOption(compilerOptions, key) {
|
|
290
|
+
let value = compilerOptions?.[key];
|
|
291
|
+
return typeof value === 'string' ? value : undefined;
|
|
292
|
+
}
|
|
293
|
+
function assertNoCompilerErrors(errors, resolvedPath, operation) {
|
|
294
|
+
if (!errors || errors.length === 0)
|
|
295
|
+
return;
|
|
296
|
+
throw createAssetServerCompilationError(`Failed to ${operation} module ${resolvedPath}. ${errors[0].message ?? 'Unknown error'}`, {
|
|
297
|
+
code: 'MODULE_TRANSFORM_FAILED',
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async function getUnresolvedImportsFromLexer(rawCode) {
|
|
301
|
+
await esModuleLexerInit;
|
|
302
|
+
let [imports] = esModuleLexer(rawCode);
|
|
303
|
+
let unresolvedImports = [];
|
|
304
|
+
for (let imported of imports) {
|
|
305
|
+
let specifier = getStaticImportSpecifier(rawCode, imported);
|
|
306
|
+
if (specifier == null || shouldSkipImportSpecifier(specifier))
|
|
307
|
+
continue;
|
|
308
|
+
unresolvedImports.push({
|
|
309
|
+
specifier,
|
|
310
|
+
start: imported.s,
|
|
311
|
+
end: imported.e,
|
|
312
|
+
quote: getImportQuote(rawCode, imported.s),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
return unresolvedImports;
|
|
316
|
+
}
|
|
317
|
+
function getStaticImportSpecifier(source, imported) {
|
|
318
|
+
if (imported.n != null) {
|
|
319
|
+
return imported.n;
|
|
320
|
+
}
|
|
321
|
+
if (imported.d < 0) {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
let rawSpecifier = source.slice(imported.s, imported.e);
|
|
325
|
+
if (!isStaticTemplateLiteral(rawSpecifier)) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
return rawSpecifier.slice(1, -1);
|
|
329
|
+
}
|
|
330
|
+
function isStaticTemplateLiteral(specifier) {
|
|
331
|
+
return specifier.startsWith('`') && specifier.endsWith('`') && !specifier.includes('${');
|
|
332
|
+
}
|
|
333
|
+
function shouldSkipImportSpecifier(specifier) {
|
|
334
|
+
return (specifier.startsWith('data:') ||
|
|
335
|
+
specifier.startsWith('http://') ||
|
|
336
|
+
specifier.startsWith('https://'));
|
|
337
|
+
}
|
|
338
|
+
function getImportQuote(source, start) {
|
|
339
|
+
let firstCharacter = source[start];
|
|
340
|
+
if (firstCharacter === '"' || firstCharacter === "'" || firstCharacter === '`') {
|
|
341
|
+
return firstCharacter;
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
function getSourceLanguageForPath(resolvedPath) {
|
|
346
|
+
let extension = path.extname(resolvedPath).toLowerCase();
|
|
347
|
+
return sourceLanguageByExtension.get(extension) ?? 'js';
|
|
348
|
+
}
|
|
349
|
+
function formatUnknownError(error) {
|
|
350
|
+
return error instanceof Error ? error.message : String(error);
|
|
351
|
+
}
|
|
352
|
+
function toTransformFailedError(error, resolvedPath) {
|
|
353
|
+
if (isAssetServerCompilationError(error))
|
|
354
|
+
return error;
|
|
355
|
+
return createAssetServerCompilationError(`Failed to transform module ${resolvedPath}. ${formatUnknownError(error)}`, {
|
|
356
|
+
cause: error,
|
|
357
|
+
code: 'MODULE_TRANSFORM_FAILED',
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
function isNoEntityError(error) {
|
|
361
|
+
return (error instanceof Error && 'code' in error && error.code === 'ENOENT');
|
|
362
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function composeSourceMaps(rewriteSourceMap: string, transformSourceMap: string): string;
|
|
2
|
+
export declare function rewriteSourceMapSources(sourceMap: string, resolvedPath: string, stableUrlPathname: string, sourceMapSourcePaths: 'absolute' | 'url'): string;
|
|
3
|
+
export declare function stringifySourceMap(map: unknown): string | null;
|
|
4
|
+
//# sourceMappingURL=source-maps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-maps.d.ts","sourceRoot":"","sources":["../../src/lib/source-maps.ts"],"names":[],"mappings":"AAIA,wBAAgB,iBAAiB,CAAC,gBAAgB,EAAE,MAAM,EAAE,kBAAkB,EAAE,MAAM,GAAG,MAAM,CA2C9F;AAED,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,EACpB,iBAAiB,EAAE,MAAM,EACzB,oBAAoB,EAAE,UAAU,GAAG,KAAK,GACvC,MAAM,CAMR;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAK9D"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { SourceMapConsumer, SourceMapGenerator } from 'source-map-js';
|
|
2
|
+
import { normalizeFilePath } from "./paths.js";
|
|
3
|
+
export function composeSourceMaps(rewriteSourceMap, transformSourceMap) {
|
|
4
|
+
let rewriteConsumer = new SourceMapConsumer(JSON.parse(rewriteSourceMap));
|
|
5
|
+
let transformConsumer = new SourceMapConsumer(JSON.parse(transformSourceMap));
|
|
6
|
+
let generator = new SourceMapGenerator();
|
|
7
|
+
rewriteConsumer.eachMapping((mapping) => {
|
|
8
|
+
if (mapping.originalLine == null ||
|
|
9
|
+
mapping.originalColumn == null ||
|
|
10
|
+
mapping.generatedLine == null ||
|
|
11
|
+
mapping.generatedColumn == null) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
let original = transformConsumer.originalPositionFor({
|
|
15
|
+
line: mapping.originalLine,
|
|
16
|
+
column: mapping.originalColumn,
|
|
17
|
+
});
|
|
18
|
+
if (original.line == null || original.column == null || original.source == null)
|
|
19
|
+
return;
|
|
20
|
+
generator.addMapping({
|
|
21
|
+
generated: {
|
|
22
|
+
line: mapping.generatedLine,
|
|
23
|
+
column: mapping.generatedColumn,
|
|
24
|
+
},
|
|
25
|
+
original: {
|
|
26
|
+
line: original.line,
|
|
27
|
+
column: original.column,
|
|
28
|
+
},
|
|
29
|
+
source: original.source,
|
|
30
|
+
name: original.name ?? mapping.name ?? undefined,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
for (let source of transformConsumer.sources) {
|
|
34
|
+
let sourceContent = transformConsumer.sourceContentFor(source, true);
|
|
35
|
+
if (sourceContent !== null) {
|
|
36
|
+
generator.setSourceContent(source, sourceContent);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return JSON.stringify(generator.toJSON());
|
|
40
|
+
}
|
|
41
|
+
export function rewriteSourceMapSources(sourceMap, resolvedPath, stableUrlPathname, sourceMapSourcePaths) {
|
|
42
|
+
let json = JSON.parse(sourceMap);
|
|
43
|
+
json.sources = [
|
|
44
|
+
sourceMapSourcePaths === 'absolute' ? normalizeFilePath(resolvedPath) : stableUrlPathname,
|
|
45
|
+
];
|
|
46
|
+
return JSON.stringify(json);
|
|
47
|
+
}
|
|
48
|
+
export function stringifySourceMap(map) {
|
|
49
|
+
if (!map)
|
|
50
|
+
return null;
|
|
51
|
+
if (typeof map === 'string')
|
|
52
|
+
return map;
|
|
53
|
+
if (typeof map === 'object' && map !== null)
|
|
54
|
+
return JSON.stringify(map);
|
|
55
|
+
return String(map);
|
|
56
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
type AssetServerWatcherOptions = {
|
|
3
|
+
ignore?: readonly string[];
|
|
4
|
+
onChokidarWatcherCreated?: (watcher: ChokidarWatcher) => void;
|
|
5
|
+
poll?: boolean;
|
|
6
|
+
pollInterval?: number;
|
|
7
|
+
onFileEvent(filePath: string, event: AssetServerWatchEvent): Promise<void>;
|
|
8
|
+
rootDir: string;
|
|
9
|
+
};
|
|
10
|
+
type AssetServerWatchEvent = 'add' | 'change' | 'unlink';
|
|
11
|
+
export type ChokidarWatcher = ReturnType<typeof chokidar.watch>;
|
|
12
|
+
export type AssetServerWatcher = {
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
getWatchedTargets(): readonly string[];
|
|
15
|
+
updateWatchedDirectories(delta: {
|
|
16
|
+
add: readonly string[];
|
|
17
|
+
remove: readonly string[];
|
|
18
|
+
}): void;
|
|
19
|
+
};
|
|
20
|
+
export declare function createAssetServerWatcher(options: AssetServerWatcherOptions): AssetServerWatcher;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/lib/watch.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,UAAU,CAAA;AAI/B,KAAK,yBAAyB,GAAG;IAC/B,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1B,wBAAwB,CAAC,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAA;IAC7D,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1E,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,KAAK,qBAAqB,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACxD,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAA;AAE/D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,iBAAiB,IAAI,SAAS,MAAM,EAAE,CAAA;IACtC,wBAAwB,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,GAAG,IAAI,CAAA;CAC7F,CAAA;AAED,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,kBAAkB,CAmD/F"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import { getFilePathDirectory, normalizeFilePath } from "./paths.js";
|
|
3
|
+
export function createAssetServerWatcher(options) {
|
|
4
|
+
let watcher = chokidar.watch([], {
|
|
5
|
+
ignoreInitial: true,
|
|
6
|
+
ignorePermissionErrors: true,
|
|
7
|
+
...resolveChokidarWatchOptions(options),
|
|
8
|
+
});
|
|
9
|
+
options.onChokidarWatcherCreated?.(watcher);
|
|
10
|
+
let watchedDirectories = new Set();
|
|
11
|
+
let watchedTargets = new Set();
|
|
12
|
+
for (let event of ['add', 'change', 'unlink']) {
|
|
13
|
+
watcher.on(event, (filePath) => {
|
|
14
|
+
options.onFileEvent(filePath, event);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
watcher.on('error', (error) => {
|
|
18
|
+
console.error('Asset server file system watcher encountered an error.', error);
|
|
19
|
+
});
|
|
20
|
+
return {
|
|
21
|
+
async close() {
|
|
22
|
+
await watcher.close();
|
|
23
|
+
},
|
|
24
|
+
getWatchedTargets() {
|
|
25
|
+
return [...watchedTargets];
|
|
26
|
+
},
|
|
27
|
+
updateWatchedDirectories(delta) {
|
|
28
|
+
let nextWatchedDirectories = new Set(watchedDirectories);
|
|
29
|
+
for (let directory of delta.add) {
|
|
30
|
+
nextWatchedDirectories.add(directory);
|
|
31
|
+
}
|
|
32
|
+
for (let directory of delta.remove) {
|
|
33
|
+
nextWatchedDirectories.delete(directory);
|
|
34
|
+
}
|
|
35
|
+
let nextTargets = getWatchTargetsForDirectories(options.rootDir, [...nextWatchedDirectories]);
|
|
36
|
+
let targetsToAdd = [...nextTargets].filter((target) => !watchedTargets.has(target));
|
|
37
|
+
let targetsToRemove = [...watchedTargets].filter((target) => !nextTargets.has(target));
|
|
38
|
+
if (targetsToRemove.length > 0) {
|
|
39
|
+
watcher.unwatch(targetsToRemove);
|
|
40
|
+
}
|
|
41
|
+
if (targetsToAdd.length > 0) {
|
|
42
|
+
watcher.add(targetsToAdd);
|
|
43
|
+
}
|
|
44
|
+
watchedDirectories = nextWatchedDirectories;
|
|
45
|
+
watchedTargets = nextTargets;
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function resolveChokidarWatchOptions(options) {
|
|
50
|
+
return {
|
|
51
|
+
awaitWriteFinish: {
|
|
52
|
+
pollInterval: 10,
|
|
53
|
+
stabilityThreshold: 10,
|
|
54
|
+
},
|
|
55
|
+
depth: 0,
|
|
56
|
+
ignored: ['**/.git/**', ...(options.ignore ?? [])],
|
|
57
|
+
interval: options.pollInterval ?? 100,
|
|
58
|
+
usePolling: options.poll ?? false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function getWatchTargetsForDirectories(rootDir, directories) {
|
|
62
|
+
let normalizedRootDir = normalizeFilePath(rootDir);
|
|
63
|
+
let targets = new Set();
|
|
64
|
+
let configAncestors = new Set();
|
|
65
|
+
for (let directory of directories) {
|
|
66
|
+
let normalizedDirectory = normalizeFilePath(directory).replace(/\/+$/, '');
|
|
67
|
+
targets.add(normalizedDirectory);
|
|
68
|
+
if (!isSameOrDescendantPath(normalizedDirectory, normalizedRootDir))
|
|
69
|
+
continue;
|
|
70
|
+
for (let ancestor of getAncestorPaths(normalizedDirectory, normalizedRootDir)) {
|
|
71
|
+
configAncestors.add(ancestor);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for (let ancestor of configAncestors) {
|
|
75
|
+
targets.add(ancestor);
|
|
76
|
+
}
|
|
77
|
+
return targets;
|
|
78
|
+
}
|
|
79
|
+
function getAncestorPaths(directoryPath, rootDir) {
|
|
80
|
+
let ancestors = [];
|
|
81
|
+
let currentDirectory = directoryPath;
|
|
82
|
+
while (isSameOrDescendantPath(currentDirectory, rootDir)) {
|
|
83
|
+
ancestors.push(currentDirectory);
|
|
84
|
+
if (currentDirectory === rootDir)
|
|
85
|
+
break;
|
|
86
|
+
let parentDirectory = getFilePathDirectory(currentDirectory);
|
|
87
|
+
if (parentDirectory === currentDirectory)
|
|
88
|
+
break;
|
|
89
|
+
currentDirectory = parentDirectory;
|
|
90
|
+
}
|
|
91
|
+
return ancestors;
|
|
92
|
+
}
|
|
93
|
+
function isSameOrDescendantPath(filePath, directoryPath) {
|
|
94
|
+
let normalizedDirectoryPath = directoryPath.replace(/\/+$/, '');
|
|
95
|
+
return filePath === normalizedDirectoryPath || filePath.startsWith(`${normalizedDirectoryPath}/`);
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,21 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remix-run/assets",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"scripts": {
|
|
7
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
8
|
-
},
|
|
9
|
-
"keywords": [],
|
|
10
|
-
"author": "",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Fetch-based server for compiling browser JS/TS assets on demand",
|
|
5
|
+
"author": "Michael Jackson <mjijackson@gmail.com>",
|
|
11
6
|
"license": "MIT",
|
|
12
|
-
"type": "commonjs",
|
|
13
7
|
"repository": {
|
|
14
8
|
"type": "git",
|
|
15
9
|
"url": "git+https://github.com/remix-run/remix.git",
|
|
16
10
|
"directory": "packages/assets"
|
|
17
11
|
},
|
|
18
|
-
"
|
|
19
|
-
|
|
12
|
+
"homepage": "https://github.com/remix-run/remix/tree/main/packages/assets#readme",
|
|
13
|
+
"files": [
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"README.md",
|
|
16
|
+
"dist",
|
|
17
|
+
"src",
|
|
18
|
+
"!src/**/*.test.ts"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/assets.d.ts",
|
|
24
|
+
"default": "./dist/assets.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"chokidar": "^5.0.0",
|
|
30
|
+
"es-module-lexer": "^2.0.0",
|
|
31
|
+
"get-tsconfig": "^4.13.6",
|
|
32
|
+
"magic-string": "^0.30.21",
|
|
33
|
+
"oxc-minify": "^0.121.0",
|
|
34
|
+
"oxc-parser": "^0.121.0",
|
|
35
|
+
"oxc-resolver": "^11.19.1",
|
|
36
|
+
"oxc-transform": "^0.121.0",
|
|
37
|
+
"source-map-js": "^1.2.1",
|
|
38
|
+
"@remix-run/headers": "0.19.0",
|
|
39
|
+
"@remix-run/route-pattern": "0.20.1"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^24.6.0",
|
|
43
|
+
"@typescript/native-preview": "7.0.0-dev.20251125.1"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"remix",
|
|
47
|
+
"scripts",
|
|
48
|
+
"assets",
|
|
49
|
+
"transform",
|
|
50
|
+
"server"
|
|
51
|
+
],
|
|
52
|
+
"scripts": {
|
|
53
|
+
"bench": "pnpm --dir ./bench run bench",
|
|
54
|
+
"build": "tsgo -p tsconfig.build.json",
|
|
55
|
+
"clean": "git clean -fdX",
|
|
56
|
+
"test": "node --disable-warning=ExperimentalWarning --test './src/**/*.test.ts'",
|
|
57
|
+
"typecheck": "tsgo --noEmit"
|
|
20
58
|
}
|
|
21
|
-
}
|
|
59
|
+
}
|
package/src/assets.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createFileMatcher } from './file-matcher.ts'
|
|
2
|
+
|
|
3
|
+
type AccessPolicy = {
|
|
4
|
+
isAllowed(filePath: string): boolean
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function createAccessPolicy(options: {
|
|
8
|
+
allow: readonly string[]
|
|
9
|
+
deny?: readonly string[]
|
|
10
|
+
rootDir: string
|
|
11
|
+
}): AccessPolicy {
|
|
12
|
+
let allowMatchers = options.allow.map((pattern) => createFileMatcher(pattern, options.rootDir))
|
|
13
|
+
let denyMatchers = (options.deny ?? []).map((pattern) =>
|
|
14
|
+
createFileMatcher(pattern, options.rootDir),
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
isAllowed(filePath) {
|
|
19
|
+
if (!allowMatchers.some((matcher) => matcher(filePath))) return false
|
|
20
|
+
if (denyMatchers.length > 0 && denyMatchers.some((matcher) => matcher(filePath))) return false
|
|
21
|
+
return true
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
}
|