Merge pull request #10545 from meteor/compile-Npm.depends-packages-with-ESM-entry-points

Allow .npm/package/node_modules to be compiled in Meteor packages.
This commit is contained in:
Ben Newman
2019-05-04 18:48:36 -04:00
committed by GitHub
11 changed files with 152 additions and 41 deletions

View File

@@ -65,7 +65,7 @@ const hasOwn = Object.prototype.hasOwnProperty;
// Cache the (slightly post-processed) results of linker.fullLink.
const CACHE_SIZE = process.env.METEOR_LINKER_CACHE_SIZE || 1024*1024*100;
const CACHE_DEBUG = !! process.env.METEOR_TEST_PRINT_LINKER_CACHE_DEBUG;
const LINKER_CACHE_SALT = 22; // Increment this number to force relinking.
const LINKER_CACHE_SALT = 23; // Increment this number to force relinking.
const LINKER_CACHE = new LRU({
max: CACHE_SIZE,
// Cache is measured in bytes. We don't care about servePath.

View File

@@ -34,7 +34,7 @@ var compiler = exports;
// dependencies. (At least for now, packages only used in target creation (eg
// minifiers) don't require you to update BUILT_BY, though you will need to quit
// and rerun "meteor run".)
compiler.BUILT_BY = 'meteor/32';
compiler.BUILT_BY = 'meteor/33';
// This is a list of all possible architectures that a build can target. (Client
// is expanded into 'web.browser' and 'web.cordova')

View File

@@ -375,6 +375,27 @@ export default class ImportScanner {
_checkSourceAndTargetPaths(file) {
file.sourcePath = this._getSourcePath(file);
// If we're scanning a Meteor package (as indicated by this.name), and we
// come across a file whose sourcePath starts with .npm/, it's a file that
// was installed by Npm.depends, so we should reroot it relative to one of
// this.nodeModulesPaths, rather than preserving the .npm/ path.
if (this.name && file.sourcePath.startsWith(".npm/")) {
const parts = file.sourcePath.split("/");
const nmi = parts.indexOf("node_modules");
if (nmi >= 0) {
const suffix = parts.slice(nmi + 1).join("/");
this.nodeModulesPaths.some(nodeModulesPath => {
const newAbsPath = pathJoin(nodeModulesPath, suffix);
if (optimisticStatOrNull(newAbsPath)) {
file.sourcePath = file.targetPath =
pathRelative(this.sourceRoot, newAbsPath);
return true;
}
return false;
});
}
}
if (! isString(file.targetPath)) {
return;
}

View File

@@ -426,6 +426,9 @@ _.extend(PackageSource.prototype, {
sourceRoot: self.sourceRoot,
uses: _.map(options.use, splitConstraint),
getFiles() {
// TODO We might want to call _findSources here, if we want plugins to
// be able to import compiled files that were not explicitly included in
// the sources array passed to Package.registerBuildPlugin.
return {
sources: sources
}
@@ -1298,10 +1301,6 @@ _.extend(PackageSource.prototype, {
subdirectories.forEach(subdir => {
if (/(^|\/)node_modules\/$/.test(subdir)) {
if (! inNodeModules) {
sourceArch.localNodeModulesDirs[subdir] = true;
}
// Defer handling node_modules until after we handle all other
// subdirectories, so that we know whether we need to descend
// further. If sources is still empty after we handle everything
@@ -1309,21 +1308,29 @@ _.extend(PackageSource.prototype, {
// imported by anthing outside of it, so we can ignore it.
nodeModulesDir = subdir;
// A "local" node_modules directory is one that's managed by the
// application developer using npm, rather than by Meteor using
// Npm.depends, which is available only in Meteor packages, and
// installs its dependencies into .npm/*/node_modules. Local
// node_modules directories may contain other nested node_modules
// directories, but we care about recording only the top-level
// node_modules directories here (hence !inNodeModules).
if (!inNodeModules && (isApp || !subdir.startsWith(".npm/"))) {
sourceArch.localNodeModulesDirs[subdir] = true;
}
} else {
sources.push(...find(subdir, depth + 1, inNodeModules));
}
});
if (isApp &&
nodeModulesDir &&
(! inNodeModules || sources.length > 0)) {
if (nodeModulesDir && (!inNodeModules || sources.length > 0)) {
// If we found a node_modules subdirectory above, and either we
// are not already inside another node_modules directory or we
// found source files elsewhere in this directory or its other
// subdirectories, and we're building an app (as opposed to a
// Meteor package), continue searching this node_modules
// directory, so that any non-.js(on) files it contains can be
// imported by the app (#6037).
// subdirectories, continue searching this node_modules directory,
// so that any non-.js(on) files it contains can be imported by
// the app (#6037).
sources.push(...find(nodeModulesDir, depth + 1, true));
}
@@ -1336,7 +1343,27 @@ _.extend(PackageSource.prototype, {
return sources;
}
return files.withCache(() => find("", 0, false));
const sources = find("", 0, false);
if (!isApp && typeof this.npmCacheDirectory === "string") {
// If this PackageSource has an npmCacheDirectory, scan it as well for
// sources that might need to be compiled.
const stat = optimisticStatOrNull(this.npmCacheDirectory);
if (stat && stat.isDirectory()) {
const relNpmDir = files.pathRelative(
this.sourceRoot,
this.npmCacheDirectory,
);
if (! relNpmDir.startsWith("..")) {
const relParts = relNpmDir.split("/");
const depth = relParts.length;
const inNodeModules = relParts.indexOf("node_modules") >= 0;
sources.push(...find(relNpmDir, depth, inNodeModules));
}
}
}
return sources;
}),
_findAssets({

View File

@@ -251,6 +251,7 @@ export class Unibuild {
// Figure out where the npm dependencies go.
let node_modules = {};
let nonLocalNodeModulesBundlePath;
_.each(unibuild.nodeModulesDirectories, nmd => {
const bundlePath = _.has(npmDirsToCopy, nmd.sourcePath)
// We already have this npm directory from another unibuild.
@@ -258,6 +259,9 @@ export class Unibuild {
: npmDirsToCopy[nmd.sourcePath] =
nmd.getPreferredBundlePath("isopack");
node_modules[bundlePath] = nmd.toJSON();
if (!nmd.local) {
nonLocalNodeModulesBundlePath = nonLocalNodeModulesBundlePath || bundlePath;
}
});
const preferredPaths = Object.keys(node_modules);
@@ -315,13 +319,37 @@ export class Unibuild {
});
// Output other resources each to their own file
_.each(unibuild.resources, function (resource) {
_.each(unibuild.resources, resource => {
if (_.contains(["head", "body"], resource.type)) {
// already did this one
return;
}
const generatedFilename =
let generatedFilename;
// Although non-local npm dependencies installed by Npm.depends start
// with relative paths like .npm/**/node_modules/*, the files are stored
// in the bundle under npm/node_modules, so we need to reroot relative
// .npm/ paths against the npm/node_modules path.
if (
unibuild.pkg.name &&
typeof nonLocalNodeModulesBundlePath === "string" &&
typeof resource.path === "string" &&
resource.path.startsWith(".npm/")
) {
const parts = resource.path.split("/");
const nmi = parts.indexOf("node_modules");
if (nmi >= 0) {
// Skip builder.writeToGeneratedFilename below since the file already
// (should) exist at this new generatedFilename path.
generatedFilename = files.pathJoin(
nonLocalNodeModulesBundlePath,
parts.slice(nmi + 1).join("/"),
);
}
}
generatedFilename = generatedFilename ||
builder.writeToGeneratedFilename(
files.pathJoin(
unibuildDir,

View File

@@ -1,6 +1,11 @@
{
"lockfileVersion": 1,
"dependencies": {
"@wry/context": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.4.0.tgz",
"integrity": "sha512-rVjwzFjVYXJ8pWJ8ZRCHv6meOebQvfTlvnUYUNX93Ce0KNeMTqCkf0GiOJc6BNVB96s7qfvwoLN3nUgDnSFOOg=="
},
"assert": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/assert/-/assert-1.3.0.tgz",

View File

@@ -5,6 +5,16 @@ export const ModulesTestPackage = "loaded";
import { parse } from "acorn";
assert.strictEqual(typeof parse, "function");
// Test that an npm package with a "module" entry point in its package.json
// file can be imported.
import { Slot } from "@wry/context";
assert.strictEqual(typeof Slot, "function");
const idPrefix = "/node_modules/meteor/modules-test-package/node_modules/@wry/context/lib/";
assert.strictEqual(
require.resolve("@wry/context"),
idPrefix + (Meteor.isClient ? "context.esm.js" : "context.js"),
);
export function checkWhere(where) {
const { where: serverWhere } = require("./server/where.js");
const { where: clientWhere } = require("./client/where.js");

View File

@@ -6,6 +6,7 @@ Package.describe({
});
Npm.depends({
"@wry/context": "0.4.0",
"os-browserify": "0.2.0",
"assert": "1.3.0",
"cheerio": "0.22.0"

View File

@@ -17,19 +17,19 @@
"integrity": "sha1-wM5mIMtfLB+xArIPZXQRVcq8REo="
},
"babel-runtime": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz",
"integrity": "sha1-CpSJ8UTecO+zzkMArM2zKeL8VDs="
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4="
},
"babel-types": {
"version": "6.25.0",
"resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.25.0.tgz",
"integrity": "sha1-cK+ySNVmDl0Y+BHZHIMDtUE0oY4="
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
"integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc="
},
"core-js": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.4.1.tgz",
"integrity": "sha1-TekR5mew6ukSTjQlS1OupvxhjT4="
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz",
"integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A=="
},
"esutils": {
"version": "2.0.2",
@@ -37,19 +37,29 @@
"integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
},
"lodash": {
"version": "4.17.4",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"to-fast-properties": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
"integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc="
},
"ts-invariant": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.1.tgz",
"integrity": "sha512-fdL8AZinDiVKMsOI0cOWHLprS85LWy2p/eVSctVe6fpZF9BAvO59sQYMEWQ37yybBtlKU2zkmILYmy1jrOf6+g=="
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
}
}
}

View File

@@ -14,6 +14,7 @@ Package.registerBuildPlugin({
npmDependencies: {
// TODO Figure out what to do about the duplication between this list
// and the Npm.depends list below.
"ts-invariant": "0.4.1",
"babel-plugin-transform-class-properties": "6.9.0",
"babel-plugin-transform-strict-mode": "6.8.0"
}

View File

@@ -1,4 +1,14 @@
import assert from "assert";
import { invariant } from "ts-invariant";
invariant(
typeof process.versions.node === "string",
"Meteor plugins should only run in Node.js",
);
invariant(
require.resolve("ts-invariant"),
"/node_modules/meteor/meteor-test-plugin/node_modules/ts-invariant/lib/invariant.js",
);
// This verifies that babel-plugin-transform-strict-mode is enabled.
let expected;
@@ -7,8 +17,8 @@ try {
} catch (e) {
expected = e;
}
assert.ok(expected instanceof TypeError);
assert.ok(/callee/.test(expected.message));
invariant(expected instanceof TypeError);
invariant(/callee/.test(expected.message), expected.message);
Plugin.registerCompiler({
extensions: ["arson"]
@@ -20,8 +30,8 @@ class ArsonCompiler {
expectedName = "compile-arson";
processFilesForTarget(inputFiles) {
assert.strictEqual(this.expectedName, "compile-arson");
assert.ok(inputFiles.length > 0);
invariant(this.expectedName === "compile-arson", this.expectedName);
invariant(inputFiles.length > 0);
let vueCheckCount = 0;
@@ -47,14 +57,12 @@ class ArsonCompiler {
const vueCompilerId = file.resolve("vue-template-compiler");
// Make sure resolution does not use the "browser" field of
// vue-template-compiler/package.json.
assert.strictEqual(
vueCompilerId.split("/").pop(),
"index.js"
);
const base = vueCompilerId.split("/").pop();
invariant(base === "index.js", base);
++vueCheckCount;
}
});
assert.ok(vueCheckCount > 0);
invariant(vueCheckCount > 0);
}
}