UNPKG

12.2 kBJavaScriptView Raw
1"use strict";
2var fs = require("fs");
3var path = require("path");
4var commandLineArgs = require("command-line-args");
5var commandLineUsage = require("command-line-usage/lib/command-line-usage");
6var semver = require("semver");
7var json5 = require("json5");
8var fableLib = require("./lib");
9var constants = require("./constants");
10var customPlugins = require("./babelPlugins");
11var bundle_1 = require("./bundle");
12// Don't use default values as they would block options from fableconfig.json
13var optionDefinitions = [
14 { name: 'projFile', defaultOption: true, multiple: true, description: "The F# project (.fsproj) or script (.fsx) to compile." },
15 { name: 'outDir', alias: 'o', description: "Where to put compiled JS files. Defaults to project directory." },
16 { name: 'module', alias: 'm', description: "Specify module code generation: `commonjs`, `umd`, `amd` or `es2015` (default)." },
17 { name: 'sourceMaps', alias: 's', description: "Generate source maps: `false` (default), `true` or `inline`." },
18 { name: 'watch', alias: 'w', multiple: 'true', description: "Recompile project much faster on file modifications." },
19 { name: 'ecma', description: "Specify ECMAScript target version: `es5` (default) or `es2015`." },
20 { name: 'verbose', type: Boolean, description: "Print more information about the compilation process." },
21 { name: 'symbols', multiple: true, description: "F# symbols for conditional compilation, like `DEBUG`." },
22 { name: 'dll', type: Boolean, description: "Generate a `.dll` assembly when creating libraries." },
23 { name: 'rollup', description: "Bundle files and dependencies with Rollup." },
24 { name: 'includeJs', type: Boolean, description: "Compile with Babel and copy to `outDir` relative imports (starting with '.')." },
25 { name: 'plugins', multiple: true, description: "Paths to Fable plugins." },
26 { name: 'babelPlugins', multiple: true, description: "Additional Babel plugins (without `babel-plugin-` prefix). Must be installed in the project directory." },
27 { name: 'refs', multiple: true, description: "Alternative location for compiled JS files of referenced libraries." },
28 { name: 'coreLib', description: "Shortcut for `--refs Fable.Core={VALUE}`." },
29 { name: 'loose', type: Boolean, description: "Enable loose transformations for babel-preset-es2015 plugins." },
30 { name: 'babelrc', type: Boolean, description: "Use a `.babelrc` file for Babel configuration (invalidates other Babel related options)." },
31 { name: 'clamp', type: Boolean, description: "Compile unsigned byte arrays as Uint8ClampedArray." },
32 { name: 'noTypedArrays', type: Boolean, description: "Don't compile numeric arrays as JS typed arrays." },
33 { name: 'target', alias: 't', description: "Use options from a specific target in `fableconfig.json`." },
34 { name: 'debug', alias: 'd', description: "Shortcut for `--target debug`." },
35 { name: 'production', alias: 'p', description: "Shortcut for `--target production`." },
36 { name: 'declaration', type: Boolean, description: "[EXPERIMENTAL] Generates corresponding ‘.d.ts’ file." },
37 { name: 'extra', multiple: true, description: "Custom options for plugins in `{KEY}={VALUE}` format." },
38 { name: 'help', alias: 'h', description: "Display usage guide." }
39];
40function getAppDescription() {
41 return [{ header: 'Fable ' + constants.PKG_VERSION, content: 'F# to JavaScript compiler' },
42 { header: 'Options', optionList: optionDefinitions },
43 { content: 'All arguments can be defined in a fableconfig.json file' }];
44}
45function resolvePath(optName, value, workingDir) {
46 function resolve(x) {
47 return fableLib.pathJoin(workingDir, x);
48 }
49 // Discard null values or empty strings
50 if (value) {
51 switch (optName) {
52 case "outDir":
53 return resolve(value);
54 // Multiple values
55 case "projFile":
56 case "plugins":
57 case "babelPlugins":
58 return value.map(resolve);
59 // Only resolve refs if they starts with '.'
60 case "refs":
61 var o = {};
62 for (var k in value) {
63 o[k] = value[k].startsWith('.') ? resolve(value[k]) : value[k];
64 }
65 return o;
66 }
67 }
68 return value;
69}
70/** Reads options from command line, requires command-line-args */
71function readCommandLineOptions() {
72 function resolveKeyValuePairs(kvs) {
73 var o = {};
74 for (var i = 0; i < kvs.length; i++) {
75 var kv = kvs[i].split("=");
76 o[kv[0]] = kv[1] || true;
77 }
78 return o;
79 }
80 var opts = commandLineArgs(optionDefinitions);
81 if (opts.help) {
82 fableLib.stdoutLog(commandLineUsage(getAppDescription()));
83 fableLib.finish(0);
84 }
85 if (opts.refs) {
86 opts.refs = resolveKeyValuePairs(opts.refs);
87 }
88 if (opts.coreLib) {
89 opts.refs = Object.assign(opts.refs || {}, { "Fable.Core": opts.coreLib });
90 delete opts.coreLib;
91 }
92 if (opts.extra) {
93 opts.extra = resolveKeyValuePairs(opts.extra);
94 }
95 return opts;
96}
97/** Reads options from fableconfig.json, requires json5 */
98function readFableConfigOptions(opts) {
99 opts.workingDir = path.resolve(opts.workingDir || process.cwd());
100 if (typeof opts.projFile === "string") {
101 opts.projFile = [opts.projFile];
102 }
103 var cfgFile = fableLib.pathJoin(opts.workingDir, constants.FABLE_CONFIG_FILE);
104 if (Array.isArray(opts.projFile) && opts.projFile.length === 1) {
105 var fullProjFile = fableLib.pathJoin(opts.workingDir, opts.projFile[0]);
106 var projDir = fs.statSync(fullProjFile).isDirectory()
107 ? fullProjFile
108 : path.dirname(fullProjFile);
109 cfgFile = fableLib.pathJoin(projDir, constants.FABLE_CONFIG_FILE);
110 // Delete projFile from opts if it isn't a true F# project
111 if (!fableLib.isFSharpProject(fullProjFile)) {
112 delete opts.projFile;
113 }
114 }
115 if (fs.existsSync(cfgFile)) {
116 // Change workingDir to where fableconfig.json is if necessary
117 if (opts.workingDir !== path.dirname(cfgFile)) {
118 for (var key in opts) {
119 opts[key] = resolvePath(key, opts[key], opts.workingDir);
120 }
121 opts.workingDir = path.dirname(cfgFile);
122 }
123 var cfg = json5.parse(fs.readFileSync(cfgFile).toString());
124 for (var key in cfg) {
125 if (key in opts === false)
126 opts[key] = cfg[key];
127 }
128 // Check if a target is requested
129 if (opts.debug) {
130 opts.target = "debug";
131 }
132 if (opts.production) {
133 opts.target = "production";
134 }
135 if (opts.target) {
136 if (!opts.targets || !opts.targets[opts.target]) {
137 throw "Target " + opts.target + " is missing";
138 }
139 cfg = opts.targets[opts.target];
140 for (key in cfg) {
141 if ((typeof cfg[key] === "object") && !Array.isArray(cfg[key]) &&
142 (typeof opts[key] === "object") && !Array.isArray(opts[key])) {
143 for (var key2 in cfg[key])
144 opts[key][key2] = cfg[key][key2];
145 }
146 else {
147 opts[key] = cfg[key];
148 }
149 }
150 }
151 }
152 return opts;
153}
154/** Reads Babel options: plugins and presets */
155function readBabelOptions(opts) {
156 var babelPresets = [],
157 // Add plugins to emit .d.ts files if necessary
158 babelPlugins = opts.declaration
159 ? [[require("babel-dts-generator"),
160 {
161 "packageName": "",
162 "typings": fableLib.pathJoin(opts.workingDir, opts.outDir),
163 "suppressAmbientDeclaration": true,
164 "ignoreEmptyInterfaces": false
165 }],
166 require("babel-plugin-transform-flow-strip-types"),
167 require("babel-plugin-transform-class-properties")]
168 : [];
169 // Add custom plugins
170 babelPlugins = babelPlugins.concat(customPlugins.transformMacroExpressions,
171 // removeUnneededNulls must come after transformMacroExpressions (see #377)
172 customPlugins.removeUnneededNulls, customPlugins.removeFunctionExpressionNames);
173 // If opts.babelrc is true, let Babel read plugins and presets from .babelrc
174 // Babel will automatically read configuration from .babelrc if we don't pass `babelrc: false`
175 if (opts.babelrc) {
176 opts.babel = { presets: babelPresets, plugins: babelPlugins };
177 return opts;
178 }
179 // ECMAScript target
180 if (opts.ecma != "es2015" && opts.ecma != "es6") {
181 var module_1 = opts.module;
182 if (opts.module === "es2015" || opts.module === "es6") {
183 module_1 = false;
184 }
185 else if (opts.module in constants.JS_MODULES === false) {
186 throw "Unknown module target: " + opts.module;
187 }
188 babelPresets.push([require.resolve("babel-preset-es2015"), {
189 "loose": opts.loose,
190 "modules": opts.rollup ? false : module_1
191 }]);
192 }
193 else if (!opts.rollup && opts.module in constants.JS_MODULES) {
194 babelPlugins.push(require("babel-plugin-transform-es2015-modules-" + opts.module));
195 }
196 // Extra Babel plugins
197 if (opts.babelPlugins) {
198 babelPlugins = babelPlugins.concat(fableLib.resolvePlugins(opts.babelPlugins, opts.workingDir, "babel-plugin-"));
199 }
200 opts.babel = { presets: babelPresets, plugins: babelPlugins };
201 return opts;
202}
203/** Prepares options: read from command line, fableconfig.json, etc */
204function readOptions(opts) {
205 opts = opts || readCommandLineOptions();
206 opts = readFableConfigOptions(opts);
207 opts.projFile = Array.isArray(opts.projFile) ? opts.projFile : [opts.projFile];
208 if (!opts.projFile[0]) {
209 throw "--projFile is empty";
210 }
211 for (var i = 0; i < opts.projFile.length; i++) {
212 var fullProjFile = fableLib.pathJoin(opts.workingDir, opts.projFile[i] || '');
213 if (!fableLib.isFSharpProject(fullProjFile)) {
214 throw "Not an F# project (.fsproj) or script (.fsx): " + fullProjFile;
215 }
216 if (!fs.existsSync(fullProjFile)) {
217 throw "Cannot find file: " + fullProjFile;
218 }
219 }
220 // Default values & option processing
221 opts.ecma = opts.ecma || "es5";
222 opts.outDir = opts.outDir ? opts.outDir : (opts.projFile.length === 1 ? path.dirname(opts.projFile[0]) : ".");
223 if (opts.module == null) {
224 opts.module = opts.rollup ? "iife" : "es2015";
225 }
226 if (opts.coreLib) {
227 opts.refs = Object.assign(opts.refs || {}, { "Fable.Core": opts.coreLib });
228 delete opts.coreLib;
229 }
230 if (opts.refs) {
231 for (var k in opts.refs) {
232 var k2 = k.replace(/\.dll$/, "");
233 if (k !== k2) {
234 opts.refs[k2] = opts.refs[k];
235 delete opts.refs[k];
236 }
237 }
238 }
239 // Check version
240 var curNpmCfgPath = fableLib.pathJoin(opts.workingDir, "package.json");
241 if (!(opts.extra && opts.extra.noVersionCheck) && fs.existsSync(curNpmCfgPath)) {
242 var curNpmCfg = JSON.parse(fs.readFileSync(curNpmCfgPath).toString());
243 if (curNpmCfg.engines && (curNpmCfg.engines.fable || curNpmCfg.engines["fable-compiler"])) {
244 var fableRequiredVersion = curNpmCfg.engines.fable || curNpmCfg.engines["fable-compiler"];
245 if (!semver.satisfies(constants.PKG_VERSION, fableRequiredVersion)) {
246 throw "Fable version: " + constants.PKG_VERSION + "\n" +
247 "Required: " + fableRequiredVersion + "\n" +
248 "Please upgrade fable-compiler package";
249 }
250 }
251 }
252 opts = readBabelOptions(opts);
253 opts = bundle_1.readRollupOptions(opts);
254 return opts;
255}
256exports.readOptions = readOptions;