diff --git a/src/dotnet/Fable.Compiler/Fable2Babel.fs b/src/dotnet/Fable.Compiler/Fable2Babel.fs index b1ee70da84..c830343c46 100644 --- a/src/dotnet/Fable.Compiler/Fable2Babel.fs +++ b/src/dotnet/Fable.Compiler/Fable2Babel.fs @@ -15,7 +15,7 @@ type ReturnStrategy = type Import = { path: string selector: string - localIdent: string + localIdent: string option internalFile: string option } @@ -1106,7 +1106,6 @@ module Util = declarePlugins member bcom.GetRootModule(file) = state.GetRootModule(file) - // TODO: Create a cache to optimize imports member bcom.GetImportExpr ctx selector path kind = let sanitizeSelector selector = if selector = "*" @@ -1118,15 +1117,19 @@ module Util = else Naming.replaceIdentForbiddenChars selector let getLocalIdent (ctx: Context) (selector: string) = match selector with - | "*" | "default" | "" -> + | "" -> None + | "*" | "default" -> let x = path.TrimEnd('/') - x.Substring(x.LastIndexOf '/' + 1) - | _ -> selector - |> Naming.sanitizeIdent (fun s -> + x.Substring(x.LastIndexOf '/' + 1) |> Some + | selector -> Some selector + |> Option.map (Naming.sanitizeIdent (fun s -> ctx.file.UsedVarNames.Contains s - || (imports.Values |> Seq.exists (fun i -> i.localIdent = s))) + || (imports.Values |> Seq.exists (fun i -> i.localIdent = Some s)))) match imports.TryGetValue(path + "::" + selector) with - | true, i -> upcast Identifier(i.localIdent) + | true, i -> + match i.localIdent with + | Some localIdent -> upcast Identifier(localIdent) + | None -> upcast NullLiteral () | false, _ -> let localId = getLocalIdent ctx selector let i = { @@ -1147,7 +1150,9 @@ module Util = |> fun path -> path.TrimEnd('/') } imports.Add(path + "::" + selector, i) - upcast Identifier (localId) + match localId with + | Some localId -> upcast Identifier(localId) + | None -> upcast NullLiteral () member bcom.GetAllImports () = upcast imports.Values member bcom.TransformExpr ctx e = transformExpr bcom ctx e member bcom.TransformStatement ctx e = transformStatement bcom ctx e @@ -1206,24 +1211,32 @@ module Compiler = // Add imports com.GetAllImports() |> Seq.mapi (fun ident import -> - let localId = Identifier(import.localIdent) let specifier = - match import.selector with - | "*" -> ImportNamespaceSpecifier(localId) |> U3.Case3 - | "default" | "" -> ImportDefaultSpecifier(localId) |> U3.Case2 - | memb -> ImportSpecifier(localId, Identifier memb) |> U3.Case1 + import.localIdent + |> Option.map (fun localId -> + let localId = Identifier(localId) + match import.selector with + | "*" -> ImportNamespaceSpecifier(localId) |> U3.Case3 + | "default" | "" -> ImportDefaultSpecifier(localId) |> U3.Case2 + | memb -> ImportSpecifier(localId, Identifier memb) |> U3.Case1) import.path, specifier) |> Seq.groupBy (fun (path, _) -> path) |> Seq.collect (fun (path, specifiers) -> let mems, defs, alls = - (([], [], []), Seq.map snd specifiers) + (([], [], []), Seq.choose snd specifiers) ||> Seq.fold (fun (mems, defs, alls) x -> match x.``type`` with | "ImportNamespaceSpecifier" -> mems, defs, x::alls | "ImportDefaultSpecifier" -> mems, x::defs, alls | _ -> x::mems, defs, alls) - [mems; defs; alls] - |> Seq.choose (function + // There seem to be errors if we mix member, default and namespace imports + // so we must issue an import statement for each kind + match [mems; defs; alls] with + | [[];[];[]] -> + // No specifiers, so this is just a import for side effects + [ImportDeclaration([], StringLiteral path) :> ModuleDeclaration |> U2.Case2] + | specifiers -> + specifiers |> List.choose (function | [] -> None | specifiers -> ImportDeclaration(specifiers, StringLiteral path) diff --git a/src/dotnet/Fable.Compiler/Replacements.fs b/src/dotnet/Fable.Compiler/Replacements.fs index faf83ad043..cd563cacde 100644 --- a/src/dotnet/Fable.Compiler/Replacements.fs +++ b/src/dotnet/Fable.Compiler/Replacements.fs @@ -577,6 +577,9 @@ module AstPass = | expr when expr.Type = Fable.Unit -> [] | expr -> [expr] match i.methodName with + | "importDynamic" -> + GlobalCall ("import", None, false, i.args) + |> makeCall i.range i.returnType |> Some | Naming.StartsWith "import" _ -> let fail() = sprintf "%s.%s only accepts literal strings" i.ownerFullName i.methodName @@ -589,7 +592,8 @@ module AstPass = | _ -> fail(); "*", [makeStrConst "unknown"] | "importMember" -> Naming.placeholder, i.args | "importDefault" -> "default", i.args - | _ -> "*", i.args // importAllFrom + | "importSideEffects" -> "", i.args + | _ -> "*", i.args // importAll let path = match args with | [Fable.Value(Fable.StringConst path)] -> path diff --git a/src/dotnet/Fable.Core/Fable.Core.JsInterop.fs b/src/dotnet/Fable.Core/Fable.Core.JsInterop.fs new file mode 100644 index 0000000000..9b10d78c87 --- /dev/null +++ b/src/dotnet/Fable.Core/Fable.Core.JsInterop.fs @@ -0,0 +1,163 @@ +module Fable.Core.JsInterop + +open System +open Fable.Core +open Fable.Import + +/// Has same effect as `unbox` (dynamic casting erased in compiled JS code). +/// The casted type can be defined on the call site: `!!myObj?bar(5): float` +let (!!) x: 'T = jsNative + +// Implicit cast for erased types +let inline (!^) (x:^t1) : ^t2 = ((^t1 or ^t2) : (static member op_ErasedCast : ^t1 -> ^t2) x) + +/// Dynamically access a property of an arbitrary object. +/// `myObj?propA` in JS becomes `myObj.propA` +/// `myObj?(propA)` in JS becomes `myObj[propA]` +let (?) (o: obj) (prop: obj): Applicable = jsNative + +/// Dynamically assign a value to a property of an arbitrary object. +/// `myObj?propA <- 5` in JS becomes `myObj.propA = 5` +/// `myObj?(propA) <- 5` in JS becomes `myObj[propA] = 5` +let (?<-) (o: obj) (prop: obj) (v: obj): unit = jsNative + +/// Destructure and apply a tuple to an arbitrary value. +/// E.g. `myFn $ (arg1, arg2)` in JS becomes `myFn(arg1, arg2)` +let ($) (callee: obj) (args: obj): obj = jsNative + +/// Upcast the right operand to obj and create a key-value tuple. +/// Mostly convenient when used with `createObj`. +/// E.g. `createObj [ "a" ==> 5 ]` in JS becomes `{ a: 5 }` +let (==>) (key: string) (v: obj): string*obj = jsNative + +/// Destructure and apply a tuple to an arbitrary value with `new` keyword. +/// E.g. `createNew myCons (arg1, arg2)` in JS becomes `new myCons(arg1, arg2)` +let createNew (o: obj) (args: obj): obj = jsNative + +/// Create a literal JS object from a collection of key-value tuples. +/// E.g. `createObj [ "a" ==> 5 ]` in JS becomes `{ a: 5 }` +let createObj (fields: #seq): obj = jsNative + +/// Create a literal JS object from a collection of union constructors. +/// E.g. `keyValueList [ MyUnion 4 ]` in JS becomes `{ myUnion: 4 }` +let keyValueList (caseRule: CaseRules) (li: 'T list): obj = jsNative + +/// Create an empty JS object: {} +let createEmpty<'T> : 'T = jsNative + +/// Internally used by Fable, not intended for general use +let applySpread (callee: obj) (args: obj) : 'T = jsNative + +/// Works like `ImportAttribute` (same semantics as ES6 imports). +/// You can use "*" or "default" selectors. +let import<'T> (selector: string) (path: string):'T = jsNative + +/// F#: let myMember = importMember "myModule" +/// JS: import { myMember } from "myModule" +/// Note the import must be immediately assigned to a value in a let binding +let importMember<'T> (path: string):'T = jsNative + +/// F#: let defaultMember = importDefaultobj> "myModule" +/// JS: import defaultMember from "myModule" +let importDefault<'T> (path: string):'T = jsNative + +/// F#: let myLib = importAll "myLib" +/// JS: import * as myLib from "myLib" +let importAll<'T> (path: string):'T = jsNative + +/// Imports a file only for its side effects +let importSideEffects (path: string): unit = jsNative + +/// Imports a file dynamically at runtime +let importDynamic<'T> (path: string): JS.Promise<'T> = jsNative + +/// Convert F# unions, records and classes into plain JS objects +/// When designing APIs, consider also using a Pojo record or union +let toPlainJsObj (o: 'T): obj = jsNative + +/// Converts an F# object into a plain JS object (POJO) +/// This is only intended if you're using a custom serialization method +/// and will produce the same object structure that `toJson` encodes +/// NOTE: `deflate` is currently NOT recursive +let deflate(o: 'T): obj = jsNative + +/// Serialize F# objects to JSON +let toJson(o: 'T): string = jsNative + +/// Instantiate F# objects from JSON +let [] ofJson<'T>(json: string): 'T = jsNative + +/// Serialize F# objects to JSON adding $type info +let toJsonWithTypeInfo(o: 'T): string = jsNative + +/// Instantiate F# objects from JSON containing $type info +let [] ofJsonWithTypeInfo<'T>(json: string): 'T = jsNative + +/// Converts a plain JS object (POJO) to an instance of the specified type. +/// This is only intended if you're using a custom serialization method +/// (that must produce same objects as `toJson`) instead of `ofJson`. +/// NOTE: `inflate` is currently NOT recursive +let [] inflate<'T>(pojo: obj): 'T = jsNative + +/// Reads the name of an identifier, a property or a type +let nameof(expr: obj): string = jsNative + +/// Reads the name of a property or a type from the lambda body +let nameofLambda(f: 'a->'b): string = jsNative + +/// Compiles to JS `this` keyword. +/// +/// ## Sample +/// let fn = JsFunc2(fun x y -> jsThis?add(x, y)) +let [] jsThis<'T> : 'T = jsNative + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor = + [] + abstract Create: []args: obj[] -> obj + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor<'Out> = + [] + abstract Create: unit->'Out + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor<'Arg1,'Out> = + [] + abstract Create: 'Arg1->'Out + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor<'Arg1,'Arg2,'Out> = + [] + abstract Create: 'Arg1*'Arg2->'Out + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor<'Arg1,'Arg2,'Arg3,'Out> = + [] + abstract Create: 'Arg1*'Arg2*'Arg3->'Out + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor<'Arg1,'Arg2,'Arg3,'Arg4,'Out> = + [] + abstract Create: 'Arg1*'Arg2*'Arg3*'Arg4->'Out + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor<'Arg1,'Arg2,'Arg3,'Arg4,'Arg5,'Out> = + [] + abstract Create: 'Arg1*'Arg2*'Arg3*'Arg4*'Arg5->'Out + +/// Use it when importing a constructor from a JS library. +type [] JsConstructor<'Arg1,'Arg2,'Arg3,'Arg4,'Arg5,'Arg6,'Out> = + [] + abstract Create: 'Arg1*'Arg2*'Arg3*'Arg4*'Arg5*'Arg6->'Out + +/// Use it to cast dynamic functions coming from JS. If you know the argument +/// and return types, use `System.Func<>` instead. If you need a constructor +/// (must be applied with `new` keyword), use `JsConstructor`. +/// +/// ## Sample +/// let f: JsFunc = import "myFunction" "./myLib" +/// f.Invoke(5, "bar") +type [] JsFunc = + [] + abstract Invoke: []args:obj[]->obj diff --git a/src/dotnet/Fable.Core/Fable.Core.fs b/src/dotnet/Fable.Core/Fable.Core.fs index a3f20ecfab..12e6ee94bc 100644 --- a/src/dotnet/Fable.Core/Fable.Core.fs +++ b/src/dotnet/Fable.Core/Fable.Core.fs @@ -117,159 +117,6 @@ type CaseRules = | None = 0 | LowerFirst = 1 -module JsInterop = - /// Has same effect as `unbox` (dynamic casting erased in compiled JS code). - /// The casted type can be defined on the call site: `!!myObj?bar(5): float` - let (!!) x: 'T = jsNative - - // Implicit cast for erased types - let inline (!^) (x:^t1) : ^t2 = ((^t1 or ^t2) : (static member op_ErasedCast : ^t1 -> ^t2) x) - - /// Dynamically access a property of an arbitrary object. - /// `myObj?propA` in JS becomes `myObj.propA` - /// `myObj?(propA)` in JS becomes `myObj[propA]` - let (?) (o: obj) (prop: obj): Applicable = jsNative - - /// Dynamically assign a value to a property of an arbitrary object. - /// `myObj?propA <- 5` in JS becomes `myObj.propA = 5` - /// `myObj?(propA) <- 5` in JS becomes `myObj[propA] = 5` - let (?<-) (o: obj) (prop: obj) (v: obj): unit = jsNative - - /// Destructure and apply a tuple to an arbitrary value. - /// E.g. `myFn $ (arg1, arg2)` in JS becomes `myFn(arg1, arg2)` - let ($) (callee: obj) (args: obj): obj = jsNative - - /// Upcast the right operand to obj and create a key-value tuple. - /// Mostly convenient when used with `createObj`. - /// E.g. `createObj [ "a" ==> 5 ]` in JS becomes `{ a: 5 }` - let (==>) (key: string) (v: obj): string*obj = jsNative - - /// Destructure and apply a tuple to an arbitrary value with `new` keyword. - /// E.g. `createNew myCons (arg1, arg2)` in JS becomes `new myCons(arg1, arg2)` - let createNew (o: obj) (args: obj): obj = jsNative - - /// Create a literal JS object from a collection of key-value tuples. - /// E.g. `createObj [ "a" ==> 5 ]` in JS becomes `{ a: 5 }` - let createObj (fields: #seq): obj = jsNative - - /// Create a literal JS object from a collection of union constructors. - /// E.g. `keyValueList [ MyUnion 4 ]` in JS becomes `{ myUnion: 4 }` - let keyValueList (caseRule: CaseRules) (li: 'T list): obj = jsNative - - /// Create an empty JS object: {} - let createEmpty<'T> : 'T = jsNative - - /// Internally used by Fable, not intended for general use - let applySpread (callee: obj) (args: obj) : 'T = jsNative - - /// Works like `ImportAttribute` (same semantics as ES6 imports). - /// You can use "*" or "default" selectors. - let import<'T> (selector: string) (path: string):'T = jsNative - - /// F#: let myMember = importMember "myModule" - /// JS: import { myMember } from "myModule" - /// Note the import must be immediately assigned to a value in a let binding - let importMember<'T> (path: string):'T = jsNative - - /// F#: let defaultMember = importDefaultobj> "myModule" - /// JS: import defaultMember from "myModule" - let importDefault<'T> (path: string):'T = jsNative - - /// F#: let myLib = importAll "myLib" - /// JS: import * as myLib from "myLib" - let importAll<'T> (path: string):'T = jsNative - - /// Convert F# unions, records and classes into plain JS objects - /// When designing APIs, consider also using a Pojo record or union - let toPlainJsObj (o: 'T): obj = jsNative - - /// Converts an F# object into a plain JS object (POJO) - /// This is only intended if you're using a custom serialization method - /// and will produce the same object structure that `toJson` encodes - /// NOTE: `deflate` is currently NOT recursive - let deflate(o: 'T): obj = jsNative - - /// Serialize F# objects to JSON - let toJson(o: 'T): string = jsNative - - /// Instantiate F# objects from JSON - let [] ofJson<'T>(json: string): 'T = jsNative - - /// Serialize F# objects to JSON adding $type info - let toJsonWithTypeInfo(o: 'T): string = jsNative - - /// Instantiate F# objects from JSON containing $type info - let [] ofJsonWithTypeInfo<'T>(json: string): 'T = jsNative - - /// Converts a plain JS object (POJO) to an instance of the specified type. - /// This is only intended if you're using a custom serialization method - /// (that must produce same objects as `toJson`) instead of `ofJson`. - /// NOTE: `inflate` is currently NOT recursive - let [] inflate<'T>(pojo: obj): 'T = jsNative - - /// Reads the name of an identifier, a property or a type - let nameof(expr: obj): string = jsNative - - /// Reads the name of a property or a type from the lambda body - let nameofLambda(f: 'a->'b): string = jsNative - - /// Compiles to JS `this` keyword. - /// - /// ## Sample - /// let fn = JsFunc2(fun x y -> jsThis?add(x, y)) - let [] jsThis<'T> : 'T = jsNative - - /// Use it when importing a constructor from a JS library. - type [] JsConstructor = - [] - abstract Create: []args: obj[] -> obj - - /// Use it when importing a constructor from a JS library. - type [] JsConstructor<'Out> = - [] - abstract Create: unit->'Out - - /// Use it when importing a constructor from a JS library. - type [] JsConstructor<'Arg1,'Out> = - [] - abstract Create: 'Arg1->'Out - - /// Use it when importing a constructor from a JS library. - type [] JsConstructor<'Arg1,'Arg2,'Out> = - [] - abstract Create: 'Arg1*'Arg2->'Out - - /// Use it when importing a constructor from a JS library. - type [] JsConstructor<'Arg1,'Arg2,'Arg3,'Out> = - [] - abstract Create: 'Arg1*'Arg2*'Arg3->'Out - - /// Use it when importing a constructor from a JS library. - type [] JsConstructor<'Arg1,'Arg2,'Arg3,'Arg4,'Out> = - [] - abstract Create: 'Arg1*'Arg2*'Arg3*'Arg4->'Out - - /// Use it when importing a constructor from a JS library. - type [] JsConstructor<'Arg1,'Arg2,'Arg3,'Arg4,'Arg5,'Out> = - [] - abstract Create: 'Arg1*'Arg2*'Arg3*'Arg4*'Arg5->'Out - - /// Use it when importing a constructor from a JS library. - type [] JsConstructor<'Arg1,'Arg2,'Arg3,'Arg4,'Arg5,'Arg6,'Out> = - [] - abstract Create: 'Arg1*'Arg2*'Arg3*'Arg4*'Arg5*'Arg6->'Out - - /// Use it to cast dynamic functions coming from JS. If you know the argument - /// and return types, use `System.Func<>` instead. If you need a constructor - /// (must be applied with `new` keyword), use `JsConstructor`. - /// - /// ## Sample - /// let f: JsFunc = import "myFunction" "./myLib" - /// f.Invoke(5, "bar") - type [] JsFunc = - [] - abstract Invoke: []args:obj[]->obj - module Testing = type TestAttribute() = inherit Attribute() diff --git a/src/dotnet/Fable.Core/Fable.Core.fsproj b/src/dotnet/Fable.Core/Fable.Core.fsproj index 7034636688..60873f18f8 100644 --- a/src/dotnet/Fable.Core/Fable.Core.fsproj +++ b/src/dotnet/Fable.Core/Fable.Core.fsproj @@ -25,6 +25,7 @@ + diff --git a/src/tools/webpack.config.js b/src/tools/webpack.config.js index 8ac28f14b6..1858986ae4 100644 --- a/src/tools/webpack.config.js +++ b/src/tools/webpack.config.js @@ -16,6 +16,7 @@ module.exports = { filename: 'QuickTest.js', path: resolve('temp'), }, + target: "node", module: { rules: [ {