elm-lang / elm-make

A build tool for Elm projects
BSD 3-Clause "New" or "Revised" License
175 stars 45 forks source link

Support for importing generated code through ES6 imports #193

Closed thomasheartman closed 6 years ago

thomasheartman commented 6 years ago

I ran into an issue today when trying to initialize an Elm app from a JS file. I wanted to do something like the following:

import * as Elm from "./elm.js"

const app = Elm.main()

but run into a TypeError: this is undefined in the generated elm.js. From what I gather, it's because of how native module imports don't bind this in the same fashion as chucking it right in the HTML does.

Here is a minimal example of what needs to be present to reproduce the issue.

I had a look at elm-webpack-loader, which does something similar (that is, kick Elm off from JS), but it uses require, while I'd rather stay with vanilla JS.

Now, I think it'd be appropriate to file this as a feature request rather than a bug, but it depends on the goals of elm-make, of course. I can imagine that you might want to avoid using anything ES6-specific due to browser incompatibility etc, but I thought I'd raise the issue anyway.

OS and browser: macOS High Sierra 10.13.4, Firefox Nightly 62.0a1 (2018-05-31) (64-bit). Elm version: 0.18.

evancz commented 6 years ago

My goal is to stick to ES3 features. I want to use a really small subset of JS in generated code to maximize interoperability and to keep things very simple in code generation.

I suspect that integration could be done by wrapping the generated JS in another closure. Do you mind figuring out what that would look like and sharing it here? Perhaps we can have some docs in the build tools about how folks can set that up if that is what they need.

thomasheartman commented 6 years ago

Right, I suspected as much, and think that's a fair goal indeed. I've been trying to make it work for the last hour or so--without much luck--though I must admit that I have very limited experience with JS closures. I'll keep digging a bit to see what more I can find, but if you have any ideas about how to do it, any suggestions would be more than welcome. I think the point that gets me stuck is that you can't apply an import statement, and they must be at the top of the file (meaning they can't be wrapped inside a function, for instance), so I'm not sure where to go next.

Thanks for the very quick reply, by the way!

harrysarson commented 6 years ago

@TheHeartmann this little node script can be run as

node elm2esm "path/to/elm.js"

and converts the elm file (inplace) to an es module exposion with one named export Elm so you can do

import { Elm } from 'path/to/elm.js';

elm2esm.js

const fs = require('fs');
const { promisify } = require('util');

const readFile = promisify(fs.readFile);
const writeFile = promisify(fs.writeFile);

(async function main() {

  const elm_path = process.argv[2];

  console.log(`Making ${elm_path} into a es module...`);

  const elm_es3 = await readFile(elm_path, 'utf8');

  const elm_esm =
    '\n' +
    'const scope = {};\n' +
    elm_es3.replace('}).call(this);', '}).call(scope);') +
    'export const { Elm } = scope;\n' +
    '\n';

  await writeFile(elm_path, elm_esm);

  console.log(`Finished.`);

})();
thomasheartman commented 6 years ago

So i've spent a couple of hours looking at this throughout the week, but in the end have not found a way around it that's suitable for my situation. The above solution with the node script is probably great if you're running a more intricate setup, but all I've got now is elm-live running and auto-generating on change. So for now, I'll leave it be and find another way around. Thanks for all your inputs!