mirror of
https://github.com/Microsoft/vscode
synced 2024-10-31 11:34:45 +00:00
457 lines
17 KiB
JavaScript
457 lines
17 KiB
JavaScript
"use strict";
|
|
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
var fs = require("fs");
|
|
var path = require("path");
|
|
var vm = require("vm");
|
|
/**
|
|
* Bundle `entryPoints` given config `config`.
|
|
*/
|
|
function bundle(entryPoints, config, callback) {
|
|
var entryPointsMap = {};
|
|
entryPoints.forEach(function (module) {
|
|
entryPointsMap[module.name] = module;
|
|
});
|
|
var allMentionedModulesMap = {};
|
|
entryPoints.forEach(function (module) {
|
|
allMentionedModulesMap[module.name] = true;
|
|
(module.include || []).forEach(function (includedModule) {
|
|
allMentionedModulesMap[includedModule] = true;
|
|
});
|
|
(module.exclude || []).forEach(function (excludedModule) {
|
|
allMentionedModulesMap[excludedModule] = true;
|
|
});
|
|
});
|
|
var code = require('fs').readFileSync(path.join(__dirname, '../../src/vs/loader.js'));
|
|
var r = vm.runInThisContext('(function(require, module, exports) { ' + code + '\n});');
|
|
var loaderModule = { exports: {} };
|
|
r.call({}, require, loaderModule, loaderModule.exports);
|
|
var loader = loaderModule.exports;
|
|
config.isBuild = true;
|
|
config.paths = config.paths || {};
|
|
config.paths['vs/nls'] = 'out-build/vs/nls.build';
|
|
config.paths['vs/css'] = 'out-build/vs/css.build';
|
|
loader.config(config);
|
|
loader(['require'], function (localRequire) {
|
|
var resolvePath = function (path) {
|
|
var r = localRequire.toUrl(path);
|
|
if (!/\.js/.test(r)) {
|
|
return r + '.js';
|
|
}
|
|
return r;
|
|
};
|
|
for (var moduleId in entryPointsMap) {
|
|
var entryPoint = entryPointsMap[moduleId];
|
|
if (entryPoint.append) {
|
|
entryPoint.append = entryPoint.append.map(resolvePath);
|
|
}
|
|
if (entryPoint.prepend) {
|
|
entryPoint.prepend = entryPoint.prepend.map(resolvePath);
|
|
}
|
|
}
|
|
});
|
|
loader(Object.keys(allMentionedModulesMap), function () {
|
|
var modules = loader.getBuildInfo();
|
|
var partialResult = emitEntryPoints(modules, entryPointsMap);
|
|
var cssInlinedResources = loader('vs/css').getInlinedResources();
|
|
callback(null, {
|
|
files: partialResult.files,
|
|
cssInlinedResources: cssInlinedResources,
|
|
bundleData: partialResult.bundleData
|
|
});
|
|
}, function (err) { return callback(err, null); });
|
|
}
|
|
exports.bundle = bundle;
|
|
function emitEntryPoints(modules, entryPoints) {
|
|
var modulesMap = {};
|
|
modules.forEach(function (m) {
|
|
modulesMap[m.id] = m;
|
|
});
|
|
var modulesGraph = {};
|
|
modules.forEach(function (m) {
|
|
modulesGraph[m.id] = m.dependencies;
|
|
});
|
|
var sortedModules = topologicalSort(modulesGraph);
|
|
var result = [];
|
|
var usedPlugins = {};
|
|
var bundleData = {
|
|
graph: modulesGraph,
|
|
bundles: {}
|
|
};
|
|
Object.keys(entryPoints).forEach(function (moduleToBundle) {
|
|
var info = entryPoints[moduleToBundle];
|
|
var rootNodes = [moduleToBundle].concat(info.include || []);
|
|
var allDependencies = visit(rootNodes, modulesGraph);
|
|
var excludes = ['require', 'exports', 'module'].concat(info.exclude || []);
|
|
excludes.forEach(function (excludeRoot) {
|
|
var allExcludes = visit([excludeRoot], modulesGraph);
|
|
Object.keys(allExcludes).forEach(function (exclude) {
|
|
delete allDependencies[exclude];
|
|
});
|
|
});
|
|
var includedModules = sortedModules.filter(function (module) {
|
|
return allDependencies[module];
|
|
});
|
|
bundleData.bundles[moduleToBundle] = includedModules;
|
|
var res = emitEntryPoint(modulesMap, modulesGraph, moduleToBundle, includedModules, info.prepend, info.append, info.dest);
|
|
result = result.concat(res.files);
|
|
for (var pluginName in res.usedPlugins) {
|
|
usedPlugins[pluginName] = usedPlugins[pluginName] || res.usedPlugins[pluginName];
|
|
}
|
|
});
|
|
Object.keys(usedPlugins).forEach(function (pluginName) {
|
|
var plugin = usedPlugins[pluginName];
|
|
if (typeof plugin.finishBuild === 'function') {
|
|
var write = function (filename, contents) {
|
|
result.push({
|
|
dest: filename,
|
|
sources: [{
|
|
path: null,
|
|
contents: contents
|
|
}]
|
|
});
|
|
};
|
|
plugin.finishBuild(write);
|
|
}
|
|
});
|
|
return {
|
|
// TODO@TS 2.1.2
|
|
files: extractStrings(removeDuplicateTSBoilerplate(result)),
|
|
bundleData: bundleData
|
|
};
|
|
}
|
|
function extractStrings(destFiles) {
|
|
var parseDefineCall = function (moduleMatch, depsMatch) {
|
|
var module = moduleMatch.replace(/^"|"$/g, '');
|
|
var deps = depsMatch.split(',');
|
|
deps = deps.map(function (dep) {
|
|
dep = dep.trim();
|
|
dep = dep.replace(/^"|"$/g, '');
|
|
dep = dep.replace(/^'|'$/g, '');
|
|
var prefix = null;
|
|
var _path = null;
|
|
var pieces = dep.split('!');
|
|
if (pieces.length > 1) {
|
|
prefix = pieces[0] + '!';
|
|
_path = pieces[1];
|
|
}
|
|
else {
|
|
prefix = '';
|
|
_path = pieces[0];
|
|
}
|
|
if (/^\.\//.test(_path) || /^\.\.\//.test(_path)) {
|
|
var res = path.join(path.dirname(module), _path).replace(/\\/g, '/');
|
|
return prefix + res;
|
|
}
|
|
return prefix + _path;
|
|
});
|
|
return {
|
|
module: module,
|
|
deps: deps
|
|
};
|
|
};
|
|
destFiles.forEach(function (destFile, index) {
|
|
if (!/\.js$/.test(destFile.dest)) {
|
|
return;
|
|
}
|
|
if (/\.nls\.js$/.test(destFile.dest)) {
|
|
return;
|
|
}
|
|
// Do one pass to record the usage counts for each module id
|
|
var useCounts = {};
|
|
destFile.sources.forEach(function (source) {
|
|
var matches = source.contents.match(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/);
|
|
if (!matches) {
|
|
return;
|
|
}
|
|
var defineCall = parseDefineCall(matches[1], matches[2]);
|
|
useCounts[defineCall.module] = (useCounts[defineCall.module] || 0) + 1;
|
|
defineCall.deps.forEach(function (dep) {
|
|
useCounts[dep] = (useCounts[dep] || 0) + 1;
|
|
});
|
|
});
|
|
var sortedByUseModules = Object.keys(useCounts);
|
|
sortedByUseModules.sort(function (a, b) {
|
|
return useCounts[b] - useCounts[a];
|
|
});
|
|
var replacementMap = {};
|
|
sortedByUseModules.forEach(function (module, index) {
|
|
replacementMap[module] = index;
|
|
});
|
|
destFile.sources.forEach(function (source) {
|
|
source.contents = source.contents.replace(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/, function (_, moduleMatch, depsMatch) {
|
|
var defineCall = parseDefineCall(moduleMatch, depsMatch);
|
|
return "define(__m[" + replacementMap[defineCall.module] + "/*" + defineCall.module + "*/], __M([" + defineCall.deps.map(function (dep) { return replacementMap[dep] + '/*' + dep + '*/'; }).join(',') + "])";
|
|
});
|
|
});
|
|
destFile.sources.unshift({
|
|
path: null,
|
|
contents: [
|
|
'(function() {',
|
|
"var __m = " + JSON.stringify(sortedByUseModules) + ";",
|
|
"var __M = function(deps) {",
|
|
" var result = [];",
|
|
" for (var i = 0, len = deps.length; i < len; i++) {",
|
|
" result[i] = __m[deps[i]];",
|
|
" }",
|
|
" return result;",
|
|
"};"
|
|
].join('\n')
|
|
});
|
|
destFile.sources.push({
|
|
path: null,
|
|
contents: '}).call(this);'
|
|
});
|
|
});
|
|
return destFiles;
|
|
}
|
|
function removeDuplicateTSBoilerplate(destFiles) {
|
|
// Taken from typescript compiler => emitFiles
|
|
var BOILERPLATE = [
|
|
{ start: /^var __extends/, end: /^}\)\(\);$/ },
|
|
{ start: /^var __assign/, end: /^};$/ },
|
|
{ start: /^var __decorate/, end: /^};$/ },
|
|
{ start: /^var __metadata/, end: /^};$/ },
|
|
{ start: /^var __param/, end: /^};$/ },
|
|
{ start: /^var __awaiter/, end: /^};$/ },
|
|
{ start: /^var __generator/, end: /^};$/ },
|
|
];
|
|
destFiles.forEach(function (destFile) {
|
|
var SEEN_BOILERPLATE = [];
|
|
destFile.sources.forEach(function (source) {
|
|
var lines = source.contents.split(/\r\n|\n|\r/);
|
|
var newLines = [];
|
|
var IS_REMOVING_BOILERPLATE = false, END_BOILERPLATE;
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i];
|
|
if (IS_REMOVING_BOILERPLATE) {
|
|
newLines.push('');
|
|
if (END_BOILERPLATE.test(line)) {
|
|
IS_REMOVING_BOILERPLATE = false;
|
|
}
|
|
}
|
|
else {
|
|
for (var j = 0; j < BOILERPLATE.length; j++) {
|
|
var boilerplate = BOILERPLATE[j];
|
|
if (boilerplate.start.test(line)) {
|
|
if (SEEN_BOILERPLATE[j]) {
|
|
IS_REMOVING_BOILERPLATE = true;
|
|
END_BOILERPLATE = boilerplate.end;
|
|
}
|
|
else {
|
|
SEEN_BOILERPLATE[j] = true;
|
|
}
|
|
}
|
|
}
|
|
if (IS_REMOVING_BOILERPLATE) {
|
|
newLines.push('');
|
|
}
|
|
else {
|
|
newLines.push(line);
|
|
}
|
|
}
|
|
}
|
|
source.contents = newLines.join('\n');
|
|
});
|
|
});
|
|
return destFiles;
|
|
}
|
|
function emitEntryPoint(modulesMap, deps, entryPoint, includedModules, prepend, append, dest) {
|
|
if (!dest) {
|
|
dest = entryPoint + '.js';
|
|
}
|
|
var mainResult = {
|
|
sources: [],
|
|
dest: dest
|
|
}, results = [mainResult];
|
|
var usedPlugins = {};
|
|
var getLoaderPlugin = function (pluginName) {
|
|
if (!usedPlugins[pluginName]) {
|
|
usedPlugins[pluginName] = modulesMap[pluginName].exports;
|
|
}
|
|
return usedPlugins[pluginName];
|
|
};
|
|
includedModules.forEach(function (c) {
|
|
var bangIndex = c.indexOf('!');
|
|
if (bangIndex >= 0) {
|
|
var pluginName = c.substr(0, bangIndex);
|
|
var plugin = getLoaderPlugin(pluginName);
|
|
mainResult.sources.push(emitPlugin(entryPoint, plugin, pluginName, c.substr(bangIndex + 1)));
|
|
return;
|
|
}
|
|
var module = modulesMap[c];
|
|
if (module.path === 'empty:') {
|
|
return;
|
|
}
|
|
var contents = readFileAndRemoveBOM(module.path);
|
|
if (module.shim) {
|
|
mainResult.sources.push(emitShimmedModule(c, deps[c], module.shim, module.path, contents));
|
|
}
|
|
else {
|
|
mainResult.sources.push(emitNamedModule(c, deps[c], module.defineLocation, module.path, contents));
|
|
}
|
|
});
|
|
Object.keys(usedPlugins).forEach(function (pluginName) {
|
|
var plugin = usedPlugins[pluginName];
|
|
if (typeof plugin.writeFile === 'function') {
|
|
var req = (function () {
|
|
throw new Error('no-no!');
|
|
});
|
|
req.toUrl = function (something) { return something; };
|
|
var write = function (filename, contents) {
|
|
results.push({
|
|
dest: filename,
|
|
sources: [{
|
|
path: null,
|
|
contents: contents
|
|
}]
|
|
});
|
|
};
|
|
plugin.writeFile(pluginName, entryPoint, req, write, {});
|
|
}
|
|
});
|
|
var toIFile = function (path) {
|
|
var contents = readFileAndRemoveBOM(path);
|
|
return {
|
|
path: path,
|
|
contents: contents
|
|
};
|
|
};
|
|
var toPrepend = (prepend || []).map(toIFile);
|
|
var toAppend = (append || []).map(toIFile);
|
|
mainResult.sources = toPrepend.concat(mainResult.sources).concat(toAppend);
|
|
return {
|
|
files: results,
|
|
usedPlugins: usedPlugins
|
|
};
|
|
}
|
|
function readFileAndRemoveBOM(path) {
|
|
var BOM_CHAR_CODE = 65279;
|
|
var contents = fs.readFileSync(path, 'utf8');
|
|
// Remove BOM
|
|
if (contents.charCodeAt(0) === BOM_CHAR_CODE) {
|
|
contents = contents.substring(1);
|
|
}
|
|
return contents;
|
|
}
|
|
function emitPlugin(entryPoint, plugin, pluginName, moduleName) {
|
|
var result = '';
|
|
if (typeof plugin.write === 'function') {
|
|
var write = (function (what) {
|
|
result += what;
|
|
});
|
|
write.getEntryPoint = function () {
|
|
return entryPoint;
|
|
};
|
|
write.asModule = function (moduleId, code) {
|
|
code = code.replace(/^define\(/, 'define("' + moduleId + '",');
|
|
result += code;
|
|
};
|
|
plugin.write(pluginName, moduleName, write);
|
|
}
|
|
return {
|
|
path: null,
|
|
contents: result
|
|
};
|
|
}
|
|
function emitNamedModule(moduleId, myDeps, defineCallPosition, path, contents) {
|
|
// `defineCallPosition` is the position in code: |define()
|
|
var defineCallOffset = positionToOffset(contents, defineCallPosition.line, defineCallPosition.col);
|
|
// `parensOffset` is the position in code: define|()
|
|
var parensOffset = contents.indexOf('(', defineCallOffset);
|
|
var insertStr = '"' + moduleId + '", ';
|
|
return {
|
|
path: path,
|
|
contents: contents.substr(0, parensOffset + 1) + insertStr + contents.substr(parensOffset + 1)
|
|
};
|
|
}
|
|
function emitShimmedModule(moduleId, myDeps, factory, path, contents) {
|
|
var strDeps = (myDeps.length > 0 ? '"' + myDeps.join('", "') + '"' : '');
|
|
var strDefine = 'define("' + moduleId + '", [' + strDeps + '], ' + factory + ');';
|
|
return {
|
|
path: path,
|
|
contents: contents + '\n;\n' + strDefine
|
|
};
|
|
}
|
|
/**
|
|
* Convert a position (line:col) to (offset) in string `str`
|
|
*/
|
|
function positionToOffset(str, desiredLine, desiredCol) {
|
|
if (desiredLine === 1) {
|
|
return desiredCol - 1;
|
|
}
|
|
var line = 1, lastNewLineOffset = -1;
|
|
do {
|
|
if (desiredLine === line) {
|
|
return lastNewLineOffset + 1 + desiredCol - 1;
|
|
}
|
|
lastNewLineOffset = str.indexOf('\n', lastNewLineOffset + 1);
|
|
line++;
|
|
} while (lastNewLineOffset >= 0);
|
|
return -1;
|
|
}
|
|
/**
|
|
* Return a set of reachable nodes in `graph` starting from `rootNodes`
|
|
*/
|
|
function visit(rootNodes, graph) {
|
|
var result = {}, queue = rootNodes;
|
|
rootNodes.forEach(function (node) {
|
|
result[node] = true;
|
|
});
|
|
while (queue.length > 0) {
|
|
var el = queue.shift();
|
|
var myEdges = graph[el] || [];
|
|
myEdges.forEach(function (toNode) {
|
|
if (!result[toNode]) {
|
|
result[toNode] = true;
|
|
queue.push(toNode);
|
|
}
|
|
});
|
|
}
|
|
return result;
|
|
}
|
|
/**
|
|
* Perform a topological sort on `graph`
|
|
*/
|
|
function topologicalSort(graph) {
|
|
var allNodes = {}, outgoingEdgeCount = {}, inverseEdges = {};
|
|
Object.keys(graph).forEach(function (fromNode) {
|
|
allNodes[fromNode] = true;
|
|
outgoingEdgeCount[fromNode] = graph[fromNode].length;
|
|
graph[fromNode].forEach(function (toNode) {
|
|
allNodes[toNode] = true;
|
|
outgoingEdgeCount[toNode] = outgoingEdgeCount[toNode] || 0;
|
|
inverseEdges[toNode] = inverseEdges[toNode] || [];
|
|
inverseEdges[toNode].push(fromNode);
|
|
});
|
|
});
|
|
// https://en.wikipedia.org/wiki/Topological_sorting
|
|
var S = [], L = [];
|
|
Object.keys(allNodes).forEach(function (node) {
|
|
if (outgoingEdgeCount[node] === 0) {
|
|
delete outgoingEdgeCount[node];
|
|
S.push(node);
|
|
}
|
|
});
|
|
while (S.length > 0) {
|
|
// Ensure the exact same order all the time with the same inputs
|
|
S.sort();
|
|
var n = S.shift();
|
|
L.push(n);
|
|
var myInverseEdges = inverseEdges[n] || [];
|
|
myInverseEdges.forEach(function (m) {
|
|
outgoingEdgeCount[m]--;
|
|
if (outgoingEdgeCount[m] === 0) {
|
|
delete outgoingEdgeCount[m];
|
|
S.push(m);
|
|
}
|
|
});
|
|
}
|
|
if (Object.keys(outgoingEdgeCount).length > 0) {
|
|
throw new Error('Cannot do topological sort on cyclic graph, remaining nodes: ' + Object.keys(outgoingEdgeCount));
|
|
}
|
|
return L;
|
|
}
|