mirror of
https://github.com/marko-js/marko.git
synced 2025-12-08 19:26:05 +00:00
* add prettierignore * switch to eslint:recommended + eslint-config-prettier * fix eslint violations * remove more .jshintrc files * better conditional structure * add prettier and update prettier ignore * add precommit hook to run prettier * add lint check to precommit and format check to ci * format all the things * add generated files * let npm do it's thing with package.json
188 lines
5.6 KiB
JavaScript
188 lines
5.6 KiB
JavaScript
"use strict";
|
|
|
|
// Someday this will become a separate package.
|
|
const vm = require("vm");
|
|
const path = require("path");
|
|
const Module = require("module");
|
|
const builtinModules = require("builtins")();
|
|
const originalCompile = Module.prototype._compile;
|
|
const originalResolve = Module._resolveFilename;
|
|
const S_POSTFIX = Symbol("postfix");
|
|
const S_CONTEXT = Symbol("context");
|
|
const S_RESOLVE = Symbol("resolve");
|
|
const S_HOOKS = Symbol("hooks");
|
|
const EXT_NAME = ".in-module-context";
|
|
let moduleId = 0;
|
|
|
|
class ContextModule extends Module {
|
|
/**
|
|
* Custom nodejs Module implementation which uses a provided
|
|
* resolver, require hooks, and context.
|
|
*/
|
|
constructor(options) {
|
|
const postfix = "." + moduleId++ + EXT_NAME;
|
|
const filename = path.join(options.dir, "index" + postfix);
|
|
super(filename);
|
|
|
|
this.filename = filename;
|
|
this[S_POSTFIX] = postfix;
|
|
this[S_CONTEXT] = options.context;
|
|
this[S_RESOLVE] = options.resolve;
|
|
this[S_HOOKS] = options.extensions;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Patch nodejs module system to support context,
|
|
* compilation and module resolution overrides.
|
|
*/
|
|
require.extensions[EXT_NAME] = requireHook;
|
|
Module._resolveFilename = resolveFileHook;
|
|
Module.prototype._compile = compileHook;
|
|
|
|
// Expose main util.
|
|
exports.createModule = createContextModule;
|
|
|
|
/**
|
|
* Creates a custom Module object which runs all required scripts in a provided vm context.
|
|
*
|
|
* @param {object} config Config for the module.
|
|
* @param {string} config.dir The directory from which to resolve requires for this module.
|
|
* @param {*|JSDOM} config.context A vm context which will be used as the context for any required modules.
|
|
* @param {function} [config.resolve] A function to which will override the native module resolution.
|
|
* @param {Object.<string,function>} [config.extensions] An object containing any context specific require hooks to be used in this module.
|
|
* @return {ContextModule}
|
|
*/
|
|
function createContextModule(options) {
|
|
return new ContextModule(options);
|
|
}
|
|
|
|
/**
|
|
* Hijack native file resolution using closest custom resolver.
|
|
*
|
|
* @param {string} request The file to resolve.
|
|
* @param {Module} parentModule The module requiring this file.
|
|
* @return {string}
|
|
*/
|
|
function resolveFileHook(request, parentModule) {
|
|
const isNotBuiltin = builtinModules.indexOf(request) === -1;
|
|
const contextModule =
|
|
isNotBuiltin && findNearestContextModule(parentModule);
|
|
|
|
if (contextModule) {
|
|
const resolver = contextModule[S_RESOLVE];
|
|
if (resolver) {
|
|
const dir = path.dirname(parentModule.filename);
|
|
const postfix = contextModule[S_POSTFIX];
|
|
|
|
if (request.endsWith(postfix)) {
|
|
return request;
|
|
}
|
|
|
|
if (path.isAbsolute(request)) {
|
|
request = path.relative(dir, request);
|
|
if (request[0] !== ".") {
|
|
request = "./" + request;
|
|
}
|
|
}
|
|
|
|
return resolver(dir, request) + postfix;
|
|
}
|
|
}
|
|
|
|
return originalResolve(request, parentModule);
|
|
}
|
|
|
|
/**
|
|
* Require hook which removes module postfix and uses custom extensions if provided.
|
|
*
|
|
* @param {Module} module
|
|
* @param {string} filename
|
|
*/
|
|
function requireHook(module, filename) {
|
|
const contextModule = findNearestContextModule(module);
|
|
const postfix = contextModule[S_POSTFIX];
|
|
const extensions = contextModule[S_HOOKS];
|
|
filename = filename.slice(0, -postfix.length);
|
|
const ext = path.extname(filename);
|
|
const compiler =
|
|
(extensions && extensions[ext]) ||
|
|
require.extensions[ext] ||
|
|
require.extensions[".js"];
|
|
return compiler(module, filename);
|
|
}
|
|
|
|
/**
|
|
* This overrides script compilation to ensure the nearest context module is used.
|
|
*
|
|
* @param {string} content The file contents of the script.
|
|
* @param {string} filename The filename for the script.
|
|
*/
|
|
function compileHook(content, filename) {
|
|
const contextModule = findNearestContextModule(this);
|
|
|
|
if (contextModule) {
|
|
const context = contextModule[S_CONTEXT];
|
|
const script = new vm.Script(Module.wrap(content), {
|
|
filename: filename,
|
|
lineOffset: 0,
|
|
displayErrors: true
|
|
});
|
|
|
|
return runScript(context, script).call(
|
|
this.exports,
|
|
this.exports,
|
|
createRequire(this),
|
|
this,
|
|
filename,
|
|
path.dirname(filename)
|
|
);
|
|
}
|
|
|
|
return originalCompile.apply(this, arguments);
|
|
}
|
|
|
|
/**
|
|
* Walks up a module tree to find the nearest context module.
|
|
*
|
|
* @param {Module} cur The starting module.
|
|
* @return {Module?}
|
|
*/
|
|
function findNearestContextModule(cur) {
|
|
do {
|
|
if (cur instanceof ContextModule) {
|
|
return cur;
|
|
}
|
|
} while ((cur = cur.parent));
|
|
}
|
|
|
|
/**
|
|
* Helper which will run a vm script in a context.
|
|
* Special case for JSDOM where `runVMScript` is used.
|
|
*
|
|
* @param {*} context The vm context to run the script in (or a jsdom instance).
|
|
* @param {Script} script The vm script to run.
|
|
* @return {object}
|
|
*/
|
|
function runScript(context, script) {
|
|
return context.runVMScript
|
|
? context.runVMScript(script)
|
|
: script.runInContext(context);
|
|
}
|
|
|
|
/**
|
|
* Creates a require function bound to a module
|
|
* and adds a `resolve` function the same as nodejs.
|
|
*
|
|
* @param {Module} module The module to create a require function for.
|
|
* @return {function}
|
|
*/
|
|
function createRequire(module) {
|
|
const require = module.require.bind(module);
|
|
require.resolve = function(request) {
|
|
return resolveFileHook(request, module);
|
|
};
|
|
|
|
return require;
|
|
}
|