Skip to content

Commit 3ed67bc

Browse files
committed
add client/server browser parts
1 parent 90861d4 commit 3ed67bc

10 files changed

Lines changed: 242 additions & 62 deletions

File tree

extensions/css-language-features/client/src/browser/cssClientBrowserMain.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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 { ExtensionContext, window } from 'vscode';
7+
import { CommonLanguageClient, LanguageClientOptions, MessageTransports } from 'vscode-languageclient';
8+
import { startClient, LanguageClientConstructor } from '../cssClient';
9+
import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/lib/browser/main';
10+
11+
declare const Worker: {
12+
new(stringUrl: string): any;
13+
};
14+
15+
class BrowserLanguageClient extends CommonLanguageClient {
16+
17+
constructor(id: string, name: string, clientOptions: LanguageClientOptions, private worker: any) {
18+
super(id, name, clientOptions);
19+
}
20+
21+
protected createMessageTransports(_encoding: string): Promise<MessageTransports> {
22+
const reader = new BrowserMessageReader(this.worker);
23+
const writer = new BrowserMessageWriter(this.worker);
24+
return Promise.resolve({ reader, writer });
25+
}
26+
27+
}
28+
29+
// this method is called when vs code is activated
30+
export function activate(context: ExtensionContext) {
31+
const serverMain = context.asAbsolutePath('server/dist/browser/cssServerMain.js');
32+
try {
33+
const worker = new Worker(serverMain);
34+
const newLanguageClient: LanguageClientConstructor = (id: string, name: string, clientOptions: LanguageClientOptions) => {
35+
return new BrowserLanguageClient(id, name, clientOptions, worker);
36+
};
37+
38+
startClient(context, newLanguageClient, {});
39+
40+
} catch (e) {
41+
console.log(e);
42+
}
43+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
export interface Options {
7+
locale?: string;
8+
cacheLanguageResolution?: boolean;
9+
}
10+
export interface LocalizeInfo {
11+
key: string;
12+
comment: string[];
13+
}
14+
export interface LocalizeFunc {
15+
(info: LocalizeInfo, message: string, ...args: any[]): string;
16+
(key: string, message: string, ...args: any[]): string;
17+
}
18+
export interface LoadFunc {
19+
(file?: string): LocalizeFunc;
20+
}
21+
22+
function format(message: string, args: any[]): string {
23+
let result: string;
24+
25+
if (args.length === 0) {
26+
result = message;
27+
} else {
28+
result = message.replace(/\{(\d+)\}/g, (match, rest) => {
29+
let index = rest[0];
30+
return typeof args[index] !== 'undefined' ? args[index] : match;
31+
});
32+
}
33+
return result;
34+
}
35+
36+
function localize(_key: string | LocalizeInfo, message: string, ...args: any[]): string {
37+
return format(message, args);
38+
}
39+
40+
export function loadMessageBundle(_file?: string): LocalizeFunc {
41+
return localize;
42+
}
43+
44+
export function config(_opt?: Options | string): LoadFunc {
45+
return loadMessageBundle;
46+
}

extensions/css-language-features/extension-browser.webpack.config.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,40 @@
99

1010
const withDefaults = require('../shared.webpack.config');
1111
const path = require('path');
12+
const webpack = require('webpack');
1213

13-
module.exports = withDefaults({
14+
const vscodeNlsReplacement = new webpack.NormalModuleReplacementPlugin(
15+
/vscode\-nls\/lib\/main\.js/,
16+
path.join(__dirname, 'client/out/browser/vscodeNlsShim.js')
17+
);
18+
19+
const clientConfig = withDefaults({
1420
target: 'webworker',
1521
context: path.join(__dirname, 'client'),
1622
entry: {
17-
extension: './src/browser/cssClientBrowserMain.ts',
23+
extension: './src/browser/cssClientMain.ts'
1824
},
1925
output: {
20-
filename: 'cssClientBrowserMain.js',
26+
filename: 'cssClientMain.js',
2127
path: path.join(__dirname, 'client', 'dist', 'browser')
2228
}
2329
});
30+
clientConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler
31+
clientConfig.module.rules[0].use.shift(); // remove nls loader
32+
33+
const serverConfig = withDefaults({
34+
target: 'webworker',
35+
context: path.join(__dirname, 'server'),
36+
entry: {
37+
extension: './src/browser/cssServerMain.ts',
38+
},
39+
output: {
40+
filename: 'cssServerMain.js',
41+
path: path.join(__dirname, 'server', 'dist', 'browser'),
42+
libraryTarget: 'var'
43+
}
44+
});
45+
serverConfig.plugins[1] = vscodeNlsReplacement; // replace nls bundler
46+
serverConfig.module.rules[0].use.shift(); // remove nls loader
47+
48+
module.exports = [clientConfig, serverConfig];

extensions/css-language-features/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"onCommand:_css.applyCodeAction"
1717
],
1818
"main": "./client/out/node/cssClientMain",
19-
"browser": "./client/dist/browser/cssClientBrowserMain",
19+
"browser": "./client/dist/browser/cssClientMain",
2020
"enableProposedApi": true,
2121
"scripts": {
2222
"compile": "gulp compile-extension:css-language-features-client compile-extension:css-language-features-server",

extensions/css-language-features/server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"node": "*"
99
},
1010
"main": "./out/node/cssServerMain",
11+
"browser": "./dist/browser/cssServerMain",
1112
"dependencies": {
1213
"vscode-css-languageservice": "4.3.0-next.1",
1314
"vscode-languageserver": "^6.1.1",
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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 { ProtocolConnection, createProtocolConnection, Logger, createConnection, InitializeParams, WatchDog } from 'vscode-languageserver';
7+
import { startServer } from '../cssServer';
8+
import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-jsonrpc/lib/browser/main';
9+
10+
declare let self: any;
11+
12+
const messageReader = new BrowserMessageReader(self);
13+
const messageWriter = new BrowserMessageWriter(self);
14+
15+
const watchDog: WatchDog = {
16+
shutdownReceived: false,
17+
initialize(_params: InitializeParams): void { },
18+
exit(_code: number): void { }
19+
};
20+
21+
const connectionFactory = (logger: Logger): ProtocolConnection => {
22+
return createProtocolConnection(messageReader, messageWriter, logger);
23+
};
24+
const connection = createConnection(connectionFactory, watchDog);
25+
26+
startServer(connection, {});

extensions/css-language-features/server/src/cssServer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import {
77
Connection, TextDocuments, InitializeParams, InitializeResult, ServerCapabilities, ConfigurationRequest, WorkspaceFolder, TextDocumentSyncKind, NotificationType
8-
} from 'vscode-languageserver';
8+
} from 'vscode-languageserver/lib/common/api';
99
import { URI } from 'vscode-uri';
1010
import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet, TextDocument, Position } from 'vscode-css-languageservice';
1111
import { getLanguageModelCache } from './languageModelCache';

extensions/css-language-features/server/src/test/completion.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'mocha';
66
import * as assert from 'assert';
77
import * as path from 'path';
88
import { URI } from 'vscode-uri';
9-
import { TextDocument, CompletionList } from 'vscode-languageserver-types';
9+
import { TextDocument, CompletionList, TextEdit } from 'vscode-languageserver-types';
1010
import { WorkspaceFolder } from 'vscode-languageserver-protocol';
1111
import { getCSSLanguageService, LanguageServiceOptions, getSCSSLanguageService } from 'vscode-css-languageservice';
1212
import { getNodeFSRequestService } from '../node/nodeFs';
@@ -26,7 +26,7 @@ suite('Completions', () => {
2626

2727
assert.equal(matches.length, 1, `${expected.label} should only existing once: Actual: ${completions.items.map(c => c.label).join(', ')}`);
2828
let match = matches[0];
29-
if (expected.resultText && match.textEdit) {
29+
if (expected.resultText && TextEdit.is(match.textEdit)) {
3030
assert.equal(TextDocument.applyEdits(document, [match.textEdit]), expected.resultText);
3131
}
3232
};

scripts/code-web.js

Lines changed: 94 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ const path = require('path');
1414
const util = require('util');
1515
const opn = require('opn');
1616
const minimist = require('minimist');
17-
18-
const webpack = require("webpack");
17+
const webpack = require('webpack');
1918

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

2423
const args = minimist(process.argv, {
2524
boolean: [
25+
'watch',
2626
'no-launch',
2727
'help'
2828
],
@@ -37,6 +37,7 @@ const args = minimist(process.argv, {
3737
if (args.help) {
3838
console.log(
3939
'yarn web [options]\n' +
40+
' --watch Watch extensions that require browser specific builds\n' +
4041
' --no-launch Do not open VSCode web in the browser\n' +
4142
' --scheme Protocol (https or http)\n' +
4243
' --host Remote host\n' +
@@ -55,6 +56,83 @@ const SCHEME = args.scheme || process.env.VSCODE_SCHEME || 'http';
5556
const HOST = args.host || 'localhost';
5657
const AUTHORITY = process.env.VSCODE_AUTHORITY || `${HOST}:${PORT}`;
5758

59+
const exists = (path) => util.promisify(fs.exists)(path);
60+
const readFile = (path) => util.promisify(fs.readFile)(path);
61+
62+
async function initialize() {
63+
const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT);
64+
65+
const staticExtensions = [];
66+
67+
const webpackConfigs = [];
68+
69+
await Promise.all(extensionFolders.map(async extensionFolder => {
70+
const packageJSONPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json');
71+
if (await exists(packageJSONPath)) {
72+
try {
73+
const packageJSON = JSON.parse((await readFile(packageJSONPath)).toString());
74+
if (packageJSON.main && !packageJSON.browser) {
75+
return; // unsupported
76+
}
77+
78+
if (packageJSON.browser) {
79+
packageJSON.main = packageJSON.browser;
80+
const webpackConfigPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'extension-browser.webpack.config.js');
81+
if ((await exists(webpackConfigPath))) {
82+
const configOrFnOrArray = require(webpackConfigPath);
83+
function addConfig(configOrFn) {
84+
if (typeof configOrFn === 'function') {
85+
webpackConfigs.push(configOrFn({}, {}));
86+
} else {
87+
webpackConfigs.push(configOrFn);
88+
}
89+
}
90+
if (Array.isArray(configOrFnOrArray)) {
91+
configOrFnOrArray.forEach(addConfig);
92+
} else {
93+
addConfig(configOrFnOrArray);
94+
}
95+
}
96+
}
97+
98+
packageJSON.extensionKind = ['web']; // enable for Web
99+
staticExtensions.push({
100+
packageJSON,
101+
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` }
102+
});
103+
} catch (e) {
104+
console.log(e);
105+
}
106+
}
107+
}));
108+
109+
return new Promise((resolve, reject) => {
110+
if (args.watch) {
111+
webpack(webpackConfigs).watch({}, (err, stats) => {
112+
if (err) {
113+
console.log(err);
114+
reject();
115+
} else {
116+
console.log(stats.toString());
117+
resolve(staticExtensions);
118+
}
119+
});
120+
} else {
121+
webpack(webpackConfigs).run((err, stats) => {
122+
if (err) {
123+
console.log(err);
124+
reject();
125+
} else {
126+
console.log(stats.toString());
127+
resolve(staticExtensions);
128+
}
129+
});
130+
}
131+
});
132+
}
133+
134+
const staticExtensionsPromise = initialize();
135+
58136
const server = http.createServer((req, res) => {
59137
const parsedUrl = url.parse(req.url, true);
60138
const pathname = parsedUrl.pathname;
@@ -141,46 +219,22 @@ function handleStaticExtension(req, res, parsedUrl) {
141219
* @param {import('http').ServerResponse} res
142220
*/
143221
async function handleRoot(req, res) {
144-
const extensionFolders = await util.promisify(fs.readdir)(EXTENSIONS_ROOT);
145-
146-
const staticExtensions = [];
147-
148-
const webpackConfigs = [];
149-
150-
await Promise.all(extensionFolders.map(async extensionFolder => {
151-
try {
152-
const packageJSON = JSON.parse((await util.promisify(fs.readFile)(path.join(EXTENSIONS_ROOT, extensionFolder, 'package.json'))).toString());
153-
if (packageJSON.main && !packageJSON.browser) {
154-
return; // unsupported
155-
}
156-
if (packageJSON.browser) {
157-
packageJSON.main = packageJSON.browser;
158-
const webpackConfigPath = path.join(EXTENSIONS_ROOT, extensionFolder, 'extension-browser.webpack.config.js');
159-
if ((await util.promisify(fs.exists)(webpackConfigPath))) {
160-
webpackConfigs.push(require(webpackConfigPath));
161-
packageJSON.main.replace('/out/', '/dist/');
162-
}
163-
}
164-
165-
packageJSON.extensionKind = ['web']; // enable for Web
166-
staticExtensions.push({
167-
packageJSON,
168-
extensionLocation: { scheme: SCHEME, authority: AUTHORITY, path: `/static-extension/${extensionFolder}` }
169-
});
170-
} catch (error) {
171-
return null;
222+
const match = req.url && req.url.match(/\?([^#]+)/);
223+
let ghPath;
224+
if (match) {
225+
const qs = new URLSearchParams(match[1]);
226+
ghPath = qs.get('gh');
227+
if (ghPath && !ghPath.startsWith('/')) {
228+
ghPath = '/' + ghPath;
172229
}
173-
}));
174-
175-
webpack(webpackConfigs).watch({}, (err, stats) => {
176-
if (err) {
177-
console.log(err);
178-
} else {
179-
console.log(stats.toString());
180-
}
181-
});
230+
}
182231

183-
const webConfiguration = escapeAttribute(JSON.stringify({ staticExtensions, folderUri: { scheme: 'memfs', path: `/sample-folder` }}));
232+
const staticExtensions = await staticExtensionsPromise;
233+
const webConfiguration = escapeAttribute(JSON.stringify({
234+
staticExtensions, folderUri: ghPath
235+
? { scheme: 'github', authority: 'github.com', path: ghPath }
236+
: { scheme: 'memfs', path: `/sample-folder` }
237+
}));
184238

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

0 commit comments

Comments
 (0)