react-native/packager/transformer.js

170 lines
4.8 KiB
JavaScript
Raw Normal View History

2015-01-30 01:10:49 +00:00
/**
* 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.
2015-01-30 01:10:49 +00:00
*
* Note: This is a fork of the fb-specific transform.js
*
* @flow
2015-01-30 01:10:49 +00:00
*/
'use strict';
const babel = require('babel-core');
const crypto = require('crypto');
const externalHelpersPlugin = require('babel-plugin-external-helpers');
const fs = require('fs');
create better debuggable source maps Summary: Introduces a new mechanism to build source maps that allows us to use real mapping segments instead of just mapping line-by-line. This mechanism is only used when building development bundles to improve the debugging experience in Chrome. The new mechanism takes advantage of a new feature in babel-generator that exposes raw mapping objects. These raw mapping objects are converted to arrays with 2, 4, or 5 for the most compact representation possible. We no longer generate a source map for the bundle that maps each line to itself in conjunction with configuring babel generator to retain lines. Instead, we create a source map with a large mappings object produced from the mappings of each individual file in conjunction with a “carry over” – the number of preceding lines in the bundle. The implementation makes a couple of assumptions that hold true for babel transform results, e.g. mappings being in the order of the generated code, and that a block of mappings always belongs to the same source file. In addition, the implementation avoids allocation of objects and strings at all costs. All calculations are purely numeric, and base64 vlq produces numeric ascii character codes. These are written to a preallocated buffer objects, which is turned to a string only at the end of the building process. This implementation is ~5x faster than using the source-map library. In addition to providing development source maps that work better, we can now also produce individual high-quality source maps for production builds and combine them to an “index source map”. This approach is unfeasable for development source maps, because index source map consistently crash Chrome. Better production source maps are useful to get precise information about source location and symbol names when symbolicating stack traces from crashes in production. Reviewed By: jeanlauliac Differential Revision: D4382290 fbshipit-source-id: 365a176fa142729d0a4cef43edeb81084361e54d
2017-01-12 22:21:59 +00:00
const generate = require('babel-generator').default;
const inlineRequiresPlugin = require('babel-preset-fbjs/plugins/inline-requires');
const json5 = require('json5');
create better debuggable source maps Summary: Introduces a new mechanism to build source maps that allows us to use real mapping segments instead of just mapping line-by-line. This mechanism is only used when building development bundles to improve the debugging experience in Chrome. The new mechanism takes advantage of a new feature in babel-generator that exposes raw mapping objects. These raw mapping objects are converted to arrays with 2, 4, or 5 for the most compact representation possible. We no longer generate a source map for the bundle that maps each line to itself in conjunction with configuring babel generator to retain lines. Instead, we create a source map with a large mappings object produced from the mappings of each individual file in conjunction with a “carry over” – the number of preceding lines in the bundle. The implementation makes a couple of assumptions that hold true for babel transform results, e.g. mappings being in the order of the generated code, and that a block of mappings always belongs to the same source file. In addition, the implementation avoids allocation of objects and strings at all costs. All calculations are purely numeric, and base64 vlq produces numeric ascii character codes. These are written to a preallocated buffer objects, which is turned to a string only at the end of the building process. This implementation is ~5x faster than using the source-map library. In addition to providing development source maps that work better, we can now also produce individual high-quality source maps for production builds and combine them to an “index source map”. This approach is unfeasable for development source maps, because index source map consistently crash Chrome. Better production source maps are useful to get precise information about source location and symbol names when symbolicating stack traces from crashes in production. Reviewed By: jeanlauliac Differential Revision: D4382290 fbshipit-source-id: 365a176fa142729d0a4cef43edeb81084361e54d
2017-01-12 22:21:59 +00:00
const makeHMRConfig = require('babel-preset-react-native/configs/hmr');
const path = require('path');
create better debuggable source maps Summary: Introduces a new mechanism to build source maps that allows us to use real mapping segments instead of just mapping line-by-line. This mechanism is only used when building development bundles to improve the debugging experience in Chrome. The new mechanism takes advantage of a new feature in babel-generator that exposes raw mapping objects. These raw mapping objects are converted to arrays with 2, 4, or 5 for the most compact representation possible. We no longer generate a source map for the bundle that maps each line to itself in conjunction with configuring babel generator to retain lines. Instead, we create a source map with a large mappings object produced from the mappings of each individual file in conjunction with a “carry over” – the number of preceding lines in the bundle. The implementation makes a couple of assumptions that hold true for babel transform results, e.g. mappings being in the order of the generated code, and that a block of mappings always belongs to the same source file. In addition, the implementation avoids allocation of objects and strings at all costs. All calculations are purely numeric, and base64 vlq produces numeric ascii character codes. These are written to a preallocated buffer objects, which is turned to a string only at the end of the building process. This implementation is ~5x faster than using the source-map library. In addition to providing development source maps that work better, we can now also produce individual high-quality source maps for production builds and combine them to an “index source map”. This approach is unfeasable for development source maps, because index source map consistently crash Chrome. Better production source maps are useful to get precise information about source location and symbol names when symbolicating stack traces from crashes in production. Reviewed By: jeanlauliac Differential Revision: D4382290 fbshipit-source-id: 365a176fa142729d0a4cef43edeb81084361e54d
2017-01-12 22:21:59 +00:00
const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins');
const {compactMapping} = require('./src/Bundler/source-map');
import type {Plugins as BabelPlugins} from 'babel-core';
import type {Transformer, TransformOptions} from './src/JSTransformer/worker/worker';
const cacheKeyParts = [
fs.readFileSync(__filename),
require('babel-plugin-external-helpers/package.json').version,
require('babel-preset-fbjs/package.json').version,
require('babel-preset-react-native/package.json').version,
];
/**
* Return a memoized function that checks for the existence of a
* project level .babelrc file, and if it doesn't exist, reads the
* default RN babelrc file and uses that.
*/
const getBabelRC = (function() {
let babelRC: ?{extends?: string, plugins: BabelPlugins} = null;
2015-01-30 01:10:49 +00:00
return function _getBabelRC(projectRoot) {
if (babelRC !== null) {
return babelRC;
}
babelRC = {plugins: []};
// Let's look for the .babelrc in the project root.
// In the future let's look into adding a command line option to specify
// this location.
let projectBabelRCPath;
if (projectRoot) {
projectBabelRCPath = path.resolve(projectRoot, '.babelrc');
}
// If a .babelrc file doesn't exist in the project,
// use the Babel config provided with react-native.
if (!projectBabelRCPath || !fs.existsSync(projectBabelRCPath)) {
babelRC = json5.parse(
fs.readFileSync(
path.resolve(__dirname, 'rn-babelrc.json'))
);
// Require the babel-preset's listed in the default babel config
// $FlowFixMe: dynamic require can't be avoided
babelRC.presets = babelRC.presets.map(preset => require('babel-preset-' + preset));
babelRC.plugins = resolvePlugins(babelRC.plugins);
} else {
// if we find a .babelrc file we tell babel to use it
babelRC.extends = projectBabelRCPath;
}
return babelRC;
};
})();
/**
* Given a filename and options, build a Babel
* config object with the appropriate plugins.
*/
function buildBabelConfig(filename, options) {
const babelRC = getBabelRC(options.projectRoot);
const extraConfig = {
create better debuggable source maps Summary: Introduces a new mechanism to build source maps that allows us to use real mapping segments instead of just mapping line-by-line. This mechanism is only used when building development bundles to improve the debugging experience in Chrome. The new mechanism takes advantage of a new feature in babel-generator that exposes raw mapping objects. These raw mapping objects are converted to arrays with 2, 4, or 5 for the most compact representation possible. We no longer generate a source map for the bundle that maps each line to itself in conjunction with configuring babel generator to retain lines. Instead, we create a source map with a large mappings object produced from the mappings of each individual file in conjunction with a “carry over” – the number of preceding lines in the bundle. The implementation makes a couple of assumptions that hold true for babel transform results, e.g. mappings being in the order of the generated code, and that a block of mappings always belongs to the same source file. In addition, the implementation avoids allocation of objects and strings at all costs. All calculations are purely numeric, and base64 vlq produces numeric ascii character codes. These are written to a preallocated buffer objects, which is turned to a string only at the end of the building process. This implementation is ~5x faster than using the source-map library. In addition to providing development source maps that work better, we can now also produce individual high-quality source maps for production builds and combine them to an “index source map”. This approach is unfeasable for development source maps, because index source map consistently crash Chrome. Better production source maps are useful to get precise information about source location and symbol names when symbolicating stack traces from crashes in production. Reviewed By: jeanlauliac Differential Revision: D4382290 fbshipit-source-id: 365a176fa142729d0a4cef43edeb81084361e54d
2017-01-12 22:21:59 +00:00
code: false,
filename,
};
let config = Object.assign({}, babelRC, extraConfig);
// Add extra plugins
const extraPlugins = [externalHelpersPlugin];
var inlineRequires = options.inlineRequires;
var blacklist = typeof inlineRequires === 'object' ? inlineRequires.blacklist : null;
if (inlineRequires && !(blacklist && filename in blacklist)) {
extraPlugins.push(inlineRequiresPlugin);
}
config.plugins = extraPlugins.concat(config.plugins);
if (options.hot) {
const hmrConfig = makeHMRConfig(options, filename);
config = Object.assign({}, config, hmrConfig);
}
return Object.assign({}, babelRC, config);
}
type Params = {
filename: string,
options: TransformOptions,
plugins?: BabelPlugins,
src: string,
};
function transform({filename, options, src}: Params) {
options = options || {};
const OLD_BABEL_ENV = process.env.BABEL_ENV;
process.env.BABEL_ENV = options.dev ? 'development' : 'production';
try {
const babelConfig = buildBabelConfig(filename, options);
Properly handle babel ignored files, returning only the contents Summary: - [x] Explain the **motivation** for making this change. - [x] Provide a **test plan** demonstrating that the code is solid. - [x] Match the **code formatting** of the rest of the codebase. - [x] Target the `master` branch, NOT a "stable" branch. I have a need to bundle a pre-optimized external lib with my RN application. Until RN 0.42 I had been using a .babelignore to prevent the packager from trying to optimize this file and choke. It seems in 0.42 and higher I'm no longer allowed to ignore the file. This issue has also been reported as #12071 Details on the reasoning for this patch can be found in the issue I originally filed: https://github.com/facebook/react-native/issues/13168 What existing problem does the pull request solve? This PR restores the functionality with babel ignoring files that existed in 0.41 before this patch: https://github.com/facebook/react-native/commit/0849f84df26e4c56f5763375363bae90d94015fe#diff-4676ea0b3c55c65c3929aa993144f07f Here's a screenshot of this patch properly ignoring the file I referenced in https://github.com/facebook/react-native/issues/13168 to be ignored. ![screen shot 2017-04-27 at 12 48 32 am](https://cloud.githubusercontent.com/assets/21967/25469653/524dbc0c-2ae3-11e7-81a6-faca2f4d21fe.png) The patch relies on the `ignored` value of the call to `babel.transform` and if true returns the src in a object per instruction from loganfsmyth from BabelJS core team. To test, add a file to the `ignore` array of a `.babelrc` file in a React Native project with this fork. I was unable to locate a test file for the transformer.js Fixes #12071, #13168 Closes https://github.com/facebook/react-native/pull/13681 Differential Revision: D5017565 Pulled By: davidaurelio fbshipit-source-id: 421f57b5ce192eedd46fae4285d8a741cb5f8e71
2017-05-06 11:36:24 +00:00
const {ast, ignored} = babel.transform(src, babelConfig);
if (ignored) {
return {
ast: null,
code: src,
filename,
map: null,
Properly handle babel ignored files, returning only the contents Summary: - [x] Explain the **motivation** for making this change. - [x] Provide a **test plan** demonstrating that the code is solid. - [x] Match the **code formatting** of the rest of the codebase. - [x] Target the `master` branch, NOT a "stable" branch. I have a need to bundle a pre-optimized external lib with my RN application. Until RN 0.42 I had been using a .babelignore to prevent the packager from trying to optimize this file and choke. It seems in 0.42 and higher I'm no longer allowed to ignore the file. This issue has also been reported as #12071 Details on the reasoning for this patch can be found in the issue I originally filed: https://github.com/facebook/react-native/issues/13168 What existing problem does the pull request solve? This PR restores the functionality with babel ignoring files that existed in 0.41 before this patch: https://github.com/facebook/react-native/commit/0849f84df26e4c56f5763375363bae90d94015fe#diff-4676ea0b3c55c65c3929aa993144f07f Here's a screenshot of this patch properly ignoring the file I referenced in https://github.com/facebook/react-native/issues/13168 to be ignored. ![screen shot 2017-04-27 at 12 48 32 am](https://cloud.githubusercontent.com/assets/21967/25469653/524dbc0c-2ae3-11e7-81a6-faca2f4d21fe.png) The patch relies on the `ignored` value of the call to `babel.transform` and if true returns the src in a object per instruction from loganfsmyth from BabelJS core team. To test, add a file to the `ignore` array of a `.babelrc` file in a React Native project with this fork. I was unable to locate a test file for the transformer.js Fixes #12071, #13168 Closes https://github.com/facebook/react-native/pull/13681 Differential Revision: D5017565 Pulled By: davidaurelio fbshipit-source-id: 421f57b5ce192eedd46fae4285d8a741cb5f8e71
2017-05-06 11:36:24 +00:00
};
} else {
const result = generate(ast, {
comments: false,
compact: false,
filename,
sourceFileName: filename,
sourceMaps: true,
}, src);
return {
ast,
code: result.code,
filename,
map: options.generateSourceMaps ? result.map : result.rawMappings.map(compactMapping),
};
}
} finally {
process.env.BABEL_ENV = OLD_BABEL_ENV;
}
2015-01-30 01:10:49 +00:00
}
function getCacheKey() {
var key = crypto.createHash('md5');
cacheKeyParts.forEach(part => key.update(part));
return key.digest('hex');
}
module.exports = ({
transform,
getCacheKey,
}: Transformer<>);