跳至内容

重大变更

服务器中的 MongoDB 方法

常见问题中所述,insertupdateremovefindfindOneupsert 方法不再在服务器中工作。

您应该迁移到使用它们的 Async 对应项。

js
const docs = MyCollection.find({ _id: '123' }).fetch(); This will not work in the server
const doc = MyCollection.findOne({ _id: '123' }); This will not work in the server


// in Meteor 3.x you should use the Async methods

const docs = await MyCollection.find({ _id: '123' }).fetchAsync(); This will work in the server
const doc = await MyCollection.findOneAsync({ _id: '123' }); This will work in the server

CLI

vue2

--vue2 标志不再可用。我们放弃了对 vue2 的支持。您可以在此PR中查看更多信息。

为什么?

做出此决定是因为 vue2 已于 2023 年 12 月 31 日达到生命周期结束,团队决定停止对其支持。

重置

meteor reset 命令默认只清除本地缓存。使用 --db 选项还会删除本地 Mongo 数据库。如果需要,请确保您的 CI 流程通过传递 --db 选项来适应任何更改。

为什么?

通常建议使用此命令通过清除缓存来修复您的开发项目。以前,它还会清除本地 MongoDB,这可能会意外删除重要数据。

Node v20

Meteor 3.0 现在使用 Node v20。这意味着如果您有任何 Node v14 的依赖项或用法,您将需要更新它们以与 Node v20 兼容。

NPM 安装程序更新

Meteor 的 npm 安装程序已更改。对于正式发布,您可以使用以下命令安装 Meteor

bash
npx meteor

在发布候选阶段,请使用

bash
npx meteor

或直接指定版本

bash
npx meteor@<version>

确保您正在使用 Node 20.0.0 或更高版本,尤其是在您的 CI/CD 工作流程中,以与最新的 Meteor 版本兼容。

Call x CallAsync

提示

您可以查看call x callAsync页面以全面了解。

由于 Meteor 现在如何与 async/await 一起工作,您应该在方法中使用 callAsync 而不是 call

在 Meteor 2.x 中,这是一种常见的模式

js
import { Meteor } from 'meteor/meteor'

Meteor.methods({
  async getAllData() {
    return await MyCollection.find().fetch(); 
  },
  async otherMethod() {
    return await MyCollection.find().fetch(); 
  }
});


Meteor.call('getAllData') 
Meteor.call('otherMethod') 

现在在 Meteor 3.x 中,它应该变成

js
import { Meteor } from 'meteor/meteor'

Meteor.methods({
  async getAllData() {
    return await MyCollection.find().fetchAsync(); 
  },
  async otherMethod() {
    return await MyCollection.find().fetchAsync(); 
  }
});

await Meteor.callAsync('getAllData') 
await Meteor.callAsync('otherMethod') 

WebApp 切换到 Express

提示

WebApp 已从 Connect 切换到 Express。此升级允许您在 Meteor 应用程序中使用所有 Express 功能。如果您之前已自定义 WebApp 包,请验证这些自定义是否适用于 Express。

webapp 包现在导出这些新属性

ts
type ExpressModule = {
  (): express.Application;
  json: typeof express.json;
  raw: typeof express.raw;
  Router: typeof express.Router;
  static: typeof express.static;
  text: typeof express.text;
  urlencoded: typeof express.urlencoded;
};

export declare module WebApp {
  // ...
  /**
   * @deprecated use handlers instead
   */
  var connectHandlers: express.Application;
  var handlers: express.Application; 
  /**
   * @deprecated use rawHandlers instead
   */
  var rawConnectHandlers: express.Application;
  var rawHandlers: express.Application;
  var httpServer: http.Server;
  var expressApp: express.Application;
  var express: ExpressModule; 
  // ...
}

// import { WebApp } from 'meteor/webapp';

使用 WebApp 和 Express 的路由

要在您的应用程序中添加 Express 路由,请查看Express 指南并遵循此示例

js
import { WebApp } from 'meteor/webapp';

const app = WebApp.express(); you can use as a normal express app

app.get('/hello', (req, res) => {
  res.send('Hello World');
});

WebApp.handlers.use(app);

以下代码演示了如何使用 handlers 属性在您的应用程序中创建路由

js
import { WebApp } from 'meteor/webapp';

