diff --git a/.eslintignore b/.eslintignore index 1c49d5b8846..829fd016672 100644 --- a/.eslintignore +++ b/.eslintignore @@ -25,8 +25,6 @@ **/src/typings/**/*.d.ts **/src/vs/*/**/*.d.ts **/src/vs/base/test/common/filters.perf.data.js -**/src/vs/css.build.js -**/src/vs/css.js **/src/vs/loader.js **/src/vs/nls.build.js **/src/vs/nls.js diff --git a/.eslintrc.json b/.eslintrc.json index 9e9b1ddabfb..fa9e0d1ba89 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -577,7 +577,7 @@ "restrictions": [] }, { - "target": "src/vs/{css.d.ts,monaco.d.ts,nls.d.ts,nls.mock.ts}", + "target": "src/vs/{loader.d.ts,css.ts,css.build.ts,monaco.d.ts,nls.d.ts,nls.mock.ts}", "restrictions": [] }, { diff --git a/build/filters.js b/build/filters.js index c2a0a52bdb9..3d72c28e10b 100644 --- a/build/filters.js +++ b/build/filters.js @@ -68,8 +68,6 @@ module.exports.indentationFilter = [ '!**/LICENSE', '!src/vs/nls.js', '!src/vs/nls.build.js', - '!src/vs/css.js', - '!src/vs/css.build.js', '!src/vs/loader.js', '!src/vs/base/browser/dompurify/*', '!src/vs/base/common/marked/marked.js', diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 45546aa9e64..04b23c2ef17 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -113,9 +113,6 @@ const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => 'vs/nls.ts', 'vs/nls.build.js', 'vs/nls.d.ts', - 'vs/css.js', - 'vs/css.build.js', - 'vs/css.d.ts', 'vs/base/worker/workerMain.ts', ], renames: { diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 192a436899f..59d8fee343e 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -102,10 +102,10 @@ function extractEditor(options) { delete tsConfig.compilerOptions.moduleResolution; writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); [ - 'vs/css.build.js', - 'vs/css.d.ts', - 'vs/css.js', + 'vs/css.build.ts', + 'vs/css.ts', 'vs/loader.js', + 'vs/loader.d.ts', 'vs/nls.build.js', 'vs/nls.d.ts', 'vs/nls.js', diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index 992d1ab5288..34d2e2c9074 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -115,10 +115,10 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str writeOutputFile('tsconfig.json', JSON.stringify(tsConfig, null, '\t')); [ - 'vs/css.build.js', - 'vs/css.d.ts', - 'vs/css.js', + 'vs/css.build.ts', + 'vs/css.ts', 'vs/loader.js', + 'vs/loader.d.ts', 'vs/nls.build.js', 'vs/nls.d.ts', 'vs/nls.js', diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 7d18928f6b4..628e877dcd4 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -18,9 +18,8 @@ "include": [ "typings/require.d.ts", "typings/thenable.d.ts", - "vs/css.d.ts", + "vs/loader.d.ts", "vs/monaco.d.ts", - "vs/nls.d.ts", "vs/editor/*", "vs/base/common/*", "vs/base/browser/*", diff --git a/src/vs/css.build.js b/src/vs/css.build.js deleted file mode 100644 index c8875859684..00000000000 --- a/src/vs/css.build.js +++ /dev/null @@ -1,359 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -/*--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - * Please make sure to make edits in the .ts file at https://github.com/microsoft/vscode-loader/ - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------*/ -'use strict'; -var _cssPluginGlobal = this; -var CSSBuildLoaderPlugin; -(function (CSSBuildLoaderPlugin) { - var global = (_cssPluginGlobal || {}); - var BrowserCSSLoader = /** @class */ (function () { - function BrowserCSSLoader() { - this._pendingLoads = 0; - } - BrowserCSSLoader.prototype.attachListeners = function (name, linkNode, callback, errorback) { - var unbind = function () { - linkNode.removeEventListener('load', loadEventListener); - linkNode.removeEventListener('error', errorEventListener); - }; - var loadEventListener = function (e) { - unbind(); - callback(); - }; - var errorEventListener = function (e) { - unbind(); - errorback(e); - }; - linkNode.addEventListener('load', loadEventListener); - linkNode.addEventListener('error', errorEventListener); - }; - BrowserCSSLoader.prototype._onLoad = function (name, callback) { - this._pendingLoads--; - callback(); - }; - BrowserCSSLoader.prototype._onLoadError = function (name, errorback, err) { - this._pendingLoads--; - errorback(err); - }; - BrowserCSSLoader.prototype._insertLinkNode = function (linkNode) { - this._pendingLoads++; - var head = document.head || document.getElementsByTagName('head')[0]; - var other = head.getElementsByTagName('link') || head.getElementsByTagName('script'); - if (other.length > 0) { - head.insertBefore(linkNode, other[other.length - 1]); - } - else { - head.appendChild(linkNode); - } - }; - BrowserCSSLoader.prototype.createLinkTag = function (name, cssUrl, externalCallback, externalErrorback) { - var _this = this; - var linkNode = document.createElement('link'); - linkNode.setAttribute('rel', 'stylesheet'); - linkNode.setAttribute('type', 'text/css'); - linkNode.setAttribute('data-name', name); - var callback = function () { return _this._onLoad(name, externalCallback); }; - var errorback = function (err) { return _this._onLoadError(name, externalErrorback, err); }; - this.attachListeners(name, linkNode, callback, errorback); - linkNode.setAttribute('href', cssUrl); - return linkNode; - }; - BrowserCSSLoader.prototype._linkTagExists = function (name, cssUrl) { - var i, len, nameAttr, hrefAttr, links = document.getElementsByTagName('link'); - for (i = 0, len = links.length; i < len; i++) { - nameAttr = links[i].getAttribute('data-name'); - hrefAttr = links[i].getAttribute('href'); - if (nameAttr === name || hrefAttr === cssUrl) { - return true; - } - } - return false; - }; - BrowserCSSLoader.prototype.load = function (name, cssUrl, externalCallback, externalErrorback) { - if (this._linkTagExists(name, cssUrl)) { - externalCallback(); - return; - } - var linkNode = this.createLinkTag(name, cssUrl, externalCallback, externalErrorback); - this._insertLinkNode(linkNode); - }; - return BrowserCSSLoader; - }()); - var NodeCSSLoader = /** @class */ (function () { - function NodeCSSLoader() { - this.fs = require.nodeRequire('fs'); - } - NodeCSSLoader.prototype.load = function (name, cssUrl, externalCallback, externalErrorback) { - var contents = this.fs.readFileSync(cssUrl, 'utf8'); - // Remove BOM - if (contents.charCodeAt(0) === NodeCSSLoader.BOM_CHAR_CODE) { - contents = contents.substring(1); - } - externalCallback(contents); - }; - NodeCSSLoader.BOM_CHAR_CODE = 65279; - return NodeCSSLoader; - }()); - // ------------------------------ Finally, the plugin - var CSSPlugin = /** @class */ (function () { - function CSSPlugin(cssLoader) { - this.cssLoader = cssLoader; - } - CSSPlugin.prototype.load = function (name, req, load, config) { - config = config || {}; - var myConfig = config['vs/css'] || {}; - global.inlineResources = myConfig.inlineResources; - global.inlineResourcesLimit = myConfig.inlineResourcesLimit || 5000; - var cssUrl = req.toUrl(name + '.css'); - this.cssLoader.load(name, cssUrl, function (contents) { - // Contents has the CSS file contents if we are in a build - if (config.isBuild) { - CSSPlugin.BUILD_MAP[name] = contents; - CSSPlugin.BUILD_PATH_MAP[name] = cssUrl; - } - load({}); - }, function (err) { - if (typeof load.error === 'function') { - load.error('Could not find ' + cssUrl + ' or it was empty'); - } - }); - }; - CSSPlugin.prototype.write = function (pluginName, moduleName, write) { - // getEntryPoint is a Monaco extension to r.js - var entryPoint = write.getEntryPoint(); - // r.js destroys the context of this plugin between calling 'write' and 'writeFile' - // so the only option at this point is to leak the data to a global - global.cssPluginEntryPoints = global.cssPluginEntryPoints || {}; - global.cssPluginEntryPoints[entryPoint] = global.cssPluginEntryPoints[entryPoint] || []; - global.cssPluginEntryPoints[entryPoint].push({ - moduleName: moduleName, - contents: CSSPlugin.BUILD_MAP[moduleName], - fsPath: CSSPlugin.BUILD_PATH_MAP[moduleName], - }); - write.asModule(pluginName + '!' + moduleName, 'define([\'vs/css!' + entryPoint + '\'], {});'); - }; - CSSPlugin.prototype.writeFile = function (pluginName, moduleName, req, write, config) { - if (global.cssPluginEntryPoints && global.cssPluginEntryPoints.hasOwnProperty(moduleName)) { - var fileName = req.toUrl(moduleName + '.css'); - var contents = [ - '/*---------------------------------------------------------', - ' * Copyright (c) Microsoft Corporation. All rights reserved.', - ' *--------------------------------------------------------*/' - ], entries = global.cssPluginEntryPoints[moduleName]; - for (var i = 0; i < entries.length; i++) { - if (global.inlineResources) { - contents.push(Utilities.rewriteOrInlineUrls(entries[i].fsPath, entries[i].moduleName, moduleName, entries[i].contents, global.inlineResources === 'base64', global.inlineResourcesLimit)); - } - else { - contents.push(Utilities.rewriteUrls(entries[i].moduleName, moduleName, entries[i].contents)); - } - } - write(fileName, contents.join('\r\n')); - } - }; - CSSPlugin.prototype.getInlinedResources = function () { - return global.cssInlinedResources || []; - }; - CSSPlugin.BUILD_MAP = {}; - CSSPlugin.BUILD_PATH_MAP = {}; - return CSSPlugin; - }()); - CSSBuildLoaderPlugin.CSSPlugin = CSSPlugin; - var Utilities = /** @class */ (function () { - function Utilities() { - } - Utilities.startsWith = function (haystack, needle) { - return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; - }; - /** - * Find the path of a file. - */ - Utilities.pathOf = function (filename) { - var lastSlash = filename.lastIndexOf('/'); - if (lastSlash !== -1) { - return filename.substr(0, lastSlash + 1); - } - else { - return ''; - } - }; - /** - * A conceptual a + b for paths. - * Takes into account if `a` contains a protocol. - * Also normalizes the result: e.g.: a/b/ + ../c => a/c - */ - Utilities.joinPaths = function (a, b) { - function findSlashIndexAfterPrefix(haystack, prefix) { - if (Utilities.startsWith(haystack, prefix)) { - return Math.max(prefix.length, haystack.indexOf('/', prefix.length)); - } - return 0; - } - var aPathStartIndex = 0; - aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, '//'); - aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, 'http://'); - aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, 'https://'); - function pushPiece(pieces, piece) { - if (piece === './') { - // Ignore - return; - } - if (piece === '../') { - var prevPiece = (pieces.length > 0 ? pieces[pieces.length - 1] : null); - if (prevPiece && prevPiece === '/') { - // Ignore - return; - } - if (prevPiece && prevPiece !== '../') { - // Pop - pieces.pop(); - return; - } - } - // Push - pieces.push(piece); - } - function push(pieces, path) { - while (path.length > 0) { - var slashIndex = path.indexOf('/'); - var piece = (slashIndex >= 0 ? path.substring(0, slashIndex + 1) : path); - path = (slashIndex >= 0 ? path.substring(slashIndex + 1) : ''); - pushPiece(pieces, piece); - } - } - var pieces = []; - push(pieces, a.substr(aPathStartIndex)); - if (b.length > 0 && b.charAt(0) === '/') { - pieces = []; - } - push(pieces, b); - return a.substring(0, aPathStartIndex) + pieces.join(''); - }; - Utilities.commonPrefix = function (str1, str2) { - var len = Math.min(str1.length, str2.length); - for (var i = 0; i < len; i++) { - if (str1.charCodeAt(i) !== str2.charCodeAt(i)) { - break; - } - } - return str1.substring(0, i); - }; - Utilities.commonFolderPrefix = function (fromPath, toPath) { - var prefix = Utilities.commonPrefix(fromPath, toPath); - var slashIndex = prefix.lastIndexOf('/'); - if (slashIndex === -1) { - return ''; - } - return prefix.substring(0, slashIndex + 1); - }; - Utilities.relativePath = function (fromPath, toPath) { - if (Utilities.startsWith(toPath, '/') || Utilities.startsWith(toPath, 'http://') || Utilities.startsWith(toPath, 'https://')) { - return toPath; - } - // Ignore common folder prefix - var prefix = Utilities.commonFolderPrefix(fromPath, toPath); - fromPath = fromPath.substr(prefix.length); - toPath = toPath.substr(prefix.length); - var upCount = fromPath.split('/').length; - var result = ''; - for (var i = 1; i < upCount; i++) { - result += '../'; - } - return result + toPath; - }; - Utilities._replaceURL = function (contents, replacer) { - // Use ")" as the terminator as quotes are oftentimes not used at all - return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, function (_) { - var matches = []; - for (var _i = 1; _i < arguments.length; _i++) { - matches[_i - 1] = arguments[_i]; - } - var url = matches[0]; - // Eliminate starting quotes (the initial whitespace is not captured) - if (url.charAt(0) === '"' || url.charAt(0) === '\'') { - url = url.substring(1); - } - // The ending whitespace is captured - while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) { - url = url.substring(0, url.length - 1); - } - // Eliminate ending quotes - if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') { - url = url.substring(0, url.length - 1); - } - if (!Utilities.startsWith(url, 'data:') && !Utilities.startsWith(url, 'http://') && !Utilities.startsWith(url, 'https://')) { - url = replacer(url); - } - return 'url(' + url + ')'; - }); - }; - Utilities.rewriteUrls = function (originalFile, newFile, contents) { - return this._replaceURL(contents, function (url) { - var absoluteUrl = Utilities.joinPaths(Utilities.pathOf(originalFile), url); - return Utilities.relativePath(newFile, absoluteUrl); - }); - }; - Utilities.rewriteOrInlineUrls = function (originalFileFSPath, originalFile, newFile, contents, forceBase64, inlineByteLimit) { - var fs = require.nodeRequire('fs'); - var path = require.nodeRequire('path'); - return this._replaceURL(contents, function (url) { - if (/\.(svg|png)$/.test(url)) { - var fsPath = path.join(path.dirname(originalFileFSPath), url); - var fileContents = fs.readFileSync(fsPath); - if (fileContents.length < inlineByteLimit) { - global.cssInlinedResources = global.cssInlinedResources || []; - var normalizedFSPath = fsPath.replace(/\\/g, '/'); - if (global.cssInlinedResources.indexOf(normalizedFSPath) >= 0) { - // console.warn('CSS INLINING IMAGE AT ' + fsPath + ' MORE THAN ONCE. CONSIDER CONSOLIDATING CSS RULES'); - } - global.cssInlinedResources.push(normalizedFSPath); - var MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; - var DATA = ';base64,' + fileContents.toString('base64'); - if (!forceBase64 && /\.svg$/.test(url)) { - // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris - var newText = fileContents.toString() - .replace(/"/g, '\'') - .replace(/%/g, '%25') - .replace(//g, '%3E') - .replace(/&/g, '%26') - .replace(/#/g, '%23') - .replace(/\s+/g, ' '); - var encodedData = ',' + newText; - if (encodedData.length < DATA.length) { - DATA = encodedData; - } - } - return '"data:' + MIME + DATA + '"'; - } - } - var absoluteUrl = Utilities.joinPaths(Utilities.pathOf(originalFile), url); - return Utilities.relativePath(newFile, absoluteUrl); - }); - }; - return Utilities; - }()); - CSSBuildLoaderPlugin.Utilities = Utilities; - (function () { - var cssLoader = null; - var isElectron = (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions['electron'] !== 'undefined'); - if (typeof process !== 'undefined' && process.versions && !!process.versions.node && !isElectron) { - cssLoader = new NodeCSSLoader(); - } - else { - cssLoader = new BrowserCSSLoader(); - } - define('vs/css', new CSSPlugin(cssLoader)); - })(); -})(CSSBuildLoaderPlugin || (CSSBuildLoaderPlugin = {})); diff --git a/src/vs/css.build.ts b/src/vs/css.build.ts new file mode 100644 index 00000000000..2107d144622 --- /dev/null +++ b/src/vs/css.build.ts @@ -0,0 +1,290 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +namespace CSSBuildLoaderPlugin { + + interface ICSSPluginConfig { + inlineResources?: boolean | 'base64'; + inlineResourcesLimit?: number; + } + + // This file gets compiled also with the standalone editor, + // so we cannot depend on types from node.d.ts + interface INodeFS { + readFileSync(path: string, encoding: 'utf8'): string; + readFileSync(path: string): INodeBuffer; + } + interface INodeBuffer { + length: number; + toString(encoding?: 'base64'): string; + } + interface INodePath { + dirname(p: string): string; + join(...paths: string[]): string; + } + + const fs: INodeFS = (require).nodeRequire('fs'); + const path: INodePath = (require).nodeRequire('path'); + + interface ICSSEntryPointData { + moduleName: string; + contents: string; + fsPath: string; + } + + export class CSSPlugin implements AMDLoader.ILoaderPlugin { + + private inlineResources: boolean | 'base64' = false; + private inlineResourcesLimit: number = 5000; + + private readonly _contentsMap: { [moduleName: string]: string } = {}; + private readonly _pathMap: { [moduleName: string]: string } = {}; + private readonly _entryPoints: { [entryPoint: string]: ICSSEntryPointData[] } = {}; + private readonly _inlinedResources: string[] = []; + + public load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void { + config = config || {}; + const myConfig = (config['vs/css'] || {}); + this.inlineResources = (typeof myConfig.inlineResources === 'undefined' ? false : myConfig.inlineResources); + this.inlineResourcesLimit = (myConfig.inlineResourcesLimit || 5000); + const cssUrl = req.toUrl(name + '.css'); + let contents = fs.readFileSync(cssUrl, 'utf8'); + if (contents.charCodeAt(0) === 65279 /* BOM */) { + // Remove BOM + contents = contents.substring(1); + } + if (config.isBuild) { + this._contentsMap[name] = contents; + this._pathMap[name] = cssUrl; + } + load({}); + } + + public write(pluginName: string, moduleName: string, write: AMDLoader.IPluginWriteCallback): void { + const entryPoint = write.getEntryPoint(); + + this._entryPoints[entryPoint] = this._entryPoints[entryPoint] || []; + this._entryPoints[entryPoint].push({ + moduleName: moduleName, + contents: this._contentsMap[moduleName], + fsPath: this._pathMap[moduleName], + }); + + write.asModule(pluginName + '!' + moduleName, + 'define([\'vs/css!' + entryPoint + '\'], {});' + ); + } + + public writeFile(pluginName: string, moduleName: string, req: AMDLoader.IRelativeRequire, write: AMDLoader.IPluginWriteFileCallback, config: AMDLoader.IConfigurationOptions): void { + if (this._entryPoints && this._entryPoints.hasOwnProperty(moduleName)) { + const fileName = req.toUrl(moduleName + '.css'); + const contents = [ + '/*---------------------------------------------------------', + ' * Copyright (c) Microsoft Corporation. All rights reserved.', + ' *--------------------------------------------------------*/' + ], + entries = this._entryPoints[moduleName]; + for (let i = 0; i < entries.length; i++) { + if (this.inlineResources) { + contents.push(this._rewriteOrInlineUrls(entries[i].fsPath, entries[i].moduleName, moduleName, entries[i].contents, this.inlineResources === 'base64', this.inlineResourcesLimit)); + } else { + contents.push(this._rewriteUrls(entries[i].moduleName, moduleName, entries[i].contents)); + } + } + write(fileName, contents.join('\r\n')); + } + } + + public getInlinedResources(): string[] { + return this._inlinedResources || []; + } + + private _rewriteOrInlineUrls(originalFileFSPath: string, originalFile: string, newFile: string, contents: string, forceBase64: boolean, inlineByteLimit: number): string { + return Utilities.replaceURL(contents, (url) => { + if (/\.(svg|png)$/.test(url)) { + const fsPath = path.join(path.dirname(originalFileFSPath), url); + const fileContents = fs.readFileSync(fsPath); + + if (fileContents.length < inlineByteLimit) { + const normalizedFSPath = fsPath.replace(/\\/g, '/'); + this._inlinedResources.push(normalizedFSPath); + + const MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; + let DATA = ';base64,' + fileContents.toString('base64'); + + if (!forceBase64 && /\.svg$/.test(url)) { + // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris + const newText = fileContents.toString() + .replace(/"/g, '\'') + .replace(/%/g, '%25') + .replace(//g, '%3E') + .replace(/&/g, '%26') + .replace(/#/g, '%23') + .replace(/\s+/g, ' '); + const encodedData = ',' + newText; + if (encodedData.length < DATA.length) { + DATA = encodedData; + } + } + return '"data:' + MIME + DATA + '"'; + } + } + + const absoluteUrl = Utilities.joinPaths(Utilities.pathOf(originalFile), url); + return Utilities.relativePath(newFile, absoluteUrl); + }); + } + + private _rewriteUrls(originalFile: string, newFile: string, contents: string): string { + return Utilities.replaceURL(contents, (url) => { + const absoluteUrl = Utilities.joinPaths(Utilities.pathOf(originalFile), url); + return Utilities.relativePath(newFile, absoluteUrl); + }); + } + } + + export class Utilities { + + public static startsWith(haystack: string, needle: string): boolean { + return haystack.length >= needle.length && haystack.substr(0, needle.length) === needle; + } + + /** + * Find the path of a file. + */ + public static pathOf(filename: string): string { + const lastSlash = filename.lastIndexOf('/'); + if (lastSlash !== -1) { + return filename.substr(0, lastSlash + 1); + } else { + return ''; + } + } + + /** + * A conceptual a + b for paths. + * Takes into account if `a` contains a protocol. + * Also normalizes the result: e.g.: a/b/ + ../c => a/c + */ + public static joinPaths(a: string, b: string): string { + + function findSlashIndexAfterPrefix(haystack: string, prefix: string): number { + if (Utilities.startsWith(haystack, prefix)) { + return Math.max(prefix.length, haystack.indexOf('/', prefix.length)); + } + return 0; + } + + let aPathStartIndex = 0; + aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, '//'); + aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, 'http://'); + aPathStartIndex = aPathStartIndex || findSlashIndexAfterPrefix(a, 'https://'); + + function pushPiece(pieces: string[], piece: string): void { + if (piece === './') { + // Ignore + return; + } + if (piece === '../') { + const prevPiece = (pieces.length > 0 ? pieces[pieces.length - 1] : null); + if (prevPiece && prevPiece === '/') { + // Ignore + return; + } + if (prevPiece && prevPiece !== '../') { + // Pop + pieces.pop(); + return; + } + } + // Push + pieces.push(piece); + } + + function push(pieces: string[], path: string): void { + while (path.length > 0) { + const slashIndex = path.indexOf('/'); + const piece = (slashIndex >= 0 ? path.substring(0, slashIndex + 1) : path); + path = (slashIndex >= 0 ? path.substring(slashIndex + 1) : ''); + pushPiece(pieces, piece); + } + } + + let pieces: string[] = []; + push(pieces, a.substr(aPathStartIndex)); + if (b.length > 0 && b.charAt(0) === '/') { + pieces = []; + } + push(pieces, b); + + return a.substring(0, aPathStartIndex) + pieces.join(''); + } + + public static commonPrefix(str1: string, str2: string): string { + const len = Math.min(str1.length, str2.length); + for (let i = 0; i < len; i++) { + if (str1.charCodeAt(i) !== str2.charCodeAt(i)) { + return str1.substring(0, i); + } + } + return str1.substring(0, len); + } + + public static commonFolderPrefix(fromPath: string, toPath: string): string { + const prefix = Utilities.commonPrefix(fromPath, toPath); + const slashIndex = prefix.lastIndexOf('/'); + if (slashIndex === -1) { + return ''; + } + return prefix.substring(0, slashIndex + 1); + } + + public static relativePath(fromPath: string, toPath: string): string { + if (Utilities.startsWith(toPath, '/') || Utilities.startsWith(toPath, 'http://') || Utilities.startsWith(toPath, 'https://')) { + return toPath; + } + + // Ignore common folder prefix + const prefix = Utilities.commonFolderPrefix(fromPath, toPath); + fromPath = fromPath.substr(prefix.length); + toPath = toPath.substr(prefix.length); + + const upCount = fromPath.split('/').length; + let result = ''; + for (let i = 1; i < upCount; i++) { + result += '../'; + } + return result + toPath; + } + + public static replaceURL(contents: string, replacer: (url: string) => string): string { + // Use ")" as the terminator as quotes are oftentimes not used at all + return contents.replace(/url\(\s*([^\)]+)\s*\)?/g, (_: string, ...matches: string[]) => { + let url = matches[0]; + // Eliminate starting quotes (the initial whitespace is not captured) + if (url.charAt(0) === '"' || url.charAt(0) === '\'') { + url = url.substring(1); + } + // The ending whitespace is captured + while (url.length > 0 && (url.charAt(url.length - 1) === ' ' || url.charAt(url.length - 1) === '\t')) { + url = url.substring(0, url.length - 1); + } + // Eliminate ending quotes + if (url.charAt(url.length - 1) === '"' || url.charAt(url.length - 1) === '\'') { + url = url.substring(0, url.length - 1); + } + + if (!Utilities.startsWith(url, 'data:') && !Utilities.startsWith(url, 'http://') && !Utilities.startsWith(url, 'https://')) { + url = replacer(url); + } + + return 'url(' + url + ')'; + }); + } + } + + define('vs/css', new CSSPlugin()); +} diff --git a/src/vs/css.d.ts b/src/vs/css.d.ts deleted file mode 100644 index 11a10ed5950..00000000000 --- a/src/vs/css.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - diff --git a/src/vs/css.js b/src/vs/css.js deleted file mode 100644 index 8a0f9912902..00000000000 --- a/src/vs/css.js +++ /dev/null @@ -1,111 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -/*--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - * Please make sure to make edits in the .ts file at https://github.com/microsoft/vscode-loader/ - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------- - *--------------------------------------------------------------------------------------------*/ -'use strict'; -var CSSLoaderPlugin; -(function (CSSLoaderPlugin) { - var BrowserCSSLoader = /** @class */ (function () { - function BrowserCSSLoader() { - this._pendingLoads = 0; - } - BrowserCSSLoader.prototype.attachListeners = function (name, linkNode, callback, errorback) { - var unbind = function () { - linkNode.removeEventListener('load', loadEventListener); - linkNode.removeEventListener('error', errorEventListener); - }; - var loadEventListener = function (e) { - unbind(); - callback(); - }; - var errorEventListener = function (e) { - unbind(); - errorback(e); - }; - linkNode.addEventListener('load', loadEventListener); - linkNode.addEventListener('error', errorEventListener); - }; - BrowserCSSLoader.prototype._onLoad = function (name, callback) { - this._pendingLoads--; - callback(); - }; - BrowserCSSLoader.prototype._onLoadError = function (name, errorback, err) { - this._pendingLoads--; - errorback(err); - }; - BrowserCSSLoader.prototype._insertLinkNode = function (linkNode) { - this._pendingLoads++; - var head = document.head || document.getElementsByTagName('head')[0]; - head.appendChild(linkNode); - }; - BrowserCSSLoader.prototype.createLinkTag = function (name, cssUrl, externalCallback, externalErrorback) { - var _this = this; - var linkNode = document.createElement('link'); - linkNode.setAttribute('rel', 'stylesheet'); - linkNode.setAttribute('type', 'text/css'); - linkNode.setAttribute('data-name', name); - var callback = function () { return _this._onLoad(name, externalCallback); }; - var errorback = function (err) { return _this._onLoadError(name, externalErrorback, err); }; - this.attachListeners(name, linkNode, callback, errorback); - linkNode.setAttribute('href', cssUrl); - return linkNode; - }; - BrowserCSSLoader.prototype._linkTagExists = function (name, cssUrl) { - var i, len, nameAttr, hrefAttr, links = document.getElementsByTagName('link'); - for (i = 0, len = links.length; i < len; i++) { - nameAttr = links[i].getAttribute('data-name'); - hrefAttr = links[i].getAttribute('href'); - if (nameAttr === name || hrefAttr === cssUrl) { - return true; - } - } - return false; - }; - BrowserCSSLoader.prototype.load = function (name, cssUrl, externalCallback, externalErrorback) { - if (this._linkTagExists(name, cssUrl)) { - externalCallback(); - return; - } - var linkNode = this.createLinkTag(name, cssUrl, externalCallback, externalErrorback); - this._insertLinkNode(linkNode); - }; - return BrowserCSSLoader; - }()); - // ------------------------------ Finally, the plugin - var CSSPlugin = /** @class */ (function () { - function CSSPlugin() { - this._cssLoader = new BrowserCSSLoader(); - } - CSSPlugin.prototype.load = function (name, req, load, config) { - config = config || {}; - var cssConfig = config['vs/css'] || {}; - if (cssConfig.disabled) { - // the plugin is asked to not create any style sheets - load({}); - return; - } - var cssUrl = req.toUrl(name + '.css'); - this._cssLoader.load(name, cssUrl, function (contents) { - load({}); - }, function (err) { - if (typeof load.error === 'function') { - load.error('Could not find ' + cssUrl + ' or it was empty'); - } - }); - }; - return CSSPlugin; - }()); - CSSLoaderPlugin.CSSPlugin = CSSPlugin; - define('vs/css', new CSSPlugin()); -})(CSSLoaderPlugin || (CSSLoaderPlugin = {})); diff --git a/src/vs/css.ts b/src/vs/css.ts new file mode 100644 index 00000000000..942040b4af5 --- /dev/null +++ b/src/vs/css.ts @@ -0,0 +1,93 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +namespace CSSLoaderPlugin { + + interface ICSSPluginConfig { + disabled?: boolean; + } + + class BrowserCSSLoader { + + public load(name: string, cssUrl: string, callback: () => void, errorback: (err: any) => void): void { + if (this._linkTagExists(name, cssUrl)) { + callback(); + return; + } + this._createLinkTag(name, cssUrl, callback, errorback); + } + + private _linkTagExists(name: string, cssUrl: string): boolean { + const links = document.getElementsByTagName('link'); + for (let i = 0, len = links.length; i < len; i++) { + const nameAttr = links[i].getAttribute('data-name'); + const hrefAttr = links[i].getAttribute('href'); + if (nameAttr === name || hrefAttr === cssUrl) { + return true; + } + } + return false; + } + + private _createLinkTag(name: string, cssUrl: string, callback: () => void, errorback: (err: any) => void): void { + const linkNode = document.createElement('link'); + linkNode.setAttribute('rel', 'stylesheet'); + linkNode.setAttribute('type', 'text/css'); + linkNode.setAttribute('data-name', name); + + this._attachListeners(name, linkNode, callback, errorback); + linkNode.setAttribute('href', cssUrl); + + const head = document.head || document.getElementsByTagName('head')[0]; + head.appendChild(linkNode); + } + + private _attachListeners(name: string, linkNode: HTMLLinkElement, callback: () => void, errorback: (err: any) => void): void { + const unbind = () => { + linkNode.removeEventListener('load', loadEventListener); + linkNode.removeEventListener('error', errorEventListener); + }; + const loadEventListener = (e: any) => { + unbind(); + callback(); + }; + const errorEventListener = (e: any) => { + unbind(); + errorback(e); + }; + linkNode.addEventListener('load', loadEventListener); + linkNode.addEventListener('error', errorEventListener); + } + } + + export class CSSPlugin implements AMDLoader.ILoaderPlugin { + + private _cssLoader = new BrowserCSSLoader(); + + public load(name: string, req: AMDLoader.IRelativeRequire, load: AMDLoader.IPluginLoadCallback, config: AMDLoader.IConfigurationOptions): void { + config = config || {}; + const cssConfig = (config['vs/css'] || {}); + + if (cssConfig.disabled) { + // the plugin is asked to not create any style sheets + load({}); + return; + } + + const cssUrl = req.toUrl(name + '.css'); + this._cssLoader.load(name, cssUrl, () => { + load({}); + }, (err: any) => { + if (typeof load.error === 'function') { + load.error('Could not find ' + cssUrl + '.'); + } + }); + } + } + + define('vs/css', new CSSPlugin()); +} diff --git a/src/vs/loader.d.ts b/src/vs/loader.d.ts new file mode 100644 index 00000000000..5495321da78 --- /dev/null +++ b/src/vs/loader.d.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare namespace AMDLoader { + interface ILoaderPlugin { + load: (pluginParam: string, parentRequire: IRelativeRequire, loadCallback: IPluginLoadCallback, options: IConfigurationOptions) => void; + write?: (pluginName: string, moduleName: string, write: IPluginWriteCallback) => void; + writeFile?: (pluginName: string, moduleName: string, req: IRelativeRequire, write: IPluginWriteFileCallback, config: IConfigurationOptions) => void; + finishBuild?: (write: (filename: string, contents: string) => void) => void; + } + interface IRelativeRequire { + (dependencies: string[], callback: Function, errorback?: (error: Error) => void): void; + toUrl(id: string): string; + } + interface IPluginLoadCallback { + (value: any): void; + error(err: any): void; + } + interface IConfigurationOptions { + isBuild: boolean | undefined; + [key: string]: any; + } + interface IPluginWriteCallback { + (contents: string): void; + getEntryPoint(): string; + asModule(moduleId: string, contents: string): void; + } + interface IPluginWriteFileCallback { + (filename: string, contents: string): void; + getEntryPoint(): string; + asModule(moduleId: string, contents: string): void; + } +}