From 5bc9daa125dd4153e76fc5b63014ef93418b9497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Wed, 10 Aug 2016 18:17:49 -0700 Subject: [PATCH] Build renderers into their individual npm packages (#7168) This copies modules into three separate packages instead of putting it all in React. The overlap in shared and between renderers gets duplicated. This allows the isomorphic package to stay minimal. It can also be used as a direct dependency without much risk. This also allow us to ship versions to each renderer independently and we can ship renderers without updating the main react package dependency. (cherry picked from commit 0f004efce2dcd0189a663f5724853eab261782e6) --- grunt/config/browserify.js | 27 ++- grunt/tasks/npm-react-addons.js | 21 +- grunt/tasks/npm-react-dom.js | 3 + grunt/tasks/npm-react-native.js | 3 + grunt/tasks/npm-react-test.js | 3 + grunt/tasks/npm-react.js | 2 +- gulpfile.js | 187 ++++++++++++++++-- package.json | 1 + packages/react-dom/index.js | 2 +- packages/react-dom/package.json | 14 +- packages/react-dom/server.js | 2 +- packages/react-linked-input/LinkedInput.js | 2 +- packages/react-native-renderer/index.js | 2 +- packages/react-native-renderer/package.json | 7 +- packages/react-test-renderer/index.js | 3 +- .../stack/event}/SyntheticEvent.js | 0 src/renderers/testing/ReactTestRenderer.js | 3 - 17 files changed, 238 insertions(+), 44 deletions(-) rename src/renderers/{dom/client/syntheticEvents => shared/stack/event}/SyntheticEvent.js (100%) diff --git a/grunt/config/browserify.js b/grunt/config/browserify.js index e7d2674b61..44d378d6e2 100644 --- a/grunt/config/browserify.js +++ b/grunt/config/browserify.js @@ -16,7 +16,7 @@ var envifyProd = envify({NODE_ENV: process.env.NODE_ENV || 'production'}); var SECRET_INTERNALS_NAME = 'React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED'; -var shimSharedModules = globalShim.configure({ +var shimSharedModulesFiles = { './ReactCurrentOwner': SECRET_INTERNALS_NAME + '.ReactCurrentOwner', './ReactComponentTreeHook': SECRET_INTERNALS_NAME + '.ReactComponentTreeHook', // The methods we used here are exposed on the main React export. @@ -26,7 +26,14 @@ var shimSharedModules = globalShim.configure({ './ReactElement': 'React', './ReactPropTypes': 'React.PropTypes', './ReactChildren': 'React.Children', -}); +}; + +// We can access these as absolute or relative. We need to shim both. +for (var key in shimSharedModulesFiles) { + shimSharedModulesFiles[key.replace(/^\.\//, 'react/lib/')] = shimSharedModulesFiles[key]; +} + +var shimSharedModules = globalShim.configure(shimSharedModulesFiles); var shimDOMModules = aliasify.configure({ 'aliases': { @@ -72,7 +79,7 @@ function simpleBannerify(src) { // Our basic config which we'll add to to make our other builds var basic = { entries: [ - './build/modules/ReactUMDEntry.js', + './build/node_modules/react/lib/ReactUMDEntry.js', ], outfile: './build/react.js', debug: false, @@ -85,7 +92,7 @@ var basic = { var min = { entries: [ - './build/modules/ReactUMDEntry.js', + './build/node_modules/react/lib/ReactUMDEntry.js', ], outfile: './build/react.min.js', debug: false, @@ -103,7 +110,7 @@ var min = { var addons = { entries: [ - './build/modules/ReactWithAddonsUMDEntry.js', + './build/node_modules/react/lib/ReactWithAddonsUMDEntry.js', ], outfile: './build/react-with-addons.js', debug: false, @@ -117,7 +124,7 @@ var addons = { var addonsMin = { entries: [ - './build/modules/ReactWithAddonsUMDEntry.js', + './build/node_modules/react/lib/ReactWithAddonsUMDEntry.js', ], outfile: './build/react-with-addons.min.js', debug: false, @@ -135,7 +142,7 @@ var addonsMin = { // The DOM Builds var dom = { entries: [ - './build/modules/ReactDOMUMDEntry.js', + './build/node_modules/react-dom/lib/ReactDOMUMDEntry.js', ], outfile: './build/react-dom.js', debug: false, @@ -149,7 +156,7 @@ var dom = { var domMin = { entries: [ - './build/modules/ReactDOMUMDEntry.js', + './build/node_modules/react-dom/lib/ReactDOMUMDEntry.js', ], outfile: './build/react-dom.min.js', debug: false, @@ -167,7 +174,7 @@ var domMin = { var domServer = { entries: [ - './build/modules/ReactDOMServerUMDEntry.js', + './build/node_modules/react-dom/lib/ReactDOMServerUMDEntry.js', ], outfile: './build/react-dom-server.js', debug: false, @@ -181,7 +188,7 @@ var domServer = { var domServerMin = { entries: [ - './build/modules/ReactDOMServerUMDEntry.js', + './build/node_modules/react-dom/lib/ReactDOMServerUMDEntry.js', ], outfile: './build/react-dom-server.min.js', debug: false, diff --git a/grunt/tasks/npm-react-addons.js b/grunt/tasks/npm-react-addons.js index 44d327fba3..c0818ff75f 100644 --- a/grunt/tasks/npm-react-addons.js +++ b/grunt/tasks/npm-react-addons.js @@ -6,47 +6,56 @@ var path = require('path'); var addons = { CSSTransitionGroup: { + peerDependency: 'react', module: 'ReactCSSTransitionGroup', name: 'css-transition-group', docs: 'animation', }, LinkedStateMixin: { + peerDependency: 'react', module: 'LinkedStateMixin', name: 'linked-state-mixin', docs: 'two-way-binding-helpers', }, Perf: { + peerDependency: 'react-dom', module: 'ReactPerf', name: 'perf', docs: 'perf', }, PureRenderMixin: { + peerDependency: 'react', module: 'ReactComponentWithPureRenderMixin', name: 'pure-render-mixin', docs: 'pure-render-mixin', }, TestUtils: { + peerDependency: 'react-dom', module: 'ReactTestUtils', name: 'test-utils', docs: 'test-utils', }, TransitionGroup: { + peerDependency: 'react', module: 'ReactTransitionGroup', name: 'transition-group', docs: 'animation', }, createFragment: { + peerDependency: 'react', module: 'ReactFragment', method: 'create', name: 'create-fragment', docs: 'create-fragment', }, shallowCompare: { + peerDependency: 'react', module: 'shallowCompare', name: 'shallow-compare', docs: 'shallow-compare', }, updates: { + peerDependency: 'react', module: 'update', name: 'update', docs: 'update', @@ -55,9 +64,11 @@ var addons = { function generateSource(info) { var pieces = [ - "module.exports = require('react/lib/", + 'module.exports = require(\'', + info.peerDependency, + '/lib/', info.module, - "')", + '\')', ]; if (info.method) { pieces.push('.', info.method); @@ -79,6 +90,12 @@ function buildReleases() { var pkgData = Object.assign({}, pkgTemplate); pkgData.name = pkgName; + var version = pkgTemplate.peerDependencies.react; + if (info.peerDependency !== 'react') { + pkgData.peerDependencies = {}; + pkgData.peerDependencies[info.peerDependency] = version; + } + grunt.file.mkdir(destDir); var link = info.docs ? info.docs : 'addons'; link = `https://facebook.github.io/react/docs/${link}.html`; diff --git a/grunt/tasks/npm-react-dom.js b/grunt/tasks/npm-react-dom.js index 9856f1b6da..859e95ac3d 100644 --- a/grunt/tasks/npm-react-dom.js +++ b/grunt/tasks/npm-react-dom.js @@ -5,6 +5,8 @@ var grunt = require('grunt'); var src = 'packages/react-dom/'; var dest = 'build/packages/react-dom/'; +var modSrc = 'build/node_modules/react-dom/lib'; +var lib = dest + 'lib/'; var dist = dest + 'dist/'; var distFiles = [ 'react-dom.js', @@ -21,6 +23,7 @@ function buildRelease() { // Copy to build/packages/react-dom var mappings = [].concat( grunt.file.expandMapping('**/*', dest, {cwd: src}), + grunt.file.expandMapping('**/*', lib, {cwd: modSrc}), grunt.file.expandMapping('{LICENSE,PATENTS}', dest) ); mappings.forEach(function(mapping) { diff --git a/grunt/tasks/npm-react-native.js b/grunt/tasks/npm-react-native.js index 7157e08f6b..4aa8b9ec69 100644 --- a/grunt/tasks/npm-react-native.js +++ b/grunt/tasks/npm-react-native.js @@ -5,6 +5,8 @@ var grunt = require('grunt'); var src = 'packages/react-native-renderer/'; var dest = 'build/packages/react-native-renderer/'; +var modSrc = 'build/node_modules/react-native/lib'; +var lib = dest + 'lib/'; function buildRelease() { if (grunt.file.exists(dest)) { @@ -14,6 +16,7 @@ function buildRelease() { // Copy to build/packages/react-native-renderer var mappings = [].concat( grunt.file.expandMapping('**/*', dest, {cwd: src}), + grunt.file.expandMapping('**/*', lib, {cwd: modSrc}), grunt.file.expandMapping('{LICENSE,PATENTS}', dest) ); mappings.forEach(function(mapping) { diff --git a/grunt/tasks/npm-react-test.js b/grunt/tasks/npm-react-test.js index 40181c8f12..a852cd6da5 100644 --- a/grunt/tasks/npm-react-test.js +++ b/grunt/tasks/npm-react-test.js @@ -5,6 +5,8 @@ var grunt = require('grunt'); var src = 'packages/react-test-renderer/'; var dest = 'build/packages/react-test-renderer/'; +var modSrc = 'build/node_modules/react-test-renderer/lib'; +var lib = dest + 'lib/'; function buildRelease() { if (grunt.file.exists(dest)) { @@ -14,6 +16,7 @@ function buildRelease() { // Copy to build/packages/react-native-renderer var mappings = [].concat( grunt.file.expandMapping('**/*', dest, {cwd: src}), + grunt.file.expandMapping('**/*', lib, {cwd: modSrc}), grunt.file.expandMapping('{LICENSE,PATENTS}', dest) ); mappings.forEach(function(mapping) { diff --git a/grunt/tasks/npm-react.js b/grunt/tasks/npm-react.js index 2e459ec93b..038d2fec51 100644 --- a/grunt/tasks/npm-react.js +++ b/grunt/tasks/npm-react.js @@ -5,7 +5,7 @@ var grunt = require('grunt'); var src = 'packages/react/'; var dest = 'build/packages/react/'; -var modSrc = 'build/modules/'; +var modSrc = 'build/node_modules/react/lib'; var lib = dest + 'lib/'; var dist = dest + 'dist/'; var distFiles = [ diff --git a/gulpfile.js b/gulpfile.js index a961e1657f..20b54944eb 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -13,6 +13,7 @@ var gulp = require('gulp'); var babel = require('gulp-babel'); var flatten = require('gulp-flatten'); var del = require('del'); +var merge = require('merge-stream'); var babelPluginModules = require('fbjs-scripts/babel-6/rewrite-modules'); var extractErrors = require('./scripts/error-codes/gulp-extract-errors'); @@ -28,22 +29,113 @@ function getTask(name) { var paths = { react: { src: [ - 'src/**/*.js', + 'src/umd/ReactUMDEntry.js', + 'src/umd/ReactWithAddonsUMDEntry.js', + 'src/umd/shims/ReactAddonsDOMDependenciesUMDShim.js', + + 'src/isomorphic/**/*.js', + 'src/addons/**/*.js', + + 'src/ReactVersion.js', + 'src/shared/**/*.js', + '!src/shared/vendor/**/*.js', '!src/**/__benchmarks__/**/*.js', '!src/**/__tests__/**/*.js', '!src/**/__mocks__/**/*.js', - '!src/renderers/art/**/*.js', - '!src/renderers/shared/fiber/**/*.js', - '!src/shared/vendor/**/*.js', ], - lib: 'build/modules', + lib: 'build/node_modules/react/lib', + }, + reactDOM: { + src: [ + 'src/umd/ReactDOMUMDEntry.js', + 'src/umd/ReactDOMServerUMDEntry.js', + + 'src/renderers/dom/**/*.js', + 'src/renderers/shared/**/*.js', + 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. + + 'src/ReactVersion.js', + 'src/shared/**/*.js', + '!src/shared/vendor/**/*.js', + '!src/**/__benchmarks__/**/*.js', + '!src/**/__tests__/**/*.js', + '!src/**/__mocks__/**/*.js', + ], + lib: 'build/node_modules/react-dom/lib', + }, + reactNative: { + src: [ + 'src/renderers/native/**/*.js', + 'src/renderers/shared/**/*.js', + + 'src/ReactVersion.js', + 'src/shared/**/*.js', + '!src/shared/vendor/**/*.js', + '!src/**/__benchmarks__/**/*.js', + '!src/**/__tests__/**/*.js', + '!src/**/__mocks__/**/*.js', + ], + lib: 'build/node_modules/react-native/lib', + }, + reactTestRenderer: { + src: [ + 'src/renderers/testing/**/*.js', + 'src/renderers/shared/**/*.js', + + 'src/ReactVersion.js', + 'src/shared/**/*.js', + '!src/shared/vendor/**/*.js', + '!src/**/__benchmarks__/**/*.js', + '!src/**/__tests__/**/*.js', + '!src/**/__mocks__/**/*.js', + ], + lib: 'build/node_modules/react-test-renderer/lib', }, }; -var moduleMap = Object.assign( +var moduleMapBase = Object.assign( {'object-assign': 'object-assign'}, - require('fbjs/module-map'), + require('fbjs/module-map') +); + +var moduleMapReact = Object.assign( { + // Addons needs to reach into DOM internals + ReactDOM: 'react-dom/lib/ReactDOM', + ReactInstanceMap: 'react-dom/lib/ReactInstanceMap', + ReactTestUtils: 'react-dom/lib/ReactTestUtils', + ReactPerf: 'react-dom/lib/ReactPerf', + getVendorPrefixedEventName: 'react-dom/lib/getVendorPrefixedEventName', + }, + moduleMapBase +); + +var rendererSharedState = { + // Shared state + ReactCurrentOwner: 'react/lib/ReactCurrentOwner', + ReactComponentTreeHook: 'react/lib/ReactComponentTreeHook', + + // TODO: Move to shared since these are actually shared and can be copied. + ReactPropTypeLocations: 'react/lib/ReactPropTypeLocations', + ReactPropTypesSecret: 'react/lib/ReactPropTypesSecret', + checkReactTypeSpec: 'react/lib/checkReactTypeSpec', + + // TODO: Update the source to just use the React module. + React: 'react/lib/React', + ReactElement: 'react/lib/ReactElement', + ReactPropTypes: 'react/lib/ReactPropTypes', + ReactChildren: 'react/lib/ReactChildren', +}; + +var moduleMapReactDOM = Object.assign( + {}, + rendererSharedState, + moduleMapBase +); + +var moduleMapReactNative = Object.assign( + { + // React Native Hooks deepDiffer: 'react-native/lib/deepDiffer', deepFreezeAndThrowOnMutationInDev: 'react-native/lib/deepFreezeAndThrowOnMutationInDev', flattenStyle: 'react-native/lib/flattenStyle', @@ -53,17 +145,46 @@ var moduleMap = Object.assign( UIManager: 'react-native/lib/UIManager', UIManagerStatTracker: 'react-native/lib/UIManagerStatTracker', View: 'react-native/lib/View', - } + }, + rendererSharedState, + moduleMapBase +); + +var moduleMapReactTestRenderer = Object.assign( + {}, + rendererSharedState, + moduleMapBase ); var errorCodeOpts = { errorMapFilePath: 'scripts/error-codes/codes.json', }; -var babelOpts = { +var babelOptsReact = { plugins: [ devExpressionWithCodes, // this pass has to run before `rewrite-modules` - [babelPluginModules, {map: moduleMap}], + [babelPluginModules, {map: moduleMapReact}], + ], +}; + +var babelOptsReactDOM = { + plugins: [ + devExpressionWithCodes, // this pass has to run before `rewrite-modules` + [babelPluginModules, {map: moduleMapReactDOM}], + ], +}; + +var babelOptsReactNative = { + plugins: [ + devExpressionWithCodes, // this pass has to run before `rewrite-modules` + [babelPluginModules, {map: moduleMapReactNative}], + ], +}; + +var babelOptsReactTestRenderer = { + plugins: [ + devExpressionWithCodes, // this pass has to run before `rewrite-modules` + [babelPluginModules, {map: moduleMapReactTestRenderer}], ], }; @@ -76,21 +197,49 @@ gulp.task('flow', getTask('flow')); gulp.task('version-check', getTask('version-check')); gulp.task('react:clean', function() { - return del([paths.react.lib]); + return del([ + paths.react.lib, + paths.reactDOM.lib, + paths.reactNative.lib, + paths.reactTestRenderer.lib, + ]); }); gulp.task('react:modules', function() { - return gulp - .src(paths.react.src) - .pipe(babel(babelOpts)) - .pipe(flatten()) - .pipe(gulp.dest(paths.react.lib)); + return merge( + gulp + .src(paths.react.src) + .pipe(babel(babelOptsReact)) + .pipe(flatten()) + .pipe(gulp.dest(paths.react.lib)), + + gulp + .src(paths.reactDOM.src) + .pipe(babel(babelOptsReactDOM)) + .pipe(flatten()) + .pipe(gulp.dest(paths.reactDOM.lib)), + + gulp + .src(paths.reactNative.src) + .pipe(babel(babelOptsReactNative)) + .pipe(flatten()) + .pipe(gulp.dest(paths.reactNative.lib)), + + gulp + .src(paths.reactTestRenderer.src) + .pipe(babel(babelOptsReactTestRenderer)) + .pipe(flatten()) + .pipe(gulp.dest(paths.reactTestRenderer.lib)) + ); }); gulp.task('react:extract-errors', function() { - return gulp - .src(paths.react.src) - .pipe(extractErrors(errorCodeOpts)); + return merge( + gulp.src(paths.react.src).pipe(extractErrors(errorCodeOpts)), + gulp.src(paths.reactDOM.src).pipe(extractErrors(errorCodeOpts)), + gulp.src(paths.reactNative.src).pipe(extractErrors(errorCodeOpts)), + gulp.src(paths.reactTestRenderer.src).pipe(extractErrors(errorCodeOpts)) + ); }); gulp.task('default', ['react:modules']); diff --git a/package.json b/package.json index 6ee654a5e5..36904a17fc 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "gzip-js": "~0.3.2", "jest": "^12.1.1", "loose-envify": "^1.1.0", + "merge-stream": "^1.0.0", "object-assign": "^4.1.0", "platform": "^1.1.0", "run-sequence": "^1.1.4", diff --git a/packages/react-dom/index.js b/packages/react-dom/index.js index 0b5deb28f4..2bf9417936 100644 --- a/packages/react-dom/index.js +++ b/packages/react-dom/index.js @@ -1,3 +1,3 @@ 'use strict'; -module.exports = require('react/lib/ReactDOM'); +module.exports = require('./lib/ReactDOM'); diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index ed9c62cb65..46f98e8724 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -12,7 +12,11 @@ "url": "https://github.com/facebook/react/issues" }, "homepage": "https://facebook.github.io/react/", - "dependencies": {}, + "dependencies": { + "fbjs": "^0.8.1", + "loose-envify": "^1.1.0", + "object-assign": "^4.1.0" + }, "peerDependencies": { "react": "^15.3.2" }, @@ -21,7 +25,13 @@ "PATENTS", "README.md", "dist/", + "lib/", "index.js", "server.js" - ] + ], + "browserify": { + "transform": [ + "loose-envify" + ] + } } diff --git a/packages/react-dom/server.js b/packages/react-dom/server.js index e56ecb3277..559676109d 100644 --- a/packages/react-dom/server.js +++ b/packages/react-dom/server.js @@ -1,3 +1,3 @@ 'use strict'; -module.exports = require('react/lib/ReactDOMServer'); +module.exports = require('./lib/ReactDOMServer'); diff --git a/packages/react-linked-input/LinkedInput.js b/packages/react-linked-input/LinkedInput.js index d40df684a8..394056e039 100644 --- a/packages/react-linked-input/LinkedInput.js +++ b/packages/react-linked-input/LinkedInput.js @@ -10,7 +10,7 @@ 'use strict'; var React = require('react'); -var LinkedValueUtils = require('react/lib/LinkedValueUtils'); +var LinkedValueUtils = require('react-dom/lib/LinkedValueUtils'); class LinkedInput extends React.Component { render() { diff --git a/packages/react-native-renderer/index.js b/packages/react-native-renderer/index.js index 6f35853e7c..e9fbbc91a5 100644 --- a/packages/react-native-renderer/index.js +++ b/packages/react-native-renderer/index.js @@ -1,3 +1,3 @@ 'use strict'; -module.exports = require('react/lib/ReactNative'); +module.exports = require('./lib/ReactNative'); diff --git a/packages/react-native-renderer/package.json b/packages/react-native-renderer/package.json index fe08a9f528..6783cd218a 100644 --- a/packages/react-native-renderer/package.json +++ b/packages/react-native-renderer/package.json @@ -14,12 +14,17 @@ }, "homepage": "https://facebook.github.io/react-native/", "dependencies": { + "fbjs": "^0.8.1", + "object-assign": "^4.1.0" + }, + "peerDependencies": { "react": "^15.3.2" }, "files": [ "LICENSE", "PATENTS", "README.md", - "index.js" + "index.js", + "lib/" ] } diff --git a/packages/react-test-renderer/index.js b/packages/react-test-renderer/index.js index 936c374724..3436e5bb3a 100644 --- a/packages/react-test-renderer/index.js +++ b/packages/react-test-renderer/index.js @@ -1,4 +1,3 @@ - 'use strict'; -module.exports = require('react/lib/ReactTestRenderer'); +module.exports = require('./lib/ReactTestRenderer'); diff --git a/src/renderers/dom/client/syntheticEvents/SyntheticEvent.js b/src/renderers/shared/stack/event/SyntheticEvent.js similarity index 100% rename from src/renderers/dom/client/syntheticEvents/SyntheticEvent.js rename to src/renderers/shared/stack/event/SyntheticEvent.js diff --git a/src/renderers/testing/ReactTestRenderer.js b/src/renderers/testing/ReactTestRenderer.js index b2d1846462..50716e55f1 100644 --- a/src/renderers/testing/ReactTestRenderer.js +++ b/src/renderers/testing/ReactTestRenderer.js @@ -20,8 +20,6 @@ var ReactTestMount = require('ReactTestMount'); var ReactTestReconcileTransaction = require('ReactTestReconcileTransaction'); var ReactUpdates = require('ReactUpdates'); -var renderSubtreeIntoContainer = require('renderSubtreeIntoContainer'); - /** * Drill down (through composites and empty components) until we get a native or * native text component. @@ -141,7 +139,6 @@ var ReactTestRenderer = { /* eslint-disable camelcase */ unstable_batchedUpdates: ReactUpdates.batchedUpdates, - unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer, /* eslint-enable camelcase */ };