anko / eslisp

un-opinionated S-expression syntax and macro system for JavaScript
ISC License
528 stars 31 forks source link

export default? #60

Closed kaukas closed 4 years ago

kaukas commented 4 years ago

Sorry for a (likely) newbie question: how does one export default {x, y, z}?

anko commented 4 years ago

Eslisp only has ES5 features for now, so it can't do anything module-related like export.

I should be the one saying sorry. I should communicate this better in the readme. In my defense, when I last properly worked on this project, ES6 was a cutting edge thing behind a feature flag in Node.js.


However, macros are magic and the code generator supports everything, so if you really need export default, you can make it anyway:

(macro exportDefault
 (function ()
  (= env this)
  (= atoms ((. Array from) arguments))
  (= propertyIdentifiers
     ((. atoms map)
      (function (atom) (return ((. env compile) atom)))))

  (= properties ((. propertyIdentifiers map)
                   (function (identifier)
                    (return (object type "Property"
                                    key identifier
                                    value identifier
                                    shorthand true)))))

  (= estree (object type "ExportDefaultDeclaration"
                    declaration (object
                                  type "ObjectExpression"
                                  properties properties)))
  (return estree)))

(exportDefault x y z)

That outputs this:

export default {
    x,
    y,
    z
};

It's very scary :scream: but does the job!

The macro constructs an object in the appropriate estree format to describe an ExportDefaultDeclaration, and returns it. The last line just calls it.

For full modern JS support, you'll have to make your own macros as above, or wait for me to find the time. Sorry! :bow:

kaukas commented 4 years ago

Thank you for such a quick and extensive explanation!

I tried to integrate eslisp into a rollup-based pipeline in an existing project. export default turned out to be a lesser problem (used commonjs) compared to the fact that I did not find a simple way to require an esl file. Apparently in the simplest case the files are expected to be pre-compiled. To fix this I suspect I would need to define a new macro and run the dependency compilation inside it?.. Not sure. Anyway, not for tonight.

Thank you!

yousefamar commented 4 years ago

I would say the path of least resistance is to write a rollup plugin for esl files. I did the same for webpack through an eslisp loader, although this was a long time ago before eslisp had macros I think, so I'm not sure what the implications of that would be.

anko commented 4 years ago

@kaukas

I did not find a simple way to require an esl file.

You're right. Calling require in eslisp literally compiles to calling require in JavaScript, and JavaScript doesn't understand .esl files.

I deal with this by using make to compile everything, with a makefile much like this:

export PATH := node_modules/.bin:$(PATH)

%.js: %.esl
    eslc < $< > $@

If you target frontend, and you'll bundle everything anyway, a rollup plugin would also be a good choice, as @yousefamar mentioned.


To fix this I suspect I would need to define a new macro and run the dependency compilation inside it?

You could indeed do that. I tried it, and it was definitely too hard to do given how useful it is. I added a built-in macroRequire in https://github.com/anko/eslisp/commit/0f9e2d0b2c4c81dcd4828c498984452415e6766c, released in v0.9.0. You should now be able to do—

(macroRequire macroName "./path/to/file.esl") ; import it
(macroName) ; use it

with file.esl containing something like

(= (. module exports)
   (lambda () (return '(hi from other file))))

Thanks for pointing this out. :sparkles:

Here's what it would have had to look like before `other-file.esl`: ```scheme (= (. module exports) (lambda () (return '(hi from other file)))) ``` `main.esl`: ```scheme (macro macroRequire (lambda (name fileName) (= fileName (. fileName value)) (= name (. name value)) (var fs (require "fs")) (var path (require "path")) (var eslisp (require "eslisp")) (var fileContents ((. fs readFileSync) fileName)) (if ((. fileName endsWith) ".esl") (= fileContents (eslisp fileContents))) ; Construct a new JavaScript module on the fly. Compile run ; it, and return its exports. (var newModule (new Module fileName module)) (= (. newModule paths) ((. Module _nodeModulePaths) fileName)) (= (. newModule filename) fileName) ((. newModule _compile) fileContents fileName) (var env this) ((. env importMacro) name (. newModule exports)) (return null))) (macroRequire x "./other-file.esl") (x) ``` Running `eslc main.esl` produces— ```js hi(from, other, file); ``` I would expect very few users to know how to navigate Node.js' internal module API this way, but it's nice to know macros can manage even this insanity if necessary...