Feathers v3(秃鹰)

Feathers v3带有一些很棒的改进和新功能,我们强烈建议您尽快升级.一开始看起来有点令人生畏,但几乎在所有情况下,新的CLI都会自动为您提供所有方式.此页面包含有关快速升级路径的信息以及有关从Feathers v2升级到v3的所有更改的更多信息.

阅读 飞入2018年 的发布帖子

快速升级

要快速升级任何Feathers插件或应用程序,您可以使用新CLI中的 upgrade 命令.首先,如果安装了它,请卸载旧的 feathers-cli:

npm uninstall feathers-cli -g

然后安装 @feathersjs/cli 并升级项目:

npm install @feathersjs/cli -g
cd path/to/project
feathers upgrade

CLI将使用 package.json 中的 directories.lib 来知道源文件的位置,如果没有提供,则默认为 src.如果您有一个已转换的应用/模块,例如使用babel,包括 libsrc 文件夹,最简单的是将 package.json 中的 directories.lib 更改为 src 而不是 lib,以便CLI将正确升级原始源文件而不是已转换的源文件.

简而言之(有关详细信息,请参见下文),这将:

  • 将所有核心软件包升级到新的作用域软件包名称及其最新版本

  • 删除所有 feathers-hooks 导入和单行 app.configure(hooks()); (链接 .configure(hooks()) 调用必须手动删除))

  • 为任何使用 feathers-rest 的应用程序添加Express兼容性(其他没有 feathers-rest 的Feathers应用程序必须手动更新)

  • 删除所有 .filter 导入并调用已被通道功能取代的 service.filter

添加频道

如果您使用实时(使用Socket.io或Primus),请将以下文件添加为 src/channels.js:

module.exports = function(app) {
  if(typeof app.channel !== 'function') {
    // If no real-time functionality has been configured just return
    return;
  }

  app.on('connection', connection => {
    // On a new real-time connection, add it to the anonymous channel
    app.channel('anonymous').join(connection);
  });

  app.on('login', (authResult, { connection }) => {
    // connection can be undefined if there is no
    // real-time connection, e.g. when logging in via REST
    if(connection) {
      // Obtain the logged in user from the connection
      // const user = connection.user;

      // The connection is no longer anonymous, remove it
      app.channel('anonymous').leave(connection);

      // Add it to the authenticated user channel
      app.channel('authenticated').join(connection);

      // Channels can be named anything and joined on any condition

      // E.g. to send real-time events only to admins use
      // if(user.isAdmin) { app.channel('admins').join(connection); }

      // If the user has joined e.g. chat rooms
      // if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(channel));

      // Easily organize users by email and userid for things like messaging
      // app.channel(`emails/${user.email}`).join(channel);
      // app.channel(`userIds/$(user.id}`).join(channel);
    }
  });

  app.publish((data, hook) => { // eslint-disable-line no-unused-vars
    // Here you can add event publishers to channels set up in `channels.js`
    // To publish only for a specific event use `app.publish(eventname, () => {})`

    // e.g. to publish all service events to all authenticated users use
    return app.channel('authenticated');
  });

  // Here you can also add service specific event publishers
  // e..g the publish the `users` service `created` event to the `admins` channel
  // app.service('users').publish('created', () => app.channel('admins'));

  // With the userid and email organization from above you can easily select involved users
  // app.service('messages').publish(() => {
  //   return [
  //     app.channel(`userIds/${data.createdBy}`),
  //     app.channel(`emails/${data.recipientEmail}`)
  //   ];
  // });
};

并且需要在 src/app.js 中配置它(注意它应该在所有服务之后配置,以便 channels.js 可以注册服务特定的发布者):

const channels = require('./channels');

// After `app.configure(services)`
app.configure(channels);

重要

The channels.js file shown above will publish all real-time events to all authenticated users. This is already safer than the previous default but you should carefully review the 事件频道 documentation and implement appropriate channels so that only the right users are going to receive real-time events.

将应用程序迁移到通道后,可以删除所有 <servicename> .filter.js 文件.

保护领域

Feathers v3有一种新机制可确保敏感信息永远不会发布到任何客户端.要保护始终保护用户密码,请在 src/services/users/users.hooks.js 中添加 api/authentication/local#protect,而不是 remove('password') 钩子:

const { hashPassword } = require('@feathersjs/authentication-local').hooks;
const { hashPassword, protect } = require('@feathersjs/authentication-local').hooks;

