Re-use pkg-lib for runtime plugin compilation

Summary: Re-use the same function for plugin building both at publishing time by "flipper-pkg" and in runtime by Flipper itself.

Reviewed By: jknoxville

Differential Revision: D21129685

fbshipit-source-id: b7bff6737310352d28a14223128f127ac4d2eebf
This commit is contained in:
Anton Nikolaev
2020-04-20 06:57:48 -07:00
committed by Facebook GitHub Bot
parent ca2d04a5da
commit 8a7470556e
7 changed files with 93 additions and 83 deletions

View File

@@ -0,0 +1,49 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
*/
import {transform} from '@babel/core';
import electronRequiresMainPlugin = require('../electron-requires-main');
const babelOptions = {
ast: true,
plugins: [electronRequiresMainPlugin],
filename: 'index.js',
};
const testCase1 = "const module = require('module');";
test(testCase1, () => {
const src = testCase1;
const code = transform(src, babelOptions)!.code;
expect(code).toMatchInlineSnapshot(
`"const module = electronRequire('module');"`,
);
});
const testCase2 = "const module = require('./module');";
test(testCase2, () => {
const src = testCase2;
const code = transform(src, babelOptions)!.code;
expect(code).toMatchInlineSnapshot(`"const module = require('./module');"`);
});
const testCase3 = "const module = require('../module');";
test(testCase3, () => {
const src = testCase3;
const code = transform(src, babelOptions)!.code;
expect(code).toMatchInlineSnapshot(`"const module = require('../module');"`);
});
const testCase4 = "const path = require.resolve('module');";
test(testCase4, () => {
const src = testCase4;
const code = transform(src, babelOptions)!.code;
expect(code).toMatchInlineSnapshot(
`"const path = electronRequire.resolve('module');"`,
);
});

View File

@@ -23,10 +23,20 @@ module.exports = () => ({
node.arguments[0].type === 'StringLiteral'
) {
const source = node.arguments[0].value;
if (!source.startsWith('./')) {
if (!source.startsWith('./') && !source.startsWith('../')) {
node.callee.name = 'electronRequire';
}
}
if (
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'require' &&
node.callee.property.name === 'resolve' &&
node.arguments.length === 1 &&
node.arguments[0].type == 'StringLiteral'
) {
node.callee.object.name = 'electronRequire';
}
},
},
});

View File

