mirror of https://github.com/status-im/metro.git
Add back support for the assetPlugin option
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 https://github.com/facebook/metro/pull/118 Differential Revision: D6711094 Pulled By: rafeca fbshipit-source-id: f42c54cfd11bac5103194f85083084eef25fa3cd
This commit is contained in:
parent
1152a69432
commit
da2fdba240
|
@ -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', () => {
|
||||
|
@ -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,
|
||||
|
@ -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({
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}),
|
||||
|
|
|
@ -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,
|
||||
|
@ -315,6 +316,7 @@ describe('DeltaCalculator', () => {
|
|||
);
|
||||
|
||||
expect(await deltaCalculator.getTransformerOptions()).toEqual({
|
||||
assetDataPlugins: [],
|
||||
dev: true,
|
||||
enableBabelRCLookup: false,
|
||||
hot: true,
|
||||
|
@ -333,6 +335,7 @@ describe('DeltaCalculator', () => {
|
|||
);
|
||||
|
||||
expect(await deltaCalculator.getTransformerOptions()).toEqual({
|
||||
assetDataPlugins: [],
|
||||
dev: true,
|
||||
enableBabelRCLookup: false,
|
||||
hot: true,
|
||||
|
|
|
@ -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], ''));
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ export type Transformer<ExtraOptions: {} = {}> = {
|
|||
};
|
||||
|
||||
export type TransformOptionsStrict = {|
|
||||
+assetDataPlugins: $ReadOnlyArray<string>,
|
||||
+enableBabelRCLookup: boolean,
|
||||
+dev: boolean,
|
||||
+hot: boolean,
|
||||
|
@ -71,6 +72,7 @@ export type TransformOptionsStrict = {|
|
|||
|};
|
||||
|
||||
export type TransformOptions = {
|
||||
+assetDataPlugins: $ReadOnlyArray<string>,
|
||||
+enableBabelRCLookup?: boolean,
|
||||
+dev?: boolean,
|
||||
+hot?: boolean,
|
||||
|
@ -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 = [
|
||||
|
|
|
@ -88,6 +88,7 @@ describe('transforming JS modules:', () => {
|
|||
});
|
||||
|
||||
const defaults = {
|
||||
assetDataPlugins: [],
|
||||
dev: false,
|
||||
hot: false,
|
||||
inlineRequires: false,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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: '',
|
||||
|
@ -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),
|
||||
|
|
|
@ -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"`;
|
||||
|
|
|
@ -122,6 +122,7 @@ type Params = {
|
|||
|
||||
function transform({filename, options, src, plugins}: Params) {
|
||||
options = options || {
|
||||
assetDataPlugins: [],
|
||||
platform: '',
|
||||
projectRoot: '',
|
||||
inlineRequires: false,
|
||||
|
|
Loading…
Reference in New Issue