Allow to pass custom transformer parameters via the request url

Reviewed By: davidaurelio

Differential Revision: D6869137

fbshipit-source-id: a985b2df7e4fca308e62adaededc2ad0038d5373
This commit is contained in:
Rafael Oleza 2018-02-04 09:14:30 -08:00 committed by Facebook Github Bot
parent cd23c579b5
commit 79fd0b341e
11 changed files with 129 additions and 20 deletions

View File

@ -160,6 +160,7 @@ class DeltaCalculator extends EventEmitter {
const transformOptionsForBlacklist = { const transformOptionsForBlacklist = {
assetDataPlugins: this._options.assetPlugins, assetDataPlugins: this._options.assetPlugins,
customTransformOptions: this._options.customTransformOptions,
enableBabelRCLookup, enableBabelRCLookup,
dev: this._options.dev, dev: this._options.dev,
hot: this._options.hot, hot: this._options.hot,

View File

@ -13,6 +13,7 @@
'use strict'; 'use strict';
import type {Options as BundleOptions} from '../DeltaBundler'; import type {Options as BundleOptions} from '../DeltaBundler';
import type {CustomTransformOptions} from '../JSTransformer/worker';
/** /**
* Module to easily create the needed configuration parameters needed for the * 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( module.exports = function getBundlingOptionsForHmr(
entryFile: string, entryFile: string,
platform: string, platform: string,
customTransformOptions: CustomTransformOptions,
): BundleOptions { ): BundleOptions {
// These are the really meaningful bundling options. The others below are // These are the really meaningful bundling options. The others below are
// not relevant for HMR. // not relevant for HMR.
@ -36,6 +38,7 @@ module.exports = function getBundlingOptionsForHmr(
...mainOptions, ...mainOptions,
assetPlugins: [], assetPlugins: [],
bundleType: 'hmr', bundleType: 'hmr',
customTransformOptions,
dev: true, dev: true,
entryModuleOnly: false, entryModuleOnly: false,
excludeSource: false, excludeSource: false,

View File

@ -15,6 +15,8 @@
const addParamsToDefineCall = require('../lib/addParamsToDefineCall'); const addParamsToDefineCall = require('../lib/addParamsToDefineCall');
const formatBundlingError = require('../lib/formatBundlingError'); const formatBundlingError = require('../lib/formatBundlingError');
const getBundlingOptionsForHmr = require('./getBundlingOptionsForHmr'); const getBundlingOptionsForHmr = require('./getBundlingOptionsForHmr');
const nullthrows = require('fbjs/lib/nullthrows');
const parseCustomTransformOptions = require('../lib/parseCustomTransformOptions');
const querystring = require('querystring'); const querystring = require('querystring');
const url = require('url'); const url = require('url');
@ -53,10 +55,10 @@ class HmrServer<TClient: Client> {
clientUrl: string, clientUrl: string,
sendFn: (data: string) => mixed, sendFn: (data: string) => mixed,
): Promise<Client> { ): Promise<Client> {
const {bundleEntry, platform} = querystring.parse( const urlObj = nullthrows(url.parse(clientUrl, true));
/* $FlowFixMe: url might be null */
url.parse(clientUrl).query, const {bundleEntry, platform} = nullthrows(urlObj.query);
); const customTransformOptions = parseCustomTransformOptions(urlObj);
// Create a new DeltaTransformer for each client. Once the clients are // Create a new DeltaTransformer for each client. Once the clients are
// modified to support Delta Bundles, they'll be able to pass the // modified to support Delta Bundles, they'll be able to pass the
@ -64,7 +66,7 @@ class HmrServer<TClient: Client> {
// the same DeltaTransformer between the WS connection and the HTTP one. // the same DeltaTransformer between the WS connection and the HTTP one.
const deltaBundler = this._packagerServer.getDeltaBundler(); const deltaBundler = this._packagerServer.getDeltaBundler();
const {deltaTransformer} = await deltaBundler.getDeltaTransformer( const {deltaTransformer} = await deltaBundler.getDeltaTransformer(
getBundlingOptionsForHmr(bundleEntry, platform), getBundlingOptionsForHmr(bundleEntry, platform, customTransformOptions),
); );
// Trigger an initial build to start up the DeltaTransformer. // Trigger an initial build to start up the DeltaTransformer.

View File

@ -62,8 +62,11 @@ export type Transformer<ExtraOptions: {} = {}> = {
getCacheKey: () => string, getCacheKey: () => string,
}; };
export type CustomTransformOptions = {[string]: mixed, __proto__: null};
export type TransformOptionsStrict = {| export type TransformOptionsStrict = {|
+assetDataPlugins: $ReadOnlyArray<string>, +assetDataPlugins: $ReadOnlyArray<string>,
+customTransformOptions?: CustomTransformOptions,
+enableBabelRCLookup: boolean, +enableBabelRCLookup: boolean,
+dev: boolean, +dev: boolean,
+hot: boolean, +hot: boolean,
@ -75,6 +78,7 @@ export type TransformOptionsStrict = {|
export type TransformOptions = { export type TransformOptions = {
+assetDataPlugins: $ReadOnlyArray<string>, +assetDataPlugins: $ReadOnlyArray<string>,
+customTransformOptions?: CustomTransformOptions,
+enableBabelRCLookup?: boolean, +enableBabelRCLookup?: boolean,
+dev?: boolean, +dev?: boolean,
+hot?: boolean, +hot?: boolean,

View File

@ -183,6 +183,7 @@ describe('processRequest', () => {
{ {
assetPlugins: [], assetPlugins: [],
bundleType: 'bundle', bundleType: 'bundle',
customTransformOptions: {},
deltaBundleId: expect.any(String), deltaBundleId: expect.any(String),
dev: true, dev: true,
entryFile: 'index.ios.js', entryFile: 'index.ios.js',
@ -214,6 +215,7 @@ describe('processRequest', () => {
{ {
assetPlugins: [], assetPlugins: [],
bundleType: 'bundle', bundleType: 'bundle',
customTransformOptions: {},
deltaBundleId: expect.any(String), deltaBundleId: expect.any(String),
dev: true, dev: true,
entryFile: 'index.js', entryFile: 'index.js',
@ -245,6 +247,7 @@ describe('processRequest', () => {
expect(Serializers.fullBundle).toBeCalledWith(expect.any(DeltaBundler), { expect(Serializers.fullBundle).toBeCalledWith(expect.any(DeltaBundler), {
assetPlugins: ['assetPlugin1', 'assetPlugin2'], assetPlugins: ['assetPlugin1', 'assetPlugin2'],
bundleType: 'bundle', bundleType: 'bundle',
customTransformOptions: {},
deltaBundleId: expect.any(String), deltaBundleId: expect.any(String),
dev: true, dev: true,
entryFile: 'index.js', entryFile: 'index.js',
@ -447,6 +450,7 @@ describe('processRequest', () => {
expect.any(DeltaBundler), expect.any(DeltaBundler),
{ {
assetPlugins: [], assetPlugins: [],
customTransformOptions: {},
deltaBundleId: null, deltaBundleId: null,
dev: true, dev: true,
entryFile: 'foo file', entryFile: 'foo file',

View File

@ -22,6 +22,8 @@ const formatBundlingError = require('../lib/formatBundlingError');
const getMaxWorkers = require('../lib/getMaxWorkers'); const getMaxWorkers = require('../lib/getMaxWorkers');
const getOrderedDependencyPaths = require('../lib/getOrderedDependencyPaths'); const getOrderedDependencyPaths = require('../lib/getOrderedDependencyPaths');
const mime = require('mime-types'); const mime = require('mime-types');
const nullthrows = require('fbjs/lib/nullthrows');
const parseCustomTransformOptions = require('../lib/parseCustomTransformOptions');
const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath'); const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath');
const path = require('path'); const path = require('path');
const symbolicate = require('./symbolicate'); const symbolicate = require('./symbolicate');
@ -762,10 +764,10 @@ class Server {
_getOptionsFromUrl(reqUrl: string): BundleOptions & DeltaBundlerOptions { _getOptionsFromUrl(reqUrl: string): BundleOptions & DeltaBundlerOptions {
// `true` to parse the query param as an object. // `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 = urlObj.pathname ? decodeURIComponent(urlObj.pathname) : '';
const pathname = decodeURIComponent(urlObj.pathname);
let isMap = false; let isMap = false;
@ -795,38 +797,38 @@ class Server {
// try to get the platform from the url // try to get the platform from the url
const platform = const platform =
/* $FlowFixMe: `query` could be empty for an invalid URL */ urlQuery.platform ||
urlObj.query.platform ||
parsePlatformFilePath(pathname, this._platforms).platform; parsePlatformFilePath(pathname, this._platforms).platform;
/* $FlowFixMe: `query` could be empty for an invalid URL */ const deltaBundleId = urlQuery.deltaBundleId;
const deltaBundleId = urlObj.query.deltaBundleId;
/* $FlowFixMe: `query` could be empty for an invalid URL */ const assetPlugin = urlQuery.assetPlugin;
const assetPlugin = urlObj.query.assetPlugin;
const assetPlugins = Array.isArray(assetPlugin) const assetPlugins = Array.isArray(assetPlugin)
? assetPlugin ? assetPlugin
: typeof assetPlugin === 'string' ? [assetPlugin] : []; : typeof assetPlugin === 'string' ? [assetPlugin] : [];
const dev = this._getBoolOptionFromQuery(urlObj.query, 'dev', true); const dev = this._getBoolOptionFromQuery(urlQuery, 'dev', true);
const minify = this._getBoolOptionFromQuery(urlObj.query, 'minify', false); const minify = this._getBoolOptionFromQuery(urlQuery, 'minify', false);
const excludeSource = this._getBoolOptionFromQuery( const excludeSource = this._getBoolOptionFromQuery(
urlObj.query, urlQuery,
'excludeSource', 'excludeSource',
false, false,
); );
const includeSource = this._getBoolOptionFromQuery( const includeSource = this._getBoolOptionFromQuery(
urlObj.query, urlQuery,
'inlineSourceMap', 'inlineSourceMap',
false, false,
); );
const customTransformOptions = parseCustomTransformOptions(urlObj);
return { return {
sourceMapUrl: url.format({ sourceMapUrl: url.format({
...urlObj, ...urlObj,
pathname: pathname.replace(/\.(bundle|delta)$/, '.map'), pathname: pathname.replace(/\.(bundle|delta)$/, '.map'),
}), }),
bundleType: isMap ? 'map' : deltaBundleId ? 'delta' : 'bundle', bundleType: isMap ? 'map' : deltaBundleId ? 'delta' : 'bundle',
customTransformOptions,
entryFile, entryFile,
deltaBundleId, deltaBundleId,
dev, dev,
@ -873,6 +875,7 @@ class Server {
static DEFAULT_BUNDLE_OPTIONS = { static DEFAULT_BUNDLE_OPTIONS = {
assetPlugins: [], assetPlugins: [],
customTransformOptions: Object.create(null),
dev: true, dev: true,
entryModuleOnly: false, entryModuleOnly: false,
excludeSource: false, excludeSource: false,

View File

@ -24,6 +24,7 @@ const path = require('path');
const throat = require('throat'); const throat = require('throat');
import type { import type {
CustomTransformOptions,
Options as TransformWorkerOptions, Options as TransformWorkerOptions,
TransformOptionsStrict, TransformOptionsStrict,
} from '../JSTransformer/worker'; } from '../JSTransformer/worker';
@ -440,6 +441,7 @@ class OptionsHasher {
): crypto$Hash { ): crypto$Hash {
const { const {
assetDataPlugins, assetDataPlugins,
customTransformOptions,
enableBabelRCLookup, enableBabelRCLookup,
dev, dev,
hot, hot,
@ -472,6 +474,9 @@ class OptionsHasher {
hash.update(JSON.stringify(assetDataPlugins)); hash.update(JSON.stringify(assetDataPlugins));
hash.update(JSON.stringify(platform)); hash.update(JSON.stringify(platform));
hash.update(JSON.stringify(this.toLocalPath(projectRoot))); hash.update(JSON.stringify(this.toLocalPath(projectRoot)));
hash.update(
JSON.stringify(this.sortTransformOptions(customTransformOptions || {})),
);
return hash; return hash;
} }
@ -483,6 +488,14 @@ class OptionsHasher {
toLocalPath(filePath: string): string { toLocalPath(filePath: string): string {
return path.relative(this._rootPath, filePath); 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 { class CannotHashOptionsError extends Error {

View File

@ -19,12 +19,12 @@ Object {
exports[`GlobalTransformCache fetches results 1`] = ` exports[`GlobalTransformCache fetches results 1`] = `
Array [ Array [
Object { Object {
"code": "/* code from http://globalcache.com/a00af5f7803566564fc1e5bd209552899c7354e1-foo.js */", "code": "/* code from http://globalcache.com/3b3b861b6b80dd038c51262ca8b8b9f76e353ada-foo.js */",
"dependencies": Array [], "dependencies": Array [],
"dependencyOffsets": Array [], "dependencyOffsets": Array [],
}, },
Object { Object {
"code": "/* code from http://globalcache.com/50013dd319b49a40f1b3d325be62320a4c2a6c82-bar.js */", "code": "/* code from http://globalcache.com/087b5bc3467f9a0670e49ce9118e6f6791aa12c6-bar.js */",
"dependencies": Array [], "dependencies": Array [],
"dependencyOffsets": Array [], "dependencyOffsets": Array [],
}, },

View File

@ -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({});
});

View File

@ -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;
};

View File

@ -17,6 +17,7 @@ import type {
PostProcessBundleSourcemap, PostProcessBundleSourcemap,
} from '../Bundler'; } from '../Bundler';
import type {PostProcessModules} from '../DeltaBundler'; import type {PostProcessModules} from '../DeltaBundler';
import type {CustomTransformOptions} from '../JSTransformer/worker';
import type {DynamicRequiresBehavior} from '../ModuleGraph/worker/collectDependencies'; import type {DynamicRequiresBehavior} from '../ModuleGraph/worker/collectDependencies';
import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; import type {GlobalTransformCache} from '../lib/GlobalTransformCache';
import type {TransformCache} from '../lib/TransformCaching'; import type {TransformCache} from '../lib/TransformCaching';
@ -34,6 +35,7 @@ type MetroSourceMapOrMappings =
export type BundleOptions = { export type BundleOptions = {
+assetPlugins: Array<string>, +assetPlugins: Array<string>,
bundleType: BundleType, bundleType: BundleType,
customTransformOptions: CustomTransformOptions,
dev: boolean, dev: boolean,
entryFile: string, entryFile: string,
+entryModuleOnly: boolean, +entryModuleOnly: boolean,