diff --git a/lib/read-wasm.js b/lib/read-wasm.js index 2ce01a75..4e76801f 100644 --- a/lib/read-wasm.js +++ b/lib/read-wasm.js @@ -9,16 +9,16 @@ const path = require("path"); module.exports = function readWasm() { return new Promise((resolve, reject) => { const wasmPath = path.join(__dirname, "mappings.wasm"); - fs.readFile(wasmPath, null, (error, data) => { - if (error) { - reject(error); - return; - } - - resolve(data.buffer); + return fs.readFile(wasmPath, null, (err, data) => { + if(err) reject(err); + else resolve(data.buffer); }); }); }; +module.exports.sync = function readWasmSync() { + const wasmPath = path.join(__dirname, "mappings.wasm"); + return fs.readFileSync(wasmPath).buffer; +}; module.exports.initialize = _ => { console.debug( diff --git a/lib/source-map-consumer.js b/lib/source-map-consumer.js index bc9fb5d2..0526e65c 100644 --- a/lib/source-map-consumer.js +++ b/lib/source-map-consumer.js @@ -16,13 +16,7 @@ const INTERNAL = Symbol("smcInternal"); class SourceMapConsumer { constructor(aSourceMap, aSourceMapURL) { - // If the constructor was called by super(), just return Promise. - // Yes, this is a hack to retain the pre-existing API of the base-class - // constructor also being an async factory function. - if (aSourceMap == INTERNAL) { - return Promise.resolve(this); - } - + if(aSourceMap === INTERNAL) return this; return _factory(aSourceMap, aSourceMapURL); } @@ -172,59 +166,56 @@ exports.SourceMapConsumer = SourceMapConsumer; */ class BasicSourceMapConsumer extends SourceMapConsumer { constructor(aSourceMap, aSourceMapURL) { - return super(INTERNAL).then(that => { - let sourceMap = aSourceMap; - if (typeof aSourceMap === "string") { - sourceMap = util.parseSourceMapInput(aSourceMap); - } + super(INTERNAL); + let sourceMap = aSourceMap; + if (typeof aSourceMap === "string") { + sourceMap = util.parseSourceMapInput(aSourceMap); + } - const version = util.getArg(sourceMap, "version"); - const sources = util.getArg(sourceMap, "sources").map(String); - // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which - // requires the array) to play nice here. - const names = util.getArg(sourceMap, "names", []); - const sourceRoot = util.getArg(sourceMap, "sourceRoot", null); - const sourcesContent = util.getArg(sourceMap, "sourcesContent", null); - const mappings = util.getArg(sourceMap, "mappings"); - const file = util.getArg(sourceMap, "file", null); - - // Once again, Sass deviates from the spec and supplies the version as a - // string rather than a number, so we use loose equality checking here. - if (version != that._version) { - throw new Error("Unsupported version: " + version); - } + const version = util.getArg(sourceMap, "version"); + const sources = util.getArg(sourceMap, "sources").map(String); + // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which + // requires the array) to play nice here. + const names = util.getArg(sourceMap, "names", []); + const sourceRoot = util.getArg(sourceMap, "sourceRoot", null); + const sourcesContent = util.getArg(sourceMap, "sourcesContent", null); + const mappings = util.getArg(sourceMap, "mappings"); + const file = util.getArg(sourceMap, "file", null); + + // Once again, Sass deviates from the spec and supplies the version as a + // string rather than a number, so we use loose equality checking here. + if (version != this._version) { + throw new Error("Unsupported version: " + version); + } - that._sourceLookupCache = new Map(); + this._sourceLookupCache = new Map(); - // Pass `true` below to allow duplicate names and sources. While source maps - // are intended to be compressed and deduplicated, the TypeScript compiler - // sometimes generates source maps with duplicates in them. See Github issue - // #72 and bugzil.la/889492. - that._names = ArraySet.fromArray(names.map(String), true); - that._sources = ArraySet.fromArray(sources, true); + // Pass `true` below to allow duplicate names and sources. While source maps + // are intended to be compressed and deduplicated, the TypeScript compiler + // sometimes generates source maps with duplicates in them. See Github issue + // #72 and bugzil.la/889492. + this._names = ArraySet.fromArray(names.map(String), true); + this._sources = ArraySet.fromArray(sources, true); - that._absoluteSources = ArraySet.fromArray( - that._sources.toArray().map(function(s) { - return util.computeSourceURL(sourceRoot, s, aSourceMapURL); - }), - true - ); + this._absoluteSources = ArraySet.fromArray( + this._sources.toArray().map(function (s) { + return util.computeSourceURL(sourceRoot, s, aSourceMapURL); + }), + true + ); - that.sourceRoot = sourceRoot; - that.sourcesContent = sourcesContent; - that._mappings = mappings; - that._sourceMapURL = aSourceMapURL; - that.file = file; + this.sourceRoot = sourceRoot; + this.sourcesContent = sourcesContent; + this._mappings = mappings; + this._sourceMapURL = aSourceMapURL; + this.file = file; - that._computedColumnSpans = false; - that._mappingsPtr = 0; - that._wasm = null; + this._computedColumnSpans = false; + this._mappingsPtr = 0; + this._wasm = null; - return wasm().then(w => { - that._wasm = w; - return that; - }); - }); + const w = wasm.sync(); + this._wasm = w; } /** @@ -542,7 +533,7 @@ class BasicSourceMapConsumer extends SourceMapConsumer { } return ( this.sourcesContent.length >= this._sources.size() && - !this.sourcesContent.some(function(sc) { + !this.sourcesContent.some(function (sc) { return sc == null; }) ); @@ -721,67 +712,61 @@ exports.BasicSourceMapConsumer = BasicSourceMapConsumer; */ class IndexedSourceMapConsumer extends SourceMapConsumer { constructor(aSourceMap, aSourceMapURL) { - return super(INTERNAL).then(that => { - let sourceMap = aSourceMap; - if (typeof aSourceMap === "string") { - sourceMap = util.parseSourceMapInput(aSourceMap); - } + super(INTERNAL); + let sourceMap = aSourceMap; + if (typeof aSourceMap === "string") { + sourceMap = util.parseSourceMapInput(aSourceMap); + } - const version = util.getArg(sourceMap, "version"); - const sections = util.getArg(sourceMap, "sections"); + const version = util.getArg(sourceMap, "version"); + const sections = util.getArg(sourceMap, "sections"); - if (version != that._version) { - throw new Error("Unsupported version: " + version); + if (version != this._version) { + throw new Error("Unsupported version: " + version); + } + + let lastOffset = { + line: -1, + column: 0 + }; + const s = sections.map(s => { + if (s.url) { + // The url field will require support for asynchronicity. + // See https://github.com/mozilla/source-map/issues/16 + throw new Error( + "Support for url field in sections not implemented." + ); } + const offset = util.getArg(s, "offset"); + const offsetLine = util.getArg(offset, "line"); + const offsetColumn = util.getArg(offset, "column"); - let lastOffset = { - line: -1, - column: 0 - }; - return Promise.all( - sections.map(s => { - if (s.url) { - // The url field will require support for asynchronicity. - // See https://github.com/mozilla/source-map/issues/16 - throw new Error( - "Support for url field in sections not implemented." - ); - } - const offset = util.getArg(s, "offset"); - const offsetLine = util.getArg(offset, "line"); - const offsetColumn = util.getArg(offset, "column"); + if ( + offsetLine < lastOffset.line || + (offsetLine === lastOffset.line && offsetColumn < lastOffset.column) + ) { + throw new Error( + "Section offsets must be ordered and non-overlapping." + ); + } + lastOffset = offset; - if ( - offsetLine < lastOffset.line || - (offsetLine === lastOffset.line && offsetColumn < lastOffset.column) - ) { - throw new Error( - "Section offsets must be ordered and non-overlapping." - ); - } - lastOffset = offset; - - const cons = new SourceMapConsumer( - util.getArg(s, "map"), - aSourceMapURL - ); - return cons.then(consumer => { - return { - generatedOffset: { - // The offset fields are 0-based, but we use 1-based indices when - // encoding/decoding from VLQ. - generatedLine: offsetLine + 1, - generatedColumn: offsetColumn + 1 - }, - consumer - }; - }); - }) - ).then(s => { - that._sections = s; - return that; - }); - }); + const consumer = new SourceMapConsumer( + util.getArg(s, "map"), + aSourceMapURL + ); + return { + generatedOffset: { + // The offset fields are 0-based, but we use 1-based indices when + // encoding/decoding from VLQ. + generatedLine: offsetLine + 1, + generatedColumn: offsetColumn + 1 + }, + consumer + }; + }) + this._sections = s; + return this; } /** @@ -824,7 +809,7 @@ class IndexedSourceMapConsumer extends SourceMapConsumer { // Find the section containing the generated position we're trying to map // to an original position. - const sectionIndex = binarySearch.search(needle, this._sections, function( + const sectionIndex = binarySearch.search(needle, this._sections, function ( aNeedle, section ) { @@ -862,7 +847,7 @@ class IndexedSourceMapConsumer extends SourceMapConsumer { * map, false otherwise. */ hasContentsOfAllSources() { - return this._sections.every(function(s) { + return this._sections.every(function (s) { return s.consumer.hasContentsOfAllSources(); }); } @@ -1003,7 +988,7 @@ class IndexedSourceMapConsumer extends SourceMapConsumer { const columnShift = generatedOffset.generatedColumn - 1; section.consumer.eachMapping( - function(mapping) { + function (mapping) { if (mapping.generatedLine === 1) { mapping.generatedColumn += columnShift; @@ -1058,7 +1043,7 @@ function _factory(aSourceMap, aSourceMapURL) { sourceMap.sections != null ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL) : new BasicSourceMapConsumer(sourceMap, aSourceMapURL); - return Promise.resolve(consumer); + return consumer; } function _factoryBSM(aSourceMap, aSourceMapURL) { diff --git a/lib/wasm.js b/lib/wasm.js index a85c2f8d..c2783527 100644 --- a/lib/wasm.js +++ b/lib/wasm.js @@ -14,110 +14,27 @@ function Mapping() { } let cachedWasm = null; +let cachedWasmSync = null; -module.exports = function wasm() { +// TODO restore the async implementation + +module.exports = async function wasm() { if (cachedWasm) { return cachedWasm; } - const callbackStack = []; + // At every step of the way, if a sync load already succeeded, abort and return + // the sync-loaded module. + cachedWasm = async () => { + try { + const callbackStack = []; - cachedWasm = readWasm() - .then(buffer => { - return WebAssembly.instantiate(buffer, { - env: { - mapping_callback( - generatedLine, - generatedColumn, - - hasLastGeneratedColumn, - lastGeneratedColumn, - - hasOriginal, - source, - originalLine, - originalColumn, - - hasName, - name - ) { - const mapping = new Mapping(); - // JS uses 1-based line numbers, wasm uses 0-based. - mapping.generatedLine = generatedLine + 1; - mapping.generatedColumn = generatedColumn; - - if (hasLastGeneratedColumn) { - // JS uses inclusive last generated column, wasm uses exclusive. - mapping.lastGeneratedColumn = lastGeneratedColumn - 1; - } - - if (hasOriginal) { - mapping.source = source; - // JS uses 1-based line numbers, wasm uses 0-based. - mapping.originalLine = originalLine + 1; - mapping.originalColumn = originalColumn; - - if (hasName) { - mapping.name = name; - } - } - - callbackStack[callbackStack.length - 1](mapping); - }, - - start_all_generated_locations_for() { - console.time("all_generated_locations_for"); - }, - end_all_generated_locations_for() { - console.timeEnd("all_generated_locations_for"); - }, - - start_compute_column_spans() { - console.time("compute_column_spans"); - }, - end_compute_column_spans() { - console.timeEnd("compute_column_spans"); - }, - - start_generated_location_for() { - console.time("generated_location_for"); - }, - end_generated_location_for() { - console.timeEnd("generated_location_for"); - }, - - start_original_location_for() { - console.time("original_location_for"); - }, - end_original_location_for() { - console.timeEnd("original_location_for"); - }, - - start_parse_mappings() { - console.time("parse_mappings"); - }, - end_parse_mappings() { - console.timeEnd("parse_mappings"); - }, - - start_sort_by_generated_location() { - console.time("sort_by_generated_location"); - }, - end_sort_by_generated_location() { - console.timeEnd("sort_by_generated_location"); - }, - - start_sort_by_original_location() { - console.time("sort_by_original_location"); - }, - end_sort_by_original_location() { - console.timeEnd("sort_by_original_location"); - } - } - }); - }) - .then(Wasm => { - return { + const cachedWasmBuffer = await readWasm(); + if(cachedWasmSync) return cachedWasmSync; + const Wasm = await WebAssembly.instantiate(buffer, getImportObject({callbackStack})); + if(cachedWasmSync) return cachedWasmSync; + + cachedWasmSync = { exports: Wasm.instance.exports, withMappingCallback: (mappingCallback, f) => { callbackStack.push(mappingCallback); @@ -128,11 +45,139 @@ module.exports = function wasm() { } } }; - }) - .then(null, e => { + } catch (e) { + if(cachedWasmSync) return cachedWasmSync; cachedWasm = null; throw e; - }); + } + return cachedWasmSync; + } return cachedWasm; }; + +module.exports.sync = function wasmSync() { + if (cachedWasmSync) { + return cachedWasmSync; + } + + const callbackStack = []; + + try { + + const cachedWasmBuffer = readWasm.sync(); + const wasmModule = new WebAssembly.Module(cachedWasmBuffer); + const Wasm = new WebAssembly.Instance(wasmModule, getImportObject({callbackStack})); + + cachedWasmSync = { + exports: Wasm.instance.exports, + withMappingCallback: (mappingCallback, f) => { + callbackStack.push(mappingCallback); + try { + f(); + } finally { + callbackStack.pop(); + } + } + }; + } catch (e) { + cachedWasmSync = null; + throw e; + } + + return cachedWasmSync; +}; + +function getImportObject({callbackStack}) { + return { + env: { + mapping_callback( + generatedLine, + generatedColumn, + + hasLastGeneratedColumn, + lastGeneratedColumn, + + hasOriginal, + source, + originalLine, + originalColumn, + + hasName, + name + ) { + const mapping = new Mapping(); + // JS uses 1-based line numbers, wasm uses 0-based. + mapping.generatedLine = generatedLine + 1; + mapping.generatedColumn = generatedColumn; + + if (hasLastGeneratedColumn) { + // JS uses inclusive last generated column, wasm uses exclusive. + mapping.lastGeneratedColumn = lastGeneratedColumn - 1; + } + + if (hasOriginal) { + mapping.source = source; + // JS uses 1-based line numbers, wasm uses 0-based. + mapping.originalLine = originalLine + 1; + mapping.originalColumn = originalColumn; + + if (hasName) { + mapping.name = name; + } + } + + callbackStack[callbackStack.length - 1](mapping); + }, + + start_all_generated_locations_for() { + console.time("all_generated_locations_for"); + }, + end_all_generated_locations_for() { + console.timeEnd("all_generated_locations_for"); + }, + + start_compute_column_spans() { + console.time("compute_column_spans"); + }, + end_compute_column_spans() { + console.timeEnd("compute_column_spans"); + }, + + start_generated_location_for() { + console.time("generated_location_for"); + }, + end_generated_location_for() { + console.timeEnd("generated_location_for"); + }, + + start_original_location_for() { + console.time("original_location_for"); + }, + end_original_location_for() { + console.timeEnd("original_location_for"); + }, + + start_parse_mappings() { + console.time("parse_mappings"); + }, + end_parse_mappings() { + console.timeEnd("parse_mappings"); + }, + + start_sort_by_generated_location() { + console.time("sort_by_generated_location"); + }, + end_sort_by_generated_location() { + console.timeEnd("sort_by_generated_location"); + }, + + start_sort_by_original_location() { + console.time("sort_by_original_location"); + }, + end_sort_by_original_location() { + console.timeEnd("sort_by_original_location"); + } + } + }; +} \ No newline at end of file