2023-04-13 14:39:35 +00:00
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* Copyright ( c ) Microsoft Corporation . All rights reserved .
* Licensed under the MIT License . See License . txt in the project root for license information .
* -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- * /
import * as fsPromise from 'fs/promises' ;
import path from 'path' ;
import * as http from 'http' ;
import * as parcelWatcher from '@parcel/watcher' ;
/ * *
* Launches the server for the monaco editor playground
* /
function main() {
const server = new HttpServer ( { host : 'localhost' , port : 5001 , cors : true } ) ;
server . use ( '/' , redirectToMonacoEditorPlayground ( ) ) ;
const rootDir = path . join ( __dirname , '..' ) ;
const fileServer = new FileServer ( rootDir ) ;
server . use ( fileServer . handleRequest ) ;
const module IdMapper = new SimpleModuleIdPathMapper ( path . join ( rootDir , 'out' ) ) ;
const editorMainBundle = new CachedBundle ( 'vs/editor/editor.main' , module IdMapper ) ;
fileServer . overrideFileContent ( editorMainBundle . entryModulePath , ( ) = > editorMainBundle . bundle ( ) ) ;
const loaderPath = path . join ( rootDir , 'out/vs/loader.js' ) ;
fileServer . overrideFileContent ( loaderPath , async ( ) = >
2023-06-19 10:42:06 +00:00
Buffer . from ( new TextEncoder ( ) . encode ( makeLoaderJsHotReloadable ( await fsPromise . readFile ( loaderPath , 'utf8' ) , new URL ( '/file-changes' , server . url ) ) ) )
2023-04-13 14:39:35 +00:00
) ;
const watcher = DirWatcher . watchRecursively ( module IdMapper.rootDir ) ;
watcher . onDidChange ( ( path , newContent ) = > {
editorMainBundle . setModuleContent ( path , newContent ) ;
editorMainBundle . bundle ( ) ;
console . log ( ` ${ new Date ( ) . toLocaleTimeString ( ) } , file change: ${ path } ` ) ;
} ) ;
2023-06-19 10:42:06 +00:00
server . use ( '/file-changes' , handleGetFileChangesRequest ( watcher , fileServer , module IdMapper ) ) ;
2023-04-13 14:39:35 +00:00
console . log ( ` Server listening on ${ server . url } ` ) ;
}
setTimeout ( main , 0 ) ;
// #region Http/File Server
type RequestHandler = ( req : http.IncomingMessage , res : http.ServerResponse ) = > Promise < void > ;
type ChainableRequestHandler = ( req : http.IncomingMessage , res : http.ServerResponse , next : RequestHandler ) = > Promise < void > ;
class HttpServer {
private readonly server : http.Server ;
public readonly url : URL ;
private handler : ChainableRequestHandler [ ] = [ ] ;
constructor ( options : { host : string ; port : number ; cors : boolean } ) {
this . server = http . createServer ( async ( req , res ) = > {
if ( options . cors ) {
res . setHeader ( 'Access-Control-Allow-Origin' , '*' ) ;
}
let i = 0 ;
const next = async ( req : http.IncomingMessage , res : http.ServerResponse ) = > {
if ( i >= this . handler . length ) {
res . writeHead ( 404 , { 'Content-Type' : 'text/plain' } ) ;
res . end ( '404 Not Found' ) ;
return ;
}
const handler = this . handler [ i ] ;
i ++ ;
await handler ( req , res , next ) ;
} ;
await next ( req , res ) ;
} ) ;
this . server . listen ( options . port , options . host ) ;
this . url = new URL ( ` http:// ${ options . host } : ${ options . port } ` ) ;
}
use ( handler : ChainableRequestHandler ) ;
use ( path : string , handler : ChainableRequestHandler ) ;
use ( . . . args : [ path : string , handler : ChainableRequestHandler ] | [ handler : ChainableRequestHandler ] ) {
const handler = args . length === 1 ? args [ 0 ] : ( req , res , next ) = > {
const path = args [ 0 ] ;
const requestedUrl = new URL ( req . url , this . url ) ;
if ( requestedUrl . pathname === path ) {
return args [ 1 ] ( req , res , next ) ;
} else {
return next ( req , res ) ;
}
} ;
this . handler . push ( handler ) ;
}
}
function redirectToMonacoEditorPlayground ( ) : ChainableRequestHandler {
return async ( req , res ) = > {
const url = new URL ( 'https://microsoft.github.io/monaco-editor/playground.html' ) ;
url . searchParams . append ( 'source' , ` http:// ${ req . headers . host } /out/vs ` ) ;
res . writeHead ( 302 , { Location : url.toString ( ) } ) ;
res . end ( ) ;
} ;
}
class FileServer {
private readonly overrides = new Map < string , ( ) = > Promise < Buffer > > ( ) ;
constructor ( public readonly publicDir : string ) { }
public readonly handleRequest : ChainableRequestHandler = async ( req , res , next ) = > {
const requestedUrl = new URL ( req . url ! , ` http:// ${ req . headers . host } ` ) ;
const pathName = requestedUrl . pathname ;
const filePath = path . join ( this . publicDir , pathName ) ;
if ( ! filePath . startsWith ( this . publicDir ) ) {
res . writeHead ( 403 , { 'Content-Type' : 'text/plain' } ) ;
res . end ( '403 Forbidden' ) ;
return ;
}
try {
const override = this . overrides . get ( filePath ) ;
let content : Buffer ;
if ( override ) {
content = await override ( ) ;
} else {
content = await fsPromise . readFile ( filePath ) ;
}
const contentType = getContentType ( filePath ) ;
res . writeHead ( 200 , { 'Content-Type' : contentType } ) ;
res . end ( content ) ;
} catch ( err ) {
if ( err . code === 'ENOENT' ) {
next ( req , res ) ;
} else {
res . writeHead ( 500 , { 'Content-Type' : 'text/plain' } ) ;
res . end ( '500 Internal Server Error' ) ;
}
}
} ;
public filePathToUrlPath ( filePath : string ) : string | undefined {
const relative = path . relative ( this . publicDir , filePath ) ;
const isSubPath = ! ! relative && ! relative . startsWith ( '..' ) && ! path . isAbsolute ( relative ) ;
if ( ! isSubPath ) {
return undefined ;
}
const relativePath = relative . replace ( /\\/g , '/' ) ;
return ` / ${ relativePath } ` ;
}
public overrideFileContent ( filePath : string , content : ( ) = > Promise < Buffer > ) : void {
this . overrides . set ( filePath , content ) ;
}
}
function getContentType ( filePath : string ) : string {
const extname = path . extname ( filePath ) ;
switch ( extname ) {
case '.js' :
return 'text/javascript' ;
case '.css' :
return 'text/css' ;
case '.json' :
return 'application/json' ;
case '.png' :
return 'image/png' ;
case '.jpg' :
return 'image/jpg' ;
2023-06-19 10:42:06 +00:00
case '.svg' :
return 'image/svg+xml' ;
case '.html' :
return 'text/html' ;
case '.wasm' :
return 'application/wasm' ;
2023-04-13 14:39:35 +00:00
default :
return 'text/plain' ;
}
}
// #endregion
// #region File Watching
interface IDisposable {
dispose ( ) : void ;
}
class DirWatcher {
public static watchRecursively ( dir : string ) : DirWatcher {
const listeners : ( ( path : string , newContent : string ) = > void ) [ ] = [ ] ;
const fileContents = new Map < string , string > ( ) ;
const event = ( handler : ( path : string , newContent : string ) = > void ) = > {
listeners . push ( handler ) ;
return {
dispose : ( ) = > {
const idx = listeners . indexOf ( handler ) ;
if ( idx >= 0 ) {
listeners . splice ( idx , 1 ) ;
}
}
} ;
} ;
parcelWatcher . subscribe ( dir , async ( err , events ) = > {
for ( const e of events ) {
if ( e . type === 'update' ) {
const newContent = await fsPromise . readFile ( e . path , 'utf8' ) ;
if ( fileContents . get ( e . path ) !== newContent ) {
fileContents . set ( e . path , newContent ) ;
listeners . forEach ( l = > l ( e . path , newContent ) ) ;
}
}
}
} ) ;
return new DirWatcher ( event ) ;
}
constructor ( public readonly onDidChange : ( handler : ( path : string , newContent : string ) = > void ) = > IDisposable ) {
}
}
2023-06-19 10:42:06 +00:00
function handleGetFileChangesRequest ( watcher : DirWatcher , fileServer : FileServer , module IdMapper : SimpleModuleIdPathMapper ) : ChainableRequestHandler {
2023-04-13 14:39:35 +00:00
return async ( req , res ) = > {
res . writeHead ( 200 , { 'Content-Type' : 'text/plain' } ) ;
const d = watcher . onDidChange ( fsPath = > {
const path = fileServer . filePathToUrlPath ( fsPath ) ;
if ( path ) {
2023-06-19 10:42:06 +00:00
res . write ( JSON . stringify ( { changedPath : path , module Id : module IdMapper.getModuleId ( fsPath ) } ) + '\n' ) ;
2023-04-13 14:39:35 +00:00
}
} ) ;
res . on ( 'close' , ( ) = > d . dispose ( ) ) ;
} ;
}
2023-06-19 10:42:06 +00:00
function makeLoaderJsHotReloadable ( loaderJsCode : string , fileChangesUrl : URL ) : string {
loaderJsCode = loaderJsCode . replace (
/constructor\(env, scriptLoader, defineFunc, requireFunc, loaderAvailableTimestamp = 0\) {/ ,
2023-06-19 15:17:17 +00:00
'$&globalThis.___globalModuleManager = this;'
2023-06-19 10:42:06 +00:00
) ;
2023-04-13 14:39:35 +00:00
2023-06-19 15:17:17 +00:00
const ___globalModuleManager : any = undefined ;
2023-06-19 10:42:06 +00:00
// This code will be appended to loader.js
function $watchChanges ( fileChangesUrl : string ) {
let reloadFn ;
if ( globalThis . $sendMessageToParent ) {
reloadFn = ( ) = > globalThis . $sendMessageToParent ( { kind : 'reload' } ) ;
} else if ( typeof window !== 'undefined' ) {
reloadFn = ( ) = > window . location . reload ( ) ;
} else {
reloadFn = ( ) = > { } ;
}
console . log ( 'Connecting to server to watch for changes...' ) ;
( fetch as any ) ( fileChangesUrl )
. then ( async request = > {
const reader = request . body . getReader ( ) ;
let buffer = '' ;
while ( true ) {
const { done , value } = await reader . read ( ) ;
if ( done ) { break ; }
buffer += new TextDecoder ( ) . decode ( value ) ;
const lines = buffer . split ( '\n' ) ;
buffer = lines . pop ( ) ! ;
for ( const line of lines ) {
const data = JSON . parse ( line ) ;
let handled = false ;
if ( data . changedPath . endsWith ( '.css' ) ) {
2023-06-19 15:17:17 +00:00
if ( typeof document !== 'undefined' ) {
console . log ( 'css changed' , data . changedPath ) ;
const styleSheet = [ . . . document . querySelectorAll ( ` link[rel='stylesheet'] ` ) ] . find ( ( l : any ) = > new URL ( l . href , document . location . href ) . pathname . endsWith ( data . changedPath ) ) as any ;
if ( styleSheet ) {
styleSheet . href = styleSheet . href . replace ( /\?.*/ , '' ) + '?' + Date . now ( ) ;
}
2023-06-19 10:42:06 +00:00
}
handled = true ;
} else if ( data . changedPath . endsWith ( '.js' ) && data . module Id ) {
console . log ( 'js changed' , data . changedPath ) ;
2023-06-19 15:17:17 +00:00
const module Id = ___globalModuleManager . _moduleIdProvider . getModuleId ( data . module Id ) ;
if ( ___globalModuleManager . _modules2 [ module Id ] ) {
const srcUrl = ___globalModuleManager . _config . module IdToPaths ( data . module Id ) ;
2023-06-19 10:42:06 +00:00
const newSrc = await ( await fetch ( srcUrl ) ) . text ( ) ;
2023-07-19 14:01:20 +00:00
( new Function ( 'define' , newSrc ) ) ( function ( deps , callback ) { // CodeQL [SM01632] This code is only executed during development (as part of the dev-only playground-server). It is required for the hot-reload functionality.
2023-06-19 15:17:17 +00:00
const oldModule = ___globalModuleManager . _modules2 [ module Id ] ;
delete ___globalModuleManager . _modules2 [ module Id ] ;
2023-06-19 10:42:06 +00:00
2023-06-19 15:17:17 +00:00
___globalModuleManager . defineModule ( data . module Id , deps , callback ) ;
const newModule = ___globalModuleManager . _modules2 [ module Id ] ;
2023-06-19 10:42:06 +00:00
const oldExports = { . . . oldModule . exports } ;
Object . assign ( oldModule . exports , newModule . exports ) ;
newModule . exports = oldModule . exports ;
handled = true ;
for ( const cb of [ . . . globalThis . $hotReload_deprecateExports ] ) {
cb ( oldExports , newModule . exports ) ;
}
if ( handled ) {
console . log ( 'hot reloaded' , data . module Id ) ;
}
} ) ;
}
2023-04-13 14:39:35 +00:00
}
2023-06-19 10:42:06 +00:00
if ( ! handled ) { reloadFn ( ) ; }
2023-04-13 14:39:35 +00:00
}
}
2023-06-19 10:42:06 +00:00
} ) . catch ( err = > {
console . error ( err ) ;
setTimeout ( ( ) = > $watchChanges ( fileChangesUrl ) , 1000 ) ;
} ) ;
2023-04-13 14:39:35 +00:00
2023-06-19 10:42:06 +00:00
}
const additionalJsCode = `
( $ { ( function ( ) {
globalThis . $hotReload_deprecateExports = new Set < ( oldExports : any , newExports : any ) = > void > ( ) ;
} ) . toString ( ) } ) ( ) ;
$ { $watchChanges . toString ( ) }
$watchChanges ( $ { JSON . stringify ( fileChangesUrl ) } ) ;
2023-04-13 14:39:35 +00:00
` ;
2023-06-19 10:42:06 +00:00
return ` ${ loaderJsCode } \ n ${ additionalJsCode } ` ;
2023-04-13 14:39:35 +00:00
}
// #endregion
// #region Bundling
class CachedBundle {
public readonly entryModulePath = this . mapper . resolveRequestToPath ( this . module Id ) ! ;
constructor (
private readonly module Id : string ,
private readonly mapper : SimpleModuleIdPathMapper ,
) {
}
private loader : ModuleLoader | undefined = undefined ;
private bundlePromise : Promise < Buffer > | undefined = undefined ;
public async bundle ( ) : Promise < Buffer > {
if ( ! this . bundlePromise ) {
this . bundlePromise = ( async ( ) = > {
if ( ! this . loader ) {
this . loader = new ModuleLoader ( this . mapper ) ;
await this . loader . addModuleAndDependencies ( this . entryModulePath ) ;
}
const editorEntryPoint = await this . loader . getModule ( this . entryModulePath ) ;
const content = bundleWithDependencies ( editorEntryPoint ! ) ;
return content ;
} ) ( ) ;
}
return this . bundlePromise ;
}
public async setModuleContent ( path : string , newContent : string ) : Promise < void > {
if ( ! this . loader ) {
return ;
}
const module = await this . loader ! . getModule ( path ) ;
if ( module ) {
if ( ! this . loader . updateContent ( module , newContent ) ) {
this . loader = undefined ;
}
}
this . bundlePromise = undefined ;
}
}
function bundleWithDependencies ( module : IModule ) : Buffer {
const visited = new Set < IModule > ( ) ;
const builder = new SourceMapBuilder ( ) ;
function visit ( module : IModule ) {
if ( visited . has ( module ) ) {
return ;
}
visited . add ( module ) ;
for ( const dep of module .dependencies ) {
visit ( dep ) ;
}
builder . addSource ( module .source ) ;
}
visit ( module ) ;
const sourceMap = builder . toSourceMap ( ) ;
sourceMap . sourceRoot = module .source.sourceMap.sourceRoot ;
const sourceMapBase64Str = Buffer . from ( JSON . stringify ( sourceMap ) ) . toString ( 'base64' ) ;
builder . addLine ( ` //# sourceMappingURL=data:application/json;base64, ${ sourceMapBase64Str } ` ) ;
return builder . toContent ( ) ;
}
class ModuleLoader {
private readonly module s = new Map < string , Promise < IModule | undefined > > ( ) ;
constructor ( private readonly mapper : SimpleModuleIdPathMapper ) { }
public getModule ( path : string ) : Promise < IModule | undefined > {
return Promise . resolve ( this . module s.get ( path ) ) ;
}
public updateContent ( module : IModule , newContent : string ) : boolean {
const parsedModule = parseModule ( newContent , module .path , this . mapper ) ;
if ( ! parsedModule ) {
return false ;
}
if ( ! arrayEquals ( parsedModule . dependencyRequests , module .dependencyRequests ) ) {
return false ;
}
module .dependencyRequests = parsedModule . dependencyRequests ;
module .source = parsedModule . source ;
return true ;
}
async addModuleAndDependencies ( path : string ) : Promise < IModule | undefined > {
if ( this . module s.has ( path ) ) {
return this . module s.get ( path ) ! ;
}
const promise = ( async ( ) = > {
const content = await fsPromise . readFile ( path , { encoding : 'utf-8' } ) ;
const parsedModule = parseModule ( content , path , this . mapper ) ;
if ( ! parsedModule ) {
return undefined ;
}
const dependencies = ( await Promise . all ( parsedModule . dependencyRequests . map ( async r = > {
if ( r === 'require' || r === 'exports' || r === 'module' ) {
return null ;
}
const depPath = this . mapper . resolveRequestToPath ( r , path ) ;
if ( ! depPath ) {
return null ;
}
return await this . addModuleAndDependencies ( depPath ) ;
} ) ) ) . filter ( ( d ) : d is IModule = > ! ! d ) ;
const module : IModule = {
id : this.mapper.getModuleId ( path ) ! ,
dependencyRequests : parsedModule.dependencyRequests ,
dependencies ,
path ,
source : parsedModule.source ,
} ;
return module ;
} ) ( ) ;
this . module s.set ( path , promise ) ;
return promise ;
}
}
function arrayEquals < T > ( a : T [ ] , b : T [ ] ) : boolean {
if ( a . length !== b . length ) {
return false ;
}
for ( let i = 0 ; i < a . length ; i ++ ) {
if ( a [ i ] !== b [ i ] ) {
return false ;
}
}
return true ;
}
const encoder = new TextEncoder ( ) ;
function parseModule ( content : string , path : string , mapper : SimpleModuleIdPathMapper ) : { source : Source ; dependencyRequests : string [ ] } | undefined {
const m = content . match ( /define\((\[.*?\])/ ) ;
if ( ! m ) {
return undefined ;
}
const dependencyRequests = JSON . parse ( m [ 1 ] . replace ( /'/g , '"' ) ) as string [ ] ;
const sourceMapHeader = '//# sourceMappingURL=data:application/json;base64,' ;
const idx = content . indexOf ( sourceMapHeader ) ;
let sourceMap : any = null ;
if ( idx !== - 1 ) {
const sourceMapJsonStr = Buffer . from ( content . substring ( idx + sourceMapHeader . length ) , 'base64' ) . toString ( 'utf-8' ) ;
sourceMap = JSON . parse ( sourceMapJsonStr ) ;
content = content . substring ( 0 , idx ) ;
}
content = content . replace ( 'define([' , ` define(" ${ mapper . getModuleId ( path ) } ", [ ` ) ;
const contentBuffer = Buffer . from ( encoder . encode ( content ) ) ;
const source = new Source ( contentBuffer , sourceMap ) ;
return { dependencyRequests , source } ;
}
class SimpleModuleIdPathMapper {
constructor ( public readonly rootDir : string ) { }
public getModuleId ( path : string ) : string | null {
if ( ! path . startsWith ( this . rootDir ) || ! path . endsWith ( '.js' ) ) {
return null ;
}
const module Id = path . substring ( this . rootDir . length + 1 ) ;
return module Id.replace ( /\\/g , '/' ) . substring ( 0 , module Id.length - 3 ) ;
}
public resolveRequestToPath ( request : string , requestingModulePath? : string ) : string | null {
if ( request . indexOf ( 'css!' ) !== - 1 ) {
return null ;
}
if ( request . startsWith ( '.' ) ) {
return path . join ( path . dirname ( requestingModulePath ! ) , request + '.js' ) ;
} else {
return path . join ( this . rootDir , request + '.js' ) ;
}
}
}
interface IModule {
id : string ;
dependencyRequests : string [ ] ;
dependencies : IModule [ ] ;
path : string ;
source : Source ;
}
// #endregion
// #region SourceMapBuilder
// From https://stackoverflow.com/questions/29905373/how-to-create-sourcemaps-for-concatenated-files with modifications
class Source {
// Ends with \n
public readonly content : Buffer ;
public readonly sourceMap : SourceMap ;
public readonly sourceLines : number ;
public readonly sourceMapMappings : Buffer ;
constructor ( content : Buffer , sourceMap : SourceMap | undefined ) {
if ( ! sourceMap ) {
sourceMap = SourceMapBuilder . emptySourceMap ;
}
let sourceLines = countNL ( content ) ;
if ( content . length > 0 && content [ content . length - 1 ] !== 10 ) {
sourceLines ++ ;
content = Buffer . concat ( [ content , Buffer . from ( [ 10 ] ) ] ) ;
}
this . content = content ;
this . sourceMap = sourceMap ;
this . sourceLines = sourceLines ;
this . sourceMapMappings = typeof this . sourceMap . mappings === 'string'
? Buffer . from ( encoder . encode ( sourceMap . mappings as string ) )
: this . sourceMap . mappings ;
}
}
class SourceMapBuilder {
public static emptySourceMap : SourceMap = { version : 3 , sources : [ ] , mappings : Buffer.alloc ( 0 ) } ;
private readonly outputBuffer = new DynamicBuffer ( ) ;
private readonly sources : string [ ] = [ ] ;
private readonly mappings = new DynamicBuffer ( ) ;
private lastSourceIndex = 0 ;
private lastSourceLine = 0 ;
private lastSourceCol = 0 ;
addLine ( text : string ) {
this . outputBuffer . addString ( text ) ;
this . outputBuffer . addByte ( 10 ) ;
this . mappings . addByte ( 59 ) ; // ;
}
addSource ( source : Source ) {
const sourceMap = source . sourceMap ;
this . outputBuffer . addBuffer ( source . content ) ;
const sourceRemap : number [ ] = [ ] ;
for ( const v of sourceMap . sources ) {
let pos = this . sources . indexOf ( v ) ;
if ( pos < 0 ) {
pos = this . sources . length ;
this . sources . push ( v ) ;
}
sourceRemap . push ( pos ) ;
}
let lastOutputCol = 0 ;
const inputMappings = source . sourceMapMappings ;
let outputLine = 0 ;
let ip = 0 ;
let inOutputCol = 0 ;
let inSourceIndex = 0 ;
let inSourceLine = 0 ;
let inSourceCol = 0 ;
let shift = 0 ;
let value = 0 ;
let valpos = 0 ;
const commit = ( ) = > {
if ( valpos === 0 ) { return ; }
this . mappings . addVLQ ( inOutputCol - lastOutputCol ) ;
lastOutputCol = inOutputCol ;
if ( valpos === 1 ) {
valpos = 0 ;
return ;
}
const outSourceIndex = sourceRemap [ inSourceIndex ] ;
this . mappings . addVLQ ( outSourceIndex - this . lastSourceIndex ) ;
this . lastSourceIndex = outSourceIndex ;
this . mappings . addVLQ ( inSourceLine - this . lastSourceLine ) ;
this . lastSourceLine = inSourceLine ;
this . mappings . addVLQ ( inSourceCol - this . lastSourceCol ) ;
this . lastSourceCol = inSourceCol ;
valpos = 0 ;
} ;
while ( ip < inputMappings . length ) {
let b = inputMappings [ ip ++ ] ;
if ( b === 59 ) { // ;
commit ( ) ;
this . mappings . addByte ( 59 ) ;
inOutputCol = 0 ;
lastOutputCol = 0 ;
outputLine ++ ;
} else if ( b === 44 ) { // ,
commit ( ) ;
this . mappings . addByte ( 44 ) ;
} else {
b = charToInteger [ b ] ;
if ( b === 255 ) { throw new Error ( 'Invalid sourceMap' ) ; }
value += ( b & 31 ) << shift ;
if ( b & 32 ) {
shift += 5 ;
} else {
const shouldNegate = value & 1 ;
value >>= 1 ;
if ( shouldNegate ) { value = - value ; }
switch ( valpos ) {
case 0 : inOutputCol += value ; break ;
case 1 : inSourceIndex += value ; break ;
case 2 : inSourceLine += value ; break ;
case 3 : inSourceCol += value ; break ;
}
valpos ++ ;
value = shift = 0 ;
}
}
}
commit ( ) ;
while ( outputLine < source . sourceLines ) {
this . mappings . addByte ( 59 ) ;
outputLine ++ ;
}
}
toContent ( ) : Buffer {
return this . outputBuffer . toBuffer ( ) ;
}
toSourceMap ( sourceRoot? : string ) : SourceMap {
return { version : 3 , sourceRoot , sources : this.sources , mappings : this.mappings.toBuffer ( ) . toString ( ) } ;
}
}
export interface SourceMap {
version : number ; // always 3
file? : string ;
sourceRoot? : string ;
sources : string [ ] ;
sourcesContent? : string [ ] ;
names? : string [ ] ;
mappings : string | Buffer ;
}
const charToInteger = Buffer . alloc ( 256 ) ;
const integerToChar = Buffer . alloc ( 64 ) ;
charToInteger . fill ( 255 ) ;
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' . split ( '' ) . forEach ( ( char , i ) = > {
charToInteger [ char . charCodeAt ( 0 ) ] = i ;
integerToChar [ i ] = char . charCodeAt ( 0 ) ;
} ) ;
class DynamicBuffer {
private buffer : Buffer ;
private size : number ;
constructor ( ) {
this . buffer = Buffer . alloc ( 512 ) ;
this . size = 0 ;
}
ensureCapacity ( capacity : number ) {
if ( this . buffer . length >= capacity ) {
return ;
}
const oldBuffer = this . buffer ;
this . buffer = Buffer . alloc ( Math . max ( oldBuffer . length * 2 , capacity ) ) ;
oldBuffer . copy ( this . buffer ) ;
}
addByte ( b : number ) {
this . ensureCapacity ( this . size + 1 ) ;
this . buffer [ this . size ++ ] = b ;
}
addVLQ ( num : number ) {
let clamped : number ;
if ( num < 0 ) {
num = ( - num << 1 ) | 1 ;
} else {
num <<= 1 ;
}
do {
clamped = num & 31 ;
num >>= 5 ;
if ( num > 0 ) {
clamped |= 32 ;
}
this . addByte ( integerToChar [ clamped ] ) ;
} while ( num > 0 ) ;
}
addString ( s : string ) {
const l = Buffer . byteLength ( s ) ;
this . ensureCapacity ( this . size + l ) ;
this . buffer . write ( s , this . size ) ;
this . size += l ;
}
addBuffer ( b : Buffer ) {
this . ensureCapacity ( this . size + b . length ) ;
b . copy ( this . buffer , this . size ) ;
this . size += b . length ;
}
toBuffer ( ) : Buffer {
return this . buffer . slice ( 0 , this . size ) ;
}
}
function countNL ( b : Buffer ) : number {
let res = 0 ;
for ( let i = 0 ; i < b . length ; i ++ ) {
if ( b [ i ] === 10 ) { res ++ ; }
}
return res ;
}
// #endregion