Skip to content

Commit

Permalink
Stop using module.createRequire (problematic under webpack), incorpor…
Browse files Browse the repository at this point in the history
…ate and export resolveModule helper from markdownlint-cli2.
  • Loading branch information
DavidAnson committed Jan 13, 2025
1 parent a6cc4cc commit 1461764
Show file tree
Hide file tree
Showing 16 changed files with 327 additions and 25 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ demo/markdownlint-browser.js
demo/markdownlint-browser.min.js
node_modules
!test/node_modules
!test/rules/node_modules
npm-debug.log
test-repos
.DS_Store
Expand Down
16 changes: 16 additions & 0 deletions lib/defer-require.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @ts-check

"use strict";

/**
* Calls require for markdownit.cjs. Used to synchronously defer loading because module.createRequire is buggy under webpack (https://github.com/webpack/webpack/issues/16724).
*
* @returns {any} Exported module content.
*/
function requireMarkdownItCjs() {
return require("./markdownit.cjs");
}

module.exports = {
requireMarkdownItCjs
};
1 change: 1 addition & 0 deletions lib/exports.d.mts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { resolveModule } from "./resolve-module.cjs";
export type Configuration = import("./markdownlint.mjs").Configuration;
export type ConfigurationParser = import("./markdownlint.mjs").ConfigurationParser;
export type ConfigurationStrict = import("./markdownlint.mjs").ConfigurationStrict;
Expand Down
1 change: 1 addition & 0 deletions lib/exports.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-check

export { applyFix, applyFixes, getVersion } from "./markdownlint.mjs";
export { resolveModule } from "./resolve-module.cjs";

/** @typedef {import("./markdownlint.mjs").Configuration} Configuration */
/** @typedef {import("./markdownlint.mjs").ConfigurationParser} ConfigurationParser */
Expand Down
20 changes: 9 additions & 11 deletions lib/markdownlint.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// @ts-check