module.exports = {
  before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  after: {
    all: [
      // Make sure the password field is never sent to the client
      // Always must be the last hook
      protect('password')
    ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};

更新客户端应用程序

Client side Feathers applications can also be updated using the CLI but may need some manual intervention. Most importantly, since Feathers core now natively ships as ES6 code, the module bundler, like Webpack, has to be instructed to transpile it. More information can be found in the Feathers 客户端. For Webpack and create-react-app usage (which both will throw a minification error without changes), see Webpack.

@feathersjs npm scope

所有Feathers核心模块都已移至 @feathersjs npm范围.这使得更清楚哪些模块被视为核心模块以及哪些模块受社区支持,并且还允许我们更轻松地管理发布权限.以下模块已重命名:

主要Feathers

旧名

范围名称

feathers

@feathersjs/feathers

feathers-cli

@feathersjs/cli

feathers-commons

@feathersjs/commons

feathers-rest

@feathersjs/express/rest

feathers-socketio

@feathersjs/socketio

feathers-primus

@feathersjs/primus

feathers-errors

@feathersjs/errors

feathers-configuration

@feathersjs/configuration

feathers-socket-commons

@feathersjs/socket-commons

认证

旧名

范围名称

feathers-authentication

@feathersjs/authentication

feathers-authentication-jwt

@feathersjs/authentication-jwt

feathers-authentication-local

@feathersjs/authentication-local

feathers-authentication-oauth1

@feathersjs/authentication-oauth1

feathers-authentication-oauth2

@feathersjs/authentication-oauth2

feathers-authentication-client

@feathersjs/authentication-client

客户端 Feathers

旧名

范围名称

feathers/client

@feathersjs/feathers

feathers-client

@feathersjs/client

feathers-rest/client

@feathersjs/rest-client

feathers-socketio/client

@feathersjs/socketio-client

feathers-primus/client

@feathersjs/primus-client

feathers-authentication/client

@feathersjs/authentication-client

文档更改

通过更好地关注Feathers核心,非核心模块的存储库,文档和指南已移至更合适的位置:

框架独立

@feathersjs/feathers v3与框架无关,可以在客户端和开箱即用的Node中运行.这意味着它不再默认扩展Express.

@feathersjs/expressrequire('@feathersjs/express').rest@feathersjs/ 中提供框架绑定和REST提供程序(以前称为 feathers-rest).express/rest``. @feathersjs/express 还带有Express内置中间件,如 express.static 和最近包含的 express.jsonexpress.urlencoded 身体解析器.一旦Feathers应用程序被 “表达”,它就可以像以前的版本一样使用:

Before

const feathers = require('feathers');
const bodyParser = require('body-parser');
const rest = require('feathers-rest');
const errorHandler = require('feathers-errors/handler');

const app = feathers();

app.configure(rest());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));

// Register an Express middleware
app.get('/somewhere', function(req, res) {
  res.json({ message: 'Data from somewhere middleware' });
});
// Statically host some files
app.use('/', feathers.static(__dirname));

// Use a Feathers friendly Express error handler
app.use(errorHandler());

Now

const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');

// Create an Express compatible Feathers application
const app = express(feathers());

// Add body parsing middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Initialize REST provider (previous in `feathers-rest`)
app.configure(express.rest());

// Register an Express middleware
app.get('/somewhere', function(req, res) {
  res.json({ message: 'Data from somewhere middleware' });
});
// Statically host some files
app.use('/', express.static(__dirname));

// Use a Feathers friendly Express error handler
app.use(express.errorHandler());

钩子在核心

feathers-hooks 插件现在是核心的一部分,不再需要导入和配置.所有服务都将立即包含挂钩功能.此外,现在还可以定义应该在 hook.dispatch 中发送给客户端的不同数据,这样可以正确保护不应该显示给客户端的属性.

Before

const feathers = require('feathers');
const hooks = require('feathers-hooks');

const app = feathers();

app.configure(hooks());
app.use('/todos', {
  get(id) {
    return Promise.resolve({
      message: `You have to do ${id}`
    });
  }
});

app.service('todos').hooks({
  after: {
    get(hook) {
      hook.result.message = `${hook.result.message}!`;
    }
  }
});

Now

const feathers = require('feathers');

const app = feathers();

app.use('/todos', {
  get(id) {
    return Promise.resolve({
      message: `You have to do ${id}`
    });
  }
});

app.service('todos').hooks({
  after: {
    get(hook) {
      hook.result.message = `${hook.result.message}!`;
    }
  }
});

事件频道和出版

以前,过滤器用于为每个事件和每个连接运行,以确定是否应该发送事件.

事件通道是一种更安全,更高效的方式,用于定义将实时事件发送到的连接.而不是为每个事件和每个连接运行,而是在建立或验证连接时定义连接属于哪个通道.

