UNPKG

3.78 kBJavaScriptView Raw
1var detective = require('detective'),
2 detectiveAmd = require('detective-amd'),
3 gmt = require('module-definition'),
4 required = require('required'),
5 fs = require('fs'),
6 path = require('path'),
7 q = require('q');
8
9// Calls the passed callback with a list of candidtate root filenames
10module.exports = function (directory, opt, cb) {
11 // opt is an optional configuration object
12 if (typeof opt === 'function') {
13 cb = opt;
14 opt = {};
15 } else {
16 opt = opt || {};
17 }
18
19 var jsFiles = getAllJSFiles(directory, opt);
20
21 // Get all files that are not depended on
22 getIndependentJSFiles(jsFiles)
23 .done(function (jsFiles) {
24 cb && cb(jsFiles);
25 });
26};
27
28function getIndependentJSFiles(jsFiles) {
29
30 // For each file, mark its non-core dependencies as used
31 return q.all(jsFiles.map(getDependencies))
32 .then(function (results) {
33 var filesUsed = {};
34
35 results.forEach(function (deps, idx) {
36 // Files with no dependencies are useless and should not be roots
37 if (! deps || ! deps.length) {
38 filesUsed[jsFiles[idx]] = true;
39
40 } else {
41 deps.forEach(function (dep) {
42 if (dep.core) return;
43
44 filesUsed[dep.filename] = true;
45 });
46 }
47 });
48
49 // Return all unused js files
50 return jsFiles.filter(function (jsFile) {
51 return typeof filesUsed[jsFile] === 'undefined';
52 });
53 });
54}
55
56// Resolve with a list of dependencies for the given file
57function getDependencies(jsFile) {
58 return getModuleType(jsFile)
59 // Configure required options
60 .then(function (moduleType) {
61 return {
62 detective: getAppropriateDetective(moduleType),
63 ignoreMissing: true
64 };
65 })
66 // Use required to get the file's dependency list
67 .then(function (options) {
68 var deferred = q.defer();
69
70 required(jsFile, options, function (err, deps) {
71 if (err) console.log(jsFile, err);
72
73 deferred.resolve(deps || []);
74 });
75
76 return deferred.promise;
77 });
78}
79
80// Returns the detective function appropriate to
81// the given module type
82// Precond: moduleType is 'commonjs' or 'amd'
83function getAppropriateDetective(moduleType) {
84 switch (moduleType) {
85 case 'commonjs':
86 return detective;
87 case 'amd':
88 return detectiveAmd;
89 }
90
91 return null;
92}
93
94// Promisified wrapper of gmt
95function getModuleType(jsFile) {
96 var deferred = q.defer();
97
98 gmt(jsFile, function (moduleType) {
99 deferred.resolve(moduleType);
100 });
101
102 return deferred.promise;
103}
104
105// Returns a list of all JavaScript filepaths
106// relative to the given directory
107function getAllJSFiles(directory, opt) {
108 var jsFilePaths = [];
109
110 fs.readdirSync(directory).forEach(function (filename) {
111 var fullName = directory + '/' + filename,
112 isDirectory = fs.lstatSync(fullName).isDirectory(),
113 ext = path.extname(filename);
114
115 if (isDirectory) {
116 if (opt.ignore && ! shouldBeIgnored(filename, opt.ignore) ||
117 (! opt.ignore || ! opt.ignore.length)) {
118
119 jsFilePaths = jsFilePaths.concat(getAllJSFiles(fullName, opt));
120 }
121 } else if (ext === '.js') {
122 jsFilePaths.push(fullName);
123 }
124 });
125
126 return jsFilePaths;
127}
128
129// This is shared with node-unique-extensions but should be
130// within exposed within its own module.
131function shouldBeIgnored(filename, exclusions) {
132 var result = false;
133
134 exclusions = exclusions || [];
135
136 for (var i = 0, l = exclusions.length; i < l; i++) {
137 // If any part of the file's name (absolute or relative)
138 // contains an excluded folder, it should be ignored
139 if (filename.indexOf(exclusions[i]) !== -1) {
140 result = true;
141 break;
142 }
143 }
144
145 return result;
146}
\No newline at end of file