Make the seven example projects run, and check the lessons' code - #185
Merged
alpersonalwebsite merged 10 commits intoAug 24, 2026
Conversation
… env globs
Three hygiene problems, and the middle one is the interesting one.
JEST SNAPSHOTS WERE IGNORED. `**/__snapshots__` was in the ignore file while three tests call
`toMatchSnapshot()`. A snapshot test with no committed snapshot does not verify anything, and what it does
instead depends on where it runs. Measured on this repository's own `Intro.test.js`:
jest's CI detection OFF -> "1 snapshot written", test PASSES
jest's CI detection ON -> "New snapshot was not written", test FAILS
Neither outcome is a check. The passing one is worse, because `toMatchSnapshot()` reads like an assertion
while quietly recording whatever it was handed, so the suite is green and the property is untested. The rule
is gone, with a comment in the file recording why, since a deleted line leaves no trace of itself.
BUILD OUTPUT WAS COMMITTED, and the ignore file already disagreed with the tree about two of the files:
`examples/basic-webpack[default]/dist/{bundle.js,index.html}` were tracked while `dist/` was ignored, which
works only because ignore rules do not apply to files already in the index.
Fourteen files untracked in total: those two, plus nine bundles under
`examples/react-redux-webpack-client-server/public/` (including the `.gz` and `.br` copies that
express-static-gzip serves), two under `examples/react-redux-webpack-client/public/`, and one
content-hashed CSS file. All of them are webpack output, and the production build's own clean step names
`public/` as a directory to wipe before rebuilding, so committing them means anyone who runs the documented
build gets a spurious diff.
`public/` cannot be ignored wholesale: it also holds template.html, manifest.json, favicon.ico and an image,
which are sources. So the new rules name the generated shapes instead, and both directions are verified with
`git check-ignore` -- the fourteen artifacts ignored, those four sources still committable.
THE ENV GLOBS WERE THE NARROW FORM. `.env` plus `.env.*`, and the dot is not cosmetic: `.env.*` requires a
SECOND dot, so it never matched .envrc, which direnv reads and people export credentials from, and nothing
matched the suffix form at all. Now `.env*` plus `*.env`, keeping the `!.env.example` negation this file
already had, which is more than the sibling repositories started with.
Also added, none of which was covered: secrets/, credentials*, *.pem, *.key, the SSH private key names in
their default extensionless forms, both sqlite spellings, *.db, __pycache__/ and the editor directories.
Verified in both directions, 21 paths that must be ignored and 4 that must stay committable, using
`git check-ignore -q`'s exit status rather than its verbose output, and every tracked file re-checked
afterwards to confirm no new pattern shadows a file already in the repository.
…e, and name the packages THREE OF THE SIX EXAMPLES COULD NOT BE INSTALLED AT ALL on a current npm: npm error ERESOLVE unable to resolve dependency tree npm error Found: @babel/core@7.6.0 npm error Could not resolve dependency: npm error peer @babel/core@">=7.0.0-beta.50 <7.0.0-rc.0" from @babel/preset-es2015@7.0.0-beta.53 `@babel/preset-es2015@7.0.0-beta.53` is a Babel 7 BETA package, and it peer-requires a beta `@babel/core`, while the project declares `@babel/core ^7.2.2`. That dependency set has been internally inconsistent since it was written. npm 6 did not enforce peer ranges and installed it anyway; npm 7 onward refuses, so the examples went from installable to not, without anything in the repository changing. THE FIX IS A DELETION, NOT AN UPGRADE. Both `@babel/preset-es2015` and `@babel/preset-stage-2` are declared in three package.json files and referenced NOWHERE. Every `.babelrc` in this repository lists only: "presets": ["@babel/preset-env", "@babel/preset-react"] "plugins": ["@babel/plugin-proposal-class-properties"] Which makes sense: `@babel/preset-stage-2` was removed from Babel 7 before release, and preset-es2015 never had a stable 7.x at all. They are leftovers from a Babel 6 to 7 migration. Removing two entries nothing imports leaves every declared version in the repository untouched, and both npm 6 and npm 11 can then resolve the tree. Verified by installing all three from a clean copy: react-redux-webpack-client 1198 packages react-redux-webpack-client-server 1416 packages react-redux-webpack-client-server-scripts 1458 packages I considered an `.npmrc` with `legacy-peer-deps=true`, which is what the sibling repositories in this series used, and it does work here (1250 packages). It is the wrong answer for this case: it tells npm to accept a resolution it has correctly identified as broken, and it would keep installing a beta preset that nothing uses. `legacy-peer-deps` is for when the conflicting dependency is actually needed. ALSO, PACKAGE NAMES. Three different projects were all named `nocra` and two were both named `BasicReactMapWithState`, one of which is the lifecycle example, not the state one. Each now carries its own directory name, lowercased and without the brackets that are not legal in an npm name, and `"private": true`, since none of these is meant to be published.
…un against the declared plugin
`npm run build` failed in all three redux examples with:
TypeError: CleanWebpackPlugin is not a constructor
The configs use clean-webpack-plugin the v1/v2 way while package.json declares `^3.0.0`, and v3 changed three
things at once:
1. it is a NAMED export, so `require('clean-webpack-plugin')` is an object, not the class,
2. it takes ONE options object instead of `(paths, options)`,
3. it cleans relative to `output.path`, and it dropped the `root` and `exclude` options.
Nothing was upgraded to fix this: v3 is already what the package.json asks for, and the config was written
for an older one. The last commit to touch the committed bundles is from 2019-04, so the build has been broken
since whenever that bump happened, and nothing ran it afterwards to notice.
THE THIRD CHANGE IS THE ONE WORTH READING TWICE, because translating this mechanically produces a config that
runs and cleans nothing. Keeping `root` and `exclude` and renaming the first argument gets you a green build
whose clean step is a silent no-op: v3 ignores both keys, and the old pattern list ('dist/', 'build/',
'public/') then resolves relative to output.path, which IS public/, so `public/` inside public/ matches
nothing. I measured that on the way past: the build succeeded and a planted stale bundle survived it.
So the patterns are rewritten relative to output.path and the exclusions become negations. Verified per
example by planting a `public/stale-bundle.js` before each build:
react-redux-webpack-client stale removed, template/manifest/favicon kept, 2 bundles
react-redux-webpack-client-server stale removed, template/manifest/favicon kept, 9 bundles
react-redux-webpack-client-server-scripts stale removed, template kept, 9 bundles
`images` is protected in the new pattern list and was NOT in the old `exclude`, so a working v1/v2 build
would have deleted `public/images/rPI-400x400.jpg`. That the image is still in the repository is evidence the
clean step has not run successfully in a long time.
The `path` require is gone from two of the configs, where it existed only for the `root` option that v3 no
longer has.
SEPARATELY, ONE EXAMPLE'S BUILD SCRIPT NAMED A FILE THAT DOES NOT EXIST.
react-redux-webpack-client-server-scripts had:
"build": "cross-env NODE_ENV=production webpack --config config/webpack.config.prod.js"
and that directory contains `webpack.config.prod.client.js` and `webpack.config.prod.server.js`, with no
`webpack.config.prod.js`. So the script failed with `Cannot find module` before webpack started. Found by
checking every `--config` path in every example's scripts against the filesystem, which is a check worth
running on any repository with more than one build entry point: it is one line and it caught a script that
could never have worked.
Note for anyone building these: webpack 4 asks OpenSSL for md4, which OpenSSL 3 removed from the default
provider, so on Node 17 and later a build fails with `ERR_OSSL_EVP_UNSUPPORTED` before any of the above
matters. `NODE_OPTIONS=--openssl-legacy-provider` is the era-appropriate answer and the next commit documents
it per example. Measured: `crypto.createHash('md4')` throws on this node and succeeds under that flag.
… actually assert something
`npm test` failed in all three, for five separate reasons, and the snapshot story turned out to have three
layers.
1. THE APP TEST RENDERED A CONNECTED COMPONENT WITH NO STORE.
Invariant Violation: Could not find "store" in the context of "Connect(App)"
All three had the create-react-app default test, which renders `<App />` on its own. That is correct until
the day you wrap App in `connect()`, and then it is a test that cannot pass. Each one now builds a real
store from the example's own `rootReducer` and renders inside a `<Provider>`. A real store rather than a
mock on purpose: the reducers run, so a reducer that throws on its initial action fails this test too.
2. THE TEST SCRIPT WAS MALFORMED, and the malformation was a half-finished fix for problem 3.
jest --env=jsdom --watchAll --colors --config=config/jest/jest.config.json --rootDir
`--watchAll` never exits, so `npm test` hung, and `--rootDir` has no value. Now a plain run, with the watch
mode moved to `test:watch` where it belongs.
3. `<rootDir>` POINTED AT THE CONFIG DIRECTORY. Passing `--config=config/jest/jest.config.json` makes jest
treat that FILE'S directory as the root, so `<rootDir>/node_modules/babel-jest` resolved to
`config/jest/node_modules/babel-jest`:
Validation Error: Module <rootDir>/node_modules/babel-jest in the transform option was not found.
That is what the dangling `--rootDir` was reaching for. Fixed in the config itself, `"rootDir": "../.."`,
so it holds however jest is invoked.
4. JEST PARSED A JPEG AS JAVASCRIPT. `App.js` imports `./images/rPI-400x400.jpg` and the mapper covered only
`.css`, giving `SyntaxError: Invalid or unexpected token` from inside the image. Mapped to a small stub that
exports a string, which is what file-loader would have produced.
Not to `identity-obj-proxy`, which is already a dependency and looks like the obvious answer. That is the
right mapper for CSS MODULES, where the import is an object and each property should return its own key.
An image import is used as a VALUE, `<img src={rPI} />`, so a Proxy reaches React as a DOM attribute and
you get `TypeError: symbol is not a function` from setValueForProperty, which is a memorable thing to debug
backwards from an image import. I tried it that way first.
The stub is added only to the two examples that import an image. The third does not, and a mapper for
something a project never does is noise.
5. A SCRATCH FILE WAS COLLECTED AS A TEST SUITE. `server/test.js` was three lines that call webpack and
`console.log(1)`, named test.js, so jest ran it and reported "Your test suite must contain at least one
test". Nothing referenced it and `npm run build:server` does the same job properly. Deleted, and
`testPathIgnorePatterns` now excludes `server/`.
THE SNAPSHOTS, WHICH HAD THREE LAYERS. The previous commit stopped ignoring `**/__snapshots__`. Committing
what jest then produced would have been worse than useless:
exports[`<Intro /> matches the previous Snapshot 1`] = `ReactWrapper {}`;
An enzyme wrapper does not serialise itself. `enzyme-to-json` was in devDependencies in all three examples and
configured in none, so `toMatchSnapshot()` recorded an empty object and would have matched an empty object
forever. Three layers of the same defect: the snapshot was not committed, so nothing was compared; when
compared, it asserted nothing; and the package that fixes that was installed and unused.
With `snapshotSerializers: ["enzyme-to-json/serializer"]` the snapshot is the rendered tree, and it is now
poison-tested rather than assumed. Changing `Hello` to `Hola` in Intro.js:
- Snapshot
+ Received
- Hello
+ Hola
Tests: 1 failed, 2 passed
All three examples: 3 tests passed, 1 snapshot passed.
…t found
Two earlier passes went through these 22 files by hand for correctness. Nothing has ever checked that the code
in them is syntactically valid, which is the floor below which a reader cannot copy anything successfully.
WHAT THE CHECKER FOUND, all real:
* `03_local-state.md` had a class with NO CLOSING BRACE. Eleven lines of `class App extends Component {`
through the end of `render()`, then the fence. Paste it and nothing works.
* `07_conditional-rendering.md` had the opposite defect TWICE: an extra `}` after the render method, on two
consecutive examples.
* `10_unit-tests.md` had two test names that close their own string:
it('updates the value of `friend` state's property', () => {
it('adds the new friend to `friends` state's property', () => {
The apostrophe in `state's` terminates the single-quoted string, so the rest of the line is a syntax error.
Both are now double-quoted. This is the finding I would keep from the whole commit: it is in the lesson about
writing tests, it is invisible to a reader skimming, and it fails the moment anyone runs it.
* Six blocks were OUTPUT wearing a `javascript` tag: two browser console dumps, three `wrapper.debug()`
renderings full of `[(Function: onChange)]`, and one `<script src=…>` pair that is HTML. One `json` block was
a JavaScript object property (`plugins: generalPlugins`).
* `11_webpack.md` linked to
`./react-redux-webpack-client-server-scripts/config/webpack.config.prod.server.js`, missing the `examples/`
prefix that its four sibling links on the same page all have.
THE SNIPPET CHECKER IS SHAPED AROUND FRAGMENTS, and that shape is the design decision worth explaining.
Teaching notes show a lifecycle method without its class, JSX without its component, three lines of a
package.json without the braces. On the first run, 64 of 74 "failures" were exactly that. Demanding an
annotation on each would have buried the real defects under 64 markers, and a checker nobody can read is a
checker nobody runs.
So a snippet passes if it parses in ANY plausible context: alone, in a class body, in a function body, as an
expression, as JSX, or as a piece of an object or array literal. JSON passes alone or wrapped in braces or
brackets, with a trailing comma tolerated, since a fragment cut from a longer list keeps one. A failure means
the text cannot be valid anywhere, which is a syntax error rather than an artefact of how much was quoted.
That is a weaker check than "every snippet is a complete program", and it is the strongest one this content
admits. It still caught everything above.
Four blocks are genuinely unparseable in any context and carry `// check: skip <reason>`: two halves of a
before-and-after pair that pick up mid-expression, an anonymous function shown as a returned value, and an
`else` branch whose `if` is described in the prose. The reason is REQUIRED and its absence is itself a failure,
so a skip has to be argued for where a reader can see it.
Tags are matched case-insensitively, which is load-bearing rather than tidy: these files use `javascript` 278
times, `javaScript` 13 times and `JavaScript` once. A case-sensitive match would skip 14 blocks and exit 0.
ONE LIMITATION, stated rather than hidden: because JSX is one of the shapes tried, HTML in a `javascript`
fence now parses. The two `<script>` blocks are retagged here because I know about them, and a future one
would not be caught. Accepting that is the price of not flagging every JSX fragment.
THE LINK CHECKER is split by timescale: relative links, images and anchors offline on every push, external
URLs on a schedule, because link rot is a function of elapsed time and this repository went from 2020 to 2026
almost untouched. HEAD first then GET on any unsuccessful status, not only on 405, per the measured
counter-example from a sibling repository where a live site answers 500 to HEAD.
It also had a bug of its own, and it reported a live link as dead: CommonMark allows a `(<...>)` destination,
which is how you write a URL containing parentheses, and this repository has one pointing at the Wikipedia
article on stacks. Reading only the bare form truncated it at the first `)`.
`npm test`: 283 javascript blocks parsed, 22 json blocks parsed, 4 skipped, no failures. All 26 relative
links, 28 images and 21 distinct external URLs resolve. `@babel/parser` is pinned at 7.9.6, which is the
release current when this repository was last substantially updated.
…ointed at
Six of the seven example projects had a README.md of ZERO BYTES, and the seventh had none at all. In a public
teaching repository with 37 forks, that is the file a reader opens first when they clone one of these
directories.
Each one now says what the project demonstrates, which lesson it belongs to and links back to it, and exactly
what to run. They are short on purpose: the lesson is the explanation, and this is the card that tells you
which project you are standing in.
FOUR OF THE SEVEN WERE UNREACHABLE. Only the three webpack projects were mentioned anywhere, all from
11_webpack.md. `basic-react-example-state`, `basic-react-example-lifecycle` and `basic-webpack[default]` were
mentioned in no lesson at all, and `basic-react-example[map-with-key]` only as inline code rather than a link.
The root README now has a table of all seven.
A note on that last one, because it cost me a false result: my first check for which examples were referenced
used `grep -l "examples/$d"`, and two of these directories have square brackets in their names, which grep
reads as a character class. So `basic-react-example[map-with-key]` matched nothing and I nearly recorded it as
orphaned when 02_1_props.md does mention it. `grep -F` is the fix. The bracketed names also need percent
encoding to work as markdown link targets, which is why the table has `%5B` and `%5D` in two rows.
THE ONE FLAG IS DOCUMENTED WHERE IT IS NEEDED rather than in a footnote. webpack 4 hashes modules with md4,
OpenSSL 3 removed md4 from its default provider, and so on Node 17 and later every webpack build here stops
before it starts:
Error: error:0308010C:digital envelope routines::unsupported
code: 'ERR_OSSL_EVP_UNSUPPORTED'
`NODE_OPTIONS=--openssl-legacy-provider` is the era-appropriate answer, and the measurement is in the READMEs
that need it: `crypto.createHash('md4')` throws on Node 24 without the flag and succeeds with it. Upgrading
webpack would also fix it and would stop these being notes about the toolchain they were written for.
The three create-react-app projects need no flag, which is worth stating too: react-scripts 3.0.1 from 2019
installs and runs its tests on a current Node, and that is unusual enough to be worth a sentence.
The root README also gains a Checking the notes section, since `npm test` is new here.
Three jobs, split by what each can go wrong from.
`notes` parses every snippet and resolves every relative link. Offline, seconds, every push.
`examples` is the job that matters. Every defect fixed in this branch was in the example projects, and none of
them was visible from the markdown: three of the seven could not be installed at all on a current npm, three
could not build, and three had failing tests. A matrix installs, tests and builds each one, with
`fail-fast: false` so the result says WHICH examples work rather than stopping at the first that does not.
`external-links` is monthly and manual only. An outbound link rots on a clock rather than on commits, and this
repository went from 2020 to 2026 almost untouched, so a push-triggered check would have looked on the handful
of days it was edited and never in between.
`NODE_OPTIONS=--openssl-legacy-provider` is set for the example jobs, because webpack 4 hashes modules with md4
and OpenSSL 3 removed md4 from its default provider. Without it every webpack build here fails before it
starts. `npm install` rather than `npm ci`, because every example carries a yarn.lock and no
package-lock.json.
`CI: true` is set deliberately. Jest refuses to write a snapshot in CI, so that setting is what makes the
suite prove the committed snapshots are real: measured, the redux examples report `Snapshots: 1 passed` under
`CI=true`, where before this branch they would have reported `New snapshot was not written` and failed.
THE WEBPACK EXAMPLE'S TEST SCRIPT WAS THE NPM PLACEHOLDER, `echo "Error: no test specified" && exit 1`, so
`npm test` there was guaranteed to fail and the CI matrix would have gone red on a project with nothing wrong
with it. It now builds and asserts the bundle exists:
npm run build && node -e "assert.ok(fs.existsSync('dist/bundle.js'), 'webpack did not produce dist/bundle.js')"
Which is a small thing worth doing properly rather than writing `echo ok`. A test that cannot fail is the
defect this branch spent three commits on elsewhere. Verified both ways: it passes after a build, and deleting
the bundle gives `AssertionError: webpack did not produce dist/bundle.js`.
It asserts `dist/bundle.js` and not `dist/main.js`, which is what I wrote first. This example is named
"basic-webpack[default]" and I assumed the default output name; it has a config that renames the bundle. The
assertion failed, which is how I found out, and is the argument for writing the assertion before believing the
description.
…finding their tests
`examples/basic-react-example[map-with-key]` contains `src/App.test.js`, and that test had NEVER RUN. Found by
running each example from a clean clone exactly as CI would, which is the only reason it turned up: the file
exists, the script looks right, and `npm test` reports success-shaped output on its way to doing nothing.
What jest actually says, with the path interpolated into its own glob:
No tests found, exiting with code 1
6 files checked.
testMatch: .../examples/basic-react-example/[map-with-key/]/src/**/*.{spec,test}.{js,jsx,ts,tsx} - 0 matches
Read the testMatch line. `[map-with-key]` is a CHARACTER CLASS to the glob engine, so the pattern became
`basic-react-example/[map-with-key/]/src/...`, a path that cannot exist, and the answer was zero matches
rather than an error. Six files checked, none of them the test file sitting in src/.
So both bracketed directories are renamed:
examples/basic-react-example[map-with-key] -> examples/basic-react-example-map-with-key
examples/basic-webpack[default] -> examples/basic-webpack-default
and the references in 02_1_props.md, the root README and the CI matrix follow. After the rename, that example
reports `PASS src/App.test.js`.
THE BRACKETS BIT ME TWICE MYSELF TODAY, in the same way, which is the argument for renaming rather than
working around it. `grep -l "examples/$d"` reported the map-with-key example as referenced by nothing, because
grep read the brackets as a character class too, and I nearly recorded it as orphaned when 02_1_props.md
mentions it. And the README table needed `%5B`/`%5D` percent-encoding to link to it at all. A directory name
that breaks glob matching in jest, in grep and in markdown links is not a naming preference.
Also here, the three create-react-app examples had `--watchAll` hardcoded in their `test` script, so `npm test`
never exited. Moved to `test:watch`. Worth being exact about what that does and does not fix, because I
initially thought it was the whole problem: `CI=true` overrides `--watchAll` anyway, measured, so the flag was
not what made the CI matrix hang. `react-scripts test` watches unless `CI` is set, which is CRA's own
behaviour and not something a script can change, and the workflow sets `CI: true` for exactly that reason.
Removing the flag is still right, since a script that watches is the wrong default for `npm test`, but it is a
tidy-up rather than the fix.
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository: alpersonalwebsite/coderabbit/.coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
CI caught what local runs could not. The three redux examples failed with: New snapshot was not written. The update flag must be explicitly passed to write a new snapshot. This is likely because this test is run in a continuous integration (CI) environment. The snapshots existed on my disk and were never tracked, because each of those three examples has its OWN .gitignore carrying the same `**/__snapshots__` rule I removed from the root one two commits ago. Fixing the root file made the local runs pass and changed nothing about what git would accept, so every local check was reading files that were never going to be pushed. This is the partial-fix pattern, and it is the second time this effort has hit it: a repository with ten .gitignore files needs all ten swept, not the one at the top. The three files now carry a note saying why the rule went, and the three snapshots are tracked. Worth being clear about which check found it. `npm test` passed locally in all three examples, both with and without CI=true, because the files were sitting there untracked. Nothing short of a clean clone or CI could have noticed, and the clean clone I ran earlier timed out before it reached these three. CI is the only reason this is not merged and broken.
…ted as broken From review, and it is a real inconsistency: `11_webpack.md` still listed `@babel/preset-es2015` and `@babel/preset-stage-2` as packages to install, and named them in its `npm install --save-dev` line, two commits after this branch removed them from three package.json files for making the install fail. A reader following the lesson installed exactly what the examples had just stopped declaring. MY OWN CLAIM WAS THE PROBLEM, not an oversight in the sweep. Commit c2a76df says the presets are "referenced NOWHERE". The command behind that was: grep -rn 'preset-es2015|preset-stage-2' --include='*.json' --include='*.js' --include='.babelrc' Those include filters exclude markdown. In a repository whose entire content is markdown lessons, I scoped an "is this referenced anywhere" search to code files and then reported the result without the qualifier. The same grep with no filter finds 11_webpack.md immediately. That is the fourth time in this series that a search narrower than the claim drawn from it has produced a confident wrong answer: brackets read as a character class, a probe list too short to test .envrc, a message alternation missing a category, and now an include filter that excluded the content. The pattern is always the same shape, so the habit worth building is to state the scope in the claim, or drop the filter. The lesson's own babel config was never involved, which is worth recording because it makes the deletion sound rather than merely convenient: the config on that same page, and every `.babelrc` in `examples/`, lists only `@babel/preset-env` and `@babel/preset-react`. The two presets appeared in the install list and nowhere else. THE COMMAND STILL WILL NOT RESOLVE, and the lesson now says so rather than leaving a reader to find out. That is a second, unrelated problem and it is not fixed here, because it cannot be fixed without pinning versions this repository deliberately does not pin. Measured with the two presets already removed: npm error Conflicting peer dependency: @babel/core@8.0.1 npm error peer @babel/core@"^8.0.0" from @babel/plugin-transform-runtime@8.0.1 npm error peer @babel/core@"^7.0.0-0" from @babel/plugin-proposal-class-properties@7.18.6 The command names no versions, so it installs whatever is current, and the current set is not internally consistent. The note points at the pinned `devDependencies` in examples/react-redux-webpack-client/package.json for a set that does install, and says plainly that this is what an unversioned install command becomes years later. THE THREE yarn.lock FILES no longer pin the removed preset either. One block per file, removed surgically rather than by regenerating: `yarn install` would re-resolve every `^` range to today's latest, which is an upgrade, and this series does not upgrade. Verified afterwards that `yarn install --frozen-lockfile` still succeeds, that @babel/preset-es2015 is no longer installed, and that the tests still pass. Orphaned transitive entries remain in the lockfiles, which yarn prunes on install and which regenerating would clean at a price not worth paying.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This repository is not untouched, and this PR deliberately does not redo what was already done. #183
(lessons cleanup: grammar, factual accuracy, code bugs) and #184 (era-correctness of the snippets) covered the
22 root lesson files in May. Everything here is what those two passes did not reach: the
examples/tree, andthe fact that nothing was automated.
Five of the seven example projects did not work
Measured on Node 24 / npm 11, before this branch:
basic-react-example-statebasic-react-example-lifecyclebasic-react-example[map-with-key]basic-webpack[default]react-redux-webpack-clientreact-redux-webpack-client-serverreact-redux-webpack-client-server-scriptsEvery cause was a config or code defect rather than a version problem, so all of them are fixed without
upgrading a single dependency.
A test that had never run.
basic-react-example[map-with-key]/src/App.test.jsexists and was neverexecuted:
Jest interpolates the path into its own glob and
[map-with-key]is a character class, so the pattern became apath that cannot exist. Both bracketed directories are renamed. The brackets also defeated my own
greptwicewhile auditing, and needed
%5Bencoding to be linkable, so this is not a naming preference.ERESOLVE, in three examples.@babel/preset-es2015@7.0.0-beta.53peer-requires a beta@babel/corewhile the project declares
^7.2.2. That set has been internally inconsistent since it was written; npm 6ignored peer ranges, npm 7+ refuses. Both that and
@babel/preset-stage-2are declared in threepackage.jsonfiles and referenced nowhere (every.babelrclists onlypreset-env,preset-reactandclass-properties). Deleting two unused entries fixes all three installs with no version change. I considered
legacy-peer-deps=true, which the sibling repos in this series used, and rejected it: it tells npm to accepta resolution it correctly identified as broken, and keeps installing a beta preset nothing uses.
CleanWebpackPlugin is not a constructor, in three examples. The configs use the v1/v2 default export and(paths, options)whilepackage.jsondeclares^3.0.0, which is a named export taking one options object.The subtlety worth reading: translating this mechanically produces a config that runs and cleans nothing,
because v3 dropped
root/excludeand cleans relative tooutput.path, so the old'public/'patternresolves inside
public/and matches nothing. I measured that on the way past, with a planted stale bundlesurviving a green build. Verified per example by planting
public/stale-bundle.js: it is removed, andtemplate.html,manifest.jsonandfavicon.icosurvive.imagesis protected now and was not in theold
excludelist, so a working v1/v2 build would have deletedpublic/images/.One build script named a file that does not exist:
config/webpack.config.prod.jsin an example whoseconfigs are
.prod.client.jsand.prod.server.js. Found by checking every--configpath in every scriptagainst the filesystem, which is one line and caught a script that could never have worked.
webpack 4 cannot hash on modern Node.
crypto.createHash('md4')throwsERR_OSSL_EVP_UNSUPPORTEDbecauseOpenSSL 3 dropped md4. Verified md4 is unavailable by default and available under
--openssl-legacy-provider, after which every build completes. Documented per example rather than fixed byupgrading, since these are notes about a 2019 toolchain.
The snapshot tests had three layers of nothing
**/__snapshots__was gitignored, so nothing was ever compared. What that does depends on where it runs:jest writes and passes outside CI, and refuses to write and fails inside it. The passing outcome is
worse, because
toMatchSnapshot()reads like an assertion.`ReactWrapper {}`and would have matched an empty objectforever.
enzyme-to-jsonwas in devDependencies in all three examples and configured in none.With
snapshotSerializersset the snapshot is the rendered tree, and it is poison-tested rather than assumed:changing
HellotoHolagives- Hello / + Holaand fails.Also fixed there: the App test rendered a
connect()ed component with no<Provider>(
Could not find "store");<rootDir>pointed at the config file's directory sobabel-jestcould not befound; jest parsed a JPEG as JavaScript; and
server/test.jswas a three-line webpack scratch script thatjest collected as a suite with no tests.
The lessons now have a checker, and it found real defects
03_local-state.mdhad a class with no closing brace.07_conditional-rendering.mdhad the opposite, an extra}, on two consecutive examples.10_unit-tests.mdhad two test names that close their own string:it('updates the value offriendstate's property', …). The apostrophe ends the string. This is the one Iwould keep: it is in the lesson about writing tests.
javascripttag, and onejsonblock was a JavaScript object property.11_webpack.mdhad a link missing itsexamples/prefix that its four siblings on the same page have.The checker is shaped around fragments, which is the design decision worth reviewing. On the first run, 64
of 74 "failures" were legitimate teaching fragments: a lifecycle method without its class, JSX without its
component, three lines of a package.json without the braces. Demanding an annotation on each would have buried
the real findings under 64 markers. So a snippet passes if it parses in any plausible context, and a
failure means the text cannot be valid anywhere. Weaker than "every snippet is a complete program", and the
strongest this content admits. Four blocks are genuinely unparseable and carry
// check: skip <reason>, withthe reason required.
One limitation stated rather than hidden: because JSX is one of the shapes tried, HTML in a
javascriptfencenow parses. The two
<script>blocks are retagged because I know about them; a future one would not becaught.
Also
.gitignore: 14 build artifacts untracked (the production build's own clean step wipespublic/, socommitting its bundles means anyone who builds gets a spurious diff), env globs widened from the narrow
.env.*form, and the credential, SSH-key, sqlite and__pycache__patterns added. Verified bothdirections.
nowhere; the root README now has a table of all seven.
nocraand two were bothBasicReactMapWithState, one ofwhich is the lifecycle example.
fail-fast: falseso theresult says which ones work, and external links monthly, because an outbound link rots on a clock and this
repository went from 2020 to 2026 almost untouched.
Verification
From a clean clone, root checks:
All 21 distinct external URLs reachable. Each example verified individually for install, build and test; the
CI matrix is the standing version of that.
Two of my own errors worth knowing about
dist/main.jsin the webpack example's new test, assuming webpack's default output name for aproject called "basic-webpack[default]". It has a config that renames the bundle. The assertion failed,
which is how I found out.
identity-obj-proxy, which is already a dependency and looks obvious. It isthe right mapper for CSS modules and the wrong one for an image used as a value: React gets a Proxy as a DOM
attribute and throws
TypeError: symbol is not a functionfromsetValueForProperty.Not done
Nothing was upgraded.
npm auditreports a great many advisories against this dependency set, and leavingthem is the standing constraint for these repositories: the notes describe a 2019 toolchain and upgrading it
would make them notes about something else. That is a decision to revisit deliberately, not a side effect of
this PR.
CI, and the finding only CI could make
All nine jobs green:
The first run of that matrix failed on the three redux examples, and it is worth reading why, because
nothing local could have caught it:
The snapshots existed on my disk and were never tracked. Each of those three examples has its own
.gitignorecarrying the same**/__snapshots__rule I had removed from the root one. Fixing the root filemade local runs pass and changed nothing about what git would accept, so every local check was reading files
that were never going to be pushed. A repository with ten
.gitignorefiles needs all ten swept.That is the second time this effort has hit the partial-fix pattern, and the first time CI was the only thing
standing between it and a merged, broken branch. The clean-clone run I did earlier timed out before it reached
those three examples, which is exactly the gap a matrix closes.
Review round
Your finding is right and is fixed in
6d0b786. All nine CI jobs still green.11_webpack.mdlisted both deleted presets and named them in its install command, so a reader following thelesson installed exactly what the examples had stopped declaring.
The cause was my claim, not a missed file.
c2a76dfsays the presets are "referenced NOWHERE". Behindthat:
Those include filters exclude markdown, in a repository whose entire content is markdown lessons. The same
grep with no filter finds
11_webpack.mdimmediately. That is the fourth time in this series that asearch narrower than the claim drawn from it produced a confident wrong answer: brackets read as a character
class, a probe list too short to test
.envrc, a message alternation missing a category, and now an includefilter that excluded the content.
Worth adding in the deletion's favour: the lesson's own babel config on that same page lists only
preset-envandpreset-react, as does every.babelrc. The two presets appeared in the install list andnowhere else.
Your second point is taken and recorded rather than fixed. With the presets removed the command still
does not resolve:
It names no versions, so it installs whatever is current, and the current set is not internally consistent.
Fixing that means pinning versions this repository deliberately does not pin, so the lesson now says so
plainly and points at the pinned
devDependenciesinexamples/react-redux-webpack-client/package.jsonfora set that does install. An unversioned install command in a tutorial becomes this eventually, which is worth
a reader knowing.
The lockfiles too. One block removed per file, surgically rather than by regenerating:
yarn installwould re-resolve every
^range to today's latest, which is an upgrade and out of bounds here. Verifiedafter:
yarn install --frozen-lockfilesucceeds,@babel/preset-es2015is not installed, tests pass.Orphaned transitive entries remain, which yarn prunes on install.