// On login and if it is a real-time connectionn, add the connection to the `authenticated` channel
app.on('login', (authResult, { connection }) => {
  if(connection) {
    const { user } = connection;

    app.channel('authenticated').join(connection);
  }
});

// Publish only `created` events from the `messages` service
app.service('messages').publish('created', (data, context) => app.channel('authenticated'));

// Publish all real-time events from all services to the authenticated channel
app.publish((data, context) => app.channel('authenticated'));

要仅发布到用户所在的房间:

// On login and if it is a real-time connection, add the connection to the `authenticated` channel
app.on('login', (authResult, { connection }) => {
  if(connection) {
    const { user } = connection;

    // Join `authenticated` channel
    app.channel('authenticated').join(connection);

    // Join rooms channels for that user
    rooms.forEach(roomId => app.channel(`rooms/${roomId}`).join(connection));
  }
});

更好地分离客户端和服务器端模块

自v2以来,Feathers核心一直在客户端和服务器上运行,但并不总是很清楚应该使用哪些相关模块.现在,所有客户端连接器都位于它们自己的存储库中,而主要的Feathers存储库在客户端和服务器上可能需要相同的方式.

Before

const io = require('socket.io-client');
const feathers = require('feathers/client');
const hooks = require('feathers-hooks');
const socketio = require('feathers-socketio/client');
const auth = require('feathers-authentication-client');

const socket = io();
const app = feathers()
  .configure(hooks())
  .configure(socketio(socket))
  .configure(auth());

Now

const io = require('socket.io-client');
const feathers = require('@feathersjs/feathers');
const socketio = require('@feathersjs/socketio-client');
const auth = require('@feathersjs/authentication-client');

const socket = io();
const app = feathers()
  .configure(socketio(socket))
  .configure(auth());

Node 6+

上面提到的核心存储库也已经被迁移到可以直接使用(例如,当npm将存储库安装为Git/GitHub依赖关系时),而不需要Babel转换步骤.

由于所有存储库都广泛使用ES6,这也意味着不再支持 node 4.

另见 /feathers/issues/608.

一种新的Socket消息格式

在尝试调用不存在的服务或方法(而不是仅仅超时)时,websocket消息传递格式已更新为支持正确的错误消息.使用新的 @feathersjs/socketio-client@feathersjs/primus-client 将自动使用该格式.您可以在 Socket.io 客户端Primus 客户端 文档中找到详细信息.

注解

仍支持旧邮件格式,因此不必同时更新客户端.

弃用和其他API更改

  • Feathers服务方法不再支持回调.所有服务方法总是返回Promise.自定义服务必须返回Promise或使用 async/await.

  • service.beforeservice.after 已被替换为单个 app.hooks({before,after})

  • app.service(path) 只返回一个服务,不再用于注册新服务(通过 app.service(path,service)).请使用 app.use(path,service).

  • 之前直接添加到“params”的路由参数现在位于 params.route

  • feathers.static 这样的快速中间件现在位于 const express = require('@feathersjs/express') 使用 express.static

  • 已从所有核心存储库中删除实验性TypeScript定义.可以在 feathersjs-ecosystem/feathers-typescript 中开发此版本的TypeScript定义.欢迎帮助.

向后兼容性polyfill

除了上面列出的步骤,现有的钩子,数据库适配器,服务和其他插件应该与Feathers v3完全兼容,无需任何额外的修改.

本节包含一些快速向后兼容性polyfill,用于突破性更改,可用于简化迁移或继续使用使用弃用语法的插件.

基本服务过滤器

这是先前事件过滤器功能的基本模拟.它不使用promises或允许修改数据(现在应该通过设置 hook.dispatch 来处理).

app.mixins.push(service => {
  service.mixin({
    filter(eventName, callback) {
      const args = callback ? [ eventName ] : [];

      // todos.filter('created', function(data, connection, hook) {});
      if(!callback) {
        callback = eventName;
      }

      // A publisher function that sends to the `authenticated`
      // channel that we defined in the quick upgrade section above
      args.push((data, hook) => app.channel('authenticated')
        .filter(connection =>
          callback(data, connection, hook)
        )
      );

      service.publish(... args);
    }
  });
});

路线参数

app.hooks({
  before(hook) {
    Object.assign(hook.params, hook.params.route);

    return hook;
  }
})

.before.after 挂钩注册

app.mixins.push(service => {
  service.mixin({
    before(before) {
      return this.hooks({ before });
    },

    after(after) {
      return this.hooks({ after });
    },
  })
});