@bleedingdev/modern-js-server-utils 3.8.3-ultramodern.1 → 3.8.3-ultramodern.2

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.
@@ -1,204 +1,109 @@
1
1
  import "node:module";
2
- const IDENTIFIER_CHAR = /[A-Za-z0-9_$]/;
3
- const WHITESPACE = /\s/;
4
- const KEYWORDS_BEFORE_REGEX = new Set([
5
- 'return',
6
- 'typeof',
7
- 'instanceof',
8
- 'in',
9
- 'of',
10
- 'new',
11
- 'delete',
12
- 'void',
13
- 'throw',
14
- 'case',
15
- 'do',
16
- 'else',
17
- 'yield',
18
- 'await'
19
- ]);
20
- const PUNCTUATORS_BEFORE_REGEX = new Set('{}(,;[=:?!&|+-*%^~<>/');
21
- const isRegexAllowed = (lastToken)=>{
22
- if (!lastToken) return true;
23
- if (KEYWORDS_BEFORE_REGEX.has(lastToken)) return true;
24
- return 1 === lastToken.length && PUNCTUATORS_BEFORE_REGEX.has(lastToken);
2
+ import { parseSync } from "@swc/core";
3
+ const assertNoNativeModuleOutputCollision = (sourceFile, fileExists)=>{
4
+ const rawModuleFile = sourceFile.endsWith('.mts') ? `${sourceFile.slice(0, -4)}.mjs` : sourceFile.endsWith('.cts') ? `${sourceFile.slice(0, -4)}.cjs` : void 0;
5
+ if (rawModuleFile && fileExists(rawModuleFile)) throw new Error(`Native module sources collide: "${sourceFile}" and "${rawModuleFile}" publish the same output.`);
25
6
  };
26
- const isSpecifierContext = (prev1, prev2, prev3)=>{
27
- if (('from' === prev1 || 'import' === prev1) && '.' !== prev2) return true;
28
- return '(' === prev1 && ('import' === prev2 || 'require' === prev2) && '.' !== prev3;
7
+ const isObjectRecord = (value)=>'object' == typeof value && null !== value;
8
+ const isAstNode = (value)=>isObjectRecord(value) && 'string' == typeof value.type;
9
+ const isStringLiteral = (value)=>isAstNode(value) && 'StringLiteral' === value.type && 'string' == typeof value.value && 'object' == typeof value.span && null !== value.span && 'number' == typeof value.span.start && 'number' == typeof value.span.end;
10
+ const getCallSpecifier = (node)=>{
11
+ if ('CallExpression' !== node.type || !Array.isArray(node.arguments)) return;
12
+ const callee = node.callee;
13
+ const isDynamicImport = isAstNode(callee) && 'Import' === callee.type;
14
+ const isCommonJsRequire = isAstNode(callee) && 'Identifier' === callee.type && 'require' === callee.value;
15
+ if (!isDynamicImport && !isCommonJsRequire) return;
16
+ const firstArgument = node.arguments[0];
17
+ if (!isObjectRecord(firstArgument)) return;
18
+ return isStringLiteral(firstArgument.expression) ? firstArgument.expression : void 0;
29
19
  };
30
- const isCallSpecifierContext = (prev1, prev2, prev3)=>'(' === prev1 && ('import' === prev2 || 'require' === prev2) && '.' !== prev3;
31
- const nextNonTriviaChar = (content, start)=>{
32
- let i = start;
33
- while(i < content.length){
34
- if (WHITESPACE.test(content[i])) {
35
- i++;
36
- continue;
37
- }
38
- if ('/' === content[i] && '/' === content[i + 1]) {
39
- const newline = content.indexOf('\n', i + 2);
40
- i = -1 === newline ? content.length : newline + 1;
41
- continue;
42
- }
43
- if ('/' === content[i] && '*' === content[i + 1]) {
44
- const end = content.indexOf('*/', i + 2);
45
- i = -1 === end ? content.length : end + 2;
46
- continue;
20
+ const collectSpecifierLiterals = (content)=>{
21
+ const parserOptions = {
22
+ decorators: true,
23
+ syntax: "typescript",
24
+ target: 'esnext'
25
+ };
26
+ let program;
27
+ try {
28
+ program = parseSync(content, parserOptions);
29
+ } catch (typescriptError) {
30
+ try {
31
+ program = parseSync(content, {
32
+ ...parserOptions,
33
+ tsx: true
34
+ });
35
+ } catch {
36
+ throw typescriptError;
47
37
  }
48
- return content[i];
49
38
  }
50
- return '';
51
- };
52
- const skipRegexLiteral = (content, start)=>{
53
- let i = start + 1;
54
- let inClass = false;
55
- while(i < content.length){
56
- const ch = content[i];
57
- if ('\\' === ch) {
58
- i += 2;
59
- continue;
39
+ const literals = [];
40
+ const seenSpans = new Set();
41
+ const add = (value)=>{
42
+ if (!isStringLiteral(value)) return;
43
+ const spanKey = `${value.span.start}:${value.span.end}`;
44
+ if (!seenSpans.has(spanKey)) {
45
+ seenSpans.add(spanKey);
46
+ literals.push(value);
60
47
  }
61
- if ('\n' === ch) break;
62
- if ('[' === ch) inClass = true;
63
- else if (']' === ch) inClass = false;
64
- else if ('/' === ch && !inClass) {
65
- i++;
66
- while(i < content.length && IDENTIFIER_CHAR.test(content[i]))i++;
67
- break;
48
+ };
49
+ const visit = (value)=>{
50
+ if (Array.isArray(value)) return void value.forEach(visit);
51
+ if (!isObjectRecord(value)) return;
52
+ if (isAstNode(value)) switch(value.type){
53
+ case 'ImportDeclaration':
54
+ case 'ExportAllDeclaration':
55
+ case 'ExportNamedDeclaration':
56
+ add(value.source);
57
+ break;
58
+ case 'TsImportType':
59
+ add(value.argument);
60
+ break;
61
+ case 'TsExternalModuleReference':
62
+ add(value.expression);
63
+ break;
64
+ case 'CallExpression':
65
+ add(getCallSpecifier(value));
66
+ break;
68
67
  }
69
- i++;
68
+ for (const [key, child] of Object.entries(value))if ('span' !== key) visit(child);
69
+ };
70
+ visit(program);
71
+ return literals.sort((left, right)=>left.span.start - right.span.start);
72
+ };
73
+ const mapUtf8ByteOffsets = (content, byteOffsets)=>{
74
+ const pending = new Set(byteOffsets);
75
+ const result = new Map();
76
+ let byteOffset = 0;
77
+ let stringOffset = 0;
78
+ while(stringOffset <= content.length && pending.size > 0){
79
+ if (pending.delete(byteOffset)) result.set(byteOffset, stringOffset);
80
+ if (stringOffset === content.length) break;
81
+ const codePoint = content.codePointAt(stringOffset);
82
+ if (void 0 === codePoint) break;
83
+ const character = String.fromCodePoint(codePoint);
84
+ byteOffset += Buffer.byteLength(character);
85
+ stringOffset += character.length;
70
86
  }
71
- return i;
87
+ if (pending.size > 0) throw new Error('SWC returned an import specifier span outside its source');
88
+ return result;
72
89
  };
73
90
  const scanSpecifiers = (content)=>{
74
- const specifiers = [];
75
- const length = content.length;
76
- const templateExpressionDepths = [];
77
- let prev1 = '';
78
- let prev2 = '';
79
- let prev3 = '';
80
- const pushToken = (token)=>{
81
- prev3 = prev2;
82
- prev2 = prev1;
83
- prev1 = token;
84
- };
85
- let mode = 'code';
86
- let i = 0;
87
- while(i < length){
88
- const ch = content[i];
89
- if ('template' === mode) {
90
- if ('\\' === ch) {
91
- i += 2;
92
- continue;
93
- }
94
- if ('`' === ch) {
95
- mode = 'code';
96
- pushToken('`');
97
- i++;
98
- continue;
99
- }
100
- if ('$' === ch && '{' === content[i + 1]) {
101
- templateExpressionDepths.push(0);
102
- mode = 'code';
103
- pushToken('{');
104
- i += 2;
105
- continue;
106
- }
107
- i++;
108
- continue;
109
- }
110
- if (WHITESPACE.test(ch)) {
111
- i++;
112
- continue;
113
- }
114
- if ('/' === ch) {
115
- const next = content[i + 1];
116
- if ('/' === next) {
117
- const newline = content.indexOf('\n', i + 2);
118
- i = -1 === newline ? length : newline + 1;
119
- continue;
120
- }
121
- if ('*' === next) {
122
- const end = content.indexOf('*/', i + 2);
123
- i = -1 === end ? length : end + 2;
124
- continue;
125
- }
126
- if (isRegexAllowed(prev1)) {
127
- i = skipRegexLiteral(content, i);
128
- pushToken('/regex/');
129
- continue;
130
- }
131
- pushToken('/');
132
- i++;
133
- continue;
134
- }
135
- if ('`' === ch) {
136
- mode = 'template';
137
- i++;
138
- continue;
139
- }
140
- if ('{' === ch) {
141
- if (templateExpressionDepths.length > 0) templateExpressionDepths[templateExpressionDepths.length - 1]++;
142
- pushToken('{');
143
- i++;
144
- continue;
145
- }
146
- if ('}' === ch) {
147
- if (templateExpressionDepths.length > 0) {
148
- const top = templateExpressionDepths.length - 1;
149
- if (0 === templateExpressionDepths[top]) {
150
- templateExpressionDepths.pop();
151
- mode = 'template';
152
- i++;
153
- continue;
154
- }
155
- templateExpressionDepths[top]--;
156
- }
157
- pushToken('}');
158
- i++;
159
- continue;
160
- }
161
- if ('"' === ch || "'" === ch) {
162
- const start = i;
163
- let j = i + 1;
164
- let terminated = false;
165
- while(j < length){
166
- const sch = content[j];
167
- if ('\\' === sch) {
168
- j += 2;
169
- continue;
170
- }
171
- if (sch === ch) {
172
- terminated = true;
173
- j++;
174
- break;
175
- }
176
- if ('\n' === sch) break;
177
- j++;
178
- }
179
- const callSpecifier = isCallSpecifierContext(prev1, prev2, prev3);
180
- const nextChar = callSpecifier ? nextNonTriviaChar(content, j) : '';
181
- const isCompleteCallSpecifier = !callSpecifier || ')' === nextChar || ',' === nextChar;
182
- if (terminated && isSpecifierContext(prev1, prev2, prev3) && isCompleteCallSpecifier) specifiers.push({
183
- start: start + 1,
184
- end: j - 1,
185
- value: content.slice(start + 1, j - 1)
186
- });
187
- pushToken('"string"');
188
- i = j;
189
- continue;
190
- }
191
- if (IDENTIFIER_CHAR.test(ch)) {
192
- let j = i + 1;
193
- while(j < length && IDENTIFIER_CHAR.test(content[j]))j++;
194
- pushToken(content.slice(i, j));
195
- i = j;
196
- continue;
197
- }
198
- pushToken(ch);
199
- i++;
200
- }
201
- return specifiers;
91
+ const literals = collectSpecifierLiterals(content);
92
+ const byteOffsets = literals.flatMap((literal)=>[
93
+ literal.span.start,
94
+ literal.span.end - 2
95
+ ]);
96
+ const stringOffsets = mapUtf8ByteOffsets(content, byteOffsets);
97
+ return literals.map((literal)=>{
98
+ const start = stringOffsets.get(literal.span.start);
99
+ const end = stringOffsets.get(literal.span.end - 2);
100
+ if (void 0 === start || void 0 === end) throw new Error('Could not locate an SWC import specifier in its source');
101
+ return {
102
+ start,
103
+ end,
104
+ value: content.slice(start, end)
105
+ };
106
+ });
202
107
  };
203
108
  const rewriteImportSpecifiers = (content, rewrite)=>{
204
109
  const specifiers = scanSpecifiers(content);
@@ -227,4 +132,4 @@ const rewriteImportSpecifiers = (content, rewrite)=>{
227
132
  changed: true
228
133
  };
229
134
  };
230
- export { rewriteImportSpecifiers };
135
+ export { assertNoNativeModuleOutputCollision, rewriteImportSpecifiers };
@@ -3,7 +3,7 @@ const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/
3
3
  import { fs, getAliasConfig, logger } from "@modern-js/utils";
4
4
  import { spawn } from "child_process";
5
5
  import path from "path";
6
- import { rewriteImportSpecifiers } from "./importRewriter.mjs";
6
+ import { assertNoNativeModuleOutputCollision, rewriteImportSpecifiers } from "./importRewriter.mjs";
7
7
  import { createTsconfigPathsMatcher, getNotAliasedPath } from "./tsconfigPathsPlugin.mjs";
8
8
  import { fileURLToPath as __rspack_fileURLToPath } from "node:url";
9
9
  import { dirname as __rspack_dirname } from "node:path";
@@ -13,12 +13,7 @@ const copyFiles = async (from, to, appDirectory)=>{
13
13
  const relativePath = path.relative(appDirectory, from);
14
14
  const targetDir = path.join(to, relativePath);
15
15
  await fs.copy(from, targetDir, {
16
- filter: (src)=>![
17
- '.ts',
18
- '.tsx',
19
- '.js',
20
- '.jsx'
21
- ].includes(path.extname(src)) && !src.endsWith('tsconfig.json')
16
+ filter: (src)=>!/\.(?:[cm]?ts|tsx|jsx?)$/u.test(src) && !src.endsWith('tsconfig.json')
22
17
  });
23
18
  }
24
19
  };
@@ -115,7 +110,7 @@ const runTsgo = (tsgoBinPath, args, options)=>new Promise((resolve, reject)=>{
115
110
  });
116
111
  const getTsgoBinEntry = (pkg)=>{
117
112
  if ('string' == typeof pkg.bin) return pkg.bin;
118
- return pkg.bin?.tsgo;
113
+ return pkg.bin?.tsc ?? pkg.bin?.tsgo;
119
114
  };
120
115
  const resolveTsgoBinPath = (pkgPath)=>{
121
116
  const pkgDir = path.dirname(pkgPath);
@@ -132,12 +127,19 @@ const getTsgoBinPath = (appDirectory, resolvePaths = [
132
127
  __rspack_import_meta_dirname__
133
128
  ])=>{
134
129
  try {
135
- const pkgPath = require.resolve("@typescript/native-preview/package.json", {
136
- paths: resolvePaths
137
- });
130
+ let pkgPath;
131
+ try {
132
+ pkgPath = require.resolve("@typescript/native/package.json", {
133
+ paths: resolvePaths
134
+ });
135
+ } catch {
136
+ pkgPath = require.resolve("@typescript/native-preview/package.json", {
137
+ paths: resolvePaths
138
+ });
139
+ }
138
140
  return resolveTsgoBinPath(pkgPath);
139
141
  } catch {
140
- throw new Error('tsgo could not be found! Please install "@typescript/native-preview" in your project to compile BFF/server code.');
142
+ throw new Error('tsgo could not be found! Please install "@typescript/native" (or legacy "@typescript/native-preview") in your project to compile BFF/server code.');
141
143
  }
142
144
  };
143
145
  const OUTPUT_SOURCE_EXTENSIONS = {
@@ -197,6 +199,7 @@ const rewriteOutputSpecifiers = async (appDirectory, distDir, baseUrl, paths, mo
197
199
  await Promise.all(files.map(async (file)=>{
198
200
  const sourceFile = getSourceFileForOutput(appDirectory, distDir, file);
199
201
  if (!sourceFile) return;
202
+ assertNoNativeModuleOutputCollision(sourceFile, fs.existsSync);
200
203
  const content = await fs.readFile(file, 'utf8');
201
204
  const { content: rewritten, changed } = rewriteImportSpecifiers(content, (specifier)=>getNotAliasedPath(sourceFile, matcher, specifier, moduleType));
202
205
  if (changed) await fs.writeFile(file, await dropStaleSourceMap(file, rewritten));
@@ -253,6 +256,7 @@ const compileByTs = async (appDirectory, config, compileOptions)=>{
253
256
  } else process.exit(1);
254
257
  }
255
258
  for (const source of sourceDirs)await copyFiles(source, distDir, appDirectory);
259
+ await rewriteOutputSpecifiers(appDirectory, distDir, absoluteBaseUrl, paths, compileOptions.moduleType);
256
260
  logger.info("TS-Go compile succeed");
257
261
  };
258
262
  export { compileByTs, createResolvedTsgoConfig, getTsgoBinPath, rewriteOutputSpecifiers };
@@ -11,18 +11,47 @@ const toImportSpecifier = (sourceFile, resolvedPath)=>{
11
11
  const relativePath = pathApi.relative(pathApi.dirname(sourceFile), resolvedPath).split(pathApi.sep).join(path.posix.sep);
12
12
  return '.' === relativePath[0] ? relativePath : `./${relativePath}`;
13
13
  };
14
- const COMPILED_TO_JS_EXTENSIONS = new Set([
15
- '.ts',
16
- '.tsx',
17
- '.js',
18
- '.jsx'
14
+ const SOURCE_TO_OUTPUT_EXTENSION = new Map([
15
+ [
16
+ '.ts',
17
+ '.js'
18
+ ],
19
+ [
20
+ '.tsx',
21
+ '.js'
22
+ ],
23
+ [
24
+ '.js',
25
+ '.js'
26
+ ],
27
+ [
28
+ '.jsx',
29
+ '.js'
30
+ ],
31
+ [
32
+ '.mts',
33
+ '.mjs'
34
+ ],
35
+ [
36
+ '.mjs',
37
+ '.mjs'
38
+ ],
39
+ [
40
+ '.cts',
41
+ '.cjs'
42
+ ],
43
+ [
44
+ '.cjs',
45
+ '.cjs'
46
+ ]
19
47
  ]);
20
48
  const toEsmOutputPath = (resolvedPath)=>{
21
49
  const sourcePath = (findSourceEntry(resolvedPath) || resolvedPath).replace(/\\/g, '/');
22
50
  const ext = path.extname(sourcePath);
23
51
  if (!ext) return `${sourcePath}.js`;
24
- if (!COMPILED_TO_JS_EXTENSIONS.has(ext)) return sourcePath;
25
- return `${sourcePath.slice(0, -ext.length)}.js`;
52
+ const outputExtension = SOURCE_TO_OUTPUT_EXTENSION.get(ext);
53
+ if (!outputExtension) return sourcePath;
54
+ return `${sourcePath.slice(0, -ext.length)}${outputExtension}`;
26
55
  };
27
56
  const resolveRelativeEsmSpecifier = (sourceFile, text)=>{
28
57
  if (!text.startsWith('./') && !text.startsWith('../')) return;
@@ -110,7 +139,7 @@ function getNotAliasedPath(sourceFile, matcher, text, moduleType) {
110
139
  if (packagePath) return text;
111
140
  } catch {}
112
141
  }
113
- if ('module' === moduleType) result = toEsmOutputPath(result);
142
+ if ('module' === moduleType || /\.[cm]ts$/iu.test(result)) result = toEsmOutputPath(result);
114
143
  return toImportSpecifier(sourceFile, result) || './';
115
144
  }
116
145
  export { createTsconfigPathsMatcher, getNotAliasedPath };
@@ -1,11 +1,6 @@
1
1
  type SpecifierRewrite = (specifier: string) => string | undefined;
2
- /**
3
- * Rewrite the import/export/require specifiers of emitted JavaScript.
4
- *
5
- * Specifiers whose contents contain escape sequences are left untouched (the
6
- * raw source slice would not round-trip), which never happens for the
7
- * compiler-emitted relative/alias specifiers this is used on.
8
- */
2
+ export declare const assertNoNativeModuleOutputCollision: (sourceFile: string, fileExists: (file: string) => boolean) => void;
3
+ /** Rewrite static module specifiers without touching runtime data or syntax. */
9
4
  export declare const rewriteImportSpecifiers: (content: string, rewrite: SpecifierRewrite) => {
10
5
  content: string;
11
6
  changed: boolean;
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "modern",
18
18
  "modern.js"
19
19
  ],
20
- "version": "3.8.3-ultramodern.1",
20
+ "version": "3.8.3-ultramodern.2",
21
21
  "types": "./dist/types/index.d.ts",
22
22
  "main": "./dist/cjs/index.js",
23
23
  "exports": {
@@ -38,11 +38,12 @@
38
38
  }
39
39
  },
40
40
  "dependencies": {
41
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.3-ultramodern.1",
41
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.3-ultramodern.2",
42
+ "@swc/core": "1.15.43",
42
43
  "@swc/helpers": "^0.5.23"
43
44
  },
44
45
  "devDependencies": {
45
- "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.8.3-ultramodern.1",
46
+ "@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.8.3-ultramodern.2",
46
47
  "@rslib/core": "0.23.2",
47
48
  "@scripts/rstest-config": "2.66.0",
48
49
  "@types/node": "^26.2.0",
@@ -62,7 +63,7 @@
62
63
  },
63
64
  "scripts": {
64
65
  "dev": "rslib build --watch",
65
- "build": "rslib build && pnpm -w tsgo:dts \"$PWD\"",
66
+ "build": "rslib build",
66
67
  "test": "rstest --passWithNoTests"
67
68
  }
68
69
  }