From 5b5a89aefa56a8ad50de4541d73efe2dc38e4b90 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Mon, 21 Mar 2016 12:31:52 -0700 Subject: [PATCH] Add sourcemap support for asset-based random access bundles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary:This adds support for source maps that can be used for “random access modules” / “unbundles” - source maps contain an extra custom field: `x_facebook_offsets` - this field maps module IDs to line offsets - the source map is built as if all files were concatenated Decoding/symbolication works as follows: - when decoding a stack trace, and a stack frame comes from a filename that contains only numbers and ends with `.js`, look up the additionally needed line offset in the offset map and add it to the original line of the stack frame. - consume the source map as usual Reviewed By: martinbigio Differential Revision: D3072426 fb-gh-sync-id: 827e6dc13b1959f02903baafa7f9e4fc2e0d4bb9 shipit-source-id: 827e6dc13b1959f02903baafa7f9e4fc2e0d4bb9 --- local-cli/bundle/output/unbundle/as-assets.js | 12 +++- .../build-unbundle-sourcemap-with-metadata.js | 22 +++++++ local-cli/bundle/output/unbundle/util.js | 60 +++++++++++++++++++ packager/react-packager/src/Bundler/Bundle.js | 4 +- 4 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 local-cli/bundle/output/unbundle/build-unbundle-sourcemap-with-metadata.js create mode 100644 local-cli/bundle/output/unbundle/util.js diff --git a/local-cli/bundle/output/unbundle/as-assets.js b/local-cli/bundle/output/unbundle/as-assets.js index 7a4378c8a7118e..da9e30af50b572 100644 --- a/local-cli/bundle/output/unbundle/as-assets.js +++ b/local-cli/bundle/output/unbundle/as-assets.js @@ -12,6 +12,7 @@ const mkdirp = require('mkdirp'); const path = require('path'); const Promise = require('promise'); +const buildSourceMapWithMetaData = require('./build-unbundle-sourcemap-with-metadata'); const writeFile = require('../writeFile'); const writeSourceMap = require('./write-sourcemap'); const MAGIC_UNBUNDLE_NUMBER = require('./magic-number'); @@ -29,12 +30,11 @@ function saveAsAssets(bundle, options, log) { const { 'bundle-output': bundleOutput, 'bundle-encoding': encoding, - dev, 'sourcemap-output': sourcemapOutput, } = options; log('start'); - const {startupCode, modules} = bundle.getUnbundle({minify: !dev}); + const {startupCode, startupModules, modules} = bundle.getUnbundle(); log('finish'); log('Writing bundle output to:', bundleOutput); @@ -49,7 +49,13 @@ function saveAsAssets(bundle, options, log) { ); writeUnbundle.then(() => log('Done writing unbundle output')); - return Promise.all([writeUnbundle, writeSourceMap(sourcemapOutput, '', log)]); + const sourceMap = + buildSourceMapWithMetaData({startupModules, modules}); + + return Promise.all([ + writeUnbundle, + writeSourceMap(sourcemapOutput, JSON.stringify(sourceMap), log) + ]); } function createDir(dirName) { diff --git a/local-cli/bundle/output/unbundle/build-unbundle-sourcemap-with-metadata.js b/local-cli/bundle/output/unbundle/build-unbundle-sourcemap-with-metadata.js new file mode 100644 index 00000000000000..9c18c1ef963ea2 --- /dev/null +++ b/local-cli/bundle/output/unbundle/build-unbundle-sourcemap-with-metadata.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const {combineSourceMaps} = require('./util'); + +module.exports = ({startupModules, modules}) => { + const startupModule = { + code: startupModules.map(m => m.code).join('\n'), + map: combineSourceMaps({modules: startupModules}), + }; + return combineSourceMaps({ + modules: [startupModule].concat(modules), + withCustomOffsets: true, + }); +}; diff --git a/local-cli/bundle/output/unbundle/util.js b/local-cli/bundle/output/unbundle/util.js new file mode 100644 index 00000000000000..9c2a14c267fc0c --- /dev/null +++ b/local-cli/bundle/output/unbundle/util.js @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const newline = /\r\n?|\n|\u2028|\u2029/g; +const countLines = + string => (string.match(newline) || []).length + 1; // fastest implementation + +function lineToLineSourceMap(source, filename) { + // The first line mapping in our package is the base64vlq code for zeros (A). + const firstLine = 'AAAA;'; + + // Most other lines in our mappings are all zeros (for module, column etc) + // except for the lineno mappinp: curLineno - prevLineno = 1; Which is C. + const line = 'AACA;'; + + return { + version: 3, + sources: [filename], + mappings: firstLine + Array(countLines(source)).join(line), + }; +} + +const wrapperEnd = wrappedCode => wrappedCode.indexOf('{') + 1; + +const Section = (line, column, map) => ({map, offset: {line, column}}); + +function combineSourceMaps({modules, withCustomOffsets}) { + let offsets; + const sections = []; + const sourceMap = { + version: 3, + sections, + }; + + if (withCustomOffsets) { + offsets = sourceMap.x_facebook_offsets = []; + } + + let line = 0; + modules.forEach(({code, id, map, name}) => { + const hasOffset = withCustomOffsets && id != null; + const column = hasOffset ? wrapperEnd(code) : 0; + sections.push(Section(line, column, map || lineToLineSourceMap(code, name))); + if (hasOffset) { + offsets[id] = line; + } + line += countLines(code); + }); + + return sourceMap; +} + +module.exports = {countLines, lineToLineSourceMap, combineSourceMaps}; diff --git a/packager/react-packager/src/Bundler/Bundle.js b/packager/react-packager/src/Bundler/Bundle.js index af265ebbc5b658..febac2dba33237 100644 --- a/packager/react-packager/src/Bundler/Bundle.js +++ b/packager/react-packager/src/Bundler/Bundle.js @@ -115,9 +115,7 @@ class Bundle extends BundleBase { return { startupCode, - modules: modules.map(({name, code, polyfill}) => - ({name, code, polyfill}) - ), + startupModules: allModules, modules, }; }