Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d364d85

Browse files
authored
Set up experimental builds (facebook#17071)
* Don't bother including `unstable_` in error The method names don't get stripped out of the production bundles because they are passed as arguments to the error decoder. Let's just always use the unprefixed APIs in the messages. * Set up experimental builds The experimental builds are packaged exactly like builds in the stable release channel: same file structure, entry points, and npm package names. The goal is to match what will eventually be released in stable as closely as possible, but with additional features turned on. Versioning and Releasing ------------------------ The experimental builds will be published to the same registry and package names as the stable ones. However, they will be versioned using a separate scheme. Instead of semver versions, experimental releases will receive arbitrary version strings based on their content hashes. The motivation is to thwart attempts to use a version range to match against future experimental releases. The only way to install or depend on an experimental release is to refer to the specific version number. Building -------- I did not use the existing feature flag infra to configure the experimental builds. The reason is because feature flags are designed to configure a single package. They're not designed to generate multiple forks of the same package; for each set of feature flags, you must create a separate package configuration. Instead, I've added a new build dimension called the **release channel**. By default, builds use the **stable** channel. There's also an **experimental** release channel. We have the option to add more in the future. There are now two dimensions per artifact: build type (production, development, or profiling), and release channel (stable or experimental). These are separate dimensions because they are combinatorial: there are stable and experimental production builds, stable and experimental developmenet builds, and so on. You can add something to an experimental build by gating on `__EXPERIMENTAL__`, similar to how we use `__DEV__`. Anything inside these branches will be excluded from the stable builds. This gives us a low effort way to add experimental behavior in any package without setting up feature flags or configuring a new package.
1 parent d5b54d0 commit d364d85

File tree

11 files changed

+126
-47
lines changed

11 files changed

+126
-47
lines changed

.circleci/config.yml

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,32 @@ aliases:
2222
- &attach_workspace
2323
at: build
2424

25+
- &process_artifacts
26+
docker: *docker
27+
environment: *environment
28+
steps:
29+
- checkout
30+
- attach_workspace: *attach_workspace
31+
- *restore_yarn_cache
32+
- *run_yarn
33+
- run: node ./scripts/rollup/consolidateBundleSizes.js
34+
- run: ./scripts/circleci/upload_build.sh
35+
- run: ./scripts/circleci/pack_and_store_artifact.sh
36+
- store_artifacts:
37+
path: ./node_modules.tgz
38+
- store_artifacts:
39+
path: ./build.tgz
40+
- store_artifacts:
41+
path: ./build/bundle-sizes.json
42+
- store_artifacts:
43+
# TODO: Update release script to use local file instead of pulling
44+
# from artifacts.
45+
path: ./scripts/error-codes/codes.json
46+
- persist_to_workspace:
47+
root: build
48+
paths:
49+
- bundle-sizes.json
50+
2551
jobs:
2652
setup:
2753
docker: *docker
@@ -74,6 +100,18 @@ jobs:
74100
- *run_yarn
75101
- run: yarn test --maxWorkers=2
76102

103+
test_source_experimental:
104+
docker: *docker
105+
environment: *environment
106+
steps:
107+
- checkout
108+
- *restore_yarn_cache
109+
- *run_yarn
110+
- run:
111+
environment:
112+
RELEASE_CHANNEL: experimental
113+
command: yarn test --maxWorkers=2
114+
77115
test_source_persistent:
78116
docker: *docker
79117
environment: *environment
@@ -105,37 +143,60 @@ jobs:
105143
- run: ./scripts/circleci/add_build_info_json.sh
106144
- run: ./scripts/circleci/update_package_versions.sh
107145
- run: yarn build
146+
- run: echo "stable" >> build/RELEASE_CHANNEL
147+
- persist_to_workspace:
148+
root: build
149+
paths:
150+
- RELEASE_CHANNEL
151+
- facebook-www
152+
- node_modules
153+
- react-native
154+
- dist
155+
- sizes/*.json
156+
157+
build_experimental:
158+
docker: *docker
159+
environment: *environment
160+
parallelism: 20
161+
steps:
162+
- checkout
163+
- *restore_yarn_cache
164+
- *run_yarn
165+
- run:
166+
environment:
167+
RELEASE_CHANNEL: experimental
168+
command: |
169+
./scripts/circleci/add_build_info_json.sh
170+
./scripts/circleci/update_package_versions.sh
171+
yarn build
172+
- run: echo "experimental" >> build/RELEASE_CHANNEL
108173
- persist_to_workspace:
109174
root: build
110175
paths:
176+
- RELEASE_CHANNEL
111177
- facebook-www
112178
- node_modules
113179
- react-native
114180
- dist
115181
- sizes/*.json
116182

117-
process_artifacts:
183+
# These jobs are named differently so we can distinguish the stable and
184+
# and experimental artifacts
185+
process_artifacts: *process_artifacts
186+
process_artifacts_experimental: *process_artifacts
187+
188+
sizebot:
118189
docker: *docker
119190
environment: *environment
120191
steps:
121192
- checkout
122193
- attach_workspace: *attach_workspace
123194
- *restore_yarn_cache
124195
- *run_yarn
196+
# This runs in the process_artifacts job, too, but it's faster to run
197+
# this step in both jobs instead of running the jobs sequentially
125198
- run: node ./scripts/rollup/consolidateBundleSizes.js
126199
- run: node ./scripts/tasks/danger
127-
- run: ./scripts/circleci/upload_build.sh
128-
- run: ./scripts/circleci/pack_and_store_artifact.sh
129-
- store_artifacts:
130-
path: ./node_modules.tgz
131-
- store_artifacts:
132-
path: ./build.tgz
133-
- store_artifacts:
134-
path: ./build/bundle-sizes.json
135-
- store_artifacts:
136-
# TODO: Update release script to use local file instead of pulling
137-
# from artifacts.
138-
path: ./scripts/error-codes/codes.json
139200

140201
lint_build:
141202
docker: *docker
@@ -208,7 +269,7 @@ jobs:
208269

209270
workflows:
210271
version: 2
211-
commit:
272+
stable:
212273
jobs:
213274
- setup
214275
- lint:
@@ -232,6 +293,9 @@ workflows:
232293
- process_artifacts:
233294
requires:
234295
- build
296+
- sizebot:
297+
requires:
298+
- build
235299
- lint_build:
236300
requires:
237301
- build
@@ -247,9 +311,24 @@ workflows:
247311
- test_dom_fixtures:
248312
requires:
249313
- build
250-
hourly:
314+
315+
experimental:
316+
jobs:
317+
- setup
318+
- test_source_experimental:
319+
requires:
320+
- setup
321+
- build_experimental:
322+
requires:
323+
- setup
324+
- process_artifacts_experimental:
325+
requires:
326+
- build_experimental
327+
328+
fuzz_tests:
251329
triggers:
252330
- schedule:
331+
# Fuzz tests run hourly
253332
cron: "0 * * * *"
254333
filters:
255334
branches:

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ module.exports = {
149149
spyOnProd: true,
150150
__PROFILE__: true,
151151
__UMD__: true,
152+
__EXPERIMENTAL__: true,
152153
trustedTypes: true,
153154
},
154155
};

packages/react-dom/src/__tests__/ReactDOMRoot-test.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ describe('ReactDOMRoot', () => {
103103
it('throws a good message on invalid containers', () => {
104104
expect(() => {
105105
ReactDOM.unstable_createRoot(<div>Hi</div>);
106-
}).toThrow(
107-
'unstable_createRoot(...): Target container is not a DOM element.',
108-
);
106+
}).toThrow('createRoot(...): Target container is not a DOM element.');
109107
});
110108

111109
it('warns when rendering with legacy API into createRoot() container', () => {
@@ -119,7 +117,7 @@ describe('ReactDOMRoot', () => {
119117
[
120118
// We care about this warning:
121119
'You are calling ReactDOM.render() on a container that was previously ' +
122-
'passed to ReactDOM.unstable_createRoot(). This is not supported. ' +
120+
'passed to ReactDOM.createRoot(). This is not supported. ' +
123121
'Did you mean to call root.render(element)?',
124122
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
125123
'Replacing React-rendered children with a new root component.',
@@ -142,7 +140,7 @@ describe('ReactDOMRoot', () => {
142140
[
143141
// We care about this warning:
144142
'You are calling ReactDOM.hydrate() on a container that was previously ' +
145-
'passed to ReactDOM.unstable_createRoot(). This is not supported. ' +
143+
'passed to ReactDOM.createRoot(). This is not supported. ' +
146144
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
147145
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
148146
'Replacing React-rendered children with a new root component.',
@@ -163,7 +161,7 @@ describe('ReactDOMRoot', () => {
163161
[
164162
// We care about this warning:
165163
'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
166-
'passed to ReactDOM.unstable_createRoot(). This is not supported. Did you mean to call root.unmount()?',
164+
'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?',
167165
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
168166
"The node you're attempting to unmount was rendered by React and is not a top-level container.",
169167
],
@@ -202,7 +200,7 @@ describe('ReactDOMRoot', () => {
202200
expect(() => {
203201
ReactDOM.unstable_createRoot(container);
204202
}).toWarnDev(
205-
'You are calling ReactDOM.unstable_createRoot() on a container that was previously ' +
203+
'You are calling ReactDOM.createRoot() on a container that was previously ' +
206204
'passed to ReactDOM.render(). This is not supported.',
207205
{withoutStack: true},
208206
);

packages/react-dom/src/client/ReactDOM.js

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -451,9 +451,8 @@ const ReactDOM: Object = {
451451
warningWithoutStack(
452452
!container._reactHasBeenPassedToCreateRootDEV,
453453
'You are calling ReactDOM.hydrate() on a container that was previously ' +
454-
'passed to ReactDOM.%s(). This is not supported. ' +
454+
'passed to ReactDOM.createRoot(). This is not supported. ' +
455455
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
456-
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
457456
);
458457
}
459458
// TODO: throw or warn if we couldn't hydrate?
@@ -479,9 +478,8 @@ const ReactDOM: Object = {
479478
warningWithoutStack(
480479
!container._reactHasBeenPassedToCreateRootDEV,
481480
'You are calling ReactDOM.render() on a container that was previously ' +
482-
'passed to ReactDOM.%s(). This is not supported. ' +
481+
'passed to ReactDOM.createRoot(). This is not supported. ' +
483482
'Did you mean to call root.render(element)?',
484-
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
485483
);
486484
}
487485
return legacyRenderSubtreeIntoContainer(
@@ -526,8 +524,7 @@ const ReactDOM: Object = {
526524
warningWithoutStack(
527525
!container._reactHasBeenPassedToCreateRootDEV,
528526
'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
529-
'passed to ReactDOM.%s(). This is not supported. Did you mean to call root.unmount()?',
530-
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
527+
'passed to ReactDOM.createRoot(). This is not supported. Did you mean to call root.unmount()?',
531528
);
532529
}
533530

@@ -650,13 +647,9 @@ function createRoot(
650647
container: DOMContainer,
651648
options?: RootOptions,
652649
): _ReactRoot {
653-
const functionName = enableStableConcurrentModeAPIs
654-
? 'createRoot'
655-
: 'unstable_createRoot';
656650
invariant(
657651
isValidContainer(container),
658-
'%s(...): Target container is not a DOM element.',
659-
functionName,
652+
'createRoot(...): Target container is not a DOM element.',
660653
);
661654
warnIfReactDOMContainerInDEV(container);
662655
return new ReactRoot(container, options);
@@ -666,13 +659,9 @@ function createSyncRoot(
666659
container: DOMContainer,
667660
options?: RootOptions,
668661
): _ReactRoot {
669-
const functionName = enableStableConcurrentModeAPIs
670-
? 'createRoot'
671-
: 'unstable_createRoot';
672662
invariant(
673663
isValidContainer(container),
674-
'%s(...): Target container is not a DOM element.',
675-
functionName,
664+
'createRoot(...): Target container is not a DOM element.',
676665
);
677666
warnIfReactDOMContainerInDEV(container);
678667
return new ReactSyncRoot(container, BatchedRoot, options);
@@ -682,9 +671,8 @@ function warnIfReactDOMContainerInDEV(container) {
682671
if (__DEV__) {
683672
warningWithoutStack(
684673
!container._reactRootContainer,
685-
'You are calling ReactDOM.%s() on a container that was previously ' +
674+
'You are calling ReactDOM.createRoot() on a container that was previously ' +
686675
'passed to ReactDOM.render(). This is not supported.',
687-
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
688676
);
689677
container._reactHasBeenPassedToCreateRootDEV = true;
690678
}

packages/shared/ReactFeatureFlags.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export const disableInputAttributeSyncing = false;
5252

5353
// These APIs will no longer be "unstable" in the upcoming 16.7 release,
5454
// Control this behavior with a flag to support 16.6 minor releases in the meanwhile.
55-
export const enableStableConcurrentModeAPIs = false;
55+
export const enableStableConcurrentModeAPIs = __EXPERIMENTAL__;
5656

5757
export const warnAboutShorthandPropertyCollision = false;
5858

scripts/error-codes/codes.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@
297297
"296": "Log of yielded values is not empty. Call expect(ReactTestRenderer).unstable_toHaveYielded(...) first.",
298298
"297": "The matcher `unstable_toHaveYielded` expects an instance of React Test Renderer.\n\nTry: expect(ReactTestRenderer).unstable_toHaveYielded(expectedYields)",
299299
"298": "Hooks can only be called inside the body of a function component.",
300-
"299": "%s(...): Target container is not a DOM element.",
300+
"299": "createRoot(...): Target container is not a DOM element.",
301301
"300": "Rendered fewer hooks than expected. This may be caused by an accidental early return statement.",
302302
"301": "Too many re-renders. React limits the number of renders to prevent an infinite loop.",
303303
"302": "It is not supported to run the profiling version of a renderer (for example, `react-dom/profiling`) without also replacing the `scheduler/tracing` module with `scheduler/tracing-profiling`. Your bundler might have a setting for aliasing both modules. Learn more at http://fb.me/react-profiling",

scripts/eslint-rules/__tests__/warning-and-invariant-args-test.internal.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ ruleTester.run('eslint-rules/warning-and-invariant-args', rule, {
2020
'arbitraryFunction(a, b)',
2121
// These messages are in the error code map
2222
"invariant(false, 'Do not override existing functions.')",
23-
"invariant(false, '%s(...): Target container is not a DOM element.', str)",
23+
"invariant(false, 'createRoot(...): Target container is not a DOM element.')",
2424
],
2525
invalid: [
2626
{

scripts/flow/environment.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
declare var __PROFILE__: boolean;
1313
declare var __UMD__: boolean;
14+
declare var __EXPERIMENTAL__: boolean;
1415

1516
declare var __REACT_DEVTOOLS_GLOBAL_HOOK__: any; /*?{
1617
inject: ?((stuff: Object) => void)

scripts/jest/setupEnvironment.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ if (NODE_ENV !== 'development' && NODE_ENV !== 'production') {
77
global.__DEV__ = NODE_ENV === 'development';
88
global.__PROFILE__ = NODE_ENV === 'development';
99
global.__UMD__ = false;
10+
global.__EXPERIMENTAL__ = process.env.RELEASE_CHANNEL === 'experimental';
1011

1112
if (typeof window !== 'undefined') {
1213
global.requestIdleCallback = function(callback) {

scripts/release/utils.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,12 +78,16 @@ const getArtifactsList = async buildID => {
7878
const getBuildInfo = async () => {
7979
const cwd = join(__dirname, '..', '..');
8080

81+
const isExperimental = process.env.RELEASE_CHANNEL === 'experimental';
82+
8183
const branch = await execRead('git branch | grep \\* | cut -d " " -f2', {
8284
cwd,
8385
});
8486
const commit = await execRead('git show -s --format=%h', {cwd});
8587
const checksum = await getChecksumForCurrentRevision(cwd);
86-
const version = `0.0.0-${commit}`;
88+
const version = isExperimental
89+
? `0.0.0-experimental-${commit}`
90+
: `0.0.0-${commit}`;
8791

8892
// Only available for Circle CI builds.
8993
// https://circleci.com/docs/2.0/env-vars/
@@ -94,7 +98,9 @@ const getBuildInfo = async () => {
9498
const packageJSON = await readJson(
9599
join(cwd, 'packages', 'react', 'package.json')
96100
);
97-
const reactVersion = `${packageJSON.version}-canary-${commit}`;
101+
const reactVersion = isExperimental
102+
? `${packageJSON.version}-experimental-canary-${commit}`
103+
: `${packageJSON.version}-canary-${commit}`;
98104

99105
return {branch, buildNumber, checksum, commit, reactVersion, version};
100106
};

0 commit comments

Comments
 (0)