fab-spec / fab

💎 FAB project specification & monorepo
https://fab.dev
MIT License
579 stars 37 forks source link

Allow build-time ES Modules/TS natively #66

Open geelen opened 4 years ago

geelen commented 4 years ago

At the moment, we use Rollup to compile the runtime stage of a plugin, which means we get native ES Modules support, and Typescript annotations stripped. But if you want to use something at build time, we just require it, so it needs to be compatible with whatever version of Node you're running. New node has .mjs support but Typescript is another matter.

I've been trying to get Rollup to compile the runtime entry point (since that's what's going to happen when we produce the FAB), then using node-eval to isntantiate it and call the build method, but it's not been working and I gotta move on to other things. Maybe nobody will hit this issue? I guess we'll find out!

evanbb commented 4 years ago

I just hit this issue 🤣

I'd be happy to look into this. Are there any general (or specific) contribution guidelines I should keep in mind?

geelen commented 4 years ago

Hmm, not really. This "kinda sucks" at the moment so any improvement would be more than welcome!

This is the code at the core: https://github.com/fab-spec/fab/blob/master/packages/actions/src/Builder.ts#L138-L154

const module = await safeRequire(path_slash_build)
// ...
if (typeof module.build !== 'function') throw Error()

I... kinda think anything that can slot in there with a call like transpileAndRequire(path_slash_build) instead of safeRequire where most non-standard syntax "just works" would be massive. We already depend on sucrase which... might be perfect here, actually:

The main configuration option in Sucrase is an array of transform names. These transforms are available:

    jsx: Transforms JSX syntax to React.createElement, e.g. <div a={b} /> becomes React.createElement('div', {a: b}). Behaves like Babel 7's React preset, including adding createReactClass display names and JSX context information.
    typescript: Compiles TypeScript code to JavaScript, removing type annotations and handling features like enums. Does not check types. Sucrase transforms each file independently, so you should enable the isolatedModules TypeScript flag so that the typechecker will disallow the few features like const enums that need cross-file compilation.
    flow: Removes Flow type annotations. Does not check types.
    imports: Transforms ES Modules (import/export) to CommonJS (require/module.exports) using the same approach as Babel and TypeScript with --esModuleInterop. Also includes dynamic import.
    react-hot-loader: Performs the equivalent of the react-hot-loader/babel transform in the react-hot-loader project. This enables advanced hot reloading use cases such as editing of bound methods.

These proposed JS features are built-in and always transformed:

    Optional chaining: a?.b
    Nullish coalescing: a ?? b
    Class fields: class C { x = 1; }. This includes static fields but not the #x private field syntax.
    Export namespace syntax: export * as a from 'a';
    Numeric separators: const n = 1_234;
    Optional catch binding: try { doThing(); } catch { }.

I've tried using Babel for some stuff but it's a bit of a nightmare running in someone else's project, since it gets confused about where the config/plugins ought to be coming from. Unless you know Babel really well, where we could, I guess, reuse the Babel that's already installed, and all its config? As in, detect whether the project getting compiled uses Babel at all, and if so, use the rules that already exist?

Imo sucrase with typescript and imports might get you 90% of the way there in 10% of the time, so I'd start there.

Very grateful for the help! Ping me on Discord (geelen#7503) if there's stuff you want some sync feedback on 🙏