@bleedingdev/modern-js-server-utils 3.2.0-ultramodern.98 → 3.4.0-ultramodern.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.
@@ -0,0 +1,204 @@
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);
24
+ };
25
+ const isSpecifierContext = (prev1, prev2, prev3)=>{
26
+ if (('from' === prev1 || 'import' === prev1) && '.' !== prev2) return true;
27
+ return '(' === prev1 && ('import' === prev2 || 'require' === prev2) && '.' !== prev3;
28
+ };
29
+ const skipRegexLiteral = (content, start)=>{
30
+ let i = start + 1;
31
+ let inClass = false;
32
+ while(i < content.length){
33
+ const ch = content[i];
34
+ if ('\\' === ch) {
35
+ i += 2;
36
+ continue;
37
+ }
38
+ if ('\n' === ch) break;
39
+ if ('[' === ch) inClass = true;
40
+ else if (']' === ch) inClass = false;
41
+ else if ('/' === ch && !inClass) {
42
+ i++;
43
+ while(i < content.length && IDENTIFIER_CHAR.test(content[i]))i++;
44
+ break;
45
+ }
46
+ i++;
47
+ }
48
+ return i;
49
+ };
50
+ const scanSpecifiers = (content)=>{
51
+ const specifiers = [];
52
+ const length = content.length;
53
+ const templateExpressionDepths = [];
54
+ let prev1 = '';
55
+ let prev2 = '';
56
+ let prev3 = '';
57
+ const pushToken = (token)=>{
58
+ prev3 = prev2;
59
+ prev2 = prev1;
60
+ prev1 = token;
61
+ };
62
+ let mode = 'code';
63
+ let i = 0;
64
+ while(i < length){
65
+ const ch = content[i];
66
+ if ('template' === mode) {
67
+ if ('\\' === ch) {
68
+ i += 2;
69
+ continue;
70
+ }
71
+ if ('`' === ch) {
72
+ mode = 'code';
73
+ pushToken('`');
74
+ i++;
75
+ continue;
76
+ }
77
+ if ('$' === ch && '{' === content[i + 1]) {
78
+ templateExpressionDepths.push(0);
79
+ mode = 'code';
80
+ pushToken('{');
81
+ i += 2;
82
+ continue;
83
+ }
84
+ i++;
85
+ continue;
86
+ }
87
+ if (WHITESPACE.test(ch)) {
88
+ i++;
89
+ continue;
90
+ }
91
+ if ('/' === ch) {
92
+ const next = content[i + 1];
93
+ if ('/' === next) {
94
+ const newline = content.indexOf('\n', i + 2);
95
+ i = -1 === newline ? length : newline + 1;
96
+ continue;
97
+ }
98
+ if ('*' === next) {
99
+ const end = content.indexOf('*/', i + 2);
100
+ i = -1 === end ? length : end + 2;
101
+ continue;
102
+ }
103
+ if (isRegexAllowed(prev1)) {
104
+ i = skipRegexLiteral(content, i);
105
+ pushToken('/regex/');
106
+ continue;
107
+ }
108
+ pushToken('/');
109
+ i++;
110
+ continue;
111
+ }
112
+ if ('`' === ch) {
113
+ mode = 'template';
114
+ i++;
115
+ continue;
116
+ }
117
+ if ('{' === ch) {
118
+ if (templateExpressionDepths.length > 0) templateExpressionDepths[templateExpressionDepths.length - 1]++;
119
+ pushToken('{');
120
+ i++;
121
+ continue;
122
+ }
123
+ if ('}' === ch) {
124
+ if (templateExpressionDepths.length > 0) {
125
+ const top = templateExpressionDepths.length - 1;
126
+ if (0 === templateExpressionDepths[top]) {
127
+ templateExpressionDepths.pop();
128
+ mode = 'template';
129
+ i++;
130
+ continue;
131
+ }
132
+ templateExpressionDepths[top]--;
133
+ }
134
+ pushToken('}');
135
+ i++;
136
+ continue;
137
+ }
138
+ if ('"' === ch || "'" === ch) {
139
+ const start = i;
140
+ let j = i + 1;
141
+ let terminated = false;
142
+ while(j < length){
143
+ const sch = content[j];
144
+ if ('\\' === sch) {
145
+ j += 2;
146
+ continue;
147
+ }
148
+ if (sch === ch) {
149
+ terminated = true;
150
+ j++;
151
+ break;
152
+ }
153
+ if ('\n' === sch) break;
154
+ j++;
155
+ }
156
+ if (terminated && isSpecifierContext(prev1, prev2, prev3)) specifiers.push({
157
+ start: start + 1,
158
+ end: j - 1,
159
+ value: content.slice(start + 1, j - 1)
160
+ });
161
+ pushToken('"string"');
162
+ i = j;
163
+ continue;
164
+ }
165
+ if (IDENTIFIER_CHAR.test(ch)) {
166
+ let j = i + 1;
167
+ while(j < length && IDENTIFIER_CHAR.test(content[j]))j++;
168
+ pushToken(content.slice(i, j));
169
+ i = j;
170
+ continue;
171
+ }
172
+ pushToken(ch);
173
+ i++;
174
+ }
175
+ return specifiers;
176
+ };
177
+ const rewriteImportSpecifiers = (content, rewrite)=>{
178
+ const specifiers = scanSpecifiers(content);
179
+ if (0 === specifiers.length) return {
180
+ content,
181
+ changed: false
182
+ };
183
+ let result = '';
184
+ let cursor = 0;
185
+ let changed = false;
186
+ for (const specifier of specifiers){
187
+ if (specifier.value.includes('\\')) continue;
188
+ const next = rewrite(specifier.value);
189
+ if (void 0 !== next && next !== specifier.value) {
190
+ changed = true;
191
+ result += content.slice(cursor, specifier.start) + next;
192
+ cursor = specifier.end;
193
+ }
194
+ }
195
+ if (!changed) return {
196
+ content,
197
+ changed: false
198
+ };
199
+ return {
200
+ content: result + content.slice(cursor),
201
+ changed: true
202
+ };
203
+ };
204
+ export { rewriteImportSpecifiers };
@@ -1,8 +1,8 @@
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
5
  import { createTsconfigPathsMatcher, getNotAliasedPath } from "./tsconfigPathsPlugin.mjs";
