Files
meteor/packages/modules-runtime/modules-runtime.js
Ben Newman 2eab0b2bf4 Preserve true "main" and "browser" fields of package.json modules.
Previously, when building a JavaScript bundle for the client, if a
package.json file had a string-valued "browser" field, we would replace
the value of the "main" field of the bundled package.json module with the
value of the "browser" field. This trick was important because it allowed
an npm package to have a different entry point on the client than it had
on the server.

However, that approach became inconsistent if the package.json file was
also explicitly imported as a module, because the package.json stub used
for module resolution prevented the real contents of package.json from
getting bundled, and disagreed with the original package.json module about
the value of the "main" field.

To resolve that inconsistency, it seems better to avoid modifying the
"main" field of package.json modules, and instead rely on the runtime
module system to make sense of the "browser" field, regardless of whether
the package.json module is a stub used only for module resolution or
contains the full contents of the original package.json file.

The ability to understand "browser" fields of package.json modules was
introduced in install@0.8.3:
377d1a3b51

This is potentially a backwards-incompatible change for developers using
this version of `ImportScanner` and `Resolver` who have not yet upgraded
their `modules-runtime` package to at least version 0.7.8. The solution is
to upgrade `modules-runtime`, though it would be nice to enforce that
better somehow.
2017-01-04 11:54:17 -05:00

87 lines
2.5 KiB
JavaScript

var options = {};
var hasOwn = options.hasOwnProperty;
// RegExp matching strings that don't start with a `.` or a `/`.
var topLevelIdPattern = /^[^./]/;
if (typeof Profile === "function" &&
process.env.METEOR_PROFILE) {
options.wrapRequire = function (require) {
return Profile(function (id) {
return "require(" + JSON.stringify(id) + ")";
}, require);
};
}
// On the client, make package resolution prefer the "browser" field of
// package.json files to the "main" field.
options.browser = Meteor.isClient;
// This function will be called whenever a module identifier that hasn't
// been installed is required. For backwards compatibility, and so that we
// can require binary dependencies on the server, we implement the
// fallback in terms of Npm.require.
options.fallback = function (id, parentId, error) {
// For simplicity, we honor only top-level module identifiers here.
// We could try to honor relative and absolute module identifiers by
// somehow combining `id` with `dir`, but we'd have to be really careful
// that the resulting modules were located in a known directory (not
// some arbitrary location on the file system), and we only really need
// the fallback for dependencies installed in node_modules directories.
if (topLevelIdPattern.test(id)) {
if (typeof Npm === "object" &&
typeof Npm.require === "function") {
return Npm.require(id);
}
}
throw error;
};
options.fallback.resolve = function (id, parentId, error) {
if (Meteor.isServer &&
topLevelIdPattern.test(id)) {
// Allow any top-level identifier to resolve to itself on the server,
// so that options.fallback can have a chance to handle it.
return id;
}
throw error;
};
meteorInstall = makeInstaller(options);
var Mp = meteorInstall.Module.prototype;
if (Meteor.isServer) {
Mp.useNode = function () {
if (typeof npmRequire !== "function") {
// Can't use Node if npmRequire is not defined.
return false;
}
var parts = this.id.split("/");
var start = 0;
if (parts[start] === "") ++start;
if (parts[start] === "node_modules" &&
parts[start + 1] === "meteor") {
start += 2;
}
if (parts.indexOf("node_modules", start) < 0) {
// Don't try to use Node for modules that aren't in node_modules
// directories.
return false;
}
try {
npmRequire.resolve(this.id);
} catch (e) {
return false;
}
this.exports = npmRequire(this.id);
return true;
};
}