Skip to content

Commit

Permalink
Add back support for the assetPlugin option
Browse files Browse the repository at this point in the history
Summary:
**Summary**
Metro used to have support for "asset plugins", which allowed developers to specify arbitrary JS modules that could export a function for adding more fields to asset data objects. Some of this functionality was removed in the delta bundler work -- this PR adds it back.

**Test plan**
Made existing unit tests pass and added unit tests to test asset plugin behavior. Also tested E2E in a React Native project by adding `assetPlugin=/path/to/pluginModule` to a JS bundle URL and ensuring that the plugin ran.
Closes #118

Differential Revision: D6711094

Pulled By: rafeca

fbshipit-source-id: f42c54cfd11bac5103194f85083084eef25fa3cd
  • Loading branch information
ide authored and facebook-github-bot committed Jan 12, 2018
1 parent 1152a69 commit da2fdba
Show file tree
Hide file tree
Showing 12 changed files with 140 additions and 25 deletions.
82 changes: 75 additions & 7 deletions packages/metro/src/Assets/__tests__/Assets-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ const {getAssetData, getAsset} = require('../');
const crypto = require('crypto');
const fs = require('fs');

const mockImageWidth = 300;
const mockImageHeight = 200;

require('image-size').mockReturnValue({
width: 300,
height: 200,
width: mockImageWidth,
height: mockImageHeight,
});

