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