Skip to content

Commit

Permalink
docs: Added english doc to how-to-migrate-from-1.x (#4630)
Browse files Browse the repository at this point in the history
* Added english doc to how-to-migrate-from-1.x

* Corrected typos and some other spelling errors, polished grammar accordingly
  • Loading branch information
ZixiaoWang authored Mar 8, 2021
1 parent 693df60 commit 603c74b
Showing 1 changed file with 300 additions and 1 deletion.
301 changes: 300 additions & 1 deletion docs/source/en/migration.md
Original file line number Diff line number Diff line change
@@ -1 +1,300 @@
this document is still waiting for translation, see [Chinese Version](/zh-cn/migration.html)
title: Egg@2 Upgrade guideline
---

## Background

With the official release of Node.js 8 LTS, egg now comes with built-in ES2017 Async Function support.

Though the TJ [co] has brought `async/await` programming experience before this, but it also has some inevitable problems:

- performance lost
- [obscure error logs](https://github.com/eggjs/egg/wiki/co-vs-async)

In the official Egg 2.x:
- Full compatibility to Egg 1.x and `generator function`.
- Koa 2.x based `async function` solutions.
- Only support Node.js 8 and above.
- Better error stack messages without [co], approximately 30% performance improvement (do not include the performance improvement brought by Node), see [benchmark](https://eggjs.github.io/benchmark/plot/) for more details.

One of the Egg's concept is `progressive`, hence we provide progressive programming experiences to developers.

- [Quick upgrade](#quick_upgrade)
- [Plugin update](#plugin)
- [Further upgrade](#forther_upgrade)
- [Upgrade documents for Plugin developers](#upgrade_documents_for_plugin_developers)

## Quick upgrade
- Use the latest Node LTS version (`>=8.9.0`).
- Change `egg` version to `^2.0.0` in `package.json`.
- Check if included plugins are the latest version (optional).
- Reinstall the dependencies, and run unit tests again.

**Done! Barely with any code changes**


## Plugin update

### egg-multipart

`yield parts` needs to change to `await parts()` or `yield parts()`

```js
// old
const parts = ctx.multipart();
while ((part = yield parts) != null) {
// do something
}

// yield parts() also work
while ((part = yield parts()) != null) {
// do something
}

// new
const parts = ctx.multipart();
while ((part = await parts()) != null) {
// do something
}
```

- [egg-multipart#upload-multiple-files](https://github.com/eggjs/egg-multipart#upload-multiple-files)

### egg-userrole

DO NOT support 1.x role definition, because koa-roles is no longer compatible.
The `Context` has changed from `this` to the first argument `ctx`, the original `scope` now is the second argument.

```js
// old
app.role.use('user', function() {
return !!this.user;
});

// new
app.role.use((ctx, scope) => {
return !!ctx.user
});

app.role.use('user', ctx => {
return !!ctx.user;
});
```

- [koajs/koa-roles#13](https://github.com/koajs/koa-roles/pull/13)
- [eggjs/egg-userrole#9](https://github.com/eggjs/egg-userrole/pull/9)

## Further upgrade
Due to the complete compatibility to Egg 1.x, we can finish the upgrade quickly.

But in order to keep the coding style consistent, as well as a better performance improvement and more developer-friendly error stack logs, we suggest developers to make a further upgrade:

- Use recommended code style, see [Style guide](./style-guide.md)
- [Use Koa style middleware](#use-koa2-style-middleware)
- [Change `yieldable` to `awaitable` in function invoke](#yieldable-to-awaitable)

### Use Koa2-styled middleware

> 2.x is compatible to 1.x-styled middleware, so it's still functional without any changes.
- Use Koa 2's `(ctx, next)` arguments style in callback function
- The 1st argument is `ctx`, means context, it is an instance of [Context](./basics/extend.md#Context)
- The 2nd argument is `next`, use await to execute it for the coming logics.
- Using `async (ctx, next) => {}` is not recommended, which prevents anonymous function in error stack.
- Change `yield next` to `await next()`.

```js
// 1.x
module.exports = () => {
return function* responseTime(next) {
const start = Date.now();
yield next;
const delta = Math.ceil(Date.now() - start);
this.set('X-Response-Time', delta + 'ms');
};
};

// 2.x
module.exports = () => {
return async function responseTime(ctx, next) {
const start = Date.now();
// Note, differ from the generator function middleware, next is a function, we're executing it here
await next();
const delta = Math.ceil(Date.now() - start);
ctx.set('X-Response-Time', delta + 'ms');
};
};
```

### yieldable to awaitable

> async was supported in Egg 1.x, thus if the middleware is already async-base, we could skip this section.
[co] supports `yieldable` compatibility types:

- promises
- array (parallel execution)
- objects (parallel execution)
- thunks (functions)
- generators (delegation)
- generator functions (delegation)

Despite both `generator` and `async` have the same program models, but we may still need to refactor our codes correspondingly after removing `co` because of the above special handling from `co`.

#### promise

We can replace it directly:

```js
function echo(msg) {
return Promise.resolve(msg);
}

yield echo('hi egg');
// change to
await echo('hi egg');
```

#### array - yield []

`yeild []` is normally used to send concurrent requests, such as:

```js
const [ news, user ] = yield [
ctx.service.news.list(topic),
ctx.service.user.get(uid),
];
```

In this case, use `Promise.all()` to wrap it:

```js
const [ news, user ] = await Promise.all([
ctx.service.news.list(topic),
ctx.service.user.get(uid),
]);
```

#### object - yield {}

Sometimes `yield {}` and `yield map` can also be used to send concurrent requests, but it may be a bit complex in this place because `Promise.all` doesn't support Object argument.

```js
// app/service/biz.js
class BizService extends Service {
* list(topic, uid) {
return {
news: ctx.service.news.list(topic),
user: ctx.service.user.get(uid),
};
}
}

// app/controller/home.js
const { news, user } = yield ctx.service.biz.list(topic, uid);
```

It's recommended to use `await Promise.all([])`:

```js
// app/service/biz.js
class BizService extends Service {
list(topic, uid) {
return Promise.all([
ctx.service.news.list(topic),
ctx.service.user.get(uid),
]);
}
}

// app/controller/home.js
const [ news, user ] = await ctx.service.biz.list(topic, uid);
```

If the interfaces are unchangeable, e can do things below as a workaround:

- Use [app.toPromise] method provided by our Utils.
- **This is built on top of the [co], so it may still cause performance issue and returning inaccurate error stacks, so this is not recommended.**

```js
const { news, user } = await app.toPromise(ctx.service.biz.list(topic, uid));
```

#### Others

- thunks (functions)
- generators (delegation)
- generator functions (delegation)

Use `async function` to replace the above functions, or use [app.toAsyncFunction] alternatively.

**Note**
- [toAsyncFunction][app.toAsyncFunction] and [toPromise][app.toPromise] are wrappers of [co], thus it may cause performance lost and error stack problems. So we're recommending developers to use all-chain upgrade.
- [toAsyncFunction][app.toAsyncFunction] doesn't have performance lost when invokes async function.

@sindresorhus has written a lot [promise-based helpers](https://github.com/sindresorhus/promise-fun), use them together with async function could make source code more readable.

## Plugin update
`App developers` just need to update the upgraded plugins by `plugin developers`, or use `egg-bin autod` command we've prepared to quickly update.

The following content is for `plugin developers`, it shows how to update the plugins:

### Update precautions

- Finish the upgrade items above.
- Replace all `generator function` with `async function`.
- Upgrade middlewares.
- Interfaces compatibility (optional), see following.
- Release a major version.

### Interfaces compatibility
In some cases, the interface provided by `Plugin developers` supports both generator and async, normally it's wrapped by co.

- In 2.x, we suggest `async-first` to get a better performance and clearer error stacks.
- If necessary, please use [toAsyncFunction][app.toAsyncFunction] and [toPromise][app.toPromise] for compatibility.

Like [egg-schedule] plugin, it supports generator or async to define the tasks in application level.

```js
// {app_root}/app/schedule/cleandb.js
exports.task = function* (ctx) {
yield ctx.service.db.clean();
};

// {app_root}/app/schedule/log.js
exports.task = async function splitLog(ctx) {
await ctx.service.log.split();
};
```

`Plugin developers` could simply wrap the following raw function:

```js
// https://github.com/eggjs/egg-schedule/blob/80252ef/lib/load_schedule.js#L38
task = app.toAsyncFunction(schedule.task);
```

### Rules of Plugin release

- **Major version releasement**
- All the APIs are promise based, and there's no `async` in source code. e.g. [egg-view-nunjucks]
- Modify `package.json`
- Change `egg` in `devDependencies` to `^2.0.0`.
- Change `engines.node` to `>=8.0.0`.
- Change `ci.version` to `8, 9`, reinstall dependencies to generate new travis config files.
- Update examples in `README.md` with async function.
- Write instructions for upgrade.
- (optional) Change `test/fixtures` to async function, and it's recommended to create a PR for code preview.

In case the previous versions still requires maintenance:
- Create a new branch which based on previous `1.x` version.
- Change the `publishConfig.tag` property in `package.json` to `release-1.x` in previous version.
- If the previous version has new Bugfix, tag it as `release-1.x` when publishing, so users may use `npm i egg-xx@release-1` to import the old version.
- See [npm documentations](https://docs.npmjs.com/cli/dist-tag).


[co]: https://github.com/tj/co
[egg-schedule]: https://github.com/eggjs/egg-schedule
[egg-view]: https://github.com/eggjs/egg-view
[egg-view-nunjucks]: https://github.com/eggjs/egg-view-nunjucks
[app.toAsyncFunction]: https://github.com/eggjs/egg-core/blob/da4ba1784175c43217125f3d5cd7f0be3d5396bf/lib/egg.js#L344
[app.toPromise]: https://github.com/eggjs/egg-core/blob/da4ba1784175c43217125f3d5cd7f0be3d5396bf/lib/egg.js#L353

0 comments on commit 603c74b

Please sign in to comment.