1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-26 12:29:06 +01:00
metamask-extension/gulpfile.js

606 lines
15 KiB
JavaScript
Raw Normal View History

2018-03-29 05:13:47 +02:00
const watchify = require('watchify')
const browserify = require('browserify')
const envify = require('envify/custom')
2018-03-29 05:13:47 +02:00
const gulp = require('gulp')
const source = require('vinyl-source-stream')
const buffer = require('vinyl-buffer')
const gutil = require('gulp-util')
const watch = require('gulp-watch')
const sourcemaps = require('gulp-sourcemaps')
const jsoneditor = require('gulp-json-editor')
const zip = require('gulp-zip')
const assign = require('lodash.assign')
const livereload = require('gulp-livereload')
const del = require('del')
const manifest = require('./app/manifest.json')
const sass = require('gulp-sass')
const autoprefixer = require('gulp-autoprefixer')
const gulpStylelint = require('gulp-stylelint')
const stylefmt = require('gulp-stylefmt')
const uglify = require('gulp-uglify-es').default
2018-03-30 07:19:15 +02:00
const pify = require('pify')
const gulpMultiProcess = require('gulp-multi-process')
2018-03-30 07:19:15 +02:00
const endOfStream = pify(require('end-of-stream'))
2018-03-29 05:13:47 +02:00
const packageJSON = require('./package.json')
const dependencies = Object.keys(packageJSON && packageJSON.dependencies || {})
const materialUIDependencies = ['@material-ui/core']
const reactDepenendencies = dependencies.filter(dep => dep.match(/react/))
const d3Dependencies = ['c3', 'd3']
const uiDependenciesToBundle = [
...materialUIDependencies,
...reactDepenendencies,
...d3Dependencies,
]
function gulpParallel (...args) {
2018-07-03 00:49:33 +02:00
return function spawnGulpChildProcess (cb) {
return gulpMultiProcess(args, cb, true)
}
}
2018-03-29 04:56:44 +02:00
const browserPlatforms = [
'firefox',
'chrome',
'brave',
'edge',
'opera',
2018-03-29 04:56:44 +02:00
]
const commonPlatforms = [
// browser extensions
2018-07-03 00:49:33 +02:00
...browserPlatforms,
]
2016-03-03 08:06:43 +01:00
// browser reload
2018-07-03 00:49:33 +02:00
gulp.task('dev:reload', function () {
2016-03-03 08:06:43 +01:00
livereload.listen({
port: 35729,
})
})
2018-03-29 04:56:44 +02:00
// copy universal
2016-03-03 08:06:43 +01:00
const copyTaskNames = []
const copyDevTaskNames = []
createCopyTasks('locales', {
2016-03-03 08:06:43 +01:00
source: './app/_locales/',
destinations: commonPlatforms.map(platform => `./dist/${platform}/_locales`),
})
createCopyTasks('images', {
2016-03-03 08:06:43 +01:00
source: './app/images/',
destinations: commonPlatforms.map(platform => `./dist/${platform}/images`),
})
createCopyTasks('contractImages', {
source: './node_modules/eth-contract-metadata/images/',
destinations: commonPlatforms.map(platform => `./dist/${platform}/images/contract`),
})
createCopyTasks('fonts', {
source: './app/fonts/',
destinations: commonPlatforms.map(platform => `./dist/${platform}/fonts`),
})
2018-08-05 08:43:02 +02:00
createCopyTasks('vendor', {
source: './app/vendor/',
destinations: commonPlatforms.map(platform => `./dist/${platform}/vendor`),
})
Serve CSS as an external file (#6894) The CSS is now served as an external file instead of being injected. This was done to improve performance. Ideally we would come to a middle ground between this and the former behaviour by injecting only the CSS that was required for the initial page load, then lazily loading the rest. However that change would be more complex. The hope was that making all CSS external would at least be a slight improvement. Performance metrics were collected before and after this change to determine whether this change actually helped. The metrics collected were the timing events provided by Chrome DevTools: * DOM Content Loaded (DCL) [1] * Load (L) [2] * First Paint (FP) [3] * First Contentful Paint (FCP) [3] * First Meaningful Paint (FMP) [3] Here are the results (units in milliseconds): Injected CSS: | Run | DCL | L | FP | FCP | FMP | | :--- | ---: | ---: | ---: | ---: | ---: | | 1 | 1569.45 | 1570.97 | 1700.36 | 1700.36 | 1700.36 | | 2 | 1517.37 | 1518.84 | 1630.98 | 1630.98 | 1630.98 | | 3 | 1603.71 | 1605.31 | 1712.56 | 1712.56 | 1712.56 | | 4 | 1522.15 | 1523.72 | 1629.3 | 1629.3 | 1629.3 | | **Min** | 1517.37 | 1518.84 | 1629.3 | 1629.3 | 1629.3 | | **Max** | 1603.71 | 1605.31 | 1712.56 | 1712.56 | 1712.56 | | **Mean** | 1553.17 | 1554.71 | 1668.3 | 1668.3 | 1668.3 | | **Std. dev.** | 33.41 | 33.43 | 38.16 | 38.16 | 38.16 | External CSS: | Run | DCL | L | FP | FCP | FMP | | :--- | ---: | ---: | ---: | ---: | ---: | | 1 | 1595.4 | 1598.91 | 284.97 | 1712.86 | 1712.86 | | 2 | 1537.55 | 1538.99 | 199.38 | 1633.5 | 1633.5 | | 3 | 1571.28 | 1572.74 | 268.65 | 1677.03 | 1677.03 | | 4 | 1510.98 | 1512.33 | 206.72 | 1607.03 | 1607.03 | | **Min** | 1510.98 | 1512.33 | 199.38 | 1607.03 | 1607.03 | | **Max** | 1595.4 | 1598.91 | 284.97 | 1712.86 | 1712.86 | | **Mean** | 1553.8025 | 1555.7425 | 239.93 | 1657.605 | 1657.605 | | **Std. dev.** | 29.5375 | 30.0825 | 36.88 | 37.34 | 37.34 | Unfortunately, using an external CSS file made no discernible improvement to the overall page load time. DCM and L were practically identical, and FCP and FMP were marginally better (well within error margins). However, the first paint time was _dramatically_ improved. This change seems worthwhile for the first paint time improvement alone. It also allows us to delete some code and remove a dependency. The old `css.js` module included two third-party CSS files as well, so those have been imported into the main Sass file. This was easier than bundling them in the gulpfile. The resulting CSS bundle needs to be served from the root because we're using a few `@include` rules that make this assumption. We could move this under `/css/` if desired, but we'd need to update each of these `@include` rules. Relates to #6646 [1]: https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded [2]: https://developer.mozilla.org/en-US/docs/Web/Events/load [3]: https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics
2019-07-23 19:10:13 +02:00
createCopyTasks('css', {
source: './ui/app/css/output/',
destinations: commonPlatforms.map(platform => `./dist/${platform}`),
})
createCopyTasks('reload', {
devOnly: true,
source: './app/scripts/',
2016-03-03 08:06:43 +01:00
pattern: '/chromereload.js',
2018-03-29 05:54:59 +02:00
destinations: commonPlatforms.map(platform => `./dist/${platform}`),
})
createCopyTasks('html', {
source: './app/',
2018-03-29 05:54:59 +02:00
pattern: '/*.html',
destinations: commonPlatforms.map(platform => `./dist/${platform}`),
})
2018-03-29 04:56:44 +02:00
// copy extension
createCopyTasks('manifest', {
2018-03-29 04:56:44 +02:00
source: './app/',
pattern: '/*.json',
destinations: browserPlatforms.map(platform => `./dist/${platform}`),
})
2018-03-29 05:54:59 +02:00
2018-07-03 00:49:33 +02:00
function createCopyTasks (label, opts) {
if (!opts.devOnly) {
const copyTaskName = `copy:${label}`
copyTask(copyTaskName, opts)
copyTaskNames.push(copyTaskName)
}
const copyDevTaskName = `dev:copy:${label}`
copyTask(copyDevTaskName, Object.assign({ devMode: true }, opts))
copyDevTaskNames.push(copyDevTaskName)
}
2018-07-03 00:49:33 +02:00
function copyTask (taskName, opts) {
2018-03-30 06:53:38 +02:00
const source = opts.source
const destination = opts.destination
const destinations = opts.destinations || [destination]
const pattern = opts.pattern || '/**/*'
const devMode = opts.devMode
return gulp.task(taskName, function () {
if (devMode) {
watch(source + pattern, (event) => {
livereload.changed(event.path)
performCopy()
})
}
return performCopy()
})
2018-07-03 00:49:33 +02:00
function performCopy () {
2018-03-30 06:53:38 +02:00
// stream from source
let stream = gulp.src(source + pattern, { base: source })
// copy to destinations
2018-07-03 00:49:33 +02:00
destinations.forEach(function (destination) {
2018-03-30 06:53:38 +02:00
stream = stream.pipe(gulp.dest(destination))
})
return stream
}
}
2018-03-29 04:56:44 +02:00
// manifest tinkering
2018-07-03 00:49:33 +02:00
gulp.task('manifest:chrome', function () {
2016-09-02 00:01:45 +02:00
return gulp.src('./dist/chrome/manifest.json')
.pipe(jsoneditor(function (json) {
delete json.applications
json.minimum_chrome_version = '58'
return json
}))
.pipe(gulp.dest('./dist/chrome', { overwrite: true }))
})
2018-07-03 00:49:33 +02:00
gulp.task('manifest:opera', function () {
return gulp.src('./dist/opera/manifest.json')
.pipe(jsoneditor(function (json) {
json.permissions = [
'storage',
'tabs',
'clipboardWrite',
'clipboardRead',
'http://localhost:8545/',
]
return json
}))
.pipe(gulp.dest('./dist/opera', { overwrite: true }))
})
2018-07-03 00:49:33 +02:00
gulp.task('manifest:production', function () {
return gulp.src([
'./dist/firefox/manifest.json',
'./dist/chrome/manifest.json',
'./dist/brave/manifest.json',
'./dist/edge/manifest.json',
'./dist/opera/manifest.json',
2018-07-03 00:49:33 +02:00
], {base: './dist/'})
// Exclude chromereload script in production:
.pipe(jsoneditor(function (json) {
json.background.scripts = json.background.scripts.filter((script) => {
return !script.includes('chromereload')
})
return json
}))
.pipe(gulp.dest('./dist/', { overwrite: true }))
})
gulp.task('manifest:testing', function () {
return gulp.src([
'./dist/firefox/manifest.json',
'./dist/chrome/manifest.json',
], {base: './dist/'})
// Exclude chromereload script in production:
.pipe(jsoneditor(function (json) {
json.permissions = [...json.permissions, 'webRequestBlocking']
return json
}))
.pipe(gulp.dest('./dist/', { overwrite: true }))
})
2018-03-29 04:56:44 +02:00
gulp.task('copy',
gulp.series(
gulp.parallel(...copyTaskNames),
'manifest:production',
'manifest:chrome',
'manifest:opera'
)
)
gulp.task('dev:copy',
gulp.series(
gulp.parallel(...copyDevTaskNames),
'manifest:chrome',
'manifest:opera'
)
)
2016-03-03 08:06:43 +01:00
gulp.task('test:copy',
gulp.series(
gulp.parallel(...copyDevTaskNames),
'manifest:chrome',
'manifest:opera',
'manifest:testing'
)
)
// scss compilation and autoprefixing tasks
gulp.task('build:scss', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: false,
}))
gulp.task('dev:scss', createScssBuildTask({
src: 'ui/app/css/index.scss',
dest: 'ui/app/css/output',
devMode: true,
pattern: 'ui/app/**/*.scss',
}))
2018-07-03 00:49:33 +02:00
function createScssBuildTask ({ src, dest, devMode, pattern }) {
return function () {
if (devMode) {
2018-03-30 07:19:15 +02:00
watch(pattern, async (event) => {
const stream = buildScss()
await endOfStream(stream)
livereload.changed(event.path)
})
2018-09-26 02:40:55 +02:00
return buildScssWithSourceMaps()
}
return buildScss()
}
2018-09-26 02:40:55 +02:00
function buildScssWithSourceMaps () {
return gulp.src(src)
.pipe(sourcemaps.init())
.pipe(sass().on('error', sass.logError))
.pipe(sourcemaps.write())
.pipe(autoprefixer())
.pipe(gulp.dest(dest))
}
2018-09-26 02:40:55 +02:00
function buildScss () {
return gulp.src(src)
.pipe(sass().on('error', sass.logError))
.pipe(autoprefixer())
.pipe(gulp.dest(dest))
}
}
2018-07-03 00:49:33 +02:00
gulp.task('lint-scss', function () {
return gulp
.src('ui/app/css/itcss/**/*.scss')
.pipe(gulpStylelint({
reporters: [
2018-07-03 00:49:33 +02:00
{ formatter: 'string', console: true },
],
fix: true,
2018-07-03 00:49:33 +02:00
}))
})
gulp.task('fmt-scss', function () {
return gulp.src('ui/app/css/itcss/**/*.scss')
.pipe(stylefmt())
2018-07-03 00:49:33 +02:00
.pipe(gulp.dest('ui/app/css/itcss'))
})
2018-03-29 05:41:56 +02:00
// build js
const buildJsFiles = [
'inpage',
'contentscript',
'background',
'ui',
2018-08-13 19:16:37 +02:00
'phishing-detect',
2018-03-29 05:41:56 +02:00
]
2017-01-10 22:46:15 +01:00
// bundle tasks
createTasksForBuildJsUIDeps({ dependenciesToBundle: uiDependenciesToBundle, filename: 'libs' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:test-extension:js', devMode: true, testing: 'true' })
2018-07-03 00:49:33 +02:00
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:test:extension:js', testing: 'true' })
function createTasksForBuildJsUIDeps ({ filename }) {
const destinations = browserPlatforms.map(platform => `./dist/${platform}`)
2019-02-25 20:10:13 +01:00
const bundleTaskOpts = Object.assign({
buildSourceMaps: true,
sourceMapDir: '../sourcemaps',
minifyBuild: true,
devMode: false,
})
gulp.task('build:extension:js:uideps', bundleTask(Object.assign({
label: filename,
filename: `${filename}.js`,
destinations,
buildLib: true,
dependenciesToBundle: uiDependenciesToBundle,
}, bundleTaskOpts)))
}
function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, testing, bundleTaskOpts = {} }) {
// inpage must be built before all other scripts:
const rootDir = './app/scripts'
2018-03-29 05:41:56 +02:00
const nonInpageFiles = buildJsFiles.filter(file => file !== 'inpage')
const buildPhase1 = ['inpage']
const buildPhase2 = nonInpageFiles
const destinations = browserPlatforms.map(platform => `./dist/${platform}`)
2018-03-29 05:41:56 +02:00
bundleTaskOpts = Object.assign({
buildSourceMaps: true,
sourceMapDir: '../sourcemaps',
2018-03-29 05:41:56 +02:00
minifyBuild: !devMode,
buildWithFullPaths: devMode,
watch: devMode,
devMode,
testing,
2018-03-29 05:41:56 +02:00
}, bundleTaskOpts)
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1, buildPhase2 })
}
2017-01-10 22:46:15 +01:00
2018-07-03 00:49:33 +02:00
function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) {
// bundle task for each file
2018-03-29 05:41:56 +02:00
const jsFiles = [].concat(buildPhase1, buildPhase2)
jsFiles.forEach((jsFile) => {
gulp.task(`${taskPrefix}:${jsFile}`, bundleTask(Object.assign({
label: jsFile,
filename: `${jsFile}.js`,
filepath: `${rootDir}/${jsFile}.js`,
externalDependencies: jsFile === 'ui' && !bundleTaskOpts.devMode && uiDependenciesToBundle,
destinations,
}, bundleTaskOpts)))
})
// compose into larger task
const subtasks = []
subtasks.push(gulp.parallel(buildPhase1.map(file => `${taskPrefix}:${file}`)))
if (buildPhase2.length) subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`)))
2017-10-12 20:03:42 +02:00
2018-03-30 02:37:49 +02:00
gulp.task(taskPrefix, gulp.series(subtasks))
}
2016-03-03 08:06:43 +01:00
2016-07-27 02:25:15 +02:00
// clean dist
2016-03-03 08:06:43 +01:00
2018-07-03 00:49:33 +02:00
gulp.task('clean', function clean () {
return del(['./dist/*'])
2016-03-03 08:06:43 +01:00
})
// zip tasks for distribution
2017-01-10 22:08:13 +01:00
gulp.task('zip:chrome', zipTask('chrome'))
gulp.task('zip:firefox', zipTask('firefox'))
gulp.task('zip:edge', zipTask('edge'))
gulp.task('zip:opera', zipTask('opera'))
gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:opera'))
2016-03-03 08:06:43 +01:00
// high level tasks
gulp.task('dev',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:extension:js',
'dev:copy',
'dev:reload'
)
)
)
gulp.task('dev:test',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:test-extension:js',
'test:copy',
'dev:reload'
)
)
)
gulp.task('dev:extension',
gulp.series(
'clean',
'dev:scss',
gulp.parallel(
'dev:extension:js',
'dev:copy',
'dev:reload'
)
)
)
gulp.task('build',
gulp.series(
'clean',
'build:scss',
gulpParallel(
'build:extension:js:uideps',
'build:extension:js',
'copy'
)
)
)
gulp.task('build:test',
gulp.series(
'clean',
'build:scss',
gulpParallel(
'build:extension:js:uideps',
'build:test:extension:js',
'copy'
),
'manifest:testing'
)
)
gulp.task('build:extension',
gulp.series(
'clean',
'build:scss',
gulp.parallel(
'build:extension:js',
'copy'
)
)
)
gulp.task('dist',
gulp.series(
'build',
'zip'
)
)
2016-03-03 08:06:43 +01:00
// task generators
2018-07-03 00:49:33 +02:00
function zipTask (target) {
2017-01-10 22:08:13 +01:00
return () => {
return gulp.src(`dist/${target}/**`)
.pipe(zip(`metamask-${target}-${manifest.version}.zip`))
.pipe(gulp.dest('builds'))
2017-01-10 22:08:13 +01:00
}
}
2018-07-03 00:49:33 +02:00
function generateBundler (opts, performBundle) {
const browserifyOpts = assign({}, watchify.args, {
2016-04-20 22:22:41 +02:00
plugin: 'browserify-derequire',
2018-03-29 05:41:56 +02:00
debug: opts.buildSourceMaps,
fullPaths: opts.buildWithFullPaths,
2016-03-03 08:06:43 +01:00
})
if (!opts.buildLib) {
if (opts.devMode && opts.filename === 'ui.js') {
browserifyOpts['entries'] = ['./development/require-react-devtools.js', opts.filepath]
} else {
browserifyOpts['entries'] = [opts.filepath]
}
}
let bundler = browserify(browserifyOpts)
.transform('babelify')
.transform('brfs')
2017-01-10 22:46:15 +01:00
if (opts.buildLib) {
bundler = bundler.require(opts.dependenciesToBundle)
}
if (opts.externalDependencies) {
bundler = bundler.external(opts.externalDependencies)
}
// inject variables into bundle
bundler.transform(envify({
METAMASK_DEBUG: opts.devMode,
NODE_ENV: opts.devMode ? 'development' : 'production',
IN_TEST: opts.testing,
2019-02-25 20:10:13 +01:00
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
}), {
global: true,
})
2017-01-10 22:46:15 +01:00
if (opts.watch) {
bundler = watchify(bundler)
// on any file update, re-runs the bundler
2018-03-30 07:19:15 +02:00
bundler.on('update', async (ids) => {
const stream = performBundle()
await endOfStream(stream)
livereload.changed(`${ids}`)
})
2017-01-10 22:46:15 +01:00
}
return bundler
}
2018-07-03 00:49:33 +02:00
function bundleTask (opts) {
const bundler = generateBundler(opts, performBundle)
2017-01-10 22:56:31 +01:00
// output build logs to terminal
bundler.on('log', gutil.log)
2016-03-03 08:06:43 +01:00
return performBundle
2018-07-03 00:49:33 +02:00
function performBundle () {
let buildStream = bundler.bundle()
// handle errors
buildStream.on('error', (err) => {
beep()
if (opts.watch) {
console.warn(err.stack)
} else {
throw err
}
})
2017-08-09 02:46:31 +02:00
// process bundles
buildStream = buildStream
2017-01-10 22:56:31 +01:00
// convert bundle stream to gulp vinyl stream
2016-03-03 08:06:43 +01:00
.pipe(source(opts.filename))
2017-01-10 22:56:31 +01:00
// buffer file contents (?)
2016-03-03 08:06:43 +01:00
.pipe(buffer())
2018-03-29 05:41:56 +02:00
// Initialize Source Maps
if (opts.buildSourceMaps) {
buildStream = buildStream
// loads map from browserify file
.pipe(sourcemaps.init({ loadMaps: true }))
}
// Minification
if (opts.minifyBuild) {
buildStream = buildStream
.pipe(uglify({
mangle: {
reserved: [ 'MetamaskInpageProvider' ],
},
}))
}
// Finalize Source Maps
2018-03-29 05:41:56 +02:00
if (opts.buildSourceMaps) {
if (opts.devMode) {
// Use inline source maps for development due to Chrome DevTools bug
// https://bugs.chromium.org/p/chromium/issues/detail?id=931675
buildStream = buildStream
.pipe(sourcemaps.write())
} else {
buildStream = buildStream
.pipe(sourcemaps.write(opts.sourceMapDir))
}
2018-03-29 05:41:56 +02:00
}
// write completed bundles
opts.destinations.forEach((dest) => {
buildStream = buildStream.pipe(gulp.dest(dest))
})
return buildStream
2016-03-03 08:06:43 +01:00
}
}
2017-08-09 02:46:31 +02:00
function beep () {
process.stdout.write('\x07')
}