@@ -9,6 +9,25 @@
import Metro from 'metro';
import getWatchFolders from './getWatchFolders';
import path from 'path';
import fs from 'fs-extra';
let metroDir: string | undefined;
const metroDirPromise = getMetroDir().then((dir) => (metroDir = dir));
async function getMetroDir() {
let dir = __dirname;
while (true) {
const dirToCheck = path.join(dir, 'node_modules', 'metro');
if (await fs.pathExists(dirToCheck)) return dirToCheck;
const nextDir = path.dirname(dir);
if (!nextDir || nextDir === '' || nextDir === dir) {
break;
}
dir = nextDir;
}
return __dirname;
}
function hash(string: string) {
let hash = 0;
@@ -41,11 +60,14 @@ export default async function runBuild(
entry: string,
out: string,
) {
const dev = process.env.NODE_ENV !== 'production';
const baseConfig = await Metro.loadConfig();
const config = Object.assign({}, baseConfig, {
reporter: {update: () => {}},
projectRoot: inputDirectory,
watchFolders: [inputDirectory, ...(await getWatchFolders(inputDirectory))],
watchFolders: [metroDir || (await metroDirPromise)].concat(
await getWatchFolders(inputDirectory),
),
serializer: {
...baseConfig.serializer,
getRunModuleStatement: (moduleID: string) =>
@@ -56,11 +78,17 @@ export default async function runBuild(
...baseConfig.transformer,
babelTransformerPath: require.resolve('flipper-babel-transformer'),
},
resolver: {
...baseConfig.resolver,
resolverMainFields: ['flipperBundlerEntry', 'module', 'main'],
sourceExts: ['js', 'jsx', 'ts', 'tsx', 'json', 'mjs'],
blacklistRE: /\.native\.js$/,
},
});
await Metro.runBuild(config, {
dev: false,
dev,
minify: false,
resetCache: false,
resetCache: !dev,
sourceMap: true,
entry,
out,

View File

@@ -9,20 +9,16 @@
import path from 'path';
import fs from 'fs-extra';
import Metro from 'metro';
import util from 'util';
import recursiveReaddir from 'recursive-readdir';
import pMap from 'p-map';
import {homedir} from 'os';
import {getWatchFolders, PluginDetails} from 'flipper-pkg-lib';
import {runBuild, PluginDetails} from 'flipper-pkg-lib';
import getPlugins from './getPlugins';
import startWatchPlugins from './startWatchPlugins';
const HOME_DIR = homedir();
let metroDir: string | undefined;
const metroDirPromise = getMetroDir().then((dir) => (metroDir = dir));
const DEFAULT_COMPILE_OPTIONS: CompileOptions = {
force: false,
failSilently: true,
@@ -97,57 +93,18 @@ async function startWatchChanges(
),
);
}
function hash(string: string) {
let hash = 0;
if (string.length === 0) {
return hash;
}
let chr;
for (let i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return hash;
}
const fileToIdMap = new Map();
const createModuleIdFactory = () => (filePath: string) => {
if (filePath === '__prelude__') {
return 0;
}
let id = fileToIdMap.get(filePath);
if (typeof id !== 'number') {
id = hash(filePath);
fileToIdMap.set(filePath, id);
}
return id;
};
async function mostRecentlyChanged(dir: string) {
const files = await util.promisify<string, string[]>(recursiveReaddir)(dir);
return files
.map((f) => fs.lstatSync(f).ctime)
.reduce((a, b) => (a > b ? a : b), new Date(0));
}
async function getMetroDir() {
let dir = __dirname;
while (true) {
const dirToCheck = path.join(dir, 'node_modules', 'metro');
if (await fs.pathExists(dirToCheck)) return dirToCheck;
const nextDir = path.dirname(dir);
if (!nextDir || nextDir === '' || nextDir === dir) {
break;
}
dir = nextDir;
}
return __dirname;
}
async function compilePlugin(
pluginDetails: PluginDetails,
pluginCache: string,
{force, failSilently}: CompileOptions,
): Promise<CompiledPluginDetails | null> {
const {dir, specVersion, version, main, source, name} = pluginDetails;
const dev = process.env.NODE_ENV !== 'production';
if (specVersion > 1) {
// eslint-disable-next-line no-console
const entry = path.join(dir, main);
@@ -176,37 +133,7 @@ async function compilePlugin(
// eslint-disable-line no-console
console.log(`⚙️ Compiling ${name}...`);
try {
await Metro.runBuild(
{
reporter: {update: () => {}},
projectRoot: dir,
watchFolders: [metroDir || (await metroDirPromise)].concat(
await getWatchFolders(dir),
),
serializer: {
getRunModuleStatement: (moduleID: string) =>
`module.exports = global.__r(${moduleID}).default;`,
createModuleIdFactory,
},
transformer: {
babelTransformerPath: global.electronResolve
? global.electronResolve('flipper-babel-transformer') // when compilation is executing in Electron main process
: require.resolve('flipper-babel-transformer'), // when compilation is is executing in Node.js script
},
resolver: {
sourceExts: ['tsx', 'ts', 'js'],
blacklistRE: /\.native\.js$/,
},
},
{
entry: source,
out: entry,
dev,
sourceMap: true,
minify: false,
resetCache: !dev,
},
);
await runBuild(dir, source, entry);
} catch (e) {
if (failSilently) {
console.error(

View File

@@ -8,7 +8,6 @@
*/
global.electronRequire = require;
global.electronResolve = require.resolve;
global.electronProcess = process;
require('./main.bundle.js');

View File

@@ -10,10 +10,8 @@
"fb-watchman": "^2.0.0",
"fix-path": "^3.0.0",
"fs-extra": "^8.1.0",
"flipper-babel-transformer": "0.37.0",
"flipper-pkg-lib": "0.37.0",
"mem": "^6.0.0",
"metro": "^0.59.0",
"mkdirp": "^1.0.0",
"p-map": "^4.0.0",
"p-filter": "^2.1.0",

View File

@@ -12,7 +12,6 @@ declare module NodeJS {
__REVISION__: string | undefined;
__VERSION__: string;
electronRequire: (name: string) => any;
electronResolve: (name: string) => string;
window: Window | undefined;
WebSocket: any;
fetch: any;