deepsweet / start

:red_circle: Functional task runner for Node.js
https://start.js.org/
MIT License
476 stars 19 forks source link

next #29

Closed deepsweet closed 6 years ago

deepsweet commented 7 years ago

Hey. I'm about to introduce next major version relatively soon, with simplified tasks API and better API for tasks runner itself. Some details from future migration.md:

Start

Repositories

Start became a monorepo.

Pros:

Cons:

Runner

import Runner from 'start';
import Reporter from 'start-pretty-reporter';

const runner = Runner(Reporter());

export const build = () => runner(
  // ...
);

With this naming our core concept became much more clear: there are tasks and tasks runners. You have to wrap tasks with runner.

"External" input

runner itself is another function in which you can pass an "initial" input:

runner(...tasks)('input!')
  .then(console.log)
  .catch(console.error);

So no more start-input-connector:

export const tasksRunner1 = () => runner(
  function task2(input) {
    console.log('input from previous runner': input);

    return Promise.resolve();
  },
);

export const tasksRunner2 = () => runner(
  function task1() {
    return Promise.resolve('output');
  },
  tasksRunner1()
);

And start-watch became really beautiful:

export const dev = runner(
  clean('lib/'),
  watch('src/**/*.js')(
    runner(
      read(),
      babel(),
      write('lib/')
    )
  )
);

Tasks

Refactoring

Tasks were slightly simplified:

export default (options) => function myTask(input, log, reporter) {
  console.log('input:', input);
  log('hey from My Task!');
  console.log('original reporter:', reporter);

  return Promise.resolve('My Task output');
}

So in the simplest case task is just a single named function which should return a Promise.

Reporters

Default reporter

console.log is no more a default reporter for Runner.

Refactoring

Reporters were splitted into composed functions:

export default (name) => (type, message) => console.log(name, type, message);

@effervescentia @tunnckoCore @laggingreflex @nikolay @eisisig I was thinking about scoped packages like @start/runner, @start/clean and so on. But unfortunately start username is already taken on NPM. So I wrote to the author – radio silence, wrote to NPM support – they can't help even with abandoned empty accounts...

Rename the whole project? Or leave it as is with start- naming convention? I'm ok with both, but I need your opinion.

import Runner from '@please/runner';
import Reporter from '@please/reporter';
yarn please build
yarn please test

πŸ€”

Pros:

Cons:

What do you think about please? The major changes described above will be implemented regardless this naming issue.

tunnckoCore commented 7 years ago

Heya! Just not force the things too fast. You should see what's the community and how big it is. Invest some time to research, because this seems like big change.

I'm agree with the monorepo and probrably with the renaming, since most of the packages are at your accont and this org.

What means "no more presets"? And why "console.log is not the default reporter"? What will be the default reporter?

If the rename happen, I suggest to start it as separate project OR release it as Start v6. If there's no impact on the community and actually don't have any let start v5 be last version and start a new project from scratch without touching start.

deepsweet commented 7 years ago

What means "no more presets"?

It's only about "no start-start-preset", because I just don't need it anymore with monorepo.

And why "console.log is not the default reporter"? What will be the default reporter?

Nothing by default? I mean you have to choose something and pass it as an argument to Runner (simple, pretty, whatever), default console.log was kinda useless anyway imo.

release it as Start v6

Actually that's the decision I tend to. Just not to break literally everything with one move. Make a monorepo, make breaking changes, major bump start to v6 and major bump every task package.

Also I want to rename start-files to start-find because it's more clear and unix-like, and also a verb :) find, read, write.

tunnckoCore commented 7 years ago

Agree. Sounds good.

effervescentia commented 7 years ago

Seems like the best option, I also don't know what I would rename re-start to, so for that reason alone I'd stick with start. Technical changes sound good too

tunnckoCore commented 7 years ago

Forgot to mention that there is also https://github.com/iamvdo/pleeease (edited)

deepsweet commented 7 years ago

alright, just got "start" username (so @start scope as well) on NPM πŸ”₯

tunnckoCore commented 7 years ago

Super :tada:

As about the tasks. Are they curried? Like all of these are equivalent

export default (options) => function myTask(input, log, reporter) {
  console.log('input:', input);
  log('hey from My Task!');
  console.log('original reporter:', reporter);

  return Promise.resolve('My Task output');
}

second variant

export default (options) => function myTask(input, log) {
  return (reporter) => {
    console.log('input:', input);
    log('hey from My Task!');
    console.log('original reporter:', reporter);

    return Promise.resolve('My Task output');
  }
}

third

