-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
136 lines (125 loc) · 4.01 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
const path = require('path');
const fs = require('fs-extra');
const replaceExt = require('replace-ext');
const nodeResolve = require('resolve');
const precinct = require('precinct');
const PluginError = require('plugin-error');
module.exports = function(destPath, opts = {}) {
opts = Object.assign(
{
cwd: process.cwd(),
precinct: {},
extension: ''
},
opts
);
if (!destPath)
throw new PluginError(
'gulp-haschanged-deps-async',
'Destination path `dest` required'
);
return file => {
return new Promise(async resolve => {
try {
// TODO: this doesn't throw the error
// the reject gets suppressed in gulp-filter
// throw new PluginError('gulp-haschanged-deps-async', 'ugh');
const deps = await recurse(file.path, opts);
const sourceMTime = await getLatestMTimeFromDeps(deps);
let fileDestPath = path.resolve(opts.cwd, destPath, file.relative);
if (opts.extension)
fileDestPath = replaceExt(fileDestPath, opts.extension);
try {
const targetStat = await fs.stat(fileDestPath);
resolve(sourceMTime >= targetStat.mtime);
} catch (err) {
if (err.code === 'ENOENT') return resolve(true);
throw err;
}
} catch (err) {
// TODO: we're temporarily outputting this
console.log(
`gulp-haschanged-deps-async error, not recompiling "${
file.path
}" due to this error:`
);
console.error(err);
// reject(err);
resolve(false);
}
});
};
};
function toDeps(contents, path, precinctOpts) {
return precinct(contents, precinctOpts);
}
function toResolvedPath(basePath, _path, opts) {
return new Promise((resolve, reject) => {
const config = { basedir: opts.cwd };
let id;
// if it's a sass, less, or stylus file then don't use require's
if (
opts.precinct.type &&
['sass', 'less', 'stylus'].includes(opts.precinct.type)
) {
if (opts.precinct.type === 'sass') config.extensions = ['.scss', '.sass'];
else if (opts.precinct.type === 'stylus') config.extensions = ['.styl'];
else config.extensions = ['.less'];
id = path.resolve(basePath, _path);
} else if (_path[0] === '.') {
// if the path starts with `./` then use the full path to the file
id = path.resolve(basePath, _path);
} else {
// otherwise just use the package name to be resolved with
id = _path;
}
nodeResolve(id, config, (err, res) => {
if (err) {
// attempt to find a local version of the file
// (e.g. less/sass/stylus all can do "@import _some-file.less")
// and it should import locally
// if (err.code === 'MODULE_NOT_FOUND') {
// return;
// }
return reject(err);
}
resolve(res);
});
});
}
async function recurse(entryPoint, opts, _path, allDeps = [], parent) {
if (!_path) _path = entryPoint;
allDeps.push(_path);
try {
const contents = await fs.readFile(_path, 'utf8');
const basePath = path.dirname(_path);
const deps = await Promise.all(
toDeps(contents, _path, opts.precinct).map(async p => {
const resolvedPath = await toResolvedPath(basePath, p, opts);
// exclude core modules from attempting to be looked up
if (
(!opts.precinct.type ||
!['sass', 'less', 'stylus'].includes(opts.precinct.type)) &&
nodeResolve.isCore(resolvedPath)
)
return false;
return resolvedPath;
})
);
await Promise.all(
deps
.filter(d => d && allDeps.indexOf(d) === -1)
.map(d => recurse(entryPoint, opts, d, allDeps, _path))
);
return allDeps;
} catch (err) {
err.parent = parent || entryPoint;
throw err;
}
}
async function getLatestMTimeFromDeps(deps) {
const stats = await Promise.all(deps.map(d => fs.stat(d)));
return stats.map(stat => stat.mtime).reduce((cur, mtime) => {
return mtime > cur ? mtime : cur;
}, 0);
}