// @ts-ignore
import { fs as nodeFs, module, os, path } from "#node-imports";
const dynamicRequire = module.createRequire(import.meta.url);
import { fs as nodeFs, os, path } from "#node-imports";
import { initialize as cacheInitialize } from "./cache.mjs";
import { version } from "./constants.mjs";
import { requireMarkdownItCjs } from "./defer-require.cjs";
import { resolveModule } from "./resolve-module.cjs";
import rules from "./rules.mjs";
import { parse as micromarkParse } from "./micromark-parse.mjs";
import * as helpers from "../helpers/helpers.cjs";
Expand Down Expand Up @@ -501,7 +502,7 @@ function lintContent(
// Parse content into lines and get markdown-it tokens
const lines = content.split(helpers.newLineRe);
const markdownitTokens = needMarkdownItTokens ?
dynamicRequire("./markdownit.cjs").getMarkdownItTokens(markdownItPlugins, preClearedContent, lines) :
requireMarkdownItCjs().getMarkdownItTokens(markdownItPlugins, preClearedContent, lines) :
[];
// Create (frozen) parameters for rules
/** @type {MarkdownParsers} */
Expand Down Expand Up @@ -1010,10 +1011,10 @@ function resolveConfigExtends(configFile, referenceId, fs, callback) {
if (err) {
// Not a file, try require.resolve
try {
return callback(null, dynamicRequire.resolve(
referenceId,
{ "paths": [ configFileDirname ] }
));
return callback(
null,
resolveModule(referenceId, [ configFileDirname ])
);
} catch {
// Unable to resolve, use resolvedExtendsFile
}
Expand Down Expand Up @@ -1041,10 +1042,7 @@ function resolveConfigExtendsSync(configFile, referenceId, fs) {
// Not a file, try require.resolve
}
try {
return dynamicRequire.resolve(
referenceId,
{ "paths": [ configFileDirname ] }
);
return resolveModule(referenceId, [ configFileDirname ]);
} catch {
// Unable to resolve, return resolvedExtendsFile
}
Expand Down
7 changes: 0 additions & 7 deletions lib/node-imports-browser-module.cjs

This file was deleted.

2 changes: 0 additions & 2 deletions lib/node-imports-browser.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export const fs = {
"readFileSync": throwForSync
};

export { default as module } from "./node-imports-browser-module.cjs";

export const os = {};

export const path = {
Expand Down
3 changes: 0 additions & 3 deletions lib/node-imports-node.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@
import { access, accessSync, readFile, readFileSync } from "node:fs";
export const fs = { access, accessSync, readFile, readFileSync };

import { createRequire } from "node:module";
export const module = { createRequire };

import { EOL, homedir } from "node:os";
export const os = { EOL, homedir };

Expand Down
52 changes: 52 additions & 0 deletions lib/resolve-module.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// @ts-check

"use strict";

// @ts-ignore
// eslint-disable-next-line camelcase, no-inline-comments, no-undef
const nativeRequire = (typeof __non_webpack_require__ === "undefined") ? require : /* c8 ignore next */ __non_webpack_require__;
// Captures the native require implementation (even under webpack).

/**
* @typedef RequireResolveOptions
* @property {string[]} [paths] Additional paths to resolve from.
*/

/**
* @callback RequireResolve
* @param {string} id Module name or path.
* @param {RequireResolveOptions} options Options to apply.
* @returns {string} Resolved module path.
*/

/**
* Resolves modules according to Node's resolution rules.
*
* @param {RequireResolve} resolve Node-like require.resolve implementation.
* @param {string} id Module name or path.
* @param {string[]} [paths] Additional paths to resolve from.
* @returns {string} Resolved module path.
*/
const resolveModuleCustomResolve = (resolve, id, paths = []) => {
// resolve.paths is sometimes not present under webpack or VS Code
// @ts-ignore
const resolvePaths = resolve.paths?.("") || [];
const allPaths = [ ...paths, ...resolvePaths ];
return resolve(id, { "paths": allPaths });
};

/**
* Resolves modules according to Node's resolution rules.
*
* @param {string} id Module name or path.
* @param {string[]} [paths] Additional paths to resolve from.
* @returns {string} Resolved module path.
*/
const resolveModule = (id, paths) => (
resolveModuleCustomResolve(nativeRequire.resolve, id, paths)
);

module.exports = {
resolveModule,
resolveModuleCustomResolve
};
34 changes: 34 additions & 0 deletions lib/resolve-module.d.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export type RequireResolveOptions = {
/**
* Additional paths to resolve from.
*/
paths?: string[];
};
export type RequireResolve = (id: string, options: RequireResolveOptions) => string;
/**
* Resolves modules according to Node's resolution rules.
*
* @param {string} id Module name or path.
* @param {string[]} [paths] Additional paths to resolve from.
* @returns {string} Resolved module path.
*/
export function resolveModule(id: string, paths?: string[]): string;
/**
* @typedef RequireResolveOptions
* @property {string[]} [paths] Additional paths to resolve from.
*/
/**
* @callback RequireResolve
* @param {string} id Module name or path.
* @param {RequireResolveOptions} options Options to apply.
* @returns {string} Resolved module path.
*/
/**
* Resolves modules according to Node's resolution rules.
*
* @param {RequireResolve} resolve Node-like require.resolve implementation.
* @param {string} id Module name or path.
* @param {string[]} [paths] Additional paths to resolve from.
* @returns {string} Resolved module path.
*/
export function resolveModuleCustomResolve(resolve: RequireResolve, id: string, paths?: string[]): string;
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"build-config": "npm run build-config-schema && npm run build-config-example",
"build-config-example": "node schema/build-config-example.mjs",
"build-config-schema": "node schema/build-config-schema.mjs",
"build-declaration": "tsc --allowJs --checkJs --declaration --emitDeclarationOnly --module nodenext --outDir dts --rootDir . --target es2015 lib/exports.mjs lib/exports-async.mjs lib/exports-promise.mjs lib/exports-sync.mjs lib/markdownlint.mjs && node scripts/index.mjs copy dts/lib/exports.d.mts lib/exports.d.mts && node scripts/index.mjs copy dts/lib/exports-async.d.mts lib/exports-async.d.mts && node scripts/index.mjs copy dts/lib/exports-promise.d.mts lib/exports-promise.d.mts && node scripts/index.mjs copy dts/lib/exports-sync.d.mts lib/exports-sync.d.mts && node scripts/index.mjs copy dts/lib/markdownlint.d.mts lib/markdownlint.d.mts && node scripts/index.mjs remove dts",
"build-declaration": "tsc --allowJs --checkJs --declaration --emitDeclarationOnly --module nodenext --outDir dts --rootDir . --target es2015 lib/exports.mjs lib/exports-async.mjs lib/exports-promise.mjs lib/exports-sync.mjs lib/markdownlint.mjs lib/resolve-module.cjs && node scripts/index.mjs copy dts/lib/exports.d.mts lib/exports.d.mts && node scripts/index.mjs copy dts/lib/exports-async.d.mts lib/exports-async.d.mts && node scripts/index.mjs copy dts/lib/exports-promise.d.mts lib/exports-promise.d.mts && node scripts/index.mjs copy dts/lib/exports-sync.d.mts lib/exports-sync.d.mts && node scripts/index.mjs copy dts/lib/markdownlint.d.mts lib/markdownlint.d.mts && node scripts/index.mjs copy dts/lib/resolve-module.d.cts lib/resolve-module.d.cts && node scripts/index.mjs remove dts",
"build-demo": "node scripts/index.mjs copy node_modules/markdown-it/dist/markdown-it.min.js demo/markdown-it.min.js && cd demo && webpack --no-stats",
"build-docs": "node doc-build/build-rules.mjs",
"build-example": "npm install --no-save --ignore-scripts grunt grunt-cli gulp through2",
Expand All @@ -59,7 +59,7 @@
"lint-test-repos": "ava --timeout=10m test/markdownlint-test-repos-*.mjs",
"serial-config-docs": "npm run build-config && npm run build-docs",
"serial-declaration-demo": "npm run build-declaration && npm-run-all --continue-on-error --parallel build-demo test-declaration",
"test": "ava --timeout=30s test/markdownlint-test.mjs test/markdownlint-test-config.mjs test/markdownlint-test-custom-rules.mjs test/markdownlint-test-fixes.mjs test/markdownlint-test-helpers.mjs test/markdownlint-test-micromark.mjs test/markdownlint-test-result-object.mjs test/markdownlint-test-scenarios.mjs helpers/test.cjs",
"test": "ava --timeout=30s test/markdownlint-test.mjs test/markdownlint-test-config.mjs test/markdownlint-test-custom-rules.mjs test/markdownlint-test-fixes.mjs test/markdownlint-test-helpers.mjs test/markdownlint-test-micromark.mjs test/markdownlint-test-result-object.mjs test/markdownlint-test-scenarios.mjs test/resolve-module-test.mjs helpers/test.cjs",
"test-cover": "c8 --100 npm test",
"test-declaration": "cd example/typescript && tsc --module commonjs && tsc --module nodenext && node type-check.js",
"test-extra": "ava --timeout=10m test/markdownlint-test-extra-parse.mjs test/markdownlint-test-extra-type.mjs",
Expand Down
147 changes: 147 additions & 0 deletions test/resolve-module-test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// @ts-check

import test from "ava";
import path from "node:path";
import { __dirname as getDirname } from "./esm-helpers.mjs";
import { resolveModule, resolveModuleCustomResolve } from "../lib/resolve-module.cjs";

import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
// eslint-disable-next-line no-underscore-dangle
const __dirname = getDirname(import.meta);

test("built-in module", (t) => {
t.plan(1);
t.deepEqual(
resolveModule("node:fs"),
require.resolve("node:fs")
);
});

test("locally-installed module", (t) => {
t.plan(1);
t.deepEqual(
resolveModule("micromark"),
require.resolve("micromark")
);
});

test("absolute path to module", (t) => {
t.plan(1);
const absolute =
path.resolve(
__dirname,
"./rules/node_modules/markdownlint-rule-sample-commonjs"
);
t.deepEqual(
resolveModule(absolute),
require.resolve(absolute)
);
});

test("relative (to __dirname) path to module", (t) => {
t.plan(1);
t.deepEqual(
resolveModule(
"./rules/node_modules/markdownlint-rule-sample-module",
// __dirname is needed because require.resolve is relative to this
// file while resolveModule is relative to resolve-module.cjs
[ __dirname ]
),
require.resolve(
"./rules/node_modules/markdownlint-rule-sample-module"
)
);
});

test("module in alternate node_modules", (t) => {
t.plan(3);
t.throws(
() => require.resolve("markdownlint-rule-sample-commonjs"),
{ "code": "MODULE_NOT_FOUND" }
);
t.throws(
() => resolveModule("markdownlint-rule-sample-commonjs"),
{ "code": "MODULE_NOT_FOUND" }
);
t.deepEqual(
resolveModule(
"markdownlint-rule-sample-commonjs",
[ path.join(__dirname, "rules") ]
),
require.resolve(
"markdownlint-rule-sample-commonjs",
{ "paths": [ path.join(__dirname, "rules") ] }
)
);
});

test("module local, relative, and in alternate node_modules (same paths)", (t) => {
t.plan(3);
const paths = [
__dirname,
path.join(__dirname, "rules")
];
t.deepEqual(
resolveModule(
"micromark",
paths
),
require.resolve(
"micromark",
{ paths }
)
);
t.deepEqual(
resolveModule(
"./rules/node_modules/markdownlint-rule-sample-commonjs",
paths
),
require.resolve(
"./rules/node_modules/markdownlint-rule-sample-commonjs",
{ paths }
)
);
t.deepEqual(
resolveModule(
"markdownlint-rule-sample-commonjs",
paths
),
require.resolve(
"markdownlint-rule-sample-commonjs",
{ paths }
)
);
});

test("custom resolve implementation", (t) => {
t.plan(1);
const expected =
require.resolve("./rules/node_modules/markdownlint-rule-sample-module");
const customResolve = (id, options) => require.resolve(id, options);
customResolve.paths = (request) => require.resolve.paths(request);
t.deepEqual(
resolveModuleCustomResolve(
customResolve,
"./rules/node_modules/markdownlint-rule-sample-module",
[ __dirname ]
),
expected
);
});

test("custom resolve implementation, missing paths", (t) => {
t.plan(1);
const expected =
require.resolve("./rules/node_modules/markdownlint-rule-sample-commonjs");
const customResolve = (id, options) => require.resolve(id, options);
t.deepEqual(
resolveModuleCustomResolve(
// @ts-ignore
customResolve,
"./rules/node_modules/markdownlint-rule-sample-commonjs",
[ __dirname ]
),
expected
);
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1461764

Please sign in to comment.