export default (options) => function myTask(input) {
  return (log, reporter) => {
    console.log('input:', input);
    log('hey from My Task!');
    console.log('original reporter:', reporter);

    return Promise.resolve('My Task output');
  }
}

and

export default (options) => function myTask(input) {
  return (log) => {
    return (reporter) => {
      console.log('input:', input);
      log('hey from My Task!');
      console.log('original reporter:', reporter);

      return Promise.resolve('My Task output');
    }
  }
}
tunnckoCore commented 7 years ago

Looking over the new code: no. But would be good to support it.

tunnckoCore commented 7 years ago

@deepsweet one more thing that may be good to add is, don't sure how to name it, the Start() call to return a function. So it may be a bit easier and bit prettier to create tasks. Because i'm using prettier it formats to a bit unclear (that's because i also enforce ESLint's arrow-body-style and prefer-arrow-callback rules)

the eslint rule config

    'arrow-body-style': [
      'error',
      'as-needed',
      { requireReturnForObjectLiteral: false },
    ],

for example your build task

export const build = () => start(
  env('NODE_ENV', 'production'),
  files('build/'),
  clean(),
  files('lib/**/*.js'),
  read(),
  babel(),
  write('build/')
);

to this one

export const build = () =>
  start(
    env('NODE_ENV', 'production'),
    files('build/'),
    clean(),
    files('lib/**/*.js'),
    read(),
    babel(),
    write('build/')
  )

which is a bit uglier for my taste (i hate this new line return, like in Python and Ruby).

All this can be fixed if the start returns a function. I currently can workaround it with that

export const start = Start(reporter())
const _start = (...args) => () => start(...args)

export const build = _start(
  env('NODE_ENV', 'production'),
  files('build/'),
  clean(),
  files('lib/**/*.js'),
  read(),
  babel(),
  write('build/')
)

So probably we can add second argument to the initial Start call, which would be boolean, so

// returns as what currently returns (a function which accept tasks)
const start = Start(reporter())

and if we pass true as second argument it will return a task function which calls the start function

const start = Start(reporter())
const _start = Start(reporter(), true)

export const bar = () => start(
  one,
  two,
  three
)

export const foo = _start(
  one,
  two,
  three
)
deepsweet commented 7 years ago

As about the tasks. Are they curried?

Task internals are kinda not exposed to the user land, that's why I was thinking about to simplify tasks API a bit because it's used only by Start itself. Will post an update here soon, I have a bunch of new ideas since March :)

_start()

These arguments are really useful when they available for the entire scope:

export const makeES = (packageName) => start(
  files(`packages/${packageName}/es/`),
  clean(),
  files(`packages/${packageName}/src/**/*.js?(x)`),
  read(),
  babel(babelConfig),
  rename((file) => file.replace(/\.jsx$/, '.js')),
  write(`packages/${packageName}/es/`)
);

Also it will play very nice with the next version of start-parallel plugin:

const build = (packageName) => start(
  parallel('makeES, makeLib')(packageName)
);
yarn start build my-package

prettier

πŸ™ˆ

tunnckoCore commented 7 years ago

These arguments are really useful

I know :laughing: I wanted them. But in most cases may be not needed.

deepsweet commented 7 years ago

So here is my latest ideas:

task()

import Task from '@start/task';
import reporter from '@start/pretty-reporter';

const task = Task(reporter());

export const build = (packageName) => task('build it!')(
  find(`packages/${packageName}/es/`),
  clean(),
  find(`packages/${packageName}/src/**/*.js`),
  read(),
  babel({}),
  write(`packages/${packageName}/dist/`)
);

And the report, something like:

> build it!
  > find ...
  > clean ...
  > read ...
  > babel ...
  > write ...

Now Start is aware of plugins list in a named "group" in advance so we can build a really beautiful reporters even with animations. Ink!

Nested tasks

export const foo = (sameArg) => task('foo!')(
  plugin3(),
  plugin4()
);
export const bar = (arg) => task('bar!')(
  plugin1(),
  foo(arg),
  plugin2()
);

CLI:

yarn start bar "Hi, this is arg"

Report:

> bar!
  > plugin1 ...
  > foo!
    > plugin3 ...
    > plugin4 ...
  > plugin2 ...

Parallel

Report is not a mess anymore because it contains plugins log grouped under the particular task name.

> build esm!
  > find ...
  > clean ...
  > read ...
  > babel ...
  > write ...

> build cjs!
  > find ...
  > clean ...
  > read ...
  > babel ...
  > write ...

plugin()

Maybe something like this?