5
- const importSpecifierRE = /((?:from\s*|import\s*\(\s*|require\s*\(\s*)['"])([^'"]+)(['"])/g;
6
6
  const copyFiles = async (from, to, appDirectory)=>{
7
7
  if (await fs.pathExists(from)) {
8
8
  const relativePath = path.relative(appDirectory, from);
@@ -15,42 +15,55 @@ const copyFiles = async (from, to, appDirectory)=>{
15
15
  });
16
16
  }
17
17
  };
18
- const createResolvedTsgoConfig = async (appDirectory, tsconfigPath, distDir, sourceDirs)=>{
19
- const output = await runTsgo([
18
+ let resolvedConfigCount = 0;
19
+ const createResolvedTsgoConfig = async (appDirectory, tsconfigPath, distDir, sourceDirs, moduleType, tsgoBinPath)=>{
20
+ const tsconfigDir = path.dirname(tsconfigPath);
21
+ const output = await runTsgo(tsgoBinPath, [
20
22
  '--showConfig',
21
23
  '-p',
22
24
  tsconfigPath
23
25
  ], {
24
- cwd: path.dirname(tsconfigPath)
26
+ cwd: tsconfigDir
25
27
  });
26
28
  const config = JSON.parse(output.stdout);
27
29
  config.compilerOptions ??= {};
28
30
  config.compilerOptions.rootDir = appDirectory;
29
31
  config.compilerOptions.outDir = distDir;
30
- config.files = filterSourceFiles(appDirectory, sourceDirs, config.files);
32
+ config.files = filterSourceFiles(tsconfigDir, sourceDirs, config.files);
31
33
  delete config.include;
32
34
  delete config.compilerOptions.baseUrl;
33
35
  if ([
34
36
  'node',
35
37
  'node10'
36
38
  ].includes(String(config.compilerOptions.moduleResolution).toLowerCase())) delete config.compilerOptions.moduleResolution;
37
- const resolvedConfigPath = path.join(appDirectory, `.tsgo.${process.pid}.resolved.json`);
39
+ if ('module' !== moduleType && [
40
+ 'preserve',
41
+ 'esnext',
42
+ 'es2015',
43
+ 'es2020',
44
+ 'es2022',
45
+ 'es6'
46
+ ].includes(String(config.compilerOptions.module).toLowerCase())) {
47
+ config.compilerOptions.module = 'commonjs';
48
+ delete config.compilerOptions.moduleResolution;
49
+ }
50
+ const resolvedConfigPath = path.join(tsconfigDir, `.tsgo.${process.pid}.${resolvedConfigCount++}.resolved.json`);
38
51
  await fs.writeFile(resolvedConfigPath, JSON.stringify(config, null, 2));
39
52
  return {
40
53
  config,
41
54
  resolvedConfigPath
42
55
  };
43
56
  };
44
- const filterSourceFiles = (appDirectory, sourceDirs, files = [])=>{
57
+ const filterSourceFiles = (tsconfigDir, sourceDirs, files = [])=>{
45
58
  const sourcePosixPaths = sourceDirs.map((sourceDir)=>sourceDir.split(path.sep).join(path.posix.sep));
46
59
  return files.filter((fileName)=>{
47
- const absoluteFileName = path.resolve(appDirectory, fileName).split(path.sep).join(path.posix.sep);
60
+ const absoluteFileName = path.resolve(tsconfigDir, fileName).split(path.sep).join(path.posix.sep);
48
61
  return fileName.endsWith('.d.ts') || sourcePosixPaths.some((sourceDir)=>absoluteFileName.includes(sourceDir));
49
62
  });
50
63
  };
51
- const runTsgo = (args, options)=>new Promise((resolve, reject)=>{
64
+ const runTsgo = (tsgoBinPath, args, options)=>new Promise((resolve, reject)=>{
52
65
  const child = spawn(process.execPath, [
53
- getTsgoBinPath(),
66
+ tsgoBinPath,
54
67
  ...args
55
68
  ], {
56
69
  cwd: options.cwd,
@@ -79,14 +92,51 @@ const runTsgo = (args, options)=>new Promise((resolve, reject)=>{
79
92
  resolve(result);
80
93
  });
81
94
  });
82
- const getTsgoBinPath = ()=>path.join(path.dirname(require.resolve("@typescript/native-preview/package.json")), 'bin/tsgo.js');
95
+ const getTsgoBinPath = (appDirectory, resolvePaths = [
96
+ appDirectory,
97
+ __dirname
98
+ ])=>{
99
+ try {
100
+ const pkgPath = require.resolve("@typescript/native-preview/package.json", {
101
+ paths: resolvePaths
102
+ });
103
+ return path.join(path.dirname(pkgPath), 'bin/tsgo.js');
104
+ } catch {
105
+ throw new Error('tsgo could not be found! Please install "@typescript/native-preview" in your project to compile BFF/server code.');
106
+ }
107
+ };
108
+ const OUTPUT_SOURCE_EXTENSIONS = {
109
+ '.js': [
110
+ '.ts',
111
+ '.tsx',
112
+ '.js',
113
+ '.jsx'
114
+ ],
115
+ '.mjs': [
116
+ '.mts',
117
+ '.mjs'
118
+ ],
119
+ '.cjs': [
120
+ '.cts',
121
+ '.cjs'
122
+ ]
123
+ };
83
124
  const getSourceFileForOutput = (appDirectory, distDir, outputFile)=>{
84
125
  const relativeOutput = path.relative(distDir, outputFile);
85
126
  const parsed = path.parse(relativeOutput);
86
127
  const sourceBase = path.join(appDirectory, parsed.dir, parsed.name);
87
- return findExistingSource(`${sourceBase}.ts`) || findExistingSource(`${sourceBase}.tsx`) || findExistingSource(`${sourceBase}.js`) || findExistingSource(`${sourceBase}.jsx`);
128
+ const extensions = OUTPUT_SOURCE_EXTENSIONS[parsed.ext] ?? OUTPUT_SOURCE_EXTENSIONS['.js'];
129
+ for (const extension of extensions){
130
+ const candidate = `${sourceBase}${extension}`;
131
+ if (fs.existsSync(candidate)) return candidate;
132
+ }
133
+ };
134
+ const sourceMappingUrlRE = /^\/\/[#@] sourceMappingURL=.*$/gm;
135
+ const dropStaleSourceMap = async (outputFile, content)=>{
136
+ const mapFile = `${outputFile}.map`;
137
+ if (await fs.pathExists(mapFile)) await fs.remove(mapFile);
138
+ return content.replace(sourceMappingUrlRE, '');
88
139
  };
89
- const findExistingSource = (filePath)=>fs.existsSync(filePath) ? filePath : void 0;
90
140
  const rewriteOutputSpecifiers = async (appDirectory, distDir, baseUrl, paths, moduleType)=>{
91
141
  if (0 === Object.keys(paths).length || !await fs.pathExists(distDir)) return;
92
142
  const matcher = createTsconfigPathsMatcher(baseUrl, paths);
@@ -96,14 +146,8 @@ const rewriteOutputSpecifiers = async (appDirectory, distDir, baseUrl, paths, mo
96
146
  const sourceFile = getSourceFileForOutput(appDirectory, distDir, file);
97
147
  if (!sourceFile) return;
98
148
  const content = await fs.readFile(file, 'utf8');
99
- let changed = false;
100
- const rewritten = content.replace(importSpecifierRE, (match, prefix, specifier, suffix)=>{
101
- const nextSpecifier = getNotAliasedPath(sourceFile, matcher, specifier, moduleType);
102
- if (!nextSpecifier || nextSpecifier === specifier) return match;
103
- changed = true;
104
- return `${prefix}${nextSpecifier}${suffix}`;
105
- });
106
- if (changed) await fs.writeFile(file, rewritten);
149
+ const { content: rewritten, changed } = rewriteImportSpecifiers(content, (specifier)=>getNotAliasedPath(sourceFile, matcher, specifier, moduleType));
150
+ if (changed) await fs.writeFile(file, await dropStaleSourceMap(file, rewritten));
107
151
  }));
108
152
  };
109
153
  const collectOutputFiles = async (dir)=>{
@@ -129,24 +173,34 @@ const compileByTs = async (appDirectory, config, compileOptions)=>{
129
173
  tsconfigPath
130
174
  });
131
175
  const { paths = {}, absoluteBaseUrl = './' } = aliasOption;
132
- const { config: tsgoConfig, resolvedConfigPath } = await createResolvedTsgoConfig(appDirectory, tsconfigPath, distDir, sourceDirs);
133
- const result = await runTsgo([
134
- '-p',
135
- resolvedConfigPath
136
- ], {
137
- cwd: appDirectory,
138
- reject: false
139
- });
140
- await fs.remove(resolvedConfigPath);
176
+ const tsgoBinPath = getTsgoBinPath(appDirectory);
177
+ const { config: tsgoConfig, resolvedConfigPath } = await createResolvedTsgoConfig(appDirectory, tsconfigPath, distDir, sourceDirs, compileOptions.moduleType, tsgoBinPath);
178
+ let result;
179
+ try {
180
+ result = await runTsgo(tsgoBinPath, [
181
+ '-p',
182
+ resolvedConfigPath
183
+ ], {
184
+ cwd: appDirectory,
185
+ reject: false
186
+ });
187
+ } finally{
188
+ await fs.remove(resolvedConfigPath);
189
+ }
141
190
  if (result.stderr) logger.error(result.stderr);
142
191
  if (result.stdout) logger.info(result.stdout);
143
192
  if (0 !== result.code) {
144
193
  const noEmitOnError = tsgoConfig.compilerOptions?.noEmitOnError;
145
- if (void 0 === noEmitOnError || true === noEmitOnError) if (compileOptions.throwErrorInsteadOfExit) logger.error('TS-Go compilation failed');
146
- else process.exit(1);
194
+ if (void 0 === noEmitOnError || true === noEmitOnError) if (compileOptions.throwErrorInsteadOfExit) {
195
+ logger.error('TS-Go compilation failed');
196
+ throw new Error([
197
+ `TS-Go compilation failed with exit code ${result.code}.`,
198
+ result.stderr.trim() || result.stdout.trim()
199
+ ].filter(Boolean).join('\n'));
200
+ } else process.exit(1);
147
201
  }
148
202
  await rewriteOutputSpecifiers(appDirectory, distDir, absoluteBaseUrl, paths, compileOptions.moduleType);
149
203
  for (const source of sourceDirs)await copyFiles(source, distDir, appDirectory);
150
204
  logger.info("TS-Go compile succeed");
151
205
  };
152
- export { compileByTs };
206
+ export { compileByTs, createResolvedTsgoConfig, getTsgoBinPath, rewriteOutputSpecifiers };
@@ -0,0 +1,205 @@
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);
25
+ };
26
+ const isSpecifierContext = (prev1, prev2, prev3)=>{
27
+ if (('from' === prev1 || 'import' === prev1) && '.' !== prev2) return true;
28
+ return '(' === prev1 && ('import' === prev2 || 'require' === prev2) && '.' !== prev3;
29
+ };
30
+ const skipRegexLiteral = (content, start)=>{
31
+ let i = start + 1;
32
+ let inClass = false;
33
+ while(i < content.length){
34
+ const ch = content[i];
35
+ if ('\\' === ch) {
36
+ i += 2;
37
+ continue;
38
+ }
39
+ if ('\n' === ch) break;
40
+ if ('[' === ch) inClass = true;
41
+ else if (']' === ch) inClass = false;
42
+ else if ('/' === ch && !inClass) {
43
+ i++;
44
+ while(i < content.length && IDENTIFIER_CHAR.test(content[i]))i++;
45
+ break;
46
+ }
47
+ i++;
48
+ }
49
+ return i;
50
+ };
51
+ const scanSpecifiers = (content)=>{
52
+ const specifiers = [];
53
+ const length = content.length;
54
+ const templateExpressionDepths = [];
55
+ let prev1 = '';
56
+ let prev2 = '';
57
+ let prev3 = '';
58
+ const pushToken = (token)=>{
59
+ prev3 = prev2;
60
+ prev2 = prev1;
61
+ prev1 = token;
62
+ };
63
+ let mode = 'code';
64
+ let i = 0;
65
+ while(i < length){
66
+ const ch = content[i];
67
+ if ('template' === mode) {
68
+ if ('\\' === ch) {
69
+ i += 2;
70
+ continue;
71
+ }
72
+ if ('`' === ch) {
73
+ mode = 'code';
74
+ pushToken('`');
75
+ i++;
76
+ continue;
77
+ }
78
+ if ('$' === ch && '{' === content[i + 1]) {
79
+ templateExpressionDepths.push(0);
80
+ mode = 'code';
81
+ pushToken('{');
82
+ i += 2;
83
+ continue;
84
+ }
85
+ i++;
86
+ continue;
87
+ }
88
+ if (WHITESPACE.test(ch)) {
89
+ i++;
90
+ continue;
91
+ }
92
+ if ('/' === ch) {
93
+ const next = content[i + 1];
94
+ if ('/' === next) {
95
+ const newline = content.indexOf('\n', i + 2);
96
+ i = -1 === newline ? length : newline + 1;
97
+ continue;
98
+ }
99
+ if ('*' === next) {
100
+ const end = content.indexOf('*/', i + 2);
101
+ i = -1 === end ? length : end + 2;
102
+ continue;
103
+ }
104
+ if (isRegexAllowed(prev1)) {
105
+ i = skipRegexLiteral(content, i);
106
+ pushToken('/regex/');
107
+ continue;
108
+ }
109
+ pushToken('/');
110
+ i++;
111
+ continue;
112
+ }
113
+ if ('`' === ch) {
114
+ mode = 'template';
115
+ i++;
116
+ continue;
117
+ }
118
+ if ('{' === ch) {
119
+ if (templateExpressionDepths.length > 0) templateExpressionDepths[templateExpressionDepths.length - 1]++;
120
+ pushToken('{');
121
+ i++;
122
+ continue;
123
+ }
124
+ if ('}' === ch) {
125
+ if (templateExpressionDepths.length > 0) {
126
+ const top = templateExpressionDepths.length - 1;
127
+ if (0 === templateExpressionDepths[top]) {
128
+ templateExpressionDepths.pop();
129
+ mode = 'template';
130
+ i++;
131
+ continue;
132
+ }
133
+ templateExpressionDepths[top]--;
134
+ }
135
+ pushToken('}');
136
+ i++;
137
+ continue;
138
+ }
139
+ if ('"' === ch || "'" === ch) {
140
+ const start = i;
141
+ let j = i + 1;
142
+ let terminated = false;
143
+ while(j < length){
144
+ const sch = content[j];
145
+ if ('\\' === sch) {
146
+ j += 2;
147
+ continue;
148
+ }
149
+ if (sch === ch) {
150
+ terminated = true;
151
+ j++;
152
+ break;
153
+ }
154
+ if ('\n' === sch) break;
155
+ j++;
156
+ }
157
+ if (terminated && isSpecifierContext(prev1, prev2, prev3)) specifiers.push({
158
+ start: start + 1,
159
+ end: j - 1,
160
+ value: content.slice(start + 1, j - 1)
161
+ });
162
+ pushToken('"string"');
163
+ i = j;
164
+ continue;
165
+ }
166
+ if (IDENTIFIER_CHAR.test(ch)) {
167
+ let j = i + 1;
168
+ while(j < length && IDENTIFIER_CHAR.test(content[j]))j++;
169
+ pushToken(content.slice(i, j));
170
+ i = j;
171
+ continue;
172
+ }
173
+ pushToken(ch);
174
+ i++;
175
+ }
176
+ return specifiers;
177
+ };
178
+ const rewriteImportSpecifiers = (content, rewrite)=>{
179
+ const specifiers = scanSpecifiers(content);
180
+ if (0 === specifiers.length) return {
181
+ content,
182
+ changed: false
183
+ };
184
+ let result = '';
185
+ let cursor = 0;
186
+ let changed = false;
187
+ for (const specifier of specifiers){
188
+ if (specifier.value.includes('\\')) continue;
189
+ const next = rewrite(specifier.value);
190
+ if (void 0 !== next && next !== specifier.value) {
191
+ changed = true;
192
+ result += content.slice(cursor, specifier.start) + next;
193
+ cursor = specifier.end;
194
+ }
195
+ }
196
+ if (!changed) return {
197
+ content,
198
+ changed: false
199
+ };
200
+ return {
201
+ content: result + content.slice(cursor),
202
+ changed: true
203
+ };
204
+ };
205
+ export { rewriteImportSpecifiers };