Skip to content

Commit 69cd143

Browse files
authored
from ESM land bring over amdX-util and bring over one of its usages (microsoft#186748)
* from ESM land bring over amdX-util and bring over one of its usages * fix tsec violation * one more exemption * grrrr * last try, fingers crossed
1 parent 3aaa81b commit 69cd143

File tree

7 files changed

+228
-5
lines changed

7 files changed

+228
-5
lines changed

.eslintplugin/code-import-patterns.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,9 @@ export = new class implements eslint.Rule.RuleModule {
180180
const restrictions = (typeof option.restrictions === 'string' ? [option.restrictions] : option.restrictions).slice(0);
181181

182182
if (targetIsVS) {
183-
// Always add "vs/nls"
183+
// Always add "vs/nls" and "vs/amdX"
184184
restrictions.push('vs/nls');
185+
restrictions.push('vs/amdX'); // TODO@jrieken remove after ESM is real
185186
}
186187

187188
if (targetIsVS && option.layer) {

.eslintrc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,12 @@
596596
"vs/workbench/workbench.common.main"
597597
]
598598
},
599+
{
600+
"target": "src/vs/amdX.ts",
601+
"restrictions": [
602+
"vs/base/common/*"
603+
]
604+
},
599605
{
600606
"target": "src/vs/workbench/{workbench.desktop.main.nls.js,workbench.web.main.nls.js}",
601607
"restrictions": []

src/tsec.exemptions.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"vs/workbench/services/keybinding/test/node/keyboardMapperTestUtils.ts"
1515
],
1616
"ban-trustedtypes-createpolicy": [
17+
"vs/amdX.ts",
1718
"vs/base/browser/trustedTypes.ts",
1819
"vs/base/worker/workerMain.ts",
1920
"vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts"
@@ -23,6 +24,7 @@
2324
"vs/workbench/services/extensions/browser/webWorkerExtensionHost.ts"
2425
],
2526
"ban-worker-importscripts": [
27+
"vs/amdX.ts",
2628
"vs/workbench/services/extensions/worker/polyfillNestedWorker.ts",
2729
"vs/workbench/api/worker/extensionHostWorker.ts",
2830
"vs/base/worker/workerMain.ts"

src/typings/vscode-globals-modules.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ declare global {
1818
net: typeof import('net');
1919
os: typeof import('os');
2020
module: typeof import('module');
21+
fs: typeof import('fs'),
22+
vm: typeof import('vm'),
2123
['native-watchdog']: typeof import('native-watchdog')
2224
perf_hooks: typeof import('perf_hooks');
2325

src/vs/amdX.ts

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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 { isESM } from 'vs/base/common/amd';
7+
import { AppResourcePath, FileAccess, nodeModulesAsarPath, nodeModulesPath } from 'vs/base/common/network';
8+
import * as platform from 'vs/base/common/platform';
9+
import { IProductConfiguration } from 'vs/base/common/product';
10+
import { URI } from 'vs/base/common/uri';
11+
12+
class DefineCall {
13+
constructor(
14+
public readonly id: string | null | undefined,
15+
public readonly dependencies: string[] | null | undefined,
16+
public readonly callback: any
17+
) { }
18+
}
19+
20+
class AMDModuleImporter {
21+
public static INSTANCE = new AMDModuleImporter();
22+
23+
private readonly _isWebWorker = (typeof self === 'object' && self.constructor && self.constructor.name === 'DedicatedWorkerGlobalScope');
24+
private readonly _isRenderer = typeof document === 'object';
25+
26+
private readonly _defineCalls: DefineCall[] = [];
27+
private _initialized = false;
28+
private _amdPolicy: Pick<TrustedTypePolicy<{
29+
createScriptURL(value: string): string;
30+
}>, 'name' | 'createScriptURL'> | undefined;
31+
32+
constructor() { }
33+
34+
private _initialize(): void {
35+
if (this._initialized) {
36+
return;
37+
}
38+
this._initialized = true;
39+
40+
(<any>globalThis).define = (id: any, dependencies: any, callback: any) => {
41+
if (typeof id !== 'string') {
42+
callback = dependencies;
43+
dependencies = id;
44+
id = null;
45+
}
46+
if (typeof dependencies !== 'object' || !Array.isArray(dependencies)) {
47+
callback = dependencies;
48+
dependencies = null;
49+
}
50+
// if (!dependencies) {
51+
// dependencies = ['require', 'exports', 'module'];
52+
// }
53+
this._defineCalls.push(new DefineCall(id, dependencies, callback));
54+
};
55+
56+
(<any>globalThis).define.amd = true;
57+
58+
if (this._isRenderer) {
59+
this._amdPolicy = window.trustedTypes?.createPolicy('amdLoader', {
60+
createScriptURL(value) {
61+
if (value.startsWith(window.location.origin)) {
62+
return value;
63+
}
64+
if (value.startsWith('vscode-file://vscode-app')) {
65+
return value;
66+
}
67+
throw new Error(`[trusted_script_src] Invalid script url: ${value}`);
68+
}
69+
});
70+
} else if (this._isWebWorker) {
71+
this._amdPolicy = (<any>globalThis).trustedTypes?.createPolicy('amdLoader', {
72+
createScriptURL(value: string) {
73+
return value;
74+
}
75+
});
76+
}
77+
}
78+
79+
public async load<T>(scriptSrc: string): Promise<T> {
80+
this._initialize();
81+
const defineCall = await (this._isWebWorker ? this._workerLoadScript(scriptSrc) : this._isRenderer ? this._rendererLoadScript(scriptSrc) : this._nodeJSLoadScript(scriptSrc));
82+
if (!defineCall) {
83+
throw new Error(`Did not receive a define call from script ${scriptSrc}`);
84+
}
85+
// TODO require, exports, module
86+
if (Array.isArray(defineCall.dependencies) && defineCall.dependencies.length > 0) {
87+
throw new Error(`Cannot resolve dependencies for script ${scriptSrc}. The dependencies are: ${defineCall.dependencies.join(', ')}`);
88+
}
89+
if (typeof defineCall.callback === 'function') {
90+
return defineCall.callback([]);
91+
} else {
92+
return defineCall.callback;
93+
}
94+
}
95+
96+
private _rendererLoadScript(scriptSrc: string): Promise<DefineCall | undefined> {
97+
return new Promise<DefineCall | undefined>((resolve, reject) => {
98+
const scriptElement = document.createElement('script');
99+
scriptElement.setAttribute('async', 'async');
100+
scriptElement.setAttribute('type', 'text/javascript');
101+
102+
const unbind = () => {
103+
scriptElement.removeEventListener('load', loadEventListener);
104+
scriptElement.removeEventListener('error', errorEventListener);
105+
};
106+
107+
const loadEventListener = (e: any) => {
108+
unbind();
109+
resolve(this._defineCalls.pop());
110+
};
111+
112+
const errorEventListener = (e: any) => {
113+
unbind();
114+
reject(e);
115+
};
116+
117+
scriptElement.addEventListener('load', loadEventListener);
118+
scriptElement.addEventListener('error', errorEventListener);
119+
if (this._amdPolicy) {
120+
scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as any as string;
121+
}
122+
scriptElement.setAttribute('src', scriptSrc);
123+
document.getElementsByTagName('head')[0].appendChild(scriptElement);
124+
});
125+
}
126+
127+
private _workerLoadScript(scriptSrc: string): Promise<DefineCall | undefined> {
128+
return new Promise<DefineCall | undefined>((resolve, reject) => {
129+
try {
130+
if (this._amdPolicy) {
131+
scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as any as string;
132+
}
133+
importScripts(scriptSrc);
134+
resolve(this._defineCalls.pop());
135+
} catch (err) {
136+
reject(err);
137+
}
138+
});
139+
}
140+
141+
private async _nodeJSLoadScript(scriptSrc: string): Promise<DefineCall | undefined> {
142+
try {
143+
const fs = <typeof import('fs')>globalThis._VSCODE_NODE_MODULES['fs'];
144+
const vm = <typeof import('vm')>globalThis._VSCODE_NODE_MODULES['vm'];
145+
const module = <typeof import('module')>globalThis._VSCODE_NODE_MODULES['module'];
146+
147+
const filePath = URI.parse(scriptSrc).fsPath;
148+
const content = fs.readFileSync(filePath).toString();
149+
const scriptSource = module.wrap(content.replace(/^#!.*/, ''));
150+
const script = new vm.Script(scriptSource);
151+
const compileWrapper = script.runInThisContext();
152+
compileWrapper.apply();
153+
return this._defineCalls.pop();
154+
155+
} catch (error) {
156+
throw error;
157+
}
158+
}
159+
}
160+
161+
const cache = new Map<string, Promise<any>>();
162+
163+
let _paths: Record<string, string> = {};
164+
if (typeof globalThis.require === 'object') {
165+
_paths = (<Record<string, any>>globalThis.require).paths ?? {};
166+
}
167+
168+
/**
169+
* e.g. pass in `vscode-textmate/release/main.js`
170+
*/
171+
export async function importAMDNodeModule<T>(nodeModuleName: string, pathInsideNodeModule: string, isBuilt?: boolean): Promise<T> {
172+
if (isESM) {
173+
174+
if (isBuilt === undefined) {
175+
const product = globalThis._VSCODE_PRODUCT_JSON as unknown as IProductConfiguration;
176+
isBuilt = !!product?.commit;
177+
}
178+
179+
if (_paths[nodeModuleName]) {
180+
nodeModuleName = _paths[nodeModuleName];
181+
}
182+
183+
const nodeModulePath = `${nodeModuleName}/${pathInsideNodeModule}`;
184+
if (cache.has(nodeModulePath)) {
185+
return cache.get(nodeModulePath)!;
186+
}
187+
let scriptSrc: string;
188+
if (/^\w[\w\d+.-]*:\/\//.test(nodeModulePath)) {
189+
// looks like a URL
190+
// bit of a special case for: src/vs/workbench/services/languageDetection/browser/languageDetectionSimpleWorker.ts
191+
scriptSrc = nodeModulePath;
192+
} else {
193+
const useASAR = (isBuilt && !platform.isWeb);
194+
const actualNodeModulesPath = (useASAR ? nodeModulesAsarPath : nodeModulesPath);
195+
const resourcePath: AppResourcePath = `${actualNodeModulesPath}/${nodeModulePath}`;
196+
scriptSrc = FileAccess.asBrowserUri(resourcePath).toString(true);
197+
}
198+
const result = AMDModuleImporter.INSTANCE.load<T>(scriptSrc);
199+
cache.set(nodeModulePath, result);
200+
return result;
201+
} else {
202+
return await import(nodeModuleName);
203+
}
204+
}

src/vs/base/common/amd.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6+
// ESM-comment-begin
7+
export const isESM = false;
8+
// ESM-comment-end
9+
// ESM-uncomment-begin
10+
// export const isESM = true;
11+
// ESM-uncomment-end
12+
613
export abstract class LoaderStats {
714
abstract get amdLoad(): [string, number][];
815
abstract get amdInvoke(): [string, number][];

src/vs/workbench/services/textfile/common/encoding.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import { Readable, ReadableStream, newWriteableStream, listenStream } from 'vs/base/common/stream';
77
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
8+
import { importAMDNodeModule } from 'vs/amdX';
89

910
export const UTF8 = 'utf8';
1011
export const UTF8_with_bom = 'utf8bom';
@@ -78,7 +79,7 @@ class DecoderStream implements IDecoderStream {
7879
static async create(encoding: string): Promise<DecoderStream> {
7980
let decoder: IDecoderStream | undefined = undefined;
8081
if (encoding !== UTF8) {
81-
const iconv = await import('@vscode/iconv-lite-umd');
82+
const iconv = await importAMDNodeModule<typeof import('@vscode/iconv-lite-umd')>('@vscode/iconv-lite-umd', 'lib/iconv-lite-umd.js');
8283
decoder = iconv.getDecoder(toNodeEncoding(encoding));
8384
} else {
8485
const utf8TextDecoder = new TextDecoder();
@@ -209,7 +210,7 @@ export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeS
209210
}
210211

211212
export async function toEncodeReadable(readable: Readable<string>, encoding: string, options?: { addBOM?: boolean }): Promise<VSBufferReadable> {
212-
const iconv = await import('@vscode/iconv-lite-umd');
213+
const iconv = await importAMDNodeModule<typeof import('@vscode/iconv-lite-umd')>('@vscode/iconv-lite-umd', 'lib/iconv-lite-umd.js');
213214
const encoder = iconv.getEncoder(toNodeEncoding(encoding), options);
214215

215216
let bytesWritten = false;
@@ -258,7 +259,7 @@ export async function toEncodeReadable(readable: Readable<string>, encoding: str
258259
}
259260

260261
export async function encodingExists(encoding: string): Promise<boolean> {
261-
const iconv = await import('@vscode/iconv-lite-umd');
262+
const iconv = await importAMDNodeModule<typeof import('@vscode/iconv-lite-umd')>('@vscode/iconv-lite-umd', 'lib/iconv-lite-umd.js');
262263

263264
return iconv.encodingExists(toNodeEncoding(encoding));
264265
}
@@ -314,7 +315,7 @@ const IGNORE_ENCODINGS = ['ascii', 'utf-16', 'utf-32'];
314315
* Guesses the encoding from buffer.
315316
*/
316317
async function guessEncodingByBuffer(buffer: VSBuffer): Promise<string | null> {
317-
const jschardet = await import('jschardet');
318+
const jschardet = await importAMDNodeModule<typeof import('jschardet')>('jschardet', 'dist/jschardet.min.js');
318319

319320
// ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53
320321
const limitedBuffer = buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES);

0 commit comments

Comments
 (0)