WebApp.handlers.get('/hello', (req, res) => {
  res.send('Hello World');
});

使用 WebApp 和 Express 的中间件

要在您的应用程序中包含**路由级** Express 中间件,请查看Express 指南并遵循此示例

js
import { WebApp } from 'meteor/webapp';

const app = WebApp.express();
const router = WebApp.express.Router();

// This middleware is executed every time the app receives a request
router.use((req, res, next) => {
    console.log('Router-level - Time:', Date.now());
    next();
})

// This middleware shows request info for any type of HTTP request to the /hello/:name path
router.use('/hello/:name', (req, res, next) => {
    console.log('Router-level - Request URL:', req.originalUrl);
    next();
}, (req, res, next) => {
    console.log('Router-level - Request Type:', req.method);
    next();
})

// mount the router on the app
app.use('/', router);

WebApp.handlers.use(app);

要在您的应用程序中包含**应用程序级** Express 中间件,请查看Express 指南并遵循此示例

js
import { WebApp } from 'meteor/webapp';

const app = WebApp.express();
const router = WebApp.express.Router()

// This middleware is executed every time the app receives a request
router.use((req, res, next) => {
    console.log('Router-level - Time:', Date.now());
    next();
})

// This middleware shows request info for any type of HTTP request to the /hello/:name path
router.use('/hello/:name', (req, res, next) => {
    console.log('Router-level - Request URL:', req.originalUrl);
    next();
}, (req, res, next) => {
    console.log('Router-level - Request Type:', req.method);
    next();
})

// mount the router on the app
app.use('/', router);

WebApp.handlers.use(app);

新的 API 名称

从 Connect 切换到 Express 后,我们更新了 API 名称以与 Express 保持一致。请参阅以下详细信息

  • WebApp.connectHandlers.use(middleware) 现在是 WebApp.handlers.use(middleware)
  • WebApp.rawConnectHandlers.use(middleware) 现在是 WebApp.rawHandlers.use(middleware)
  • WebApp.connectApp 现在是 WebApp.expressApp

WebApp 内部的一些方法现在是异步的

  • WebAppInternals.reloadClientPrograms()
  • WebAppInternals.pauseClient()
  • WebAppInternals.generateClientProgram()
  • WebAppInternals.generateBoilerplate()
  • WebAppInternals.setInlineScriptsAllowed()
  • WebAppInternals.enableSubresourceIntegrity()
  • WebAppInternals.setBundledJsCssUrlRewriteHook()
  • WebAppInternals.setBundledJsCssPrefix()
  • WebAppInternals.getBoilerplate

Meteor.userAsync

您应该在代码中使用 Meteor.userAsync 而不是 Meteor.user,尤其是在您想要同构或想要在服务器中获取用户时。

js
// Before
const user = Meteor.user(); 
// After
const user = await Meteor.userAsync(); 

环境变量

Meteor 提供了 Meteor.EnvironmentVariable 类,它有助于在不同的边界之间维护上下文。

使用 Meteor 3.0,我们添加了对异步流程的支持并改进了上下文管理方式。结果,某些包开始丢失上下文数据,如此问题中所述。

如果您的应用程序或包使用 EnvironmentVariable,请确保在顶层使用 EnvironmentVariable.withValue 以正确保留和传播上下文。

例如,在更新发布行为并引入 new EnvironmentVariable 上下文时,您需要调整代码如下

javascript
const _publishConnectionId = new Meteor.EnvironmentVariable<
  string | undefined
  >();

// Before
function patchPublish(publish: typeof Meteor.publish) {
  return function (this: typeof Meteor, name, func, ...args) {
    return publish.call( 
      this,
      name,
      function (...args) {
        return _publishConnectionId.withValue(this?.connection?.id, () =>
          func.apply(this, args),
        );
      },
      ...args,
    ); 
  } as typeof Meteor.publish;
}

// After
function patchPublish(publish: typeof Meteor.publish) {
  return function (this: typeof Meteor, name, func, ...args) {
    return _publishConnectionId.withValue(this?.connection?.id, () => { 
      return publish.call(
        this,
        name,
        function (...args) {
          return func.apply(this, args);
        },
        ...args,
      );
    }); 
  } as typeof Meteor.publish;
}

此示例演示了应用于universe:i18n的迁移。