Skip to content

Commit

Permalink
refactor: [NodeModule] encapsulate node module utils.
Browse files Browse the repository at this point in the history
  • Loading branch information
avocadowastaken committed Jul 14, 2021
1 parent c18e9d2 commit bd03b0b
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 114 deletions.
66 changes: 66 additions & 0 deletions lib/internal/NodeModule.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
"use strict";

const assert = require("assert");
const resolve = require("resolve");

/** @type {Map<string, NodeModule>} */
const cache = new Map();

class NodeModule {
/**
* @param {string} id
* @param {string} [basedir]
* @returns {null | string}
*/
static resolve(id, basedir) {
try {
return resolve.sync(id, {
basedir,
packageFilter(pkg) {
return {
...pkg,
main: pkg.module || pkg.esnext || pkg["jsnext:main"] || pkg.main,
};
},
});
} catch (error) {
if (
error instanceof Error &&
/** @type {NodeJS.ErrnoException} */ (error).code === "MODULE_NOT_FOUND"
) {
return null;
}

throw error;
}
}

/**
* @param {string} id
* @returns {NodeModule}
*/
static get(id) {
let module = cache.get(id);

if (!module) {
const entry = this.resolve(id);
assert.ok(entry, `failed to find entry file of '${id}'.`);
module = new NodeModule(id, entry);
cache.set(id, module);
}

return module;
}

/**
* @param {string} id
* @param {string} entry
* @protected
*/
constructor(id, entry) {
this.id = id;
this.entry = entry;
}
}

module.exports = NodeModule;
52 changes: 52 additions & 0 deletions lib/internal/NodeModule.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"use strict";

const path = require("path");
const NodeModule = require("./NodeModule");

describe(".resolve", () => {
test("basic", () => {
expect(NodeModule.resolve("lodash")).toBe(
path.join(process.cwd(), "node_modules", "lodash", "lodash.js")
);

expect(NodeModule.resolve("lodash/map")).toBe(
path.join(process.cwd(), "node_modules", "lodash", "map.js")
);

expect(NodeModule.resolve("lodash/hey")).toBeNull();
});

test("errors", () => {
expect(() => {
// @ts-expect-error
NodeModule.resolve(null);
}).toThrowErrorMatchingInlineSnapshot('"Path must be a string."');
});
});

describe(".get", () => {
test("basic", () => {
expect(NodeModule.get("luxon")).toEqual({
id: "luxon",
entry: path.join(
process.cwd(),
"node_modules",
"luxon",
"src",
"luxon.js"
),
});

expect(NodeModule.get("@material-ui/core")).toEqual({
id: "@material-ui/core",
entry: path.join(
process.cwd(),
"node_modules",
"@material-ui",
"core",
"esm",
"index.js"
),
});
});
});
13 changes: 7 additions & 6 deletions lib/internal/PluginOptions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";

const assert = require("assert");
const NodeModule = require("./NodeModule");

/** @type {WeakMap<object, PluginOptions>} */
const pluginOptionsCache = new WeakMap();
Expand Down Expand Up @@ -32,17 +33,17 @@ class PluginOptions {
);

const pluginOptions = new PluginOptions(
new Set(
modules.map((module, idx) => {
new Map(
modules.map((id, idx) => {
const optionPath = `options.modules[${idx}]`;

assert.ok(
typeof module == "string",
typeof id == "string",
`invalid '${optionPath}': not a 'string'`
);
assert.ok(!!module.length, `invalid '${optionPath}': value is empty`);
assert.ok(!!id.length, `invalid '${optionPath}': value is empty`);

return module;
return [id, NodeModule.get(id)];
})
)
);
Expand All @@ -53,7 +54,7 @@ class PluginOptions {
}

/**
* @param {Set<string>} modules
* @param {Map<string, NodeModule>} modules
* @protected
*/
constructor(modules) {
Expand Down
46 changes: 45 additions & 1 deletion lib/internal/PluginOptions.spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,55 @@
"use strict";

const path = require("path");
const PluginOptions = require("./PluginOptions.js");

test.each([
[
["luxon", "@material-ui/core", "@material-ui/core/Button"],
new Set(["luxon", "@material-ui/core", "@material-ui/core/Button"]),
new Map([
[
"luxon",
{
id: "luxon",
entry: path.join(
process.cwd(),
"node_modules",
"luxon",
"src",
"luxon.js"
),
},
],
[
"@material-ui/core",
{
id: "@material-ui/core",
entry: path.join(
process.cwd(),
"node_modules",
"@material-ui",
"core",
"esm",
"index.js"
),
},
],
[
"@material-ui/core/Button",
{
id: "@material-ui/core/Button",
entry: path.join(
process.cwd(),
"node_modules",
"@material-ui",
"core",
"esm",
"Button",
"index.js"
),
},
],
]),
],
])("parses %j -> %j", (input, modules) => {
expect(PluginOptions.parse({ modules: input })).toEqual({ modules });
Expand Down
17 changes: 8 additions & 9 deletions lib/internal/getModuleExports.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
const fs = require("fs");
const path = require("path");
const assert = require("assert");
const resolveModule = require("./resolveModule");
const resolvePackageEntry = require("./resolvePackageEntry");
const NodeModule = require("./NodeModule");

/**
* @typedef {object} ModuleExport
Expand Down Expand Up @@ -53,7 +52,7 @@ function* traverseDeep(filename, babel) {

for (const node of ast.program.body) {
if (t.isImportDeclaration(node)) {
const sourcePath = resolveModule(node.source.value, dir);
const sourcePath = NodeModule.resolve(node.source.value, dir);

if (sourcePath) {
for (const specifier of node.specifiers) {
Expand Down Expand Up @@ -94,7 +93,8 @@ function* traverseDeep(filename, babel) {
}
}

const sourcePath = node.source && resolveModule(node.source.value, dir);
const sourcePath =
node.source && NodeModule.resolve(node.source.value, dir);

for (const specifier of node.specifiers) {
if (t.isExportSpecifier(specifier)) {
Expand Down Expand Up @@ -125,7 +125,7 @@ function* traverseDeep(filename, babel) {
}
}
} else if (t.isExportAllDeclaration(node)) {
const sourcePath = resolveModule(node.source.value, dir);
const sourcePath = NodeModule.resolve(node.source.value, dir);

assert.ok(
sourcePath,
Expand All @@ -151,16 +151,15 @@ function* traverseDeep(filename, babel) {
}

/**
* @param {string} name
* @param {NodeModule} nodeModule
* @param {import('@babel/core')} babel
* @returns {Map<string, ModuleExport>}
*/
module.exports = function getModuleExports(name, babel) {
module.exports = function getModuleExports(nodeModule, babel) {
/** @type {Map<string, ModuleExport>} */
const exports = new Map();
const packageEntry = resolvePackageEntry(name);

for (const entry of traverseDeep(packageEntry, babel)) {
for (const entry of traverseDeep(nodeModule.entry, babel)) {
exports.set(entry.external, entry);
}

Expand Down
31 changes: 0 additions & 31 deletions lib/internal/resolveModule.js

This file was deleted.

21 changes: 0 additions & 21 deletions lib/internal/resolveModule.spec.js

This file was deleted.

16 changes: 0 additions & 16 deletions lib/internal/resolvePackageEntry.js

This file was deleted.

21 changes: 0 additions & 21 deletions lib/internal/resolvePackageEntry.spec.js

This file was deleted.

Loading

0 comments on commit bd03b0b

Please sign in to comment.