Skip to content

Commit

Permalink
refactor: replace module-deps-sortable on own implementation to contr…
Browse files Browse the repository at this point in the history
…ol how parse files

BREAKING CHANGE:
all Extensions whould contains '.' so that mean if you have just 'ts' then need to convert to '.ts'
  • Loading branch information
anthony-redFox committed Nov 11, 2021
1 parent e3c59d7 commit abb781a
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 353 deletions.
4 changes: 2 additions & 2 deletions __tests__/lib/input/dependency.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ test('dependency', async function () {
});
{
const dependencies = await dependency([paths['index.js']], {
parseExtension: ['js']
parseExtension: ['.js']
});
expect(dependencies.length).toEqual(1);
}
{
const dependencies = await dependency([paths['requires.js']], {
parseExtension: ['js']
parseExtension: ['.js']
});
expect(dependencies.length).toEqual(2);
}
Expand Down
7 changes: 1 addition & 6 deletions docs/NODE_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,9 @@ Formats documentation as HTML.

```javascript
var documentation = require('documentation');
var streamArray = require('stream-array');
var vfs = require('vinyl-fs');

documentation.build(['index.js'])
.then(documentation.formats.html)
.then(output => {
streamArray(output).pipe(vfs.dest('./output-directory'));
});
.then(documentation.formats.html);
```

Returns **[Promise][21]<[Array][17]<[Object][19]>>** Promise with results
Expand Down
488 changes: 200 additions & 288 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"@babel/types": "^7.14.1",
"chalk": "^4.1.2",
"chokidar": "^3.4.0",
"concat-stream": "^2.0.0",
"diff": "^5.0.0",
"doctrine-temporary-fork": "2.1.0",
"git-url-parse": "^11.1.2",
Expand All @@ -30,7 +29,6 @@
"mdast-util-find-and-replace": "^2.1.0",
"mdast-util-inject": "^1.1.0",
"micromark-util-character": "^1.1.0",
"module-deps-sortable": "^5.0.3",
"parse-filepath": "^1.0.2",
"pify": "^5.0.0",
"read-pkg-up": "^9.0.0",
Expand All @@ -39,8 +37,9 @@
"remark-html": "^15.0.0",
"remark-reference-links": "^6.0.0",
"remark-toc": "^8.0.0",
"detective": "^5.2.0",
"konan": "^2.1.1",
"resolve": "^1.8.1",
"stream-array": "^1.1.2",
"strip-json-comments": "^4.0.0",
"unist-builder": "^3.0.0",
"unist-util-visit": "^4.1.0",
Expand All @@ -59,6 +58,7 @@
"documentation-schema": "0.0.1",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"concat-stream": "^2.0.0",
"fs-extra": "^10.0.0",
"husky": "^4.3.8",
"jest": "^27.1.0",
Expand Down
2 changes: 1 addition & 1 deletion src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const defaultConfig = {
// package.json ignored and don't get project infromation
'no-package': false,
// Extenstions which by dafault are parse
parseExtension: ['mjs', 'js', 'jsx', 'es5', 'es6', 'vue', 'ts', 'tsx']
parseExtension: ['.mjs', '.js', '.jsx', '.es5', '.es6', '.vue', '.ts', '.tsx']
};

