mirror of
https://github.com/jsdoc/jsdoc.git
synced 2025-12-08 19:46:11 +00:00
JSDoc has a few requirements that are somewhat unusual for a Node.js app:
1. We need `require('jsdoc/foo')` to work from any module.
2. We need `require('jsdoc/foo')` to work from external code, such as templates and plugins.
Prior to this commit, JSDoc did two separate things to meet these requirements:
1. Use an npm post-install script to create a symlink from `lib/jsdoc` to `node_modules/jsdoc`.
2. When a user runs JSDoc, copy templates and plugins into the JSDoc directory.
These fixes worked, sort of. But they also caused numerous issues with file permissions, especially on Windows.
We now use the Requizzle module, which hacks the Node.js module system to support JSDoc's use cases. There's no longer a post-install script, and there's no need for a symlink in `node_modules`.
168 lines
4.4 KiB
JavaScript
168 lines
4.4 KiB
JavaScript
/*
|
|
Copyright (c) 2014 Google Inc. All rights reserved.
|
|
Copyright (c) 2012-2013 Johannes Ewald.
|
|
|
|
Use of this source code is governed by the MIT License, available in this package's LICENSE file
|
|
or at http://opensource.org/licenses/MIT.
|
|
*/
|
|
'use strict';
|
|
|
|
var _ = require('underscore');
|
|
var path = require('path');
|
|
var Module = require('module');
|
|
|
|
var originalWrapper = Module.wrapper.slice(0);
|
|
|
|
function wrap(wrappers, script) {
|
|
return wrappers[0] + script + wrappers[1];
|
|
}
|
|
|
|
function replaceWrapper(wrapperObj) {
|
|
var joiner = '\n';
|
|
var before = wrapperObj.before.join(joiner);
|
|
var after = wrapperObj.after.join(joiner);
|
|
var wrappers = [
|
|
originalWrapper[0] + before,
|
|
after + originalWrapper[1]
|
|
];
|
|
|
|
Module.wrap = wrap.bind(null, wrappers);
|
|
}
|
|
|
|
function restoreWrapper() {
|
|
Module.wrap = wrap.bind(null, originalWrapper);
|
|
}
|
|
|
|
function createModule(targetPath, parentModule, moduleCache) {
|
|
moduleCache[targetPath] = moduleCache[targetPath] || new Module(targetPath, parentModule);
|
|
|
|
return moduleCache[targetPath];
|
|
}
|
|
|
|
/**
|
|
* Wrapper for `require()` to prevent the target module's dependencies from being swizzled.
|
|
*
|
|
* @param {!Module} targetModule - The module that is being swizzled.
|
|
* @param {!function} nodeRequire - The original `require()` method for the target module.
|
|
* @param {!string} filepath - The value passed to `require()`.
|
|
* @return {!Module} The requested module dependency.
|
|
*/
|
|
function requireProxy(targetModule, nodeRequire, filepath) {
|
|
restoreWrapper();
|
|
targetModule.require = nodeRequire;
|
|
|
|
return nodeRequire.call(targetModule, filepath);
|
|
}
|
|
|
|
/**
|
|
* Wrapper for `require()` to swizzle the target module's dependencies, using the same settings as
|
|
* the target module.
|
|
*
|
|
* @param {!Module} targetModule - The module that is being swizzled.
|
|
* @param {!Object} opts - The Requizzle options object.
|
|
* @param {!string} filepath - The value passed to `require()`.
|
|
* @return {!Module} The requested module dependency.
|
|
*/
|
|
function infectProxy(targetModule, cache, opts, filepath) {
|
|
var moduleExports;
|
|
var Requizzle = require('./requizzle');
|
|
var requizzle;
|
|
|
|
opts = _.clone(opts);
|
|
opts.parent = targetModule;
|
|
requizzle = new Requizzle(opts, cache);
|
|
|
|
moduleExports = requizzle.requizzle(filepath);
|
|
|
|
return moduleExports;
|
|
}
|
|
|
|
var load = exports.load = function load(targetPath, parentModule, wrapper, cache, options) {
|
|
var nodeRequire;
|
|
var targetModule;
|
|
|
|
// Handle circular requires, and avoid reloading modules unnecessarily
|
|
if (cache.module[targetPath]) {
|
|
return cache.module[targetPath];
|
|
}
|
|
|
|
targetModule = createModule(targetPath, parentModule, cache.module);
|
|
nodeRequire = targetModule.require;
|
|
|
|
if (options.infect) {
|
|
targetModule.require = function(filepath) {
|
|
return infectProxy(targetModule, cache, options, filepath);
|
|
};
|
|
} else {
|
|
targetModule.require = function(filepath) {
|
|
return requireProxy(targetModule, nodeRequire, filepath);
|
|
};
|
|
}
|
|
|
|
// update the wrapper before we load the target module
|
|
replaceWrapper(wrapper);
|
|
|
|
targetModule.load(targetModule.id);
|
|
|
|
// make sure the wrapper is restored even if the target module doesn't load any dependencies
|
|
restoreWrapper();
|
|
|
|
return targetModule;
|
|
};
|
|
|
|
/**
|
|
* Check whether the entire module includes a `'use strict'` declaration.
|
|
*
|
|
* @param {string} src - The source file to check.
|
|
* @return {boolean} Set to `true` if the module includes a `use strict` declaration.
|
|
*/
|
|
function detectStrictMode(src) {
|
|
return (/^\s*(?:["']use strict["'])[ \t]*(?:[\r\n]|;)/g).test(src);
|
|
}
|
|
|
|
function loadSource(targetPath, sourceCache) {
|
|
var fs = require('fs');
|
|
|
|
if (sourceCache[targetPath] === undefined) {
|
|
sourceCache[targetPath] = fs.readFileSync(targetPath, 'utf8');
|
|
}
|
|
|
|
return sourceCache[targetPath];
|
|
}
|
|
|
|
exports.createWrapper = function createWrapper(targetPath, parentModule, cache, options) {
|
|
var src;
|
|
var wrapperObject = {
|
|
before: [],
|
|
after: []
|
|
};
|
|
|
|
function add(wrapperFunctions, opts) {
|
|
var params = [targetPath, parentModule, opts];
|
|
|
|
['before', 'after'].forEach(function(item) {
|
|
var result = wrapperFunctions[item].apply(null, params);
|
|
|
|
if (result) {
|
|
wrapperObject[item].push(result);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Preserve the module's `use strict` declaration if present
|
|
src = loadSource(targetPath, cache.source);
|
|
if (detectStrictMode(src) === true) {
|
|
add(require('./wrappers/strict'));
|
|
}
|
|
|
|
if (options.requirePaths) {
|
|
add(require('./wrappers/requirepaths'), options.requirePaths);
|
|
}
|
|
|
|
if (options.extras) {
|
|
add(require('./wrappers/extras'), options.extras);
|
|
}
|
|
|
|
return wrapperObject;
|
|
};
|