From 79fd0b341e684d3e88cb0f1da311e2a549e70b2d Mon Sep 17 00:00:00 2001 From: Rafael Oleza Date: Sun, 4 Feb 2018 09:14:30 -0800 Subject: [PATCH] Allow to pass custom transformer parameters via the request url Reviewed By: davidaurelio Differential Revision: D6869137 fbshipit-source-id: a985b2df7e4fca308e62adaededc2ad0038d5373 --- .../metro/src/DeltaBundler/DeltaCalculator.js | 1 + .../src/HmrServer/getBundlingOptionsForHmr.js | 3 ++ packages/metro/src/HmrServer/index.js | 12 +++--- .../metro/src/JSTransformer/worker/index.js | 4 ++ .../metro/src/Server/__tests__/Server-test.js | 4 ++ packages/metro/src/Server/index.js | 29 +++++++------ .../metro/src/lib/GlobalTransformCache.js | 13 ++++++ .../GlobalTransformCache-test.js.snap | 4 +- .../parseCustomTransformOptions-test.js | 42 +++++++++++++++++++ .../src/lib/parseCustomTransformOptions.js | 35 ++++++++++++++++ packages/metro/src/shared/types.flow.js | 2 + 11 files changed, 129 insertions(+), 20 deletions(-) create mode 100644 packages/metro/src/lib/__tests__/parseCustomTransformOptions-test.js create mode 100644 packages/metro/src/lib/parseCustomTransformOptions.js diff --git a/packages/metro/src/DeltaBundler/DeltaCalculator.js b/packages/metro/src/DeltaBundler/DeltaCalculator.js index 6e73df58..968ccaf5 100644 --- a/packages/metro/src/DeltaBundler/DeltaCalculator.js +++ b/packages/metro/src/DeltaBundler/DeltaCalculator.js @@ -160,6 +160,7 @@ class DeltaCalculator extends EventEmitter { const transformOptionsForBlacklist = { assetDataPlugins: this._options.assetPlugins, + customTransformOptions: this._options.customTransformOptions, enableBabelRCLookup, dev: this._options.dev, hot: this._options.hot, diff --git a/packages/metro/src/HmrServer/getBundlingOptionsForHmr.js b/packages/metro/src/HmrServer/getBundlingOptionsForHmr.js index dbbad627..e879ca5d 100644 --- a/packages/metro/src/HmrServer/getBundlingOptionsForHmr.js +++ b/packages/metro/src/HmrServer/getBundlingOptionsForHmr.js @@ -13,6 +13,7 @@ 'use strict'; import type {Options as BundleOptions} from '../DeltaBundler'; +import type {CustomTransformOptions} from '../JSTransformer/worker'; /** * Module to easily create the needed configuration parameters needed for the @@ -21,6 +22,7 @@ import type {Options as BundleOptions} from '../DeltaBundler'; module.exports = function getBundlingOptionsForHmr( entryFile: string, platform: string, + customTransformOptions: CustomTransformOptions, ): BundleOptions { // These are the really meaningful bundling options. The others below are // not relevant for HMR. @@ -36,6 +38,7 @@ module.exports = function getBundlingOptionsForHmr( ...mainOptions, assetPlugins: [], bundleType: 'hmr', + customTransformOptions, dev: true, entryModuleOnly: false, excludeSource: false, diff --git a/packages/metro/src/HmrServer/index.js b/packages/metro/src/HmrServer/index.js index 03ae0fd9..707eb7dc 100644 --- a/packages/metro/src/HmrServer/index.js +++ b/packages/metro/src/HmrServer/index.js @@ -15,6 +15,8 @@ const addParamsToDefineCall = require('../lib/addParamsToDefineCall'); const formatBundlingError = require('../lib/formatBundlingError'); const getBundlingOptionsForHmr = require('./getBundlingOptionsForHmr'); +const nullthrows = require('fbjs/lib/nullthrows'); +const parseCustomTransformOptions = require('../lib/parseCustomTransformOptions'); const querystring = require('querystring'); const url = require('url'); @@ -53,10 +55,10 @@ class HmrServer { clientUrl: string, sendFn: (data: string) => mixed, ): Promise { - const {bundleEntry, platform} = querystring.parse( - /* $FlowFixMe: url might be null */ - url.parse(clientUrl).query, - ); + const urlObj = nullthrows(url.parse(clientUrl, true)); + + const {bundleEntry, platform} = nullthrows(urlObj.query); + const customTransformOptions = parseCustomTransformOptions(urlObj); // Create a new DeltaTransformer for each client. Once the clients are // modified to support Delta Bundles, they'll be able to pass the @@ -64,7 +66,7 @@ class HmrServer { // the same DeltaTransformer between the WS connection and the HTTP one. const deltaBundler = this._packagerServer.getDeltaBundler(); const {deltaTransformer} = await deltaBundler.getDeltaTransformer( - getBundlingOptionsForHmr(bundleEntry, platform), + getBundlingOptionsForHmr(bundleEntry, platform, customTransformOptions), ); // Trigger an initial build to start up the DeltaTransformer. diff --git a/packages/metro/src/JSTransformer/worker/index.js b/packages/metro/src/JSTransformer/worker/index.js index 37cc9215..657b3e30 100644 --- a/packages/metro/src/JSTransformer/worker/index.js +++ b/packages/metro/src/JSTransformer/worker/index.js @@ -62,8 +62,11 @@ export type Transformer = { getCacheKey: () => string, }; +export type CustomTransformOptions = {[string]: mixed, __proto__: null}; + export type TransformOptionsStrict = {| +assetDataPlugins: $ReadOnlyArray, + +customTransformOptions?: CustomTransformOptions, +enableBabelRCLookup: boolean, +dev: boolean, +hot: boolean, @@ -75,6 +78,7 @@ export type TransformOptionsStrict = {| export type TransformOptions = { +assetDataPlugins: $ReadOnlyArray, + +customTransformOptions?: CustomTransformOptions, +enableBabelRCLookup?: boolean, +dev?: boolean, +hot?: boolean, diff --git a/packages/metro/src/Server/__tests__/Server-test.js b/packages/metro/src/Server/__tests__/Server-test.js index 25f786a5..2a765a8d 100644 --- a/packages/metro/src/Server/__tests__/Server-test.js +++ b/packages/metro/src/Server/__tests__/Server-test.js @@ -183,6 +183,7 @@ describe('processRequest', () => { { assetPlugins: [], bundleType: 'bundle', + customTransformOptions: {}, deltaBundleId: expect.any(String), dev: true, entryFile: 'index.ios.js', @@ -214,6 +215,7 @@ describe('processRequest', () => { { assetPlugins: [], bundleType: 'bundle', + customTransformOptions: {}, deltaBundleId: expect.any(String), dev: true, entryFile: 'index.js', @@ -245,6 +247,7 @@ describe('processRequest', () => { expect(Serializers.fullBundle).toBeCalledWith(expect.any(DeltaBundler), { assetPlugins: ['assetPlugin1', 'assetPlugin2'], bundleType: 'bundle', + customTransformOptions: {}, deltaBundleId: expect.any(String), dev: true, entryFile: 'index.js', @@ -447,6 +450,7 @@ describe('processRequest', () => { expect.any(DeltaBundler), { assetPlugins: [], + customTransformOptions: {}, deltaBundleId: null, dev: true, entryFile: 'foo file', diff --git a/packages/metro/src/Server/index.js b/packages/metro/src/Server/index.js index 808dcdb3..0990b3cd 100644 --- a/packages/metro/src/Server/index.js +++ b/packages/metro/src/Server/index.js @@ -22,6 +22,8 @@ const formatBundlingError = require('../lib/formatBundlingError'); const getMaxWorkers = require('../lib/getMaxWorkers'); const getOrderedDependencyPaths = require('../lib/getOrderedDependencyPaths'); const mime = require('mime-types'); +const nullthrows = require('fbjs/lib/nullthrows'); +const parseCustomTransformOptions = require('../lib/parseCustomTransformOptions'); const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath'); const path = require('path'); const symbolicate = require('./symbolicate'); @@ -762,10 +764,10 @@ class Server { _getOptionsFromUrl(reqUrl: string): BundleOptions & DeltaBundlerOptions { // `true` to parse the query param as an object. - const urlObj = url.parse(reqUrl, true); + const urlObj = nullthrows(url.parse(reqUrl, true)); + const urlQuery = nullthrows(urlObj.query); - /* $FlowFixMe: `pathname` could be empty for an invalid URL */ - const pathname = decodeURIComponent(urlObj.pathname); + const pathname = urlObj.pathname ? decodeURIComponent(urlObj.pathname) : ''; let isMap = false; @@ -795,38 +797,38 @@ class Server { // try to get the platform from the url const platform = - /* $FlowFixMe: `query` could be empty for an invalid URL */ - urlObj.query.platform || + urlQuery.platform || parsePlatformFilePath(pathname, this._platforms).platform; - /* $FlowFixMe: `query` could be empty for an invalid URL */ - const deltaBundleId = urlObj.query.deltaBundleId; + const deltaBundleId = urlQuery.deltaBundleId; - /* $FlowFixMe: `query` could be empty for an invalid URL */ - const assetPlugin = urlObj.query.assetPlugin; + const assetPlugin = urlQuery.assetPlugin; const assetPlugins = Array.isArray(assetPlugin) ? assetPlugin : typeof assetPlugin === 'string' ? [assetPlugin] : []; - const dev = this._getBoolOptionFromQuery(urlObj.query, 'dev', true); - const minify = this._getBoolOptionFromQuery(urlObj.query, 'minify', false); + const dev = this._getBoolOptionFromQuery(urlQuery, 'dev', true); + const minify = this._getBoolOptionFromQuery(urlQuery, 'minify', false); const excludeSource = this._getBoolOptionFromQuery( - urlObj.query, + urlQuery, 'excludeSource', false, ); const includeSource = this._getBoolOptionFromQuery( - urlObj.query, + urlQuery, 'inlineSourceMap', false, ); + const customTransformOptions = parseCustomTransformOptions(urlObj); + return { sourceMapUrl: url.format({ ...urlObj, pathname: pathname.replace(/\.(bundle|delta)$/, '.map'), }), bundleType: isMap ? 'map' : deltaBundleId ? 'delta' : 'bundle', + customTransformOptions, entryFile, deltaBundleId, dev, @@ -873,6 +875,7 @@ class Server { static DEFAULT_BUNDLE_OPTIONS = { assetPlugins: [], + customTransformOptions: Object.create(null), dev: true, entryModuleOnly: false, excludeSource: false, diff --git a/packages/metro/src/lib/GlobalTransformCache.js b/packages/metro/src/lib/GlobalTransformCache.js index eb807095..40aa2c39 100644 --- a/packages/metro/src/lib/GlobalTransformCache.js +++ b/packages/metro/src/lib/GlobalTransformCache.js @@ -24,6 +24,7 @@ const path = require('path'); const throat = require('throat'); import type { + CustomTransformOptions, Options as TransformWorkerOptions, TransformOptionsStrict, } from '../JSTransformer/worker'; @@ -440,6 +441,7 @@ class OptionsHasher { ): crypto$Hash { const { assetDataPlugins, + customTransformOptions, enableBabelRCLookup, dev, hot, @@ -472,6 +474,9 @@ class OptionsHasher { hash.update(JSON.stringify(assetDataPlugins)); hash.update(JSON.stringify(platform)); hash.update(JSON.stringify(this.toLocalPath(projectRoot))); + hash.update( + JSON.stringify(this.sortTransformOptions(customTransformOptions || {})), + ); return hash; } @@ -483,6 +488,14 @@ class OptionsHasher { toLocalPath(filePath: string): string { return path.relative(this._rootPath, filePath); } + + sortTransformOptions( + options: CustomTransformOptions, + ): Array<[string, mixed]> { + return Object.keys(options) + .sort() + .map(key => [key, options[key]]); + } } class CannotHashOptionsError extends Error { diff --git a/packages/metro/src/lib/__tests__/__snapshots__/GlobalTransformCache-test.js.snap b/packages/metro/src/lib/__tests__/__snapshots__/GlobalTransformCache-test.js.snap index 12b63a5e..e9a82cdf 100644 --- a/packages/metro/src/lib/__tests__/__snapshots__/GlobalTransformCache-test.js.snap +++ b/packages/metro/src/lib/__tests__/__snapshots__/GlobalTransformCache-test.js.snap @@ -19,12 +19,12 @@ Object { exports[`GlobalTransformCache fetches results 1`] = ` Array [ Object { - "code": "/* code from http://globalcache.com/a00af5f7803566564fc1e5bd209552899c7354e1-foo.js */", + "code": "/* code from http://globalcache.com/3b3b861b6b80dd038c51262ca8b8b9f76e353ada-foo.js */", "dependencies": Array [], "dependencyOffsets": Array [], }, Object { - "code": "/* code from http://globalcache.com/50013dd319b49a40f1b3d325be62320a4c2a6c82-bar.js */", + "code": "/* code from http://globalcache.com/087b5bc3467f9a0670e49ce9118e6f6791aa12c6-bar.js */", "dependencies": Array [], "dependencyOffsets": Array [], }, diff --git a/packages/metro/src/lib/__tests__/parseCustomTransformOptions-test.js b/packages/metro/src/lib/__tests__/parseCustomTransformOptions-test.js new file mode 100644 index 00000000..9aaec4f8 --- /dev/null +++ b/packages/metro/src/lib/__tests__/parseCustomTransformOptions-test.js @@ -0,0 +1,42 @@ +/** + * 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. + * + * @emails oncall+javascript_foundation + * @format + * @flow + */ + +'use strict'; + +const parseCustomTransformOptions = require('../parseCustomTransformOptions'); +const url = require('url'); + +it('should parse some custom options from a http url', () => { + const myUrl = + 'http://localhost/my/bundle.bundle?dev=true&transform.foo=value&transform.bar=other'; + + expect(parseCustomTransformOptions(url.parse(myUrl, true))).toEqual({ + foo: 'value', + bar: 'other', + }); +}); + +it('should parse some custom options from a websocket url', () => { + const myUrl = 'ws://localhost/hot?transform.foo=value&transform.bar=other'; + + expect(parseCustomTransformOptions(url.parse(myUrl, true))).toEqual({ + foo: 'value', + bar: 'other', + }); +}); + +it('should return an empty object if there are no custom params', () => { + const myUrl = 'http://localhost/my/bundle.bundle?dev=true'; + + expect(parseCustomTransformOptions(url.parse(myUrl, true))).toEqual({}); +}); diff --git a/packages/metro/src/lib/parseCustomTransformOptions.js b/packages/metro/src/lib/parseCustomTransformOptions.js new file mode 100644 index 00000000..d67e4865 --- /dev/null +++ b/packages/metro/src/lib/parseCustomTransformOptions.js @@ -0,0 +1,35 @@ +/** + * 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. + * + * @emails oncall+javascript_foundation + * @format + * @flow + */ + +'use strict'; + +const nullthrows = require('fbjs/lib/nullthrows'); + +import type {CustomTransformOptions} from '../JSTransformer/worker'; + +const PREFIX = 'transform.'; + +module.exports = function parseCustomTransformOptions(urlObj: { + query?: {[string]: string}, +}): CustomTransformOptions { + const customTransformOptions = Object.create(null); + const query = nullthrows(urlObj.query); + + Object.keys(query).forEach(key => { + if (key.startsWith(PREFIX)) { + customTransformOptions[key.substr(PREFIX.length)] = query[key]; + } + }); + + return customTransformOptions; +}; diff --git a/packages/metro/src/shared/types.flow.js b/packages/metro/src/shared/types.flow.js index f292a95d..425d0a62 100644 --- a/packages/metro/src/shared/types.flow.js +++ b/packages/metro/src/shared/types.flow.js @@ -17,6 +17,7 @@ import type { PostProcessBundleSourcemap, } from '../Bundler'; import type {PostProcessModules} from '../DeltaBundler'; +import type {CustomTransformOptions} from '../JSTransformer/worker'; import type {DynamicRequiresBehavior} from '../ModuleGraph/worker/collectDependencies'; import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; import type {TransformCache} from '../lib/TransformCaching'; @@ -34,6 +35,7 @@ type MetroSourceMapOrMappings = export type BundleOptions = { +assetPlugins: Array, bundleType: BundleType, + customTransformOptions: CustomTransformOptions, dev: boolean, entryFile: string, +entryModuleOnly: boolean,