From 04d8de45f1adcb2f544a8512f74d462c0005fa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petter=20H=C3=A4ggholm?= Date: Mon, 17 Sep 2018 11:20:22 -0700 Subject: [PATCH 1/2] Create `metro-minify-terser` as copy of `metro-minify-uglify` with all instances of `uglify(-es)?` replaced with `terser`. --- packages/metro-minify-terser/.npmignore | 5 ++ .../__tests__/minify-test.js | 87 +++++++++++++++++++ packages/metro-minify-terser/package.json | 19 ++++ packages/metro-minify-terser/src/index.js | 19 ++++ packages/metro-minify-terser/src/minifier.js | 85 ++++++++++++++++++ .../metro-minify-terser/src/types.js.flow | 41 +++++++++ 6 files changed, 256 insertions(+) create mode 100644 packages/metro-minify-terser/.npmignore create mode 100644 packages/metro-minify-terser/__tests__/minify-test.js create mode 100644 packages/metro-minify-terser/package.json create mode 100644 packages/metro-minify-terser/src/index.js create mode 100644 packages/metro-minify-terser/src/minifier.js create mode 100644 packages/metro-minify-terser/src/types.js.flow diff --git a/packages/metro-minify-terser/.npmignore b/packages/metro-minify-terser/.npmignore new file mode 100644 index 0000000000..a0d4570202 --- /dev/null +++ b/packages/metro-minify-terser/.npmignore @@ -0,0 +1,5 @@ +**/__mocks__/** +**/__tests__/** +build +src.real +yarn.lock diff --git a/packages/metro-minify-terser/__tests__/minify-test.js b/packages/metro-minify-terser/__tests__/minify-test.js new file mode 100644 index 0000000000..444f764490 --- /dev/null +++ b/packages/metro-minify-terser/__tests__/minify-test.js @@ -0,0 +1,87 @@ +/** + * 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. + * + * @flow strict-local + * @format + * @emails oncall+js_foundation + */ +'use strict'; + +import type {BabelSourceMap} from '@babel/core'; + +jest.mock('terser', () => ({ + minify: jest.fn(code => { + return { + code: code.replace(/(^|\W)\s+/g, '$1'), + map: {}, + }; + }), +})); + +const minify = require('..'); +const {objectContaining} = jasmine; + +function getFakeMap(): BabelSourceMap { + return { + version: 3, + sources: ['?'], + mappings: '', + names: [], + }; +} + +describe('Minification:', () => { + const filename = '/arbitrary/file.js'; + const code = 'arbitrary(code)'; + let map: BabelSourceMap; + let terser; + + beforeEach(() => { + terser = require('terser'); + terser.minify.mockClear(); + terser.minify.mockReturnValue({code: '', map: '{}'}); + map = getFakeMap(); + }); + + it('passes file name, code, and source map to `terser`', () => { + minify.withSourceMap(code, map, filename); + expect(terser.minify).toBeCalledWith( + code, + objectContaining({ + sourceMap: { + content: map, + includeSources: false, + }, + }), + ); + }); + + it('passes code to `terser` when minifying without source map', () => { + minify.noSourceMap(code); + expect(terser.minify).toBeCalledWith( + code, + objectContaining({ + sourceMap: { + content: undefined, + includeSources: false, + }, + }), + ); + }); + + it('returns the code provided by terser', () => { + terser.minify.mockReturnValue({code, map: '{}'}); + const result = minify.withSourceMap('', getFakeMap(), ''); + expect(result.code).toBe(code); + expect(minify.noSourceMap('')).toBe(code); + }); + + it('parses the source map object provided by terser and sets the sources property', () => { + terser.minify.mockReturnValue({map: JSON.stringify(map), code: ''}); + const result = minify.withSourceMap('', getFakeMap(), filename); + expect(result.map).toEqual({...map, sources: [filename]}); + }); +}); diff --git a/packages/metro-minify-terser/package.json b/packages/metro-minify-terser/package.json new file mode 100644 index 0000000000..df9040502e --- /dev/null +++ b/packages/metro-minify-terser/package.json @@ -0,0 +1,19 @@ +{ + "name": "metro-minify-terser", + "version": "0.45.3", + "description": "🚇 Alternative minifier for Metro", + "main": "src/index.js", + "repository": { + "type": "git", + "url": "git@github.com:facebook/metro.git" + }, + "scripts": { + "prepare-release": "test -d build && rm -rf src.real && mv src src.real && mv build src", + "cleanup-release": "test ! -e build && mv src build && mv src.real src" + }, + "license": "MIT", + "dependencies": { + "terser": "^3.8.2" + }, + "devDependencies": {} +} diff --git a/packages/metro-minify-terser/src/index.js b/packages/metro-minify-terser/src/index.js new file mode 100644 index 0000000000..8b1dd7bcd7 --- /dev/null +++ b/packages/metro-minify-terser/src/index.js @@ -0,0 +1,19 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const minifier = require('./minifier'); + +export type {MetroMinifier} from './types.js.flow'; +export type {ResultWithMap} from './types.js.flow'; +export type {ResultWithoutMap} from './types.js.flow'; + +module.exports = minifier; diff --git a/packages/metro-minify-terser/src/minifier.js b/packages/metro-minify-terser/src/minifier.js new file mode 100644 index 0000000000..e7edbb09c7 --- /dev/null +++ b/packages/metro-minify-terser/src/minifier.js @@ -0,0 +1,85 @@ +/** + * 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. + * + * @flow strict-local + * @format + */ + +'use strict'; + +const terser = require('terser'); + +import type { + MetroMinifier, + ResultWithMap, + ResultWithoutMap, + MinifyOptions, +} from './types.js.flow'; +import type {BabelSourceMap} from '@babel/core'; + +function noSourceMap( + code: string, + options?: MinifyOptions = {}, +): ResultWithoutMap { + return minify(code, undefined, options).code; +} + +function withSourceMap( + code: string, + sourceMap: ?BabelSourceMap, + filename: string, + options?: MinifyOptions = {}, +): ResultWithMap { + const result = minify(code, sourceMap, options); + const map: BabelSourceMap = JSON.parse(result.map); + + map.sources = [filename]; + + return {code: result.code, map}; +} + +function minify( + inputCode: string, + inputMap: ?BabelSourceMap, + options: MinifyOptions, +) { + const result = terser.minify(inputCode, { + mangle: { + toplevel: false, + reserved: options.reserved, + }, + output: { + ascii_only: true, + quote_style: 3, + wrap_iife: true, + }, + sourceMap: { + content: inputMap, + includeSources: false, + }, + toplevel: false, + compress: { + // reduce_funcs inlines single-use function, which cause perf regressions. + reduce_funcs: false, + }, + }); + + if (result.error) { + throw result.error; + } + + return { + code: result.code, + map: result.map, + }; +} + +const metroMinifier: MetroMinifier = { + noSourceMap, + withSourceMap, +}; + +module.exports = metroMinifier; diff --git a/packages/metro-minify-terser/src/types.js.flow b/packages/metro-minify-terser/src/types.js.flow new file mode 100644 index 0000000000..8df3ac1aa8 --- /dev/null +++ b/packages/metro-minify-terser/src/types.js.flow @@ -0,0 +1,41 @@ +/** + * 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. + * + * @flow + * @format + */ + +'use strict'; + +import type {BabelSourceMap} from '@babel/core'; +import type {MinifyOptions} from 'metro/src/JSTransformer/worker'; + +export type {MinifyOptions}; + +export type ResultWithMap = { + code: string, + map: BabelSourceMap, + options?: MinifyOptions, +}; + +export type ResultWithoutMap = string; + +type MinifierWithSourceMap = ( + code: string, + inputMap?: ?BabelSourceMap, + filename: string, + options?: MinifyOptions, +) => ResultWithMap; + +type MinifierWithoutSourceMap = ( + code: string, + options?: MinifyOptions, +) => ResultWithoutMap; + +export type MetroMinifier = { + noSourceMap: MinifierWithoutSourceMap, + withSourceMap: MinifierWithSourceMap, +}; From a55c699afa2df5a11eacc19e648544ad81d6acb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petter=20H=C3=A4ggholm?= Date: Mon, 17 Sep 2018 11:54:38 -0700 Subject: [PATCH 2/2] As requested by @rafeca in https://github.com/facebook/metro/pull/243#issuecomment-421587544 -- (1) make `metro-uglify-terser` a (modified) copy of `-uglify`; (2) remove the `noSourceMap` function (and associated tests &c.); (3) move Flow types from minifier to core `metro` package. --- .../__tests__/minify-test.js | 20 ++------- packages/metro-minify-terser/package.json | 2 +- packages/metro-minify-terser/src/index.js | 4 -- packages/metro-minify-terser/src/minifier.js | 21 +++------- .../metro-minify-terser/src/types.js.flow | 41 ------------------- .../__tests__/minify-test.js | 20 ++------- packages/metro-minify-uglify/src/index.js | 4 -- packages/metro-minify-uglify/src/minifier.js | 21 +++------- .../metro-minify-uglify/src/types.js.flow | 41 ------------------- packages/metro/src/JSTransformer/worker.js | 2 +- .../worker/__tests__/worker-test.js | 8 ++-- packages/metro/src/lib/getMinifier.js | 2 +- packages/metro/src/shared/types.flow.js | 17 ++++++++ 13 files changed, 39 insertions(+), 164 deletions(-) delete mode 100644 packages/metro-minify-terser/src/types.js.flow delete mode 100644 packages/metro-minify-uglify/src/types.js.flow diff --git a/packages/metro-minify-terser/__tests__/minify-test.js b/packages/metro-minify-terser/__tests__/minify-test.js index 444f764490..f8a1b54dcd 100644 --- a/packages/metro-minify-terser/__tests__/minify-test.js +++ b/packages/metro-minify-terser/__tests__/minify-test.js @@ -47,7 +47,7 @@ describe('Minification:', () => { }); it('passes file name, code, and source map to `terser`', () => { - minify.withSourceMap(code, map, filename); + minify(code, map, filename); expect(terser.minify).toBeCalledWith( code, objectContaining({ @@ -59,29 +59,15 @@ describe('Minification:', () => { ); }); - it('passes code to `terser` when minifying without source map', () => { - minify.noSourceMap(code); - expect(terser.minify).toBeCalledWith( - code, - objectContaining({ - sourceMap: { - content: undefined, - includeSources: false, - }, - }), - ); - }); - it('returns the code provided by terser', () => { terser.minify.mockReturnValue({code, map: '{}'}); - const result = minify.withSourceMap('', getFakeMap(), ''); + const result = minify('', getFakeMap(), ''); expect(result.code).toBe(code); - expect(minify.noSourceMap('')).toBe(code); }); it('parses the source map object provided by terser and sets the sources property', () => { terser.minify.mockReturnValue({map: JSON.stringify(map), code: ''}); - const result = minify.withSourceMap('', getFakeMap(), filename); + const result = minify('', getFakeMap(), filename); expect(result.map).toEqual({...map, sources: [filename]}); }); }); diff --git a/packages/metro-minify-terser/package.json b/packages/metro-minify-terser/package.json index df9040502e..ebbb7da303 100644 --- a/packages/metro-minify-terser/package.json +++ b/packages/metro-minify-terser/package.json @@ -13,7 +13,7 @@ }, "license": "MIT", "dependencies": { - "terser": "^3.8.2" + "terser": "^3.1.9" }, "devDependencies": {} } diff --git a/packages/metro-minify-terser/src/index.js b/packages/metro-minify-terser/src/index.js index 8b1dd7bcd7..9e43fa13c9 100644 --- a/packages/metro-minify-terser/src/index.js +++ b/packages/metro-minify-terser/src/index.js @@ -12,8 +12,4 @@ const minifier = require('./minifier'); -export type {MetroMinifier} from './types.js.flow'; -export type {ResultWithMap} from './types.js.flow'; -export type {ResultWithoutMap} from './types.js.flow'; - module.exports = minifier; diff --git a/packages/metro-minify-terser/src/minifier.js b/packages/metro-minify-terser/src/minifier.js index e7edbb09c7..cd7e8d23ca 100644 --- a/packages/metro-minify-terser/src/minifier.js +++ b/packages/metro-minify-terser/src/minifier.js @@ -14,25 +14,17 @@ const terser = require('terser'); import type { MetroMinifier, - ResultWithMap, - ResultWithoutMap, + MetroMinifierResult, MinifyOptions, -} from './types.js.flow'; +} from 'metro/src/shared/types.flow.js'; import type {BabelSourceMap} from '@babel/core'; -function noSourceMap( - code: string, - options?: MinifyOptions = {}, -): ResultWithoutMap { - return minify(code, undefined, options).code; -} - -function withSourceMap( +function minifier( code: string, sourceMap: ?BabelSourceMap, filename: string, options?: MinifyOptions = {}, -): ResultWithMap { +): MetroMinifierResult { const result = minify(code, sourceMap, options); const map: BabelSourceMap = JSON.parse(result.map); @@ -77,9 +69,6 @@ function minify( }; } -const metroMinifier: MetroMinifier = { - noSourceMap, - withSourceMap, -}; +const metroMinifier: MetroMinifier = minifier; module.exports = metroMinifier; diff --git a/packages/metro-minify-terser/src/types.js.flow b/packages/metro-minify-terser/src/types.js.flow deleted file mode 100644 index 8df3ac1aa8..0000000000 --- a/packages/metro-minify-terser/src/types.js.flow +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -import type {BabelSourceMap} from '@babel/core'; -import type {MinifyOptions} from 'metro/src/JSTransformer/worker'; - -export type {MinifyOptions}; - -export type ResultWithMap = { - code: string, - map: BabelSourceMap, - options?: MinifyOptions, -}; - -export type ResultWithoutMap = string; - -type MinifierWithSourceMap = ( - code: string, - inputMap?: ?BabelSourceMap, - filename: string, - options?: MinifyOptions, -) => ResultWithMap; - -type MinifierWithoutSourceMap = ( - code: string, - options?: MinifyOptions, -) => ResultWithoutMap; - -export type MetroMinifier = { - noSourceMap: MinifierWithoutSourceMap, - withSourceMap: MinifierWithSourceMap, -}; diff --git a/packages/metro-minify-uglify/__tests__/minify-test.js b/packages/metro-minify-uglify/__tests__/minify-test.js index 811784057a..5259961b95 100644 --- a/packages/metro-minify-uglify/__tests__/minify-test.js +++ b/packages/metro-minify-uglify/__tests__/minify-test.js @@ -47,7 +47,7 @@ describe('Minification:', () => { }); it('passes file name, code, and source map to `uglify`', () => { - minify.withSourceMap(code, map, filename); + minify(code, map, filename); expect(uglify.minify).toBeCalledWith( code, objectContaining({ @@ -59,29 +59,15 @@ describe('Minification:', () => { ); }); - it('passes code to `uglify` when minifying without source map', () => { - minify.noSourceMap(code); - expect(uglify.minify).toBeCalledWith( - code, - objectContaining({ - sourceMap: { - content: undefined, - includeSources: false, - }, - }), - ); - }); - it('returns the code provided by uglify', () => { uglify.minify.mockReturnValue({code, map: '{}'}); - const result = minify.withSourceMap('', getFakeMap(), ''); + const result = minify('', getFakeMap(), ''); expect(result.code).toBe(code); - expect(minify.noSourceMap('')).toBe(code); }); it('parses the source map object provided by uglify and sets the sources property', () => { uglify.minify.mockReturnValue({map: JSON.stringify(map), code: ''}); - const result = minify.withSourceMap('', getFakeMap(), filename); + const result = minify('', getFakeMap(), filename); expect(result.map).toEqual({...map, sources: [filename]}); }); }); diff --git a/packages/metro-minify-uglify/src/index.js b/packages/metro-minify-uglify/src/index.js index 8b1dd7bcd7..9e43fa13c9 100644 --- a/packages/metro-minify-uglify/src/index.js +++ b/packages/metro-minify-uglify/src/index.js @@ -12,8 +12,4 @@ const minifier = require('./minifier'); -export type {MetroMinifier} from './types.js.flow'; -export type {ResultWithMap} from './types.js.flow'; -export type {ResultWithoutMap} from './types.js.flow'; - module.exports = minifier; diff --git a/packages/metro-minify-uglify/src/minifier.js b/packages/metro-minify-uglify/src/minifier.js index ce4f78c296..613ab68d16 100644 --- a/packages/metro-minify-uglify/src/minifier.js +++ b/packages/metro-minify-uglify/src/minifier.js @@ -14,25 +14,17 @@ const uglify = require('uglify-es'); import type { MetroMinifier, - ResultWithMap, - ResultWithoutMap, + MetroMinifierResult, MinifyOptions, -} from './types.js.flow'; +} from 'metro/src/shared/types.flow.js'; import type {BabelSourceMap} from '@babel/core'; -function noSourceMap( - code: string, - options?: MinifyOptions = {}, -): ResultWithoutMap { - return minify(code, undefined, options).code; -} - -function withSourceMap( +function minifier( code: string, sourceMap: ?BabelSourceMap, filename: string, options?: MinifyOptions = {}, -): ResultWithMap { +): MetroMinifierResult { const result = minify(code, sourceMap, options); const map: BabelSourceMap = JSON.parse(result.map); @@ -77,9 +69,6 @@ function minify( }; } -const metroMinifier: MetroMinifier = { - noSourceMap, - withSourceMap, -}; +const metroMinifier: MetroMinifier = minifier; module.exports = metroMinifier; diff --git a/packages/metro-minify-uglify/src/types.js.flow b/packages/metro-minify-uglify/src/types.js.flow deleted file mode 100644 index 8df3ac1aa8..0000000000 --- a/packages/metro-minify-uglify/src/types.js.flow +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -import type {BabelSourceMap} from '@babel/core'; -import type {MinifyOptions} from 'metro/src/JSTransformer/worker'; - -export type {MinifyOptions}; - -export type ResultWithMap = { - code: string, - map: BabelSourceMap, - options?: MinifyOptions, -}; - -export type ResultWithoutMap = string; - -type MinifierWithSourceMap = ( - code: string, - inputMap?: ?BabelSourceMap, - filename: string, - options?: MinifyOptions, -) => ResultWithMap; - -type MinifierWithoutSourceMap = ( - code: string, - options?: MinifyOptions, -) => ResultWithoutMap; - -export type MetroMinifier = { - noSourceMap: MinifierWithoutSourceMap, - withSourceMap: MinifierWithSourceMap, -}; diff --git a/packages/metro/src/JSTransformer/worker.js b/packages/metro/src/JSTransformer/worker.js index a2fd406402..80668e1168 100644 --- a/packages/metro/src/JSTransformer/worker.js +++ b/packages/metro/src/JSTransformer/worker.js @@ -323,7 +323,7 @@ async function minifyCode( const minify = getMinifier(minifierPath); try { - const minified = minify.withSourceMap(code, sourceMap, filename, options); + const minified = minify(code, sourceMap, filename, options); return { code: minified.code, diff --git a/packages/metro/src/JSTransformer/worker/__tests__/worker-test.js b/packages/metro/src/JSTransformer/worker/__tests__/worker-test.js index 591e53788a..4764b43f78 100644 --- a/packages/metro/src/JSTransformer/worker/__tests__/worker-test.js +++ b/packages/metro/src/JSTransformer/worker/__tests__/worker-test.js @@ -12,11 +12,9 @@ jest .mock('../constant-folding-plugin') .mock('../inline-plugin') - .mock('../../../lib/getMinifier', () => () => ({ - withSourceMap: (code, map) => ({ - code: code.replace('arbitrary(code)', 'minified(code)'), - map, - }), + .mock('../../../lib/getMinifier', () => () => (code, map) => ({ + code: code.replace('arbitrary(code)', 'minified(code)'), + map, })) .mock('metro-minify-uglify'); diff --git a/packages/metro/src/lib/getMinifier.js b/packages/metro/src/lib/getMinifier.js index 62b22510c5..2094e1aa3f 100644 --- a/packages/metro/src/lib/getMinifier.js +++ b/packages/metro/src/lib/getMinifier.js @@ -10,7 +10,7 @@ 'use strict'; -import type {MetroMinifier} from 'metro-minify-uglify'; +import type {MetroMinifier} from '../shared/types.flow.js'; function getMinifier(minifierPath: string): MetroMinifier { // Note: minifierPath should be an absolute path OR a module name here! diff --git a/packages/metro/src/shared/types.flow.js b/packages/metro/src/shared/types.flow.js index a354af9a52..eb573153a9 100644 --- a/packages/metro/src/shared/types.flow.js +++ b/packages/metro/src/shared/types.flow.js @@ -9,6 +9,8 @@ */ 'use strict'; +import type {BabelSourceMap} from '@babel/core'; +import type {MinifyOptions} from '../JSTransformer/worker'; import type {TransformResult} from '../DeltaBundler'; import type {CustomTransformOptions} from '../JSTransformer/worker'; import type {DynamicRequiresBehavior} from '../ModuleGraph/worker/collectDependencies'; @@ -160,3 +162,18 @@ export type RequestOptions = {| createModuleIdFactory?: () => (path: string) => number, onProgress?: (transformedFileCount: number, totalFileCount: number) => void, |}; + +export type {MinifyOptions}; + +export type MetroMinifierResult = { + code: string, + map: BabelSourceMap, + options?: MinifyOptions, +}; + +export type MetroMinifier = ( + code: string, + inputMap?: ?BabelSourceMap, + filename: string, + options?: MinifyOptions, +) => MetroMinifierResult;