1 | "use strict";
|
2 | var fs = require("fs");
|
3 | var path = require("path");
|
4 | var commandLineArgs = require("command-line-args");
|
5 | var commandLineUsage = require("command-line-usage/lib/command-line-usage");
|
6 | var semver = require("semver");
|
7 | var json5 = require("json5");
|
8 | var fableLib = require("./lib");
|
9 | var constants = require("./constants");
|
10 | var customPlugins = require("./babelPlugins");
|
11 | var bundle_1 = require("./bundle");
|
12 |
|
13 | var 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 | ];
|
40 | function 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 | }
|
45 | function resolvePath(optName, value, workingDir) {
|
46 | function resolve(x) {
|
47 | return fableLib.pathJoin(workingDir, x);
|
48 | }
|
49 |
|
50 | if (value) {
|
51 | switch (optName) {
|
52 | case "outDir":
|
53 | return resolve(value);
|
54 |
|
55 | case "projFile":
|
56 | case "plugins":
|
57 | case "babelPlugins":
|
58 | return value.map(resolve);
|
59 |
|
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 |
|
71 | function 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 |
|
98 | function 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 |
|
111 | if (!fableLib.isFSharpProject(fullProjFile)) {
|
112 | delete opts.projFile;
|
113 | }
|
114 | }
|
115 | if (fs.existsSync(cfgFile)) {
|
116 |
|
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 |
|
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 |
|
155 | function readBabelOptions(opts) {
|
156 | var babelPresets = [],
|
157 |
|
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 |
|
170 | babelPlugins = babelPlugins.concat(customPlugins.transformMacroExpressions,
|
171 |
|
172 | customPlugins.removeUnneededNulls, customPlugins.removeFunctionExpressionNames);
|
173 |
|
174 |
|
175 | if (opts.babelrc) {
|
176 | opts.babel = { presets: babelPresets, plugins: babelPlugins };
|
177 | return opts;
|
178 | }
|
179 |
|
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 |
|
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 |
|
204 | function 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 |
|
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 |
|
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 | }
|
256 | exports.readOptions = readOptions;
|