diff --git a/packager/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js b/packager/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js new file mode 100644 index 000000000..ddf8f2403 --- /dev/null +++ b/packager/react-packager/src/transforms/babel-plugin-system-import/__tests__/babel-plugin-system-import-test.js @@ -0,0 +1,71 @@ +/** + * Copyright 2004-present Facebook. All Rights Reserved. + * + * @emails oncall+jsinfra + */ + +'use strict'; + +jest.autoMockOff(); +jest.mock('../../../BundlesLayout'); + +const babel = require('babel-core'); +const BundlesLayout = require('../../../BundlesLayout'); + +const testData = { + isolated: { + input: 'System.import("moduleA");', + output: 'loadBundles(["bundle.0"]);' + }, + single: { + input: 'System.import("moduleA").then(function (bundleA) {});', + output: 'loadBundles(["bundle.0"]).then(function (bundleA) {});' + }, + multiple: { + input: [ + 'Promise.all([', + 'System.import("moduleA"), System.import("moduleB"),', + ']).then(function (bundlesA, bundlesB) {});', + ].join('\n'), + output: [ + 'Promise.all([', + 'loadBundles(["bundle.0"]), loadBundles(["bundle.1"])', + ']).then(function (bundlesA, bundlesB) {});', + ].join(''), + }, +}; + +describe('System.import', () => { + let layout = new BundlesLayout(); + BundlesLayout.prototype.getBundleIDForModule.mockImpl(module => { + switch (module) { + case 'moduleA': return 'bundle.0'; + case 'moduleB': return 'bundle.1'; + } + }); + + function transform(source) { + return babel.transform(source, { + plugins: [require('../')], + blacklist: ['strict'], + extra: { bundlesLayout: layout }, + }).code; + } + + function test(data) { + // transform and remove new lines + expect(transform(data.input).replace(/(\r\n|\n|\r)/gm,'')).toEqual(data.output); + } + + it('should transform isolated `System.import`', () => { + test(testData.isolated); + }); + + it('should transform single `System.import`', () => { + test(testData.single); + }); + + it('should transform multiple `System.import`s', () => { + test(testData.multiple); + }); +}); diff --git a/packager/react-packager/src/transforms/babel-plugin-system-import/index.js b/packager/react-packager/src/transforms/babel-plugin-system-import/index.js new file mode 100644 index 000000000..21ef64c0c --- /dev/null +++ b/packager/react-packager/src/transforms/babel-plugin-system-import/index.js @@ -0,0 +1,65 @@ + /** + * 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. + * + */ +/*jslint node: true */ +'use strict'; + +var t = require('babel-core').types; + +/** + * Transforms asynchronous module importing into a function call + * that includes which bundle needs to be loaded + * + * Transforms: + * + * System.import('moduleA') + * + * to: + * + * loadBundles('bundleA') + */ +module.exports = function systemImportTransform(babel) { + return new babel.Transformer('system-import', { + CallExpression: function(node, parent, scope, state) { + if (!isAppropriateSystemImportCall(node, parent)) { + return node; + } + + var bundlesLayout = state.opts.extra.bundlesLayout; + var bundleID = bundlesLayout.getBundleIDForModule( + node.arguments[0].value + ); + + var bundles = bundleID.split('.'); + bundles.splice(0, 1); + bundles = bundles.map(function(id) { + return t.literal('bundle.' + id); + }); + + return t.callExpression( + t.identifier('loadBundles'), + [t.arrayExpression(bundles)] + ); + }, + + metadata: { + group: 'fb' + } + }); +}; + +function isAppropriateSystemImportCall(node) { + return ( + node.callee.type === 'MemberExpression' && + node.callee.object.name === 'System' && + node.callee.property.name === 'import' && + node.arguments.length === 1 && + node.arguments[0].type === 'Literal' + ); +}