-
Notifications
You must be signed in to change notification settings - Fork 3.4k
/
import-manager.js
183 lines (162 loc) · 8.54 KB
/
import-manager.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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import contexts from './contexts';
import Parser from './parser/parser';
import LessError from './less-error';
import * as utils from './utils';
import logger from './logger';
export default function(environment) {
// FileInfo = {
// 'rewriteUrls' - option - whether to adjust URL's to be relative
// 'filename' - full resolved filename of current file
// 'rootpath' - path to append to normal URLs for this node
// 'currentDirectory' - path to the current file, absolute
// 'rootFilename' - filename of the base file
// 'entryPath' - absolute path to the entry file
// 'reference' - whether the file should not be output and only output parts that are referenced
class ImportManager {
constructor(less, context, rootFileInfo) {
this.less = less;
this.rootFilename = rootFileInfo.filename;
this.paths = context.paths || []; // Search paths, when importing
this.contents = {}; // map - filename to contents of all the files
this.contentsIgnoredChars = {}; // map - filename to lines at the beginning of each file to ignore
this.mime = context.mime;
this.error = null;
this.context = context;
// Deprecated? Unused outside of here, could be useful.
this.queue = []; // Files which haven't been imported yet
this.files = {}; // Holds the imported parse trees.
}
/**
* Add an import to be imported
* @param path - the raw path
* @param tryAppendExtension - whether to try appending a file extension (.less or .js if the path has no extension)
* @param currentFileInfo - the current file info (used for instance to work out relative paths)
* @param importOptions - import options
* @param callback - callback for when it is imported
*/
push(path, tryAppendExtension, currentFileInfo, importOptions, callback) {
const importManager = this, pluginLoader = this.context.pluginManager.Loader;
this.queue.push(path);
const fileParsedFunc = function (e, root, fullPath) {
importManager.queue.splice(importManager.queue.indexOf(path), 1); // Remove the path from the queue
const importedEqualsRoot = fullPath === importManager.rootFilename;
if (importOptions.optional && e) {
callback(null, {rules:[]}, false, null);
logger.info(`The file ${fullPath} was skipped because it was not found and the import was marked optional.`);
}
else {
// Inline imports aren't cached here.
// If we start to cache them, please make sure they won't conflict with non-inline imports of the
// same name as they used to do before this comment and the condition below have been added.
if (!importManager.files[fullPath] && !importOptions.inline) {
importManager.files[fullPath] = { root, options: importOptions };
}
if (e && !importManager.error) { importManager.error = e; }
callback(e, root, importedEqualsRoot, fullPath);
}
};
const newFileInfo = {
rewriteUrls: this.context.rewriteUrls,
entryPath: currentFileInfo.entryPath,
rootpath: currentFileInfo.rootpath,
rootFilename: currentFileInfo.rootFilename
};
const fileManager = environment.getFileManager(path, currentFileInfo.currentDirectory, this.context, environment);
if (!fileManager) {
fileParsedFunc({ message: `Could not find a file-manager for ${path}` });
return;
}
const loadFileCallback = function(loadedFile) {
let plugin;
const resolvedFilename = loadedFile.filename;
const contents = loadedFile.contents.replace(/^\uFEFF/, '');
// Pass on an updated rootpath if path of imported file is relative and file
// is in a (sub|sup) directory
//
// Examples:
// - If path of imported file is 'module/nav/nav.less' and rootpath is 'less/',
// then rootpath should become 'less/module/nav/'
// - If path of imported file is '../mixins.less' and rootpath is 'less/',
// then rootpath should become 'less/../'
newFileInfo.currentDirectory = fileManager.getPath(resolvedFilename);
if (newFileInfo.rewriteUrls) {
newFileInfo.rootpath = fileManager.join(
(importManager.context.rootpath || ''),
fileManager.pathDiff(newFileInfo.currentDirectory, newFileInfo.entryPath));
if (!fileManager.isPathAbsolute(newFileInfo.rootpath) && fileManager.alwaysMakePathsAbsolute()) {
newFileInfo.rootpath = fileManager.join(newFileInfo.entryPath, newFileInfo.rootpath);
}
}
newFileInfo.filename = resolvedFilename;
const newEnv = new contexts.Parse(importManager.context);
newEnv.processImports = false;
importManager.contents[resolvedFilename] = contents;
if (currentFileInfo.reference || importOptions.reference) {
newFileInfo.reference = true;
}
if (importOptions.isPlugin) {
plugin = pluginLoader.evalPlugin(contents, newEnv, importManager, importOptions.pluginArgs, newFileInfo);
if (plugin instanceof LessError) {
fileParsedFunc(plugin, null, resolvedFilename);
}
else {
fileParsedFunc(null, plugin, resolvedFilename);
}
} else if (importOptions.inline) {
fileParsedFunc(null, contents, resolvedFilename);
} else {
// import (multiple) parse trees apparently get altered and can't be cached.
// TODO: investigate why this is
if (importManager.files[resolvedFilename]
&& !importManager.files[resolvedFilename].options.multiple
&& !importOptions.multiple) {
fileParsedFunc(null, importManager.files[resolvedFilename].root, resolvedFilename);
}
else {
new Parser(newEnv, importManager, newFileInfo).parse(contents, function (e, root) {
fileParsedFunc(e, root, resolvedFilename);
});
}
}
};
let loadedFile;
let promise;
const context = utils.clone(this.context);
if (tryAppendExtension) {
context.ext = importOptions.isPlugin ? '.js' : '.less';
}
if (importOptions.isPlugin) {
context.mime = 'application/javascript';
if (context.syncImport) {
loadedFile = pluginLoader.loadPluginSync(path, currentFileInfo.currentDirectory, context, environment, fileManager);
} else {
promise = pluginLoader.loadPlugin(path, currentFileInfo.currentDirectory, context, environment, fileManager);
}
}
else {
if (context.syncImport) {
loadedFile = fileManager.loadFileSync(path, currentFileInfo.currentDirectory, context, environment);
} else {
promise = fileManager.loadFile(path, currentFileInfo.currentDirectory, context, environment,
(err, loadedFile) => {
if (err) {
fileParsedFunc(err);
} else {
loadFileCallback(loadedFile);
}
});
}
}
if (loadedFile) {
if (!loadedFile.filename) {
fileParsedFunc(loadedFile);
} else {
loadFileCallback(loadedFile);
}
} else if (promise) {
promise.then(loadFileCallback, fileParsedFunc);
}
}
}
return ImportManager;
};