Merge remote-tracking branch 'origin/release-3.0' into feature/meteor3-npm-tests

This commit is contained in:
Jan Dvorak
2023-03-17 09:06:29 +01:00
22 changed files with 203 additions and 27 deletions

View File

@@ -56,6 +56,9 @@
* `boilerplate-generator`:
- `toHTML` is no longer available (it was already deprecated). Use `toHTMLStream` instead.
* `ddp`:
- Added method `Meteor.isAsyncCall` that can be used to check if the current method call is async or not.
* `oauth`:
- `_endOfPopupResponseTemplate` and `_endOfRedirectResponseTemplate` are no longer a property but now a function that returns a promise of the same value as before
- the following server methods are now async:

View File

@@ -80,6 +80,12 @@ with this ID has already been made. Alternatively, you can use
Read more about methods and how to use them in the [Methods](http://guide.meteor.com/methods.html) article in the Meteor Guide.
{% apibox "Meteor.isAsyncCall" %}
This method can be used to determine if the current method invocation is
asynchronous. It returns true if the method is running on the server and came from
an async call(`Meteor.callAsync`)
{% apibox "DDPCommon.MethodInvocation#userId" %}
The user id is an arbitrary string — typically the id of the user record

View File

@@ -188,16 +188,15 @@ function getDefaultsForNode8(features) {
);
// TODO [fibers]: instead of removing the code below, consider this comment:
// https://github.com/meteor/meteor/pull/12471/files#r1089610144
const isFiberDisabled = process.env.DISABLE_FIBERS === '1';
const ignoreAsyncPlugin = process.env.IGNORE_ASYNC_PLUGIN === '1';
if (!ignoreAsyncPlugin) {
if (!features.useNativeAsyncAwait && !ignoreAsyncPlugin) {
combined.plugins.push([
require('./plugins/async-await.js'),
{
// Do not transform `await x` to `Promise.await(x)`, since Node
// 8 has native support for await expressions.
useNativeAsyncAwait: isFiberDisabled,
// Even though Node 8 supports native async/await, it is not
// compatible with fibers.
useNativeAsyncAwait: false,
},
]);
}

View File

@@ -0,0 +1,74 @@
"use strict";
module.exports = function (babel) {
const t = babel.types;
return {
name: "transform-meteor-async-await",
visitor: {
Function: {
exit: function (path) {
const node = path.node;
if (!node.async) {
return;
}
// The original function becomes a non-async function that
// returns a Promise.
node.async = false;
// The inner function should inherit lexical environment items
// like `this`, `super`, and `arguments` from the outer
// function, and arrow functions provide exactly that behavior.
const innerFn = t.arrowFunctionExpression(
// The inner function has no parameters of its own, but can
// refer to the outer parameters of the original function.
[],
node.body,
// The inner function called by Promise.asyncApply should be
// async if we have native async/await support.
!!this.opts.useNativeAsyncAwait
);
const promiseResultExpression = t.callExpression(
t.memberExpression(
t.identifier("Promise"),
t.identifier("asyncApply"),
false
), [innerFn]
);
// Calling the async function with Promise.asyncApply is
// important to ensure that the part before the first await
// expression runs synchronously in its own Fiber, even when
// there is native support for async/await.
if (node.type === "ArrowFunctionExpression") {
node.body = promiseResultExpression;
} else {
node.body = t.blockStatement([
t.returnStatement(promiseResultExpression)
]);
}
}
},
AwaitExpression: function (path) {
if (this.opts.useNativeAsyncAwait) {
// No need to transform await expressions if we have native
// support for them.
return;
}
const node = path.node;
path.replaceWith(t.callExpression(
t.memberExpression(
t.identifier("Promise"),
t.identifier(node.all ? "awaitAll" : "await"),
false
),
[node.argument]
));
}
}
};
};

View File

@@ -3,6 +3,8 @@
"dependencies": {
"@meteorjs/babel": {
"version": "7.19.0-beta.1",
"resolved": "https://registry.npmjs.org/@meteorjs/babel/-/babel-7.19.0-beta.1.tgz",
"integrity": "sha512-4dy7oSXEo6Eb2PHfPkMX0VVnkQJ9Kb6Qv6/ssiXOqQRtTRpBAgeWeMzUd42u/8VzxG6l8NoNqIhPSOHZjC2usg==",
"dependencies": {
"@ampproject/remapping": {
"version": "2.1.1",
@@ -1188,11 +1190,6 @@
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
},
"fibers": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fibers/-/fibers-5.0.0.tgz",
"integrity": "sha512-UpGv/YAZp7mhKHxDvC1tColrroGRX90sSvh8RMZV9leo+e5+EkRVgCEZPlmXeo3BUNQTZxUaVdLskq1Q2FyCPg=="
},
"find-up": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",

View File

@@ -96,6 +96,8 @@ BCp.processOneFileForTarget = function (inputFile, source) {
features.topLevelAwait = arch.startsWith('os.') || enableClientTLA
features.useNativeAsyncAwait = Meteor.isFibersDisabled;
if (! features.hasOwnProperty("jscript")) {
// Perform some additional transformations to improve compatibility
// in older browsers (e.g. wrapping named function expressions, per

View File

@@ -47,6 +47,7 @@ Meteor.connection = DDP.connect(ddpUrl, {
[
'subscribe',
'methods',
'isAsyncCall',
'call',
'callAsync',
'apply',

View File

@@ -511,6 +511,17 @@ export class Connection {
return handle;
}
/**
* @summary Tells if the method call came from a call or a callAsync.
* @alias Meteor.isAsyncCall
* @locus Anywhere
* @memberOf Meteor
* @importFromPackage meteor
* @returns boolean
*/
isAsyncCall(){
return DDP._CurrentMethodInvocation._isCallAsyncMethodRunning()
}
methods(methods) {
Object.entries(methods).forEach(([name, func]) => {
if (typeof func !== 'function') {
@@ -722,7 +733,6 @@ export class Connection {
_apply(name, stubCallValue, args, options, callback) {
const self = this;
// We were passed 3 arguments. They may be either (name, args, options)
// or (name, args, callback)
if (!callback && typeof options === 'function') {

View File

@@ -378,3 +378,9 @@ Meteor.methods({
resultByValueArrays[testId].push(value);
}
});
/// Helper for "livedata - isAsync call"
Meteor.methods({
isCallAsync: function () {
return Meteor.isAsyncCall()
}
})

View File

@@ -1208,6 +1208,12 @@ testAsyncMulti('livedata - methods with nested stubs', [
},
]);
Tinytest.addAsync('livedata - isAsync call', async function (test) {
Meteor.call('isCallAsync', (err, result) => test.equal(result, false))
const result = await Meteor.callAsync('isCallAsync', { returnStubValue: true })
test.equal(result, true)
})
// XXX some things to test in greater detail:
// staying in simulation mode
// time warp

View File

@@ -1726,6 +1726,17 @@ Object.assign(Server.prototype, {
self.sessions.delete(session.id);
},
/**
* @summary Tells if the method call came from a call or a callAsync.
* @locus Anywhere
* @memberOf Meteor
* @importFromPackage meteor
* @returns boolean
*/
isAsyncCall: function(){
return DDP._CurrentMethodInvocation._isCallAsyncMethodRunning()
},
/**
* @summary Defines functions that can be invoked over the network by clients.
* @locus Anywhere
@@ -1759,7 +1770,20 @@ Object.assign(Server.prototype, {
const options = args[0]?.hasOwnProperty('returnStubValue')
? args.shift()
: {};
return this.applyAsync(name, args, options);
DDP._CurrentMethodInvocation._set();
DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(true);
const promise = new Promise((resolve, reject) => {
DDP._CurrentCallAsyncInvocation._set({ name, hasCallAsyncParent: true });
this.applyAsync(name, args, { isFromCallAsync: true, ...options })
.then(resolve)
.catch(reject)
.finally(() => {
DDP._CurrentCallAsyncInvocation._set();
});
});
return promise.finally(() =>
DDP._CurrentMethodInvocation._setCallAsyncMethodRunning(false)
);
},
apply: function (name, args, options, callback) {
@@ -1771,7 +1795,6 @@ Object.assign(Server.prototype, {
} else {
options = options || {};
}
const promise = this.applyAsync(name, args, options);
// Return the result in whichever way the caller asked for it. Note that we
@@ -1799,7 +1822,6 @@ Object.assign(Server.prototype, {
new Meteor.Error(404, `Method '${name}' not found`)
);
}
// If this is a method call from within another method or publish function,
// get the user state from the outer method or publish function, otherwise
// don't allow setUserId to be called

View File

@@ -14,6 +14,7 @@ Meteor.refresh = async function (notification) {
_.each(
[
'publish',
'isAsyncCall',
'methods',
'call',
'callAsync',

View File

@@ -1,3 +1,5 @@
Meteor.isFibersDisabled = true;
Meteor._isPromise = (r) => {
return r && typeof r.then === 'function';
};

View File

@@ -2,7 +2,11 @@ const getAslStore = () => (Meteor.isServer && global?.asyncLocalStorage?.getStor
const getValueFromAslStore = key => getAslStore()[key];
const updateAslStore = (key, value) => getAslStore()[key] = value;
Meteor._isFibersEnabled = !process.env.DISABLE_FIBERS && Meteor.isServer;
const bootstrap = global.__meteor_bootstrap__;
Meteor.isFibersDisabled = !!(bootstrap && bootstrap.isFibersDisabled);
Meteor._isFibersEnabled = !Meteor.isFibersDisabled;
Meteor._getAslStore = getAslStore;
Meteor._getValueFromAslStore = getValueFromAslStore;
Meteor._updateAslStore = updateAslStore;

View File

@@ -207,7 +207,7 @@ body {
color: #ea5555;
}
pre {
.exception pre {
color: var(--primary-white);
color: #F9FAFB;
}

View File

@@ -369,7 +369,8 @@ Previous builder: ${previousBuilder.outputPath}, this builder: ${outputPath}`
const babel = require("@meteorjs/babel");
const commonBabelOptions = babel.getDefaultOptions({
nodeMajorVersion: parseInt(process.versions.node),
typescript: true
typescript: true,
useNativeAsyncAwait: true
});
commonBabelOptions.sourceMaps = true;

View File

@@ -472,8 +472,19 @@ Object.assign(Isopack.prototype, {
// case right.)
}, async function () {
// Make a new Plugin API object for this plugin.
var Plugin = self._makePluginApi(name);
await plugin.load({ Plugin: Plugin, Profile: Profile });
const Plugin = self._makePluginApi(name);
const __meteor_bootstrap__ = {
isFibersDisabled: true,
// Set to null to tell Meteor.startup to call hooks immediately
// XXX: should we fully support startup hooks in build plugins?
startupHooks: null
};
await plugin.load({
Plugin,
Profile,
__meteor_bootstrap__
});
});
}

View File

@@ -60,6 +60,7 @@ var Module = function (options) {
// options
self.useGlobalNamespace = options.useGlobalNamespace;
self.combinedServePath = options.combinedServePath;
self.addEagerRequires = !!options.addEagerRequires;
};
Object.assign(Module.prototype, {
@@ -209,6 +210,10 @@ Object.assign(Module.prototype, {
if (file.mainModule) {
result.mainModulePath = file.absModuleId;
}
if (self.addEagerRequires) {
chunks.push(`\nrequire(${JSON.stringify(file.absModuleId)});`);
}
}
}
} else {
@@ -1151,6 +1156,10 @@ export var fullLink = Profile("linker.fullLink", async function (inputFiles, {
bundleArch,
useGlobalNamespace: isApp,
combinedServePath,
// To support `/client/compatibility`, we can't use the runtime for the
// app on the client when TLA is disabled since it wraps all of
// the app code in a function. Instead, we have the module add eager requires.
addEagerRequires: !bundleArch.startsWith('os.') && isApp && !enableClientTLA
});
// Check if the core-runtime package will already be loaded

View File

@@ -184,7 +184,7 @@ export class HMRServer {
}
}
compare({ name, arch, hmrAvailable, files, cacheKey }, getFileOutput) {
async compare({ name, arch, hmrAvailable, files, cacheKey }, getFileOutput) {
if (this.firstBuild = null) {
this.firstBuild = Date.now();
}
@@ -248,20 +248,36 @@ export class HMRServer {
onlyReplaceableChanges &&
removedFilePaths.length === 0;
function saveFileDetails(file) {
async function saveFileDetails(file) {
const content = await getFileOutput(file);
return {
content: getFileOutput(file).toStringWithSourceMap({}),
content: content.toStringWithSourceMap({}),
path: file.absModuleId,
meteorInstallOptions: file.meteorInstallOptions
};
}
// TODO: try to improve the performance of this
const iterWithFn = async (iter, fn) => {
let arr = [];
for (let i = 0; i < iter.length; i++) {
try {
const d = await fn(iter[i]);
arr.push(d);
} catch (e) {
console.log(e);
}
}
return arr;
}
const result = {
fileHashes,
unreloadableHashes: unreloadable,
reloadable,
addedFiles: reloadable ? addedFiles.map(saveFileDetails) : [],
changedFiles: reloadable ? changedFiles.map(saveFileDetails) : [],
addedFiles: reloadable ? await iterWithFn(addedFiles, saveFileDetails) : [],
changedFiles: reloadable ? await iterWithFn(changedFiles, saveFileDetails) : [],
linkedAt: Date.now(),
id: this._createId(),
name

View File

@@ -33,7 +33,8 @@ var starJson = JSON.parse(fs.readFileSync(path.join(buildDir, "star.json")));
__meteor_bootstrap__ = {
startupHooks: [],
serverDir: serverDir,
configJson: configJson
configJson: configJson,
isFibersDisabled: true
};
__meteor_runtime_config__ = {
@@ -508,3 +509,4 @@ var runMain = Profile("Run main()", async function () {
process.exit(1)
});

View File

@@ -11,7 +11,8 @@ function babelRegister() {
const cacheDir = path.join(meteorPath, ".babel-cache");
const babelOptions = meteorBabel.getDefaultOptions({
nodeMajorVersion: parseInt(process.versions.node),
typescript: true
typescript: true,
useNativeAsyncAwait: true
});
// Make sure that source maps are included in the generated code for

View File

@@ -303,7 +303,10 @@ var loadIsopacketFromDisk = async function (isopacketName) {
// An incredibly minimalist version of the environment from
// tools/server/boot.js. Kind of a hack.
var env = {
__meteor_bootstrap__: { startupHooks: [] },
__meteor_bootstrap__: {
startupHooks: [],
isFibersDisabled: true
},
__meteor_runtime_config__: { meteorRelease: "ISOPACKET" }
};
env.Profile = Profile;