import plugin from '@start/plugin';

export default (options) => plugin('Oh My Plugin')((input) => (log, reporter) => {
  log('weee');
  return Promise.resolve(input);
});

I actually never liked that function.name "hack" we used to get a plugin name.

tunnckoCore commented 7 years ago

I actually never liked that function.name "hack" we used to get a plugin name.

It's not a hack and is enough in most cases. Otherwise you can use get-fn-name which is a bit better than fn-name by Sindre. Created and exists for reason. So you we can just skip the needless naming. Like

export default (options) => plugin('foo bar plug', (input, log, reporter) => {})

// if not a string, then fallback to `get-fn-name`
export default (options) => plugin((input, log, reporter) => {})

evenmore, this plugin factory should return a function which accepts options. So things would be a more standard and clear

export default plugin('Oh Buble Plugin', (input, options, log, reporter) => {
  if (options.foo) {
    log('woohoo')
  }
})

usage

import bublePlugin from '@start/buble'

export const build = start(
  buble({ foo: 'bar' })
)

where, of course this (input, options, log, reporter) => {} is curried internally, so plugin creators may use it in what way they want.

tunnckoCore commented 7 years ago

Even more clean and beauty may be if we enforce conventions, using the destructuring. The destructuring js feature is just freaking awesome and amazing, exactly because these two things:

So probably it will be the best if our signature is

export default plugin('Oh Buble Plugin', (input, { options, log, reporter }) => {
  if (options.foo) {
    log('woohoo')
  }
})

Haha, and so, no need for currying support :rofl:

deepsweet commented 7 years ago

THIS πŸ”₯

export default plugin('Oh Buble Plugin', ({ input, options, log, reporter })
tunnckoCore commented 7 years ago

Exactly! Exactly this was typing in the editor, because i'm working on 40 lines version :P Please don't rush with the new release :D

tunnckoCore commented 7 years ago

What about Start to accept emitter instead of reporter function? It will allow better reporting and even if someone think and need: TAP producing reporter ;p

deepsweet commented 7 years ago

What about Start to accept emitter instead of reporter function

πŸ’Ž

tunnckoCore commented 7 years ago

Great! I'll PR with my idea for next :) And we can discuss there instead.

deepsweet commented 7 years ago

next branch is outdated, please wait until I push the ideas described above :)

tunnckoCore commented 7 years ago

Anyway, it's written from scratch, so i don't need a branch :D I'll create new branch anyway ;] Just to see it in files and not in issue codeblocks. :)

radum commented 7 years ago

@deepsweet Looking fwd for the new changes. Need any help?

I was looking for a module that does pretty much what you are doing before I start building my own. Do you have an ETA or need some help?

deepsweet commented 7 years ago

@radum I'm expecting start@next to build itself next week, so all the core utils and few important plugins like @start/babel should be ready. depends on which plugins you want, we have something like 30, and I'll migrate them one by one in some special (but still unclear) priority.

deepsweet commented 7 years ago

so far current state is something like this and this – not sure that I'm not going to change @start/plugin entirely soon.

radum commented 7 years ago

Plugins are not important for me right now. The fact that I can use functions that return a promise as tasks and start using async await are the key features. Thank you for your quick reply.

tunnckoCore commented 7 years ago

Hey @deepsweet start's great to hear! Sorry about the promotion, but someone may be interested in hela too. It's is similar, but takes a bit different path by using just execa and executing the commands, instead of using task's/package's APIs, which requires a lot small boilerplate. You can see hela-config-tunnckocore for complete example.

I invest heavily in it. And everything just started after complete rewrite of Start (again around 30-40 lines). Btw... i have it somewhere locally and it was meant to be landed as PR here to discuss.

edit: (...after few seconds) damn, i reinstalled my machine last night and don't remember if i had it somewhere backedup in the github.

edit2: The next branch looks good too! The reporter to be an emitter is great (we probably discussed that? hm)

radum commented 6 years ago

Hey @deepsweet, did you manage to finish this?

I'm quite keen to see how the final Plugin code looks like.

deepsweet commented 6 years ago

😿

radum commented 6 years ago

@deepsweet I would gladly help to port the current plugins. For my own personal use and for my company, the concurrent and parallel plugins are the most important ones.

But before I start we need the final version of the next release :P

deepsweet commented 6 years ago

see master branch for current state – it's almost stabilized and all known API experiments are completed. I've been waiting for this since forever as well πŸ™

there should be docs/migration.md at some point.

deepsweet commented 6 years ago

I've just published v0.1.0 of everything.