Skip to content

Commit

Permalink
add client/server browser parts
Browse files Browse the repository at this point in the history
  • Loading branch information
aeschli committed May 27, 2020
1 parent 90861d4 commit 3ed67bc
Show file tree
Hide file tree
Showing 10 changed files with 242 additions and 62 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ExtensionContext, window } from 'vscode';
import { CommonLanguageClient, LanguageClientOptions, MessageTransports } from 'vscode-languageclient';
import { startClient, LanguageClientConstructor } from '../cssClient';
import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/lib/browser/main';

declare const Worker: {
new(stringUrl: string): any;
};

class BrowserLanguageClient extends CommonLanguageClient {

constructor(id: string, name: string, clientOptions: LanguageClientOptions, private worker: any) {
super(id, name, clientOptions);
}

protected createMessageTransports(_encoding: string): Promise<MessageTransports> {
const reader = new BrowserMessageReader(this.worker);
const writer = new BrowserMessageWriter(this.worker);
return Promise.resolve({ reader, writer });
}

}

// this method is called when vs code is activated
export function activate(context: ExtensionContext) {
const serverMain = context.asAbsolutePath('server/dist/browser/cssServerMain.js');
try {
const worker = new Worker(serverMain);
const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
return new BrowserLanguageClient(id, name, clientOptions, worker);
};

startClient(context, newLanguageClient, {});

} catch (e) {
console.log(e);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

export interface Options {
locale?: string;
cacheLanguageResolution?: boolean;
}
export interface LocalizeInfo {
key: string;
comment: string[];
}
export interface LocalizeFunc {
(info: LocalizeInfo, message: string, ...args: any[]): string;
(key: string, message: string, ...args: any[]): string;
}
export interface LoadFunc {
(file?: string): LocalizeFunc;
}

function format(message: string, args: any[]): string {
let result: string;

if (args.length === 0) {
result = message;
} else {
result = message.replace(/\{(\d+)\}/g, (match, rest) => {
let index = rest[0];
return typeof args[index] !== 'undefined' ? args[index] : match;
});
}
return result;
}

function localize(_key: string | LocalizeInfo, message: string, ...args: any[]): string {
return format(message, args);
}

export function loadMessageBundle(_file?: string): LocalizeFunc {
return localize;
}

export function config(_opt?: Options | string): LoadFunc {
return loadMessageBundle;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,40 @@

const withDefaults = require('../shared.webpack.config');
const path = require('path');
const webpack = require('webpack');

module.exports = withDefaults({
const vscodeNlsReplacement = new webpack.NormalModuleReplacementPlugin(
/vscode\-nls\/lib\/main\.js/,
path.join(__dirname, 'client/out/browser/vscodeNlsShim.js')
);

const clientConfig = withDefaults({
target: 'webworker',
context: path.join(__dirname, 'client'),
entry: {
extension: './src/browser/cssClientBrowserMain.ts',
extension: './src/browser/cssClientMain.ts'
},
output: {
filename: 'cssClientBrowserMain.js',
filename: 'cssClientMain.js',
path: path.join(__dirname, 'client', 'dist', 'browser')
}
});
clientConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler
clientConfig.module.rules[0].use.shift(); // remove nls loader

const serverConfig = withDefaults({
target: 'webworker',
context: path.join(__dirname, 'server'),
entry: {
extension: './src/browser/cssServerMain.ts',
},
output: {
filename: 'cssServerMain.js',
path: path.join(__dirname, 'server', 'dist', 'browser'),
libraryTarget: 'var'
}
});
serverConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler
serverConfig.module.rules[0].use.shift(); // remove nls loader

module.exports = [clientConfig, serverConfig];
2 changes: 1 addition & 1 deletion extensions/css-language-features/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"onCommand:_css.applyCodeAction"
],
"main": "./client/out/node/cssClientMain",
"browser": "./client/dist/browser/cssClientBrowserMain",
"browser": "./client/dist/browser/cssClientMain",
"enableProposedApi": true,
"scripts": {
"compile": "gulp compile-extension:css-language-features-client compile-extension:css-language-features-server",
Expand Down
1 change: 1 addition & 0 deletions extensions/css-language-features/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"node": "*"
},
"main": "./out/node/cssServerMain",
"browser": "./dist/browser/cssServerMain",
"dependencies": {
"vscode-css-languageservice": "4.3.0-next.1",
"vscode-languageserver": "^6.1.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ProtocolConnection, createProtocolConnection, Logger, createConnection, InitializeParams, WatchDog } from 'vscode-languageserver';
import { startServer } from '../cssServer';
import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/lib/browser/main';

declare let self: any;

const messageReader = new BrowserMessageReader(self);
const messageWriter = new BrowserMessageWriter(self);

const watchDog: WatchDog = {
shutdownReceived: false,
initialize(_params: InitializeParams): void { },
exit(_code: number): void { }
};

const connectionFactory = (logger: Logger): ProtocolConnection => {
return createProtocolConnection(messageReader, messageWriter, logger);
};
const connection = createConnection(connectionFactory, watchDog);

startServer(connection, {});
2 changes: 1 addition & 1 deletion extensions/css-language-features/server/src/cssServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import {
Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType
} from 'vscode-languageserver';
} from 'vscode-languageserver/lib/common/api';
import { URI } from 'vscode-uri';
import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position } from 'vscode-css-languageservice';
import { getLanguageModelCache } from './languageModelCache';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'mocha';
import * as assert from 'assert';
import * as path from 'path';
import { URI } from 'vscode-uri';
import { TextDocument, CompletionList } from 'vscode-languageserver-types';
import { TextDocument, CompletionList, TextEdit } from 'vscode-languageserver-types';
import { WorkspaceFolder } from 'vscode-languageserver-protocol';
import { getCSSLanguageService, LanguageServiceOptions, getSCSSLanguageService } from 'vscode-css-languageservice';
import { getNodeFSRequestService } from '../node/nodeFs';
Expand All @@ -26,7 +26,7 @@ suite('Completions', () => {

assert.equal(matches.length, 1, `${expected.label} should only existing once: Actual: ${completions.items.map(c => c.label).join(', ')}`);
let match = matches[0];
if (expected.resultText && match.textEdit) {
if (expected.resultText && TextEdit.is(match.textEdit)) {
assert.equal(TextDocument.applyEdits(document, [match.textEdit]), expected.resultText);
}
};
Expand Down
134 changes: 94 additions & 40 deletions scripts/code-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ const path = require('path');
const util = require('util');
const opn = require('opn');
const minimist = require('minimist');

const webpack = require("webpack");
const webpack = require('webpack');

const APP_ROOT = path.dirname(__dirname);
const EXTENSIONS_ROOT = path.join(APP_ROOT, 'extensions');
const WEB_MAIN = path.join(APP_ROOT, 'src', 'vs', 'code', 'browser', 'workbench', 'workbench-dev.html');

const args = minimist(process.argv, {
boolean: [
'watch',
'no-launch',
'help'
],
Expand All @@ -37,6 +37,7 @@ const args = minimist(process.argv, {
if (args.help) {
console.log(
'yarn web [options]\n' +
' --watch Watch extensions that require browser specific builds\n' +
' --no-launch Do not open VSCode web in the browser\n' +
' --scheme Protocol (https or http)\n' +
' --host Remote host\n' +
Expand All @@ -55,6 +56,83 @@ const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http';
const HOST = args.host || 'localhost';
const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`;

const exists = (path) => util.promisify(fs.exists)(path);
const readFile = (path) => util.promisify(fs.readFile)(path);

async function initialize() {
const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT);

const staticExtensions = [];

const webpackConfigs = [];

await Promise.all(extensionFolders.map(async extensionFolder => {
const packageJSONPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json');
if (await exists(packageJSONPath)) {
try {
const packageJSON = JSON.parse((await readFile(packageJSONPath)).toString());
if (packageJSON.main && !packageJSON.browser) {
return; // unsupported
}

if (packageJSON.browser) {
packageJSON.main = packageJSON.browser;
const webpackConfigPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'extension-browser.webpack.config.js');
if ((await exists(webpackConfigPath))) {
const configOrFnOrArray = require(webpackConfigPath);
function addConfig(configOrFn) {
if (typeof configOrFn === 'function') {
webpackConfigs.push(configOrFn({}, {}));
} else {
webpackConfigs.push(configOrFn);
}
}
if (Array.isArray(configOrFnOrArray)) {
configOrFnOrArray.forEach(addConfig);
} else {
addConfig(configOrFnOrArray);
}
}
}

packageJSON.extensionKind = ['web']; // enable for Web
staticExtensions.push({
packageJSON,
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` }
});
} catch (e) {
console.log(e);
}
}
}));

return new Promise((resolve, reject) => {
if (args.watch) {
webpack(webpackConfigs).watch({}, (err, stats) => {
if (err) {
console.log(err);
reject();
} else {
console.log(stats.toString());
resolve(staticExtensions);
}
});
} else {
webpack(webpackConfigs).run((err, stats) => {
if (err) {
console.log(err);
reject();
} else {
console.log(stats.toString());
resolve(staticExtensions);
}
});
}
});
}

const staticExtensionsPromise = initialize();

const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
Expand Down Expand Up @@ -141,46 +219,22 @@ function handleStaticExtension(req, res, parsedUrl) {
* @param {import('http').ServerResponse} res
*/
async function handleRoot(req, res) {
const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT);

const staticExtensions = [];

const webpackConfigs = [];

await Promise.all(extensionFolders.map(async extensionFolder => {
try {
const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json'))).toString());
if (packageJSON.main && !packageJSON.browser) {
return; // unsupported
}
if (packageJSON.browser) {
packageJSON.main = packageJSON.browser;
const webpackConfigPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'extension-browser.webpack.config.js');
if ((await util.promisify(fs.exists)(webpackConfigPath))) {
webpackConfigs.push(require(webpackConfigPath));
packageJSON.main.replace('/out/', '/dist/');
}
}

packageJSON.extensionKind = ['web']; // enable for Web
staticExtensions.push({
packageJSON,
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` }
});
} catch (error) {
return null;
const match = req.url && req.url.match(/\?([^#]+)/);
let ghPath;
if (match) {
const qs = new URLSearchParams(match[1]);
ghPath = qs.get('gh');
if (ghPath && !ghPath.startsWith('/')) {
ghPath = '/' + ghPath;
}
}));

webpack(webpackConfigs).watch({}, (err, stats) => {
if (err) {
console.log(err);
} else {
console.log(stats.toString());
}
});
}

const webConfiguration = escapeAttribute(JSON.stringify({ staticExtensions, folderUri: { scheme: 'memfs', path: `/sample-folder` }}));
const staticExtensions = await staticExtensionsPromise;
const webConfiguration = escapeAttribute(JSON.stringify({
staticExtensions, folderUri: ghPath
? { scheme: 'github', authority: 'github.com', path: ghPath }
: { scheme: 'memfs', path: `/sample-folder` }
}));

const data = (await util.promisify(fs.readFile)(WEB_MAIN)).toString()
.replace('{{WORKBENCH_WEB_CONFIGURATION}}', () => webConfiguration) // use a replace function to avoid that regexp replace patterns ($&, $0, ...) are applied
Expand Down

0 comments on commit 3ed67bc

Please sign in to comment.