From 50f2d5e6e9db3e6e3e72a66cdb7514ef16d7d4f9 Mon Sep 17 00:00:00 2001 From: Jared Palmer Date: Thu, 22 Aug 2019 16:41:06 -0400 Subject: [PATCH] Add ability to extend babel and rollup --- .gitignore | 3 +- package.json | 3 + src/babelPluginTsdx.ts | 153 ++++++++++++++++++++++++++++++++++++++ src/createRollupConfig.ts | 81 ++++++-------------- src/env.d.ts | 3 + src/index.ts | 71 ++++++++++++++---- src/types.ts | 11 +++ yarn.lock | 21 ++++++ 8 files changed, 271 insertions(+), 75 deletions(-) create mode 100644 src/babelPluginTsdx.ts create mode 100644 src/types.ts diff --git a/.gitignore b/.gitignore index 18d3cde4a..0d64a873d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules .rts2_cache_cjs .rts2_cache_esm .rts2_cache_umd -dist \ No newline at end of file +dist +tester \ No newline at end of file diff --git a/package.json b/package.json index 6572c31e0..54b8c3579 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@babel/core": "^7.4.4", "@babel/helper-module-imports": "^7.0.0", "@babel/plugin-proposal-class-properties": "^7.4.4", + "@babel/plugin-transform-regenerator": "^7.4.5", "@babel/polyfill": "^7.4.4", "@babel/preset-env": "^7.4.4", "@typescript-eslint/eslint-plugin": "^1.13.0", @@ -43,6 +44,7 @@ "asyncro": "^3.0.0", "babel-plugin-annotate-pure-calls": "^0.4.0", "babel-plugin-dev-expression": "^0.2.1", + "babel-plugin-macros": "^2.6.1", "babel-plugin-transform-async-to-promises": "^0.8.11", "babel-plugin-transform-rename-import": "^2.3.0", "babel-traverse": "^6.26.0", @@ -65,6 +67,7 @@ "jest": "^24.8.0", "jest-watch-typeahead": "^0.3.1", "jpjs": "^1.2.1", + "lodash.merge": "^4.6.2", "mkdirp": "^0.5.1", "ora": "^3.4.0", "pascal-case": "^2.0.1", diff --git a/src/babelPluginTsdx.ts b/src/babelPluginTsdx.ts new file mode 100644 index 000000000..25e0f61c0 --- /dev/null +++ b/src/babelPluginTsdx.ts @@ -0,0 +1,153 @@ +import { createConfigItem } from '@babel/core'; +import babelPlugin from 'rollup-plugin-babel'; +import merge from 'lodash.merge'; + +export const isTruthy = (obj?: any) => { + if (!obj) { + return false; + } + + return obj.constructor !== Object || Object.keys(obj).length > 0; +}; + +const replacements = [{ original: 'lodash', replacement: 'lodash-es' }]; + +export const mergeConfigItems = (type: any, ...configItemsToMerge: any[]) => { + const mergedItems: any[] = []; + + configItemsToMerge.forEach(configItemToMerge => { + configItemToMerge.forEach((item: any) => { + const itemToMergeWithIndex = mergedItems.findIndex( + mergedItem => mergedItem.file.resolved === item.file.resolved + ); + + if (itemToMergeWithIndex === -1) { + mergedItems.push(item); + return; + } + + mergedItems[itemToMergeWithIndex] = createConfigItem( + [ + mergedItems[itemToMergeWithIndex].file.resolved, + merge(mergedItems[itemToMergeWithIndex].options, item.options), + ], + { + type, + } + ); + }); + }); + + return mergedItems; +}; + +export const createConfigItems = (type: any, items: any[]) => { + return items.map(({ name, ...options }) => { + return createConfigItem([require.resolve(name), options], { type }); + }); +}; + +export const babelPluginTsdx = babelPlugin.custom((babelCore: any) => ({ + // Passed the plugin options. + options({ custom: customOptions, ...pluginOptions }: any) { + return { + // Pull out any custom options that the plugin might have. + customOptions, + + // Pass the options back with the two custom options removed. + pluginOptions, + }; + }, + config(config: any, { customOptions }: any) { + const defaultPlugins = createConfigItems( + 'plugin', + [ + // { + // name: '@babel/plugin-transform-react-jsx', + // pragma: customOptions.jsx || 'h', + // pragmaFrag: customOptions.jsxFragment || 'Fragment', + // }, + { name: 'babel-plugin-annotate-pure-calls' }, + { name: 'babel-plugin-dev-expression' }, + { + name: 'babel-plugin-transform-rename-import', + replacements, + }, + isTruthy(customOptions.defines) && { + name: 'babel-plugin-transform-replace-expressions', + replace: customOptions.defines, + }, + { + name: 'babel-plugin-transform-async-to-promises', + inlineHelpers: true, + externalHelpers: true, + }, + { + name: '@babel/plugin-proposal-class-properties', + loose: true, + }, + { + name: '@babel/plugin-transform-regenerator', + async: false, + }, + { + name: 'babel-plugin-macros', + }, + isTruthy(customOptions.extractErrors) && { + name: './errors/transformErrorMessages', + }, + ].filter(Boolean) + ); + + const babelOptions = config.options || {}; + + const envIdx = (babelOptions.presets || []).findIndex((preset: any) => + preset.file.request.includes('@babel/preset-env') + ); + + if (envIdx !== -1) { + const preset = babelOptions.presets[envIdx]; + babelOptions.presets[envIdx] = createConfigItem( + [ + preset.file.resolved, + merge( + { + loose: true, + targets: customOptions.targets, + }, + preset.options, + { + modules: false, + exclude: merge( + ['transform-async-to-generator', 'transform-regenerator'], + preset.options.exclude || [] + ), + } + ), + ], + { + type: `preset`, + } + ); + } else { + babelOptions.presets = createConfigItems('preset', [ + { + name: '@babel/preset-env', + targets: customOptions.targets, + modules: false, + loose: true, + exclude: ['transform-async-to-generator', 'transform-regenerator'], + }, + ]); + } + + // Merge babelrc & our plugins together + babelOptions.plugins = mergeConfigItems( + 'plugin', + defaultPlugins, + babelOptions.plugins || [] + ); + + return babelOptions; + }, +})); diff --git a/src/createRollupConfig.ts b/src/createRollupConfig.ts index f7fe58ff5..816682e0c 100644 --- a/src/createRollupConfig.ts +++ b/src/createRollupConfig.ts @@ -1,8 +1,8 @@ -import { DEFAULT_EXTENSIONS } from '@babel/core'; import { safeVariableName, safePackageName, external } from './utils'; import { paths } from './constants'; import { terser } from 'rollup-plugin-terser'; -import babel from 'rollup-plugin-babel'; +import { DEFAULT_EXTENSIONS } from '@babel/core'; +// import babel from 'rollup-plugin-babel'; import commonjs from 'rollup-plugin-commonjs'; import json from 'rollup-plugin-json'; import replace from 'rollup-plugin-replace'; @@ -10,64 +10,17 @@ import resolve from 'rollup-plugin-node-resolve'; import sourceMaps from 'rollup-plugin-sourcemaps'; import typescript from 'rollup-plugin-typescript2'; import { extractErrors } from './errors/extractErrors'; - -const replacements = [{ original: 'lodash', replacement: 'lodash-es' }]; +import { babelPluginTsdx } from './babelPluginTsdx'; +import { TsdxOptions } from './types'; const errorCodeOpts = { - errorMapFilePath: paths.appRoot + '/errors/codes.json', + errorMapFilePath: paths.appErrorsJson, }; -interface TsdxOptions { - input: string; - name: string; - target: 'node' | 'browser'; - env: 'development' | 'production'; - tsconfig?: string; - extractErrors?: string; - minify?: boolean; -} - -const babelOptions = (format: 'cjs' | 'esm' | 'umd', opts: TsdxOptions) => ({ - exclude: 'node_modules/**', - extensions: [...DEFAULT_EXTENSIONS, 'ts', 'tsx'], - passPerPreset: true, // @see https://babeljs.io/docs/en/options#passperpreset - presets: [ - [ - require.resolve('@babel/preset-env'), - { - loose: true, - modules: false, - targets: opts.target === 'node' ? { node: '8' } : undefined, - exclude: ['transform-async-to-generator'], - }, - ], - ], - plugins: [ - require.resolve('babel-plugin-annotate-pure-calls'), - require.resolve('babel-plugin-dev-expression'), - format !== 'cjs' && [ - require.resolve('babel-plugin-transform-rename-import'), - { replacements }, - ], - [ - require.resolve('babel-plugin-transform-async-to-promises'), - { inlineHelpers: true, externalHelpers: true }, - ], - [ - require.resolve('@babel/plugin-proposal-class-properties'), - { loose: true }, - ], - opts.extractErrors && require('./errors/transformErrorMessages'), - ].filter(Boolean), -}); - // shebang cache map thing because the transform only gets run once let shebang: any = {}; -export function createRollupConfig( - format: 'cjs' | 'umd' | 'esm', - opts: TsdxOptions -) { +export function createRollupConfig(opts: TsdxOptions) { const findAndRecordErrorCodes = extractErrors({ ...errorCodeOpts, ...opts, @@ -78,7 +31,7 @@ export function createRollupConfig( const outputName = [ `${paths.appDist}/${safePackageName(opts.name)}`, - format, + opts.format, opts.env, shouldMinify ? 'min' : '', 'js', @@ -101,7 +54,7 @@ export function createRollupConfig( // Set filenames of the consumer's package file: outputName, // Pass through the file format - format, + format: opts.format, // Do not let Rollup call Object.freeze() on namespace import objects // (i.e. import * as namespaceImportObject from...) that are accessed dynamically. freeze: false, @@ -146,7 +99,7 @@ export function createRollupConfig( opts.target !== 'node' ? 'browser' : undefined, ].filter(Boolean) as string[], }), - format === 'umd' && + opts.format === 'umd' && commonjs({ // use a regex to make sure to include eventual hoisted packages include: /\/node_modules\//, @@ -174,7 +127,7 @@ export function createRollupConfig( }, typescript({ typescript: require('typescript'), - cacheRoot: `./.rts2_cache_${format}`, + cacheRoot: `./.rts2_cache_${opts.format}`, tsconfig: opts.tsconfig, tsconfigDefaults: { compilerOptions: { @@ -189,7 +142,17 @@ export function createRollupConfig( }, }, }), - babel(babelOptions(format, opts)), + babelPluginTsdx({ + exclude: 'node_modules/**', + extensions: [...DEFAULT_EXTENSIONS, 'ts', 'tsx'], + passPerPreset: true, + custom: { + targets: opts.target === 'node' ? { node: '8' } : undefined, + extractErrors: opts.extractErrors, + format: opts.format, + // defines: opts.defines, + }, + }), opts.env !== undefined && replace({ 'process.env.NODE_ENV': JSON.stringify(opts.env), @@ -208,7 +171,7 @@ export function createRollupConfig( passes: 10, }, ecma: 5, - toplevel: format === 'cjs', + toplevel: opts.format === 'cjs', warnings: true, }), ], diff --git a/src/env.d.ts b/src/env.d.ts index af155ada4..2ee987ed1 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -11,6 +11,7 @@ declare module 'eslint-config-react-app'; // @see line 226 of https://unpkg.com/@babel/core@7.4.4/lib/index.js declare module '@babel/core' { export const DEFAULT_EXTENSIONS: string[]; + export function createConfigItem(boop: any[], options: any) {} } // Rollup plugins @@ -22,3 +23,5 @@ declare module 'camelcase'; declare module 'babel-traverse'; declare module 'babylon'; declare module '@babel/helper-module-imports'; + +declare module 'lodash.merge'; diff --git a/src/index.ts b/src/index.ts index e134f5c9e..be7c2284e 100755 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ import { concatAllArray } from 'jpjs'; import getInstallCmd from './getInstallCmd'; import getInstallArgs from './getInstallArgs'; import { Input, Select } from 'enquirer'; +import { TsdxOptions } from './types'; const pkg = require('../package.json'); const createLogger = require('progress-estimator'); // All configuration keys are optional, but it's recommended to specify a storage location. @@ -47,10 +48,22 @@ let appPackageJson: { jest?: any; eslint?: any; }; + try { appPackageJson = fs.readJSONSync(resolveApp('package.json')); } catch (e) {} +// check for custom tsdx.config.js +let tsdxConfig = { + rollup(config: any, _options: any) { + return config; + }, +}; + +if (fs.existsSync(paths.appConfig)) { + tsdxConfig = require(paths.appConfig); +} + export const isDir = (name: string) => fs .stat(name) @@ -88,23 +101,51 @@ async function getInputs(entries: string[], source?: string) { return concatAllArray(inputs); } + function createBuildConfigs( opts: any ): Array { return concatAllArray( - opts.input.map((input: string) => [ - opts.format.includes('cjs') && - createRollupConfig('cjs', { env: 'development', ...opts, input }), - opts.format.includes('cjs') && - createRollupConfig('cjs', { env: 'production', ...opts, input }), - opts.format.includes('esm') && - createRollupConfig('esm', { ...opts, input }), - opts.format.includes('umd') && - createRollupConfig('umd', { env: 'development', ...opts, input }), - opts.format.includes('umd') && - createRollupConfig('umd', { env: 'production', ...opts, input }), - ]) - ).filter(Boolean); + opts.input.map((input: string) => + [ + opts.format.includes('cjs') && { + ...opts, + format: 'cjs', + env: 'development', + input, + }, + opts.format.includes('cjs') && { + ...opts, + format: 'cjs', + env: 'production', + input, + }, + opts.format.includes('esm') && { ...opts, format: 'esm', input }, + opts.format.includes('umd') && { + ...opts, + format: 'umd', + env: 'development', + input, + }, + opts.format.includes('umd') && { + ...opts, + format: 'umd', + env: 'production', + input, + }, + ] + .filter(Boolean) + .map((options: TsdxOptions, index: number) => ({ + ...options, + // We want to know if this is the first run for each entryfile + // for certain plugins (e.g. css) + writeMeta: index === 0, + })) + ) + ).map((options: TsdxOptions) => + // pass the full rollup config to tsdx.config.js override + tsdxConfig.rollup(createRollupConfig(options), options) + ); } async function moveTypes() { @@ -285,7 +326,7 @@ prog .example('watch --tsconfig ./tsconfig.foo.json') .option( '--extractErrors', - 'Extract errors to codes.json and provide a url for decoding.' + 'Extract errors to ./errors/codes.json and provide a url for decoding.' ) .example( 'build --extractErrors=https://reactjs.org/docs/error-decoder.html?invariant=' @@ -349,7 +390,7 @@ prog .example('build --tsconfig ./tsconfig.foo.json') .option( '--extractErrors', - 'Extract errors to codes.json and provide a url for decoding.' + 'Extract errors to ./errors/codes.json and provide a url for decoding.' ) .example( 'build --extractErrors=https://reactjs.org/docs/error-decoder.html?invariant=' diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..7b8de0c30 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,11 @@ +export interface TsdxOptions { + input: string; + name: string; + target: 'node' | 'browser'; + format: 'cjs' | 'umd' | 'esm'; + env: 'development' | 'production'; + tsconfig?: string; + extractErrors?: string; + minify?: boolean; + writeMeta?: boolean; +} diff --git a/yarn.lock b/yarn.lock index 63753fcca..046f823b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -641,6 +641,13 @@ js-levenshtein "^1.1.3" semver "^5.5.0" +"@babel/runtime@^7.4.2": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" + integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/runtime@^7.4.5": version "7.5.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.4.tgz#cb7d1ad7c6d65676e66b47186577930465b5271b" @@ -1663,6 +1670,15 @@ babel-plugin-jest-hoist@^24.6.0: dependencies: "@types/babel__traverse" "^7.0.6" +babel-plugin-macros@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.6.1.tgz#41f7ead616fc36f6a93180e89697f69f51671181" + integrity sha512-6W2nwiXme6j1n2erPOnmRiWfObUhWH7Qw1LMi9XZy8cj+KtESu3T6asZvtk5bMQQjX8te35o7CFueiSdL/2NmQ== + dependencies: + "@babel/runtime" "^7.4.2" + cosmiconfig "^5.2.0" + resolve "^1.10.0" + babel-plugin-transform-async-to-promises@^0.8.11: version "0.8.12" resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-promises/-/babel-plugin-transform-async-to-promises-0.8.12.tgz#281917387606f2f925eb6e9e368703cb6c436337" @@ -4763,6 +4779,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"