Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/build/lib/standalone.ts
4770 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import fs from 'fs';
7
import path from 'path';
8
import * as tss from './treeshaking.ts';
9
import ts from 'typescript';
10
11
const dirCache: { [dir: string]: boolean } = {};
12
13
function writeFile(filePath: string, contents: Buffer | string): void {
14
function ensureDirs(dirPath: string): void {
15
if (dirCache[dirPath]) {
16
return;
17
}
18
dirCache[dirPath] = true;
19
20
ensureDirs(path.dirname(dirPath));
21
if (fs.existsSync(dirPath)) {
22
return;
23
}
24
fs.mkdirSync(dirPath);
25
}
26
ensureDirs(path.dirname(filePath));
27
fs.writeFileSync(filePath, contents);
28
}
29
30
export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string; tsOutDir: string; additionalFilesToCopyOut?: string[] }): void {
31
const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString());
32
let compilerOptions: { [key: string]: any };
33
if (tsConfig.extends) {
34
const extendedConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, tsConfig.extends)).toString());
35
compilerOptions = Object.assign({}, extendedConfig.compilerOptions, tsConfig.compilerOptions);
36
delete tsConfig.extends;
37
} else {
38
compilerOptions = tsConfig.compilerOptions;
39
}
40
tsConfig.compilerOptions = compilerOptions;
41
tsConfig.compilerOptions.sourceMap = true;
42
tsConfig.compilerOptions.outDir = options.tsOutDir;
43
44
compilerOptions.noEmit = false;
45
compilerOptions.noUnusedLocals = false;
46
compilerOptions.preserveConstEnums = false;
47
compilerOptions.declaration = false;
48
49
50
options.compilerOptions = compilerOptions;
51
52
console.log(`Running tree shaker with shakeLevel ${tss.toStringShakeLevel(options.shakeLevel)}`);
53
54
// Take the extra included .d.ts files from `tsconfig.monaco.json`
55
options.typings = (tsConfig.include as string[]).filter(includedFile => /\.d\.ts$/.test(includedFile));
56
57
const result = tss.shake(options);
58
for (const fileName in result) {
59
if (result.hasOwnProperty(fileName)) {
60
let fileContents = result[fileName];
61
// Replace .ts? with .js? in new URL() patterns
62
fileContents = fileContents.replace(
63
/(new\s+URL\s*\(\s*['"`][^'"`]*?)\.ts(\?[^'"`]*['"`])/g,
64
'$1.js$2'
65
);
66
const relativePath = path.relative(options.sourcesRoot, fileName);
67
writeFile(path.join(options.destRoot, relativePath), fileContents);
68
}
69
}
70
const copied: { [fileName: string]: boolean } = {};
71
const copyFile = (fileName: string, toFileName?: string) => {
72
if (copied[fileName]) {
73
return;
74
}
75
copied[fileName] = true;
76
77
if (path.isAbsolute(fileName)) {
78
const relativePath = path.relative(options.sourcesRoot, fileName);
79
const dstPath = path.join(options.destRoot, toFileName ?? relativePath);
80
writeFile(dstPath, fs.readFileSync(fileName));
81
} else {
82
const srcPath = path.join(options.sourcesRoot, fileName);
83
const dstPath = path.join(options.destRoot, toFileName ?? fileName);
84
writeFile(dstPath, fs.readFileSync(srcPath));
85
}
86
};
87
const writeOutputFile = (fileName: string, contents: string | Buffer) => {
88
const relativePath = path.isAbsolute(fileName) ? path.relative(options.sourcesRoot, fileName) : fileName;
89
writeFile(path.join(options.destRoot, relativePath), contents);
90
};
91
for (const fileName in result) {
92
if (result.hasOwnProperty(fileName)) {
93
const fileContents = result[fileName];
94
const info = ts.preProcessFile(fileContents);
95
96
for (let i = info.importedFiles.length - 1; i >= 0; i--) {
97
const importedFileName = info.importedFiles[i].fileName;
98
99
let importedFilePath = importedFileName;
100
if (/(^\.\/)|(^\.\.\/)/.test(importedFilePath)) {
101
importedFilePath = path.join(path.dirname(fileName), importedFilePath);
102
}
103
104
if (/\.css$/.test(importedFilePath)) {
105
transportCSS(importedFilePath, copyFile, writeOutputFile);
106
} else {
107
const pathToCopy = path.join(options.sourcesRoot, importedFilePath);
108
if (fs.existsSync(pathToCopy) && !fs.statSync(pathToCopy).isDirectory()) {
109
copyFile(importedFilePath);
110
}
111
}
112
}
113
}
114
}
115
116
delete tsConfig.compilerOptions.moduleResolution;
117
writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t'));
118
119
options.additionalFilesToCopyOut?.forEach((file) => {
120
copyFile(file);
121
});
122
123
copyFile('typings/css.d.ts');
124
copyFile('../node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts', '@vscode/tree-sitter-wasm.d.ts');
125
}
126
127
function transportCSS(module: string, enqueue: (module: string) => void, write: (path: string, contents: string | Buffer) => void): boolean {
128
129
if (!/\.css/.test(module)) {
130
return false;
131
}
132
133
const fileContents = fs.readFileSync(module).toString();
134
const inlineResources = 'base64'; // see https://github.com/microsoft/monaco-editor/issues/148
135
136
const newContents = _rewriteOrInlineUrls(fileContents, inlineResources === 'base64');
137
write(module, newContents);
138
return true;
139
140
function _rewriteOrInlineUrls(contents: string, forceBase64: boolean): string {
141
return _replaceURL(contents, (url) => {
142
const fontMatch = url.match(/^(.*).ttf\?(.*)$/);
143
if (fontMatch) {
144
const relativeFontPath = `${fontMatch[1]}.ttf`; // trim the query parameter
145
const fontPath = path.join(path.dirname(module), relativeFontPath);
146
enqueue(fontPath);
147
return relativeFontPath;
148
}
149
150
const imagePath = path.join(path.dirname(module), url);
151
const fileContents = fs.readFileSync(imagePath);
152
const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png';
153
let DATA = ';base64,' + fileContents.toString('base64');
154
155
if (!forceBase64 && /\.svg$/.test(url)) {
156
// .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris
157
const newText = fileContents.toString()
158
.replace(/"/g, '\'')
159
.replace(/</g, '%3C')
160
.replace(/>/g, '%3E')
161
.replace(/&/g, '%26')
162
.replace(/#/g, '%23')
163
.replace(/\s+/g, ' ');
164
const encodedData = ',' + newText;
165
if (encodedData.length < DATA.length) {
166
DATA = encodedData;
167
}
168
}
169
return '"data:' + MIME + DATA + '"';
170
});
171
}
172
173
function _replaceURL(contents: string, replacer: (url: string) => string): string {
174
// Use ")" as the terminator as quotes are oftentimes not used at all
175
return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, (_: string, ...matches: string[]) => {
176
let url = matches[0];
177
// Eliminate starting quotes (the initial whitespace is not captured)
178
if (url.charAt(0) === '"' || url.charAt(0) === '\'') {
179
url = url.substring(1);
180
}
181
// The ending whitespace is captured
182
while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) {
183
url = url.substring(0, url.length - 1);
184
}
185
// Eliminate ending quotes
186
if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') {
187
url = url.substring(0, url.length - 1);
188
}
189
190
if (!_startsWith(url, 'data:') && !_startsWith(url, 'http://') && !_startsWith(url, 'https://')) {
191
url = replacer(url);
192
}
193
194
return 'url(' + url + ')';
195
});
196
}
197
198
function _startsWith(haystack: string, needle: string): boolean {
199
return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle;
200
}
201
}
202
203