From 8a7470556efba780e16be4a8558b4aea23268dd4 Mon Sep 17 00:00:00 2001 From: Anton Nikolaev Date: Mon, 20 Apr 2020 06:57:48 -0700 Subject: [PATCH] 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 --- .../__tests__/electron-requires-main.node.ts | 49 ++++++++++++ .../src/electron-requires-main.ts | 12 ++- desktop/pkg-lib/src/runBuild.ts | 34 +++++++- desktop/static/compilePlugins.ts | 77 +------------------ desktop/static/index.js | 1 - desktop/static/package.json | 2 - desktop/types/nodejs.tsx | 1 - 7 files changed, 93 insertions(+), 83 deletions(-) create mode 100644 desktop/babel-transformer/src/__tests__/electron-requires-main.node.ts diff --git a/desktop/babel-transformer/src/__tests__/electron-requires-main.node.ts b/desktop/babel-transformer/src/__tests__/electron-requires-main.node.ts new file mode 100644 index 000000000..df10398e1 --- /dev/null +++ b/desktop/babel-transformer/src/__tests__/electron-requires-main.node.ts @@ -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');"`, + ); +}); diff --git a/desktop/babel-transformer/src/electron-requires-main.ts b/desktop/babel-transformer/src/electron-requires-main.ts index 5edd2e8ee..0bf523158 100644 --- a/desktop/babel-transformer/src/electron-requires-main.ts +++ b/desktop/babel-transformer/src/electron-requires-main.ts @@ -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'; + } }, }, }); diff --git a/desktop/pkg-lib/src/runBuild.ts b/desktop/pkg-lib/src/runBuild.ts index e4b3e35f1..be987025d 100644 --- a/desktop/pkg-lib/src/runBuild.ts +++ b/desktop/pkg-lib/src/runBuild.ts @@ -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, diff --git a/desktop/static/compilePlugins.ts b/desktop/static/compilePlugins.ts index 6b5a08502..7d80f8060 100644 --- a/desktop/static/compilePlugins.ts +++ b/desktop/static/compilePlugins.ts @@ -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(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 { 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( diff --git a/desktop/static/index.js b/desktop/static/index.js index 157b44aee..fbe62a06d 100644 --- a/desktop/static/index.js +++ b/desktop/static/index.js @@ -8,7 +8,6 @@ */ global.electronRequire = require; -global.electronResolve = require.resolve; global.electronProcess = process; require('./main.bundle.js'); diff --git a/desktop/static/package.json b/desktop/static/package.json index 9c4db712b..5ca3351b2 100644 --- a/desktop/static/package.json +++ b/desktop/static/package.json @@ -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", diff --git a/desktop/types/nodejs.tsx b/desktop/types/nodejs.tsx index 77adc7cd6..3a9f5bd41 100644 --- a/desktop/types/nodejs.tsx +++ b/desktop/types/nodejs.tsx @@ -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;