function normalaze(config, global) {
Expand Down
56 changes: 9 additions & 47 deletions src/input/dependency.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import mdeps from 'module-deps-sortable';
import path from 'path';
import concat from 'concat-stream';
import mdeps from './moduleDeps.js';
import internalOnly from '../module_filters.js';
import smartGlob from '../smart_glob.js';

/**
* Returns a readable stream of dependencies, given an array of entry
* Returns a array of dependencies, given an array of entry
* points and an object of options to provide to module-deps.
*
* This stream requires filesystem access, and thus isn't suitable
Expand All @@ -15,55 +13,19 @@ import smartGlob from '../smart_glob.js';
* @param config optional options passed
* @returns results
*/
export default function dependencyStream(indexes, config) {
const md = mdeps({
export default async function dependencyStream(
indexes,
{ parseExtension = [], requireExtension = [] }
) {
const md = await mdeps(smartGlob(indexes, parseExtension), {
/**
* Determine whether a module should be included in documentation
* @param {string} id path to a module
* @returns {boolean} true if the module should be included.
*/
filter: id => internalOnly(id),
extensions: []
.concat(config.requireExtension || [])
.map(ext => '.' + ext.replace(/^\./, ''))
.concat(['.mjs', '.js', '.json', '.es6', '.jsx']),
resolve:
config.resolve === 'node' &&
((id, opts, cb) => {
const r = require('resolve');
opts.basedir = path.dirname(opts.filename);
r(id, opts, cb);
})
extensions: [...parseExtension, ...requireExtension]
});
smartGlob(indexes, config.parseExtension).forEach(index => {
md.write(path.resolve(index));
});
md.end();

return new Promise((resolve, reject) => {
md.once('error', reject);
md.pipe(
concat(function (inputs) {
resolve(
inputs
.filter(
input =>
// At this point, we may have allowed a JSON file to be caught by
// module-deps, or anything else allowed by requireExtension.
// otherwise module-deps would complain about
// it not being found. But Babel can't parse JSON, so we filter non-JavaScript
// files away.
config.parseExtension.indexOf(
path.extname(input.file).replace(/^\./, '')
) > -1
)
.map(input => {
// remove source file, since it's transformed anyway
delete input.source;
return input;
})
);
})
);
});
return md;
}
114 changes: 114 additions & 0 deletions src/input/moduleDeps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import path from 'path';
import util from 'util';
import { readFile } from 'fs/promises';
import r from 'resolve';
import detective from 'detective';
import konan from 'konan';

// const parseExst = ['.js', '.mjs', '.jsx', '.vue', '.ts', '.tsx'];
const resolveExst = ['.json', '.css', '.less', '.sass'];
const resolve = util.promisify(r);

class Deps {
constructor(opts = {}) {
this.fileCache = opts.fileCache || {};
this.visited = {};
this.res = [];

this.options = { ...opts };
}

async flush(input) {
const promises = input.map(file => {
const dir = path.dirname(file);
return this.walk(file, {
basedir: dir,
filename: 'root'
});
});
await Promise.all(promises);

return this.res;
}

async readFile(file) {
if (this.fileCache[file]) {
return this.fileCache[file];
}
return readFile(file, {
encoding: 'utf8'
});
}

async walk(id, parent) {
const extensions = this.options.extensions;
const sortKey = parent.sortKey || '';
let file = null;

try {
file = await resolve(id, { ...parent, extensions });
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
console.warn(`module not found: "${id}" from file ${parent.filename}`);
return;
}
throw err;
}

if (this.visited[file] || resolveExst.includes(path.extname(file))) {
return file;
}
this.visited[file] = true;

const source = await this.readFile(file);
const depsArray = this.parseDeps(file, source);
if (!depsArray) {
return file;
}

const deps = {};
const promises = depsArray.map(async (id, i) => {
const filter = this.options.filter;
if (filter && !filter(id)) {
deps[id] = false;
return;
}
const number = i.toString().padStart(8, '0');
deps[id] = await this.walk(id, {
filename: file,
basedir: path.dirname(file),
sortKey: sortKey + '!' + file + ':' + number
});
});

await Promise.all(promises);

this.res.push({
id: file,
source,
deps,
file,
sortKey: sortKey + '!' + file
});
return file;
}

parseDeps(file, src) {
try {
try {
return konan(src).strings;
} catch (ex) {
// konan does not support Vue (component) file, try to parse using detective (as a fallback)
return detective(src);
}
} catch (ex) {
console.error(`Parsing file ${file}: ${ex}`);
return;
}
}
}

export default async function (input = [], opts = {}) {
const dep = new Deps(opts);
return dep.flush(Array.from(new Set(input)));
}
7 changes: 1 addition & 6 deletions src/output/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,9 @@ import mergeConfig from '../merge_config.js';
* @public
* @example
* var documentation = require('documentation');
* var streamArray = require('stream-array');
* var vfs = require('vinyl-fs');
*
* documentation.build(['index.js'])
* .then(documentation.formats.html)
* .then(output => {
* streamArray(output).pipe(vfs.dest('./output-directory'));
* });
* .then(documentation.formats.html);
*/
export default async function html(comments, localConfig = {}) {
const config = await mergeConfig(localConfig);
Expand Down

3 comments on commit abb781a

@mmomtchev
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@anthony-redFox after this PR, if I have

require('something.unknownext`)

it will get parsed, before this PR this was not the case, is this intended?

@anthony-redFox
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll take a look

@anthony-redFox
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mmomtchev Did you use external option? if yes then it is expected because just to support external resources is difficult because now days package.json can contains many entry points which valid for cjs, ES Modules, browser entry point.
So as BREKING CHANGES you can just add you external library as input and you will get the same result if the code just parse package.json and try to guess which file need to parse.

Please sign in to comment.