UNPKG

8.41 kBJavaScriptView Raw
1/* eslint-disable no-console */
2import commander from "commander";
3import globCb from "glob";
4import {exists, mkdir, readdir, readFile, stat, writeFile} from "mz/fs";
5import {dirname, join, relative} from "path";
6import {promisify} from "util";
7
8import { transform} from "./index";
9
10
11
12
13
14
15
16
17
18
19
20const glob = promisify(globCb);
21
22export default function run() {
23 commander
24 .description(`Sucrase: super-fast Babel alternative.`)
25 .usage("[options] <srcDir>")
26 .option(
27 "-d, --out-dir <out>",
28 "Compile an input directory of modules into an output directory.",
29 )
30 .option(
31 "-p, --project <dir>",
32 "Compile a TypeScript project, will read from tsconfig.json in <dir>",
33 )
34 .option("--out-extension <extension>", "File extension to use for all output files.", "js")
35 .option("--exclude-dirs <paths>", "Names of directories that should not be traversed.")
36 .option("-t, --transforms <transforms>", "Comma-separated list of transforms to run.")
37 .option("-q, --quiet", "Don't print the names of converted files.")
38 .option(
39 "--enable-legacy-typescript-module-interop",
40 "Use default TypeScript ESM/CJS interop strategy.",
41 )
42 .option("--enable-legacy-babel5-module-interop", "Use Babel 5 ESM/CJS interop strategy.")
43 .option("--jsx-pragma <string>", "Element creation function, defaults to `React.createElement`")
44 .option("--jsx-fragment-pragma <string>", "Fragment component, defaults to `React.Fragment`")
45 .option("--production", "Disable debugging information from JSX in output.")
46 .parse(process.argv);
47
48 if (commander.project) {
49 if (
50 commander.outDir ||
51 commander.transforms ||
52 commander.args[0] ||
53 commander.enableLegacyTypescriptModuleInterop
54 ) {
55 console.error(
56 "If TypeScript project is specified, out directory, transforms, source " +
57 "directory, and --enable-legacy-typescript-module-interop may not be specified.",
58 );
59 process.exit(1);
60 }
61 } else {
62 if (!commander.outDir) {
63 console.error("Out directory is required");
64 process.exit(1);
65 }
66
67 if (!commander.transforms) {
68 console.error("Transforms option is required.");
69 process.exit(1);
70 }
71
72 if (!commander.args[0]) {
73 console.error("Source directory is required.");
74 process.exit(1);
75 }
76 }
77
78 const options = {
79 outDirPath: commander.outDir,
80 srcDirPath: commander.args[0],
81 project: commander.project,
82 outExtension: commander.outExtension,
83 excludeDirs: commander.excludeDirs ? commander.excludeDirs.split(",") : [],
84 quiet: commander.quiet,
85 sucraseOptions: {
86 transforms: commander.transforms ? commander.transforms.split(",") : [],
87 enableLegacyTypeScriptModuleInterop: commander.enableLegacyTypescriptModuleInterop,
88 enableLegacyBabel5ModuleInterop: commander.enableLegacyBabel5ModuleInterop,
89 jsxPragma: commander.jsxPragma || "React.createElement",
90 jsxFragmentPragma: commander.jsxFragmentPragma || "React.Fragment",
91 production: commander.production,
92 },
93 };
94
95 buildDirectory(options).catch((e) => {
96 process.exitCode = 1;
97 console.error(e);
98 });
99}
100
101
102
103
104
105
106async function findFiles(options) {
107 const outDirPath = options.outDirPath;
108 const srcDirPath = options.srcDirPath;
109
110 const extensions = options.sucraseOptions.transforms.includes("typescript")
111 ? [".ts", ".tsx"]
112 : [".js", ".jsx"];
113
114 if (!(await exists(outDirPath))) {
115 await mkdir(outDirPath);
116 }
117
118 const outArr = [];
119 for (const child of await readdir(srcDirPath)) {
120 if (["node_modules", ".git"].includes(child) || options.excludeDirs.includes(child)) {
121 continue;
122 }
123 const srcChildPath = join(srcDirPath, child);
124 const outChildPath = join(outDirPath, child);
125 if ((await stat(srcChildPath)).isDirectory()) {
126 const innerOptions = {...options};
127 innerOptions.srcDirPath = srcChildPath;
128 innerOptions.outDirPath = outChildPath;
129 const innerFiles = await findFiles(innerOptions);
130 outArr.push(...innerFiles);
131 } else if (extensions.some((ext) => srcChildPath.endsWith(ext))) {
132 const outPath = outChildPath.replace(/\.\w+$/, `.${options.outExtension}`);
133 outArr.push({
134 srcPath: srcChildPath,
135 outPath,
136 });
137 }
138 }
139
140 return outArr;
141}
142
143async function runGlob(options) {
144 const tsConfigPath = join(options.project, "tsconfig.json");
145
146 let str;
147 try {
148 str = await readFile(tsConfigPath, "utf8");
149 } catch (err) {
150 console.error("Could not find project tsconfig.json");
151 console.error(` --project=${options.project}`);
152 console.error(err);
153 process.exit(1);
154 }
155 const json = JSON.parse(str);
156
157 const foundFiles = [];
158
159 const files = json.files;
160 const include = json.include;
161
162 const absProject = join(process.cwd(), options.project);
163 const outDirs = [];
164
165 if (!(await exists(options.outDirPath))) {
166 await mkdir(options.outDirPath);
167 }
168
169 if (files) {
170 for (const file of files) {
171 if (file.endsWith(".d.ts")) {
172 continue;
173 }
174 if (!file.endsWith(".ts") && !file.endsWith(".js")) {
175 continue;
176 }
177
178 const srcFile = join(absProject, file);
179 const outFile = join(options.outDirPath, file);
180 const outPath = outFile.replace(/\.\w+$/, `.${options.outExtension}`);
181
182 const outDir = dirname(outPath);
183 if (!outDirs.includes(outDir)) {
184 outDirs.push(outDir);
185 }
186
187 foundFiles.push({
188 srcPath: srcFile,
189 outPath,
190 });
191 }
192 }
193 if (include) {
194 for (const pattern of include) {
195 const globFiles = await glob(join(absProject, pattern));
196 for (const file of globFiles) {
197 if (!file.endsWith(".ts") && !file.endsWith(".js")) {
198 continue;
199 }
200 if (file.endsWith(".d.ts")) {
201 continue;
202 }
203
204 const relativeFile = relative(absProject, file);
205 const outFile = join(options.outDirPath, relativeFile);
206 const outPath = outFile.replace(/\.\w+$/, `.${options.outExtension}`);
207
208 const outDir = dirname(outPath);
209 if (!outDirs.includes(outDir)) {
210 outDirs.push(outDir);
211 }
212
213 foundFiles.push({
214 srcPath: file,
215 outPath,
216 });
217 }
218 }
219 }
220
221 for (const outDirPath of outDirs) {
222 if (!(await exists(outDirPath))) {
223 await mkdir(outDirPath);
224 }
225 }
226
227 // TODO: read exclude
228
229 return foundFiles;
230}
231
232async function updateOptionsFromProject(options) {
233 /**
234 * Read the project information and assign the following.
235 * - outDirPath
236 * - transform: imports
237 * - transform: typescript
238 * - enableLegacyTypescriptModuleInterop: true/false.
239 */
240
241 const tsConfigPath = join(options.project, "tsconfig.json");
242
243 let str;
244 try {
245 str = await readFile(tsConfigPath, "utf8");
246 } catch (err) {
247 console.error("Could not find project tsconfig.json");
248 console.error(` --project=${options.project}`);
249 console.error(err);
250 process.exit(1);
251 }
252 const json = JSON.parse(str);
253 const sucraseOpts = options.sucraseOptions;
254 if (!sucraseOpts.transforms.includes("typescript")) {
255 sucraseOpts.transforms.push("typescript");
256 }
257
258 const compilerOpts = json.compilerOptions;
259 if (compilerOpts.outDir) {
260 options.outDirPath = join(process.cwd(), options.project, compilerOpts.outDir);
261 }
262 if (compilerOpts.esModuleInterop !== true) {
263 sucraseOpts.enableLegacyTypeScriptModuleInterop = true;
264 }
265 if (compilerOpts.module === "commonjs") {
266 if (!sucraseOpts.transforms.includes("imports")) {
267 sucraseOpts.transforms.push("imports");
268 }
269 }
270}
271
272async function buildDirectory(options) {
273 let files;
274 if (options.outDirPath && options.srcDirPath) {
275 files = await findFiles(options);
276 } else if (options.project) {
277 await updateOptionsFromProject(options);
278 files = await runGlob(options);
279 } else {
280 console.error("Project or Source directory required.");
281 process.exit(1);
282 }
283
284 for (const file of files) {
285 await buildFile(file.srcPath, file.outPath, options);
286 }
287}
288
289async function buildFile(srcPath, outPath, options) {
290 if (!options.quiet) {
291 console.log(`${srcPath} -> ${outPath}`);
292 }
293 const code = (await readFile(srcPath)).toString();
294 const transformedCode = transform(code, {...options.sucraseOptions, filePath: srcPath}).code;
295 await writeFile(outPath, transformedCode);
296}