build - refactor build system for easier configuration (#10718)

* build - refactor build system for easier configuration of before and after bundle

* build - fix dependenciesToBundle option

* build - fix bify external options and other config

* build - refactor for cleanliness

* build - fix minify argument

* build - fix sourcemaps setup

* scripts - refactor setupBundlerDefaults in anticipation of factor bundles

* build - scripts - remove unused pipeline label

* build - scripts - make filepath entry optional

* build - scripts - rename filepath and filename options to entryFilepath and destFilepath

* Update development/build/scripts.js

Co-authored-by: Mark Stacey <markjstacey@gmail.com>

Co-authored-by: Mark Stacey <markjstacey@gmail.com>
feature/default_network_editable
kumavis 4 years ago committed by GitHub
parent b668a90303
commit 715f699ed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 424
      development/build/scripts.js
  2. 1
      package.json
  3. 8
      yarn.lock

@ -1,11 +1,9 @@
const EventEmitter = require('events');
const gulp = require('gulp'); const gulp = require('gulp');
const watch = require('gulp-watch'); const watch = require('gulp-watch');
const pify = require('pify');
const pump = pify(require('pump'));
const source = require('vinyl-source-stream'); const source = require('vinyl-source-stream');
const buffer = require('vinyl-buffer'); const buffer = require('vinyl-buffer');
const log = require('fancy-log'); const log = require('fancy-log');
const { assign } = require('lodash');
const watchify = require('watchify'); const watchify = require('watchify');
const browserify = require('browserify'); const browserify = require('browserify');
const envify = require('loose-envify/custom'); const envify = require('loose-envify/custom');
@ -13,8 +11,11 @@ const sourcemaps = require('gulp-sourcemaps');
const terser = require('gulp-terser-js'); const terser = require('gulp-terser-js');
const babelify = require('babelify'); const babelify = require('babelify');
const brfs = require('brfs'); const brfs = require('brfs');
const pify = require('pify');
const endOfStream = pify(require('end-of-stream'));
const labeledStreamSplicer = require('labeled-stream-splicer').obj;
const conf = require('rc')('metamask', { const metamaskrc = require('rc')('metamask', {
INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID, INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID,
SEGMENT_HOST: process.env.SEGMENT_HOST, SEGMENT_HOST: process.env.SEGMENT_HOST,
SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY, SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,
@ -67,10 +68,10 @@ function createScriptTasks({ browserPlatforms, livereload }) {
}; };
const deps = { const deps = {
background: createTasksForBuildJsDeps({ background: createTasksForBuildJsDeps({
filename: 'bg-libs', label: 'bg-libs',
key: 'background', key: 'background',
}), }),
ui: createTasksForBuildJsDeps({ filename: 'ui-libs', key: 'ui' }), ui: createTasksForBuildJsDeps({ label: 'ui-libs', key: 'ui' }),
}; };
// high level tasks // high level tasks
@ -83,15 +84,15 @@ function createScriptTasks({ browserPlatforms, livereload }) {
return { prod, dev, testDev, test }; return { prod, dev, testDev, test };
function createTasksForBuildJsDeps({ key, filename }) { function createTasksForBuildJsDeps({ key, label }) {
return createTask( return createTask(
`scripts:deps:${key}`, `scripts:deps:${key}`,
bundleTask({ createNormalBundle({
label: filename, label,
filename: `${filename}.js`, destFilepath: `${label}.js`,
buildLib: true, modulesToExpose: externalDependenciesMap[key],
dependenciesToBundle: externalDependenciesMap[key],
devMode: false, devMode: false,
browserPlatforms,
}), }),
); );
} }
@ -104,13 +105,18 @@ function createScriptTasks({ browserPlatforms, livereload }) {
'initSentry', 'initSentry',
]; ];
const standardSubtasks = standardBundles.map((filename) => { const standardSubtasks = standardBundles.map((label) => {
let extraEntries;
if (devMode && label === 'ui') {
extraEntries = ['./development/require-react-devtools.js'];
}
return createTask( return createTask(
`${taskPrefix}:${filename}`, `${taskPrefix}:${label}`,
createBundleTaskForBuildJsExtensionNormal({ createBundleTaskForBuildJsExtensionNormal({
filename, label,
devMode, devMode,
testing, testing,
extraEntries,
}), }),
); );
}); });
@ -128,7 +134,7 @@ function createScriptTasks({ browserPlatforms, livereload }) {
createTaskForBuildJsExtensionDisableConsole({ devMode }), createTaskForBuildJsExtensionDisableConsole({ devMode }),
); );
// task for initiating livereload // task for initiating browser livereload
const initiateLiveReload = async () => { const initiateLiveReload = async () => {
if (devMode) { if (devMode) {
// trigger live reload when the bundles are updated // trigger live reload when the bundles are updated
@ -156,29 +162,33 @@ function createScriptTasks({ browserPlatforms, livereload }) {
} }
function createBundleTaskForBuildJsExtensionNormal({ function createBundleTaskForBuildJsExtensionNormal({
filename, label,
devMode, devMode,
testing, testing,
extraEntries,
}) { }) {
return bundleTask({ return createNormalBundle({
label: filename, label,
filename: `${filename}.js`, entryFilepath: `./app/scripts/${label}.js`,
filepath: `./app/scripts/${filename}.js`, destFilepath: `${label}.js`,
extraEntries,
externalDependencies: devMode externalDependencies: devMode
? undefined ? undefined
: externalDependenciesMap[filename], : externalDependenciesMap[label],
devMode, devMode,
testing, testing,
browserPlatforms,
}); });
} }
function createTaskForBuildJsExtensionDisableConsole({ devMode }) { function createTaskForBuildJsExtensionDisableConsole({ devMode }) {
const filename = 'disable-console'; const label = 'disable-console';
return bundleTask({ return createNormalBundle({
label: filename, label,
filename: `${filename}.js`, entryFilepath: `./app/scripts/${label}.js`,
filepath: `./app/scripts/${filename}.js`, destFilepath: `${label}.js`,
devMode, devMode,
browserPlatforms,
}); });
} }
@ -186,190 +196,266 @@ function createScriptTasks({ browserPlatforms, livereload }) {
const inpage = 'inpage'; const inpage = 'inpage';
const contentscript = 'contentscript'; const contentscript = 'contentscript';
return composeSeries( return composeSeries(
bundleTask({ createNormalBundle({
label: inpage, label: inpage,
filename: `${inpage}.js`, entryFilepath: `./app/scripts/${inpage}.js`,
filepath: `./app/scripts/${inpage}.js`, destFilepath: `${inpage}.js`,
externalDependencies: devMode externalDependencies: devMode
? undefined ? undefined
: externalDependenciesMap[inpage], : externalDependenciesMap[inpage],
devMode, devMode,
testing, testing,
browserPlatforms,
}), }),
bundleTask({ createNormalBundle({
label: contentscript, label: contentscript,
filename: `${contentscript}.js`, entryFilepath: `./app/scripts/${contentscript}.js`,
filepath: `./app/scripts/${contentscript}.js`, destFilepath: `${contentscript}.js`,
externalDependencies: devMode externalDependencies: devMode
? undefined ? undefined
: externalDependenciesMap[contentscript], : externalDependenciesMap[contentscript],
devMode, devMode,
testing, testing,
browserPlatforms,
}), }),
); );
} }
}
function bundleTask(opts) { function createNormalBundle({
let bundler; destFilepath,
entryFilepath,
return performBundle; extraEntries = [],
modulesToExpose,
externalDependencies,
devMode,
testing,
browserPlatforms,
}) {
return async function () {
// create bundler setup and apply defaults
const buildConfiguration = createBuildConfiguration();
const { bundlerOpts, events } = buildConfiguration;
const envVars = getEnvironmentVariables({ devMode, testing });
setupBundlerDefaults(buildConfiguration, {
devMode,
envVars,
});
async function performBundle() { // set bundle entries
// initialize bundler if not available yet bundlerOpts.entries = [...extraEntries];
// dont create bundler until task is actually run if (entryFilepath) {
if (!bundler) { bundlerOpts.entries.push(entryFilepath);
bundler = generateBundler(opts, performBundle); }
// output build logs to terminal
bundler.on('log', log);
}
const buildPipeline = [ if (modulesToExpose) {
bundler.bundle(), bundlerOpts.require = bundlerOpts.require.concat(modulesToExpose);
// convert bundle stream to gulp vinyl stream }
source(opts.filename),
// Initialize Source Maps
buffer(),
// loads map from browserify file
sourcemaps.init({ loadMaps: true }),
];
// Minification
if (!opts.devMode) {
buildPipeline.push(
terser({
mangle: {
reserved: ['MetamaskInpageProvider'],
},
sourceMap: {
content: true,
},
}),
);
}
// Finalize Source Maps if (externalDependencies) {
if (opts.devMode) { // there doesnt seem to be a standard bify option for this
// Use inline source maps for development due to Chrome DevTools bug // so we'll put it here but manually call it after bundle
// https://bugs.chromium.org/p/chromium/issues/detail?id=931675 bundlerOpts.manualExternal = bundlerOpts.manualExternal.concat(
// note: sourcemaps call arity is important externalDependencies,
buildPipeline.push(sourcemaps.write()); );
} else { }
buildPipeline.push(
sourcemaps.write('../sourcemaps', { addComment: false }),
);
}
// write completed bundles // instrument pipeline
events.on('configurePipeline', ({ pipeline }) => {
// convert bundle stream to gulp vinyl stream
// and ensure file contents are buffered
pipeline.get('vinyl').push(source(destFilepath));
pipeline.get('vinyl').push(buffer());
// setup bundle destination
browserPlatforms.forEach((platform) => { browserPlatforms.forEach((platform) => {
const dest = `./dist/${platform}`; const dest = `./dist/${platform}/`;
buildPipeline.push(gulp.dest(dest)); pipeline.get('dest').push(gulp.dest(dest));
}); });
});
// process bundles await bundleIt(buildConfiguration);
if (opts.devMode) { };
try { }
await pump(buildPipeline);
} catch (err) {
gracefulError(err);
}
} else {
await pump(buildPipeline);
}
}
}
function generateBundler(opts, performBundle) { function createBuildConfiguration() {
const browserifyOpts = assign({}, watchify.args, { const events = new EventEmitter();
plugin: [], const bundlerOpts = {
transform: [], entries: [],
debug: true, transform: [],
fullPaths: opts.devMode, plugin: [],
}); require: [],
// not a standard bify option
manualExternal: [],
};
return { bundlerOpts, events };
}
if (!opts.buildLib) { function setupBundlerDefaults(buildConfiguration, { devMode, envVars }) {
if (opts.devMode && opts.filename === 'ui.js') { const { bundlerOpts } = buildConfiguration;
browserifyOpts.entries = [ // devMode options
'./development/require-react-devtools.js', const reloadOnChange = Boolean(devMode);
opts.filepath, const minify = Boolean(devMode) === false;
];
} else { Object.assign(bundlerOpts, {
browserifyOpts.entries = [opts.filepath]; // source transforms
} transform: [
} // transpile top-level code
babelify,
// inline `fs.readFileSync` files
brfs,
],
// use entryFilepath for moduleIds, easier to determine origin file
fullPaths: devMode,
// for sourcemaps
debug: true,
});
// inject environment variables via node-style `process.env`
if (envVars) {
bundlerOpts.transform.push([envify(envVars), { global: true }]);
}
let bundler = browserify(browserifyOpts) // setup reload on change
.transform(babelify) if (reloadOnChange) {
.transform(brfs); setupReloadOnChange(buildConfiguration);
}
if (opts.buildLib) { if (minify) {
bundler = bundler.require(opts.dependenciesToBundle); setupMinification(buildConfiguration);
} }
if (opts.externalDependencies) { // setup source maps
bundler = bundler.external(opts.externalDependencies); setupSourcemaps(buildConfiguration, { devMode });
} }
const environment = getEnvironment({ function setupReloadOnChange({ bundlerOpts, events }) {
devMode: opts.devMode, // add plugin to options
test: opts.testing, Object.assign(bundlerOpts, {
plugin: [...bundlerOpts.plugin, watchify],
// required by watchify
cache: {},
packageCache: {},
});
// instrument pipeline
events.on('configurePipeline', ({ bundleStream }) => {
// handle build error to avoid breaking build process
// (eg on syntax error)
bundleStream.on('error', (err) => {
gracefulError(err);
}); });
if (environment === 'production' && !process.env.SENTRY_DSN) { });
throw new Error('Missing SENTRY_DSN environment variable'); }
}
// Inject variables into bundle function setupMinification(buildConfiguration) {
bundler.transform( const { events } = buildConfiguration;
envify({ events.on('configurePipeline', ({ pipeline }) => {
METAMASK_DEBUG: opts.devMode, pipeline.get('minify').push(
METAMASK_ENVIRONMENT: environment, terser({
METAMASK_VERSION: baseManifest.version, mangle: {
NODE_ENV: opts.devMode ? 'development' : 'production', reserved: ['MetamaskInpageProvider'],
IN_TEST: opts.testing ? 'true' : false, },
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '', sourceMap: {
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '', content: true,
CONF: opts.devMode ? conf : {}, },
SENTRY_DSN: process.env.SENTRY_DSN,
INFURA_PROJECT_ID: opts.testing
? '00000000000000000000000000000000'
: conf.INFURA_PROJECT_ID,
SEGMENT_HOST: conf.SEGMENT_HOST,
// When we're in the 'production' environment we will use a specific key only set in CI
// Otherwise we'll use the key from .metamaskrc or from the environment variable. If
// the value of SEGMENT_WRITE_KEY that we envify is undefined then no events will be tracked
// in the build. This is intentional so that developers can contribute to MetaMask without
// inflating event volume.
SEGMENT_WRITE_KEY:
environment === 'production'
? process.env.SEGMENT_PROD_WRITE_KEY
: conf.SEGMENT_WRITE_KEY,
SEGMENT_LEGACY_WRITE_KEY:
environment === 'production'
? process.env.SEGMENT_PROD_LEGACY_WRITE_KEY
: conf.SEGMENT_LEGACY_WRITE_KEY,
}), }),
{
global: true,
},
); );
});
}
// Live reload - minimal rebundle on change function setupSourcemaps(buildConfiguration, { devMode }) {
if (opts.devMode) { const { events } = buildConfiguration;
bundler = watchify(bundler); events.on('configurePipeline', ({ pipeline }) => {
// on any file update, re-runs the bundler pipeline.get('sourcemaps:init').push(sourcemaps.init({ loadMaps: true }));
bundler.on('update', () => { pipeline
performBundle(); .get('sourcemaps:write')
}); // Use inline source maps for development due to Chrome DevTools bug
} // https://bugs.chromium.org/p/chromium/issues/detail?id=931675
.push(
devMode
? sourcemaps.write()
: sourcemaps.write('../sourcemaps', { addComment: false }),
);
});
}
return bundler; async function bundleIt(buildConfiguration) {
const { bundlerOpts, events } = buildConfiguration;
const bundler = browserify(bundlerOpts);
// manually apply non-standard option
bundler.external(bundlerOpts.manualExternal);
// output build logs to terminal
bundler.on('log', log);
// forward update event (used by watchify)
bundler.on('update', () => performBundle());
await performBundle();
async function performBundle() {
// this pipeline is created for every bundle
// the labels are all the steps you can hook into
const pipeline = labeledStreamSplicer([
'vinyl',
[],
'sourcemaps:init',
[],
'minify',
[],
'sourcemaps:write',
[],
'dest',
[],
]);
const bundleStream = bundler.bundle();
// trigger build pipeline instrumentations
events.emit('configurePipeline', { pipeline, bundleStream });
// start bundle, send into pipeline
bundleStream.pipe(pipeline);
// nothing will consume pipeline, so let it flow
pipeline.resume();
await endOfStream(pipeline);
} }
} }
function getEnvironment({ devMode, test }) { function getEnvironmentVariables({ devMode, testing }) {
const environment = getEnvironment({ devMode, testing });
if (environment === 'production' && !process.env.SENTRY_DSN) {
throw new Error('Missing SENTRY_DSN environment variable');
}
return {
METAMASK_DEBUG: devMode,
METAMASK_ENVIRONMENT: environment,
METAMASK_VERSION: baseManifest.version,
NODE_ENV: devMode ? 'development' : 'production',
IN_TEST: testing ? 'true' : false,
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
CONF: devMode ? metamaskrc : {},
SENTRY_DSN: process.env.SENTRY_DSN,
INFURA_PROJECT_ID: testing
? '00000000000000000000000000000000'
: metamaskrc.INFURA_PROJECT_ID,
SEGMENT_HOST: metamaskrc.SEGMENT_HOST,
// When we're in the 'production' environment we will use a specific key only set in CI
// Otherwise we'll use the key from .metamaskrc or from the environment variable. If
// the value of SEGMENT_WRITE_KEY that we envify is undefined then no events will be tracked
// in the build. This is intentional so that developers can contribute to MetaMask without
// inflating event volume.
SEGMENT_WRITE_KEY:
environment === 'production'
? process.env.SEGMENT_PROD_WRITE_KEY
: metamaskrc.SEGMENT_WRITE_KEY,
SEGMENT_LEGACY_WRITE_KEY:
environment === 'production'
? process.env.SEGMENT_PROD_LEGACY_WRITE_KEY
: metamaskrc.SEGMENT_LEGACY_WRITE_KEY,
};
}
function getEnvironment({ devMode, testing }) {
// get environment slug // get environment slug
if (devMode) { if (devMode) {
return 'development'; return 'development';
} else if (test) { } else if (testing) {
return 'testing'; return 'testing';
} else if (process.env.CIRCLE_BRANCH === 'master') { } else if (process.env.CIRCLE_BRANCH === 'master') {
return 'production'; return 'production';

@ -141,6 +141,7 @@
"json-rpc-engine": "^6.1.0", "json-rpc-engine": "^6.1.0",
"json-rpc-middleware-stream": "^2.1.1", "json-rpc-middleware-stream": "^2.1.1",
"jsonschema": "^1.2.4", "jsonschema": "^1.2.4",
"labeled-stream-splicer": "^2.0.2",
"localforage": "^1.9.0", "localforage": "^1.9.0",
"lodash": "^4.17.19", "lodash": "^4.17.19",
"loglevel": "^1.4.1", "loglevel": "^1.4.1",

@ -15184,6 +15184,14 @@ labeled-stream-splicer@^2.0.0:
isarray "^2.0.4" isarray "^2.0.4"
stream-splicer "^2.0.0" stream-splicer "^2.0.0"
labeled-stream-splicer@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.2.tgz#42a41a16abcd46fd046306cf4f2c3576fffb1c21"
integrity sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==
dependencies:
inherits "^2.0.1"
stream-splicer "^2.0.0"
last-run@^1.1.0: last-run@^1.1.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b" resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b"

Loading…
Cancel
Save