describe('getAsset', () => {
Expand Down Expand Up @@ -160,7 +163,7 @@ describe('getAssetData', () => {
},
});

return getAssetData('/root/imgs/b.png', 'imgs/b.png').then(data => {
return getAssetData('/root/imgs/b.png', 'imgs/b.png', []).then(data => {
expect(data).toEqual(
expect.objectContaining({
__packager_asset: true,
Expand Down Expand Up @@ -192,7 +195,7 @@ describe('getAssetData', () => {
},
});

const data = await getAssetData('/root/imgs/b.jpg', 'imgs/b.jpg');
const data = await getAssetData('/root/imgs/b.jpg', 'imgs/b.jpg', []);

expect(data).toEqual(
expect.objectContaining({
Expand All @@ -212,6 +215,67 @@ describe('getAssetData', () => {
);
});

it('loads and runs asset plugins', async () => {
jest.mock(
'mockPlugin1',
() => {
return asset => {
asset.extraReverseHash = asset.hash
.split('')
.reverse()
.join('');
return asset;
};
},
{virtual: true},
);

jest.mock(
'asyncMockPlugin2',
() => {
return async asset => {
expect(asset.extraReverseHash).toBeDefined();
asset.extraPixelCount = asset.width * asset.height;
return asset;
};
},
{virtual: true},
);

fs.__setMockFilesystem({
root: {
imgs: {
'b@1x.png': 'b1 image',
'b@2x.png': 'b2 image',
'b@3x.png': 'b3 image',
},
},
});

const data = await getAssetData('/root/imgs/b.png', 'imgs/b.png', [
'mockPlugin1',
'asyncMockPlugin2',
]);

expect(data).toEqual(
expect.objectContaining({
__packager_asset: true,
type: 'png',
name: 'b',
scales: [1, 2, 3],
fileSystemLocation: '/root/imgs',
httpServerLocation: '/assets/imgs',
files: [
'/root/imgs/b@1x.png',
'/root/imgs/b@2x.png',
'/root/imgs/b@3x.png',
],
extraPixelCount: mockImageWidth * mockImageHeight,
}),
);
expect(typeof data.extraReverseHash).toBe('string');
});

describe('hash:', () => {
let mockFS;

Expand All @@ -237,17 +301,21 @@ describe('getAssetData', () => {
hash.update(mockFS.root.imgs[name]);
}

expect(await getAssetData('/root/imgs/b.jpg', 'imgs/b.jpg')).toEqual(
expect(await getAssetData('/root/imgs/b.jpg', 'imgs/b.jpg', [])).toEqual(
expect.objectContaining({hash: hash.digest('hex')}),
);
});

it('changes the hash when the passed-in file watcher emits an `all` event', async () => {
const initialData = await getAssetData('/root/imgs/b.jpg', 'imgs/b.jpg');
const initialData = await getAssetData(
'/root/imgs/b.jpg',
'imgs/b.jpg',
[],
);

mockFS.root.imgs['b@4x.jpg'] = 'updated data';

const data = await getAssetData('/root/imgs/b.jpg', 'imgs/b.jpg');
const data = await getAssetData('/root/imgs/b.jpg', 'imgs/b.jpg', []);
expect(data.hash).not.toEqual(initialData.hash);
});
});
Expand Down
39 changes: 30 additions & 9 deletions packages/metro/src/Assets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ export type AssetData = AssetDataWithoutFiles & {
+files: Array<string>,
};

export type AssetDataPlugin = (
assetData: AssetData,
) => AssetData | Promise<AssetData>;

const hashFiles = denodeify(function hashFilesCb(files, hash, callback) {
if (!files.length) {
callback(null);
Expand Down Expand Up @@ -234,6 +238,7 @@ async function getAbsoluteAssetInfo(
async function getAssetData(
assetPath: string,
localPath: string,
assetDataPlugins: $ReadOnlyArray<string>,
platform: ?string = null,
): Promise<AssetData> {
let assetUrlPath = path.join('/assets', path.dirname(localPath));
Expand All @@ -244,22 +249,38 @@ async function getAssetData(
}

const isImage = isAssetTypeAnImage(path.extname(assetPath).slice(1));
const assetData = await getAbsoluteAssetInfo(assetPath, platform);
const dimensions = isImage ? imageSize(assetData.files[0]) : null;
const scale = assetData.scales[0];
const assetInfo = await getAbsoluteAssetInfo(assetPath, platform);
const dimensions = isImage ? imageSize(assetInfo.files[0]) : null;
const scale = assetInfo.scales[0];

return {
const assetData = {
__packager_asset: true,
fileSystemLocation: path.dirname(assetPath),
httpServerLocation: assetUrlPath,
width: dimensions ? dimensions.width / scale : undefined,
height: dimensions ? dimensions.height / scale : undefined,
scales: assetData.scales,
files: assetData.files,
hash: assetData.hash,
name: assetData.name,
type: assetData.type,
scales: assetInfo.scales,
files: assetInfo.files,
hash: assetInfo.hash,
name: assetInfo.name,
type: assetInfo.type,
};
return await applyAssetDataPlugins(assetDataPlugins, assetData);
}

async function applyAssetDataPlugins(
assetDataPlugins: $ReadOnlyArray<string>,
assetData: AssetData,
): Promise<AssetData> {
if (!assetDataPlugins.length) {
return assetData;
}

const [currentAssetPlugin, ...remainingAssetPlugins] = assetDataPlugins;
// $FlowFixMe: impossible to type a dynamic require.
const assetPluginFunction: AssetDataPlugin = require(currentAssetPlugin);
const resultAssetData = await assetPluginFunction(assetData);
return await applyAssetDataPlugins(remainingAssetPlugins, resultAssetData);
}

/**
Expand Down
1 change: 1 addition & 0 deletions packages/metro/src/DeltaBundler/DeltaCalculator.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class DeltaCalculator extends EventEmitter {
} = this._bundler.getGlobalTransformOptions();

const transformOptionsForBlacklist = {
assetDataPlugins: this._options.assetPlugins,
enableBabelRCLookup,
dev: this._options.dev,
hot: this._options.hot,
Expand Down
7 changes: 6 additions & 1 deletion packages/metro/src/DeltaBundler/Serializers.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,12 @@ async function getAssets(
module.path,
);

return getAssetData(module.path, localPath, options.platform);
return getAssetData(
module.path,
localPath,
options.assetPlugins,
options.platform,
);
}
return null;
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ describe('DeltaCalculator', () => {
describe('getTransformerOptions()', () => {
it('should calculate the transform options correctly', async () => {
expect(await deltaCalculator.getTransformerOptions()).toEqual({
assetDataPlugins: [],
dev: true,
enableBabelRCLookup: false,
hot: true,
Expand All @@ -315,6 +316,7 @@ describe('DeltaCalculator', () => {
);

expect(await deltaCalculator.getTransformerOptions()).toEqual({
assetDataPlugins: [],
dev: true,
enableBabelRCLookup: false,
hot: true,
Expand All @@ -333,6 +335,7 @@ describe('DeltaCalculator', () => {
);

expect(await deltaCalculator.getTransformerOptions()).toEqual({
assetDataPlugins: [],
dev: true,
enableBabelRCLookup: false,
hot: true,
Expand Down
12 changes: 7 additions & 5 deletions packages/metro/src/DeltaBundler/__tests__/Serializers-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,13 @@ describe('Serializers', () => {
},
};

getAssetData.mockImplementation((path, localPath, platform) => ({
path,
platform,
assetData: true,
}));
getAssetData.mockImplementation(
(path, localPath, assetDataPlugins, platform) => ({
path,
platform,
assetData: true,
}),
);

toLocalPath.mockImplementation((roots, path) => path.replace(roots[0], ''));

Expand Down
8 changes: 7 additions & 1 deletion packages/metro/src/JSTransformer/worker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type Transformer<ExtraOptions: {} = {}> = {
};

export type TransformOptionsStrict = {|
+assetDataPlugins: $ReadOnlyArray<string>,
+enableBabelRCLookup: boolean,
+dev: boolean,
+hot: boolean,
Expand All @@ -71,6 +72,7 @@ export type TransformOptionsStrict = {|
|};

export type TransformOptions = {
+assetDataPlugins: $ReadOnlyArray<string>,
+enableBabelRCLookup?: boolean,
+dev?: boolean,
+hot?: boolean,
Expand Down Expand Up @@ -200,7 +202,11 @@ function transformCode(
};

const transformResult = isAsset(filename, assetExts)
? assetTransformer.transform(transformerArgs, assetRegistryPath)
? assetTransformer.transform(
transformerArgs,
assetRegistryPath,
options.assetDataPlugins,
)
: transformer.transform(transformerArgs);

const postTransformArgs = [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ describe('transforming JS modules:', () => {
});

const defaults = {
assetDataPlugins: [],
dev: false,
hot: false,
inlineRequires: false,
Expand Down
1 change: 1 addition & 0 deletions packages/metro/src/ModuleGraph/worker/transform-module.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export type TransformOptions<ExtraOptions> = {|

const NODE_MODULES = path.sep + 'node_modules' + path.sep;
const defaultTransformOptions = {
assetDataPlugins: [],
dev: false,
hot: false,
inlineRequires: false,
Expand Down
8 changes: 7 additions & 1 deletion packages/metro/src/assetTransformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Params = {
async function transform(
{filename, localPath, options, src}: Params,
assetRegistryPath: string,
assetDataPlugins: $ReadOnlyArray<string>,
): Promise<{ast: Ast}> {
options = options || {
platform: '',
Expand All @@ -35,7 +36,12 @@ async function transform(
minify: false,
};

const data = await getAssetData(filename, localPath, options.platform);
const data = await getAssetData(
filename,
localPath,
assetDataPlugins,
options.platform,
);

return {
ast: generateAssetCodeFileAst(assetRegistryPath, data),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`getTransformCacheKeyFn Should return always the same key for the same params 1`] = `"4f055d9baf74ca63c449a03d12d3ea5e06dce486012af2c6d0afa156e0a3350ab9187479"`;
exports[`getTransformCacheKeyFn Should return always the same key for the same params 1`] = `"78c7c4254ad9c8290da4f45eec858cee4466daae32be6252a9bc5b8df65171914db10f84"`;
1 change: 1 addition & 0 deletions packages/metro/src/transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ type Params = {

function transform({filename, options, src, plugins}: Params) {
options = options || {
assetDataPlugins: [],
platform: '',
projectRoot: '',
inlineRequires: false,
Expand Down

0 comments on commit da2fdba

Please sign in to comment.