yarn web
で起動する scripts/code-web.js
を読む。
node の http-server になって、パスを動的に書き換えている。
(サーバーが本当に必要なのか?静的アセットにならないんだろうか?という目線で読む)
試しに変数をダンプしてみた。
{
APP_ROOT: '/Users/mizchi/github/vscode',
EXTENSIONS_ROOT: '/Users/mizchi/github/vscode/extensions',
WEB_MAIN: '/Users/mizchi/github/vscode/src/vs/code/browser/workbench/workbench-dev.html'
}
WEB_MAIN
が差す workbench-dev.html
というファイルがそれっぽい。
JS 初期化してそうな部分を探す。
<script>
// NOTE: Changes to inline scripts require update of content security policy
self.require = {
baseUrl: `${window.location.origin}/static/out`,
paths: {
'vscode-textmate': `${window.location.origin}/static/remote/web/node_modules/vscode-textmate/release/main`,
'onigasm-umd': `${window.location.origin}/static/remote/web/node_modules/onigasm-umd/release/main`,
xterm: `${window.location.origin}/static/remote/web/node_modules/xterm/lib/xterm.js`,
'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
'xterm-addon-webgl': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-webgl/lib/xterm-addon-webgl.js`,
'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`
}
};
</script>
<script src="./static/out/vs/loader.js"></script>
<script>
// NOTE: Changes to inline scripts require update of content security policy
require(['vs/code/browser/workbench/workbench'], function() {});
</script>
サーバーの実装を見ると、 static/
の部分をプロジェクトルートに指し直して実行してるっぽい。
/**
* @param {import('http').IncomingMessage} req
* @param {import('http').ServerResponse} res
* @param {import('url').UrlWithParsedQuery} parsedUrl
*/
function handleStatic(req, res, parsedUrl) {
// Strip `/static/` from the path
const relativeFilePath = path.normalize(decodeURIComponent(parsedUrl.pathname.substr('/static/'.length)));
return serveFile(req, res, path.join(APP_ROOT, relativeFilePath));
}
out/
以下のコードは src/
と相対パスで対応しているっぽいので、適宜読み替える。
src/vs/loader.js
はコミット済みのファイルで、その実体はコメント読む限り、 https://github.com/Microsoft/vscode-loader
ざっと見た感じ、要は AMD loader なので、 vs/code/browser/workbench/workbench
が起動してそう。
そのエントリポイントっぽい部分
// Find config by checking for DOM
const configElement = document.getElementById('vscode-workbench-web-configuration');
const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined;
if (!configElement || !configElementAttribute) {
throw new Error('Missing web configuration element');
}
const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents } = JSON.parse(
configElementAttribute
);
workbench-dev.html
のそれっぽい部分
<!-- Workbench Configuration -->
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONFIGURATION}}" />
これは scripts/code-web.js
のサーバーから注入されてて、
const data = (await util.promisify(fs.readFile)(WEB_MAIN))
.toString()
.replace(
'{{WORKBENCH_WEB_CONFIGURATION}}',
escapeAttribute(
JSON.stringify({
staticExtensions,
folderUri: { scheme: 'memfs', path: `/sample-folder` }
})
)
)
.replace('{{WEBVIEW_ENDPOINT}}', '')
.replace('{{REMOTE_USER_DATA_URI}}', '');
だから、ここで起動する staticExtensions
や 内部的な folder
を memfs で起動する、みたいなことが行われている。
// Finally create workbench
create(document.body, {
...config,
workspaceProvider: new WorkspaceProvider(workspace, payload),
urlCallbackProvider: new PollingURLCallbackProvider(),
credentialsProvider: new LocalStorageCredentialsProvider()
});
この create の実装 workbench.web.api.ts
にあって
/**
* Creates the workbench with the provided options in the provided container.
*
* @param domElement the container to create the workbench in
* @param options for setting up the workbench
*/
function create(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise<void> {
return main(domElement, options);
}
つまりここまで main 関数に渡す諸々を初期化していたっぽい。
この main は web.main.ts
export function main(domElement: HTMLElement, options: IWorkbenchConstructionOptions): Promise<void> {
const renderer = new BrowserMain(domElement, options);
return renderer.open();
}
BrowserMain の constructor をみると空だったので、 open をみる
async open(): Promise<void> {
const services = await this.initServices();
await domContentLoaded();
mark('willStartWorkbench');
// Base Theme
this.restoreBaseTheme();
// Create Workbench
const workbench = new Workbench(this.domElement, services.serviceCollection, services.logService);
// Listeners
this.registerListeners(workbench, services.storageService);
// Driver
if (this.configuration.driver) {
(async () => this._register(await registerWindowDriver()))();
}
// Startup
workbench.startup();
}
this.initServices()
をざっと読んだ感じ
- LogService: ログ情報
- BrowserWorkbenchEnvironmentService: 実行環境情報っぽい
- ProductService: 中を見るとバージョンの定数などが埋まっていただけ
- RemoteAuthorityResolverService: vscode リモートの接続の認証周り?
- SignService: 認証周りかな?
- RemoteAgentService: リモートに接続しに行くサービス?
- FileService: ファイルの読み書きを行ってそう。
ここまでで、 FileService が memfs で起動してるのだろう、という直感が得られた。永続化したいので、ここを読みにいく。
FileService は実装ではなく、これに fileService.registerProvider
で各種環境ごとのアダプタを実装する形っぽい。
ここにログを仕込むと、以下のものが流れてきた。
- vscode-userdata: UserDataProvider
- http: FetchFileSystemProvider
- https: FetchFileSystemProvider
- trustedDomains: TrustedDomainsFileSystemProvider
- (ここで workbench が起動した)
- vscode-log: IndexedDBLogProvider
- memfs: RemoteFileSystemProvider
つまり memfs とは RemoteFileSystemProvider
の特殊実装なのでは。
WorkspaceService
と StorageService
の初期化が Web 用に特殊化されてる。
// Long running services (workspace, config, storage)
const services = await Promise.all([
this.createWorkspaceService(payload, environmentService, fileService, remoteAgentService, logService).then(
service => {
// Workspace
serviceCollection.set(IWorkspaceContextService, service);
// Configuration
serviceCollection.set(IConfigurationService, service);
return service;
}
),
this.createStorageService(payload, environmentService, fileService, logService).then(service => {
// Storage
serviceCollection.set(IStorageService, service);
return service;
})
]);
private async createStorageService(
payload: IWorkspaceInitializationPayload,
environmentService: IWorkbenchEnvironmentService,
fileService: IFileService,
logService: ILogService
): Promise<BrowserStorageService> {
const storageService = new BrowserStorageService(environmentService, fileService);
try {
await storageService.initialize(payload);
return storageService;
} catch (error) {
onUnexpectedError(error);
logService.error(error);
return storageService;
}
}
BrowserStorageService
をみると良いっぽい。
Layout を継承してる