From the engine to the plugin
Node, npm, npx, package, registry, scope, library, framework, dev server, bundler, API, callback, hook, plugin, utility class. Starting a React project means meeting all of these words at once. We put each one where it lives, from the node binary up to the Tailwind class on our button, and show the real code behind it. Two labs read commands and class names exactly the way npm and Tailwind do. The last part covers how to keep all of it in our heads: what memory research says, and a trainer that uses it. This page picks up below the V8 page and the event-loop page, which explain how the engine runs our code once it gets there.
Web dev track: 1 · From the engine to the plugin · next: 2 · From a JSON file to the screen · then 3 · From JSX to the DOM, 4 · From setState to the screen and 5 · From a URL to a Next.js page (planned). After the track: the Browser track.
How this was checked: the command model gives the same answer as npm 10.9.4's own code on 4,793 cases, comparing word splitting with bash, then npx's rewrite, the option parser, the command name and every package spec. The class model writes exactly the CSS that Tailwind CSS 4.3.3 writes for 2,183 classes. The real output was captured from a fresh Vite + React + TypeScript project (Node 22.22.0, npm 10.9.4, Vite 8.3.4, Chromium 141) by the script in this folder's data/. The source excerpts are from node v22.22.0, npm v10.9.4, npm-package-arg v12.0.2, Vite v8.3.4, Tailwind CSS v4.3.3 and React v19.3.0, trimmed with // ….
A program is a file the operating system runs
When we type node hello.js, the shell asks the operating system to start the program file node, passing the words node and hello.js as its arguments (argv). That file is ordinary compiled C++, like any program built with gcc. Its main function is three lines, and it calls node::Start.
Start does three jobs. It initializes V8, Google's JavaScript engine, the same one inside Chrome. It creates an environment with Node's own objects: process, require and the file and network modules. Then it runs our script and spins the event loop (libuv's loop), which keeps the process alive while timers, files or sockets are pending. So "Node" is a C++ program that embeds V8 and adds system abilities. It is not a compiler and not a language.
The one thing to remember: every tool on this page (npm, npx, vite, tsc) is a JavaScript program, and node is the program that runs it. When we type npm, the operating system starts node with npm's script, npm-cli.js.
The same engine in two different houses
An engine executes one language: it parses the text, compiles it to bytecode, interprets it and compiles hot code to machine code (just-in-time, or JIT). The V8 page follows one expression through those steps. A runtime is an engine plus everything the place it runs in adds, such as the global objects, the APIs and an event loop. The language is the same in both, but the objects available to it differ.
We asked both runtimes the same question, typeof window, typeof document, typeof process, typeof require, and recorded what each one answered.
| Word | Plain meaning | In our project |
|---|---|---|
| source code | The text we write | App.tsx, Navbar.tsx |
| compiler | Translates a whole program into another form before it runs (gcc: C to machine code) | Not used directly |
| transpiler | A compiler whose output is source code again (TSX to JavaScript) | Inside Vite's React plugin |
| interpreter | Executes code step by step, with no output file | V8's Ignition |
| JIT compiler | Turns the busiest code into machine code while the program runs | V8's Maglev and TurboFan |
| engine | Executes a language: V8 (Chrome, Edge, Node), SpiderMonkey (Firefox), JavaScriptCore (Safari) | V8 runs React in Chrome |
| runtime | Engine + the APIs and event loop of one environment | The browser runs our app; Node runs our tools |
| DOM | The live tree of objects the browser builds from HTML; the screen is drawn from it (HTML to pixels) | What React creates and updates |
| TypeScript | JavaScript plus types, checked on our machine and then deleted, so the browser only ever sees JavaScript | .ts and .tsx files |
| JSX / TSX | HTML-like syntax inside JavaScript, compiled into function calls that return plain objects | <Navbar /> |
The one thing to remember: one engine, two runtimes. The browser has document but no require, and Node has require but no document. Our React code needs document, so only the browser can run it. Node runs the tools that prepare that code.
Modules, packages, the registry and scopes
A module is one file that shares things with other files through export and import. Navbar.tsx ends with export default Navbar, and App.tsx gets it back with import Navbar from './components/Navbar'. A package is a folder of modules with a package.json label, published so anyone can install it. Our own project is a package too.
The registry is the server that stores every published package, and npm's is registry.npmjs.org. It works like an app store for code: authors publish to it, and npm install downloads from it. A package name is unique across the whole registry and belongs to whoever published it first, which causes two problems. Good short names run out, and anyone can take a name that sounds official. A scope solves both. It is a prefix such as @tailwindcss/ owned by one account or organisation, and only that owner can publish under it. Scopes work like folders: @tailwindcss/vite and @sentry/vite are two different packages, both called vite.
Here is the real conversation with the registry during our npm install. Each line is one HTTP request asking the registry about a package, and the answer lists every version with its download address. The scoped name @types/node goes out as @types%2fnode: the slash is URL-encoded, so the server sees one name and not a folder path. That is the line this.escapedName = name.replace('/', '%2f') in npm-package-arg.
The two @ signs mean different things. An @ at the start begins a scope, and an @ after the name begins what we want: an exact version (4.3.3), a range (^4.3.0 means 4.3.0 or newer but below 5), or a tag (latest, a name the registry points at a version, shown above). With nothing after the name, npm uses the range *, any version, and picks the newest.
Library or framework: who is in charge
The difference is who calls whom. We call a library: our code decides when to use it, and it does one job and hands control back. toast("Added!") from react-toastify is a library call. A framework calls us. It runs the program and calls our code when it needs to, much like a form whose questions and order are already decided, where we only fill in the blanks. In Next.js we write app/about/page.tsx, export a function and never call it ourselves. Next.js creates the /about address and calls our function when someone visits. This is called inversion of control, also known as the Hollywood principle: "don't call us, we'll call you".
| Library | Framework | |
|---|---|---|
| Who calls whom | We call it | It calls us |
| How much it decides | One job (show a notification) | The shape of the app: files, web addresses, build, server |
| If we remove it | We replace a few calls | We rewrite the app |
| Examples | react-toastify, React | Next.js, Angular |
Where React fits: in between. We call it (createRoot(…).render(<App />)), and then it calls our components. It calls itself a library because it handles only the UI and leaves routing, data loading and building to us. Next.js makes those decisions for us, and that is what makes it a framework.
How npm reads a command line
Every command has the same grammar: program (which tool), command (what to do), arguments (what to do it to), and options (settings that start with a dash: one dash and a letter like -D, or two dashes and a word like --save-dev). Spaces separate the parts. The shell (bash, PowerShell) splits the line into words and starts the program; the program never sees the spaces. After that, npm runs four small steps of its own code. We follow each one.
- npx is npm exec. The
npxprogram is a tiny script: it insertsexecas the first word and puts--in front of the first word that is not an option. Sonpx tsc --noEmitbecomesnpm exec -- tsc --noEmit, and--noEmitgoes totsc, not to npm. - Options are sorted out by nopt. nopt walks the words and looks each option up in npm's definitions.
-Dis a shorthand for--save-dev, and-gDmeans-g -Dbecause every letter is a one-letter shorthand. An option npm does not know, such as--template, becomes a true/false npm setting, and the word after it is left as an ordinary argument. A lone--stops option parsing, and everything after it is kept as it is. - The first plain word becomes a command.
cmd-list.jsholds the 67 commands and the aliases:iisinstall,runisrun-script, andcreateisinit. It also accepts any unambiguous abbreviation (insta) and a few common typos (isntall). - Each package argument is read by npm-package-arg into a name, a scope and a version, range or tag, as in Frame 2.
Why npm create vite@latest dev-stack -- --template react-ts needs the --. create is init, and npm init vite@latest turns the name into create-vite@latest and runs it with npm exec. The real output in Frame 6 shows this: npm prints > create-vite dev-stack --template react-ts. Without the --, nopt reads --template as an unknown npm setting (template = true), so react-ts becomes a stray argument and create-vite never sees --template. The command lab shows both versions.
Why npm run dev finds vite when typing vite in the terminal says "command not found". Every package that provides a command declares a bin, and npm links it into node_modules/.bin. Before running a script, npm's run-script library puts that folder at the front of PATH, the list of folders the operating system searches for programs. It adds node_modules/.bin for the project folder and for every folder above it. We added a script that prints the first entries of PATH and ran it.
The one thing to remember: npm run dev, npx vite and node node_modules/vite/bin/vite.js all start the same program. They differ only in how the word vite gets turned into a file. And npm always works on the package.json in the folder the terminal is in.
Type a command, see what npm will do with it
Type an npm, npx, node or git command, or pick a preset. Each word is coloured by its role, and every package spec is broken into scope, name and version. Try removing a space, or the --, and watch what changes.
What it is. Our model of npm 10's reading path: shell word splitting, npx's rewrite to npm exec, nopt's option parsing with npm's real option table, cmd-list.js's aliases and abbreviations, and npm-package-arg's spec types (version, range, tag, alias, git, remote, file, directory) with npm's name rules.
Checked against. npm 10.9.4 itself on 4,793 cases: 2,977 distinct command lines (the presets and seeded random lines), each compared word by word with bash, then npx's real bin/npx-cli.js, nopt's parse with npm's definitions, and npm's own deref(); and every package spec compared with npm-package-arg for type, name, scope and fetch spec, or the same error (verify/compare-commands.js).
Not covered. What happens next: network requests, the dependency tree and package-lock.json. Also nopt's later type cleaning (it turns a path option into an absolute path, for example), shell features beyond quotes (variables, globs, pipes: the lab refuses them), the hosted-git URL forms beyond common GitHub, GitLab and Bitbucket ones, and node and git lines, which the lab only labels.
Words, as the shell splits them
What npm does with them
In plain words
Package specs
program command argument option -- end of options passed on
Vite serves our files, transforming each one on request
Browsers run JavaScript, not TypeScript or JSX, and they import modules by URL, not by package name. Something has to translate. Vite does that in two ways. As a dev server (npm run dev), it serves our project on localhost and transforms each file when the browser asks for it, so starting is instant and a saved file updates in place (hot module replacement). As a build tool (npm run build), it transforms everything once, bundles the many modules into a few files and minifies them into dist/ for a host to serve.
Every request goes through loadAndTransform: load the file, then transform it, which means calling the transform function of every plugin in order. Each plugin receives the code and the file's id, and can return new code.
This is what the browser actually received when it asked our dev server for /src/App.tsx. The type annotation : Props is gone, because Vite removes types without checking them. That is the job of our editor and of tsc in npm run build. Each <nav> became a call, _jsxDEV("nav", { className: … }), that returns a plain object. And import { useState } from "react" became a real URL, /node_modules/.vite/deps/react.js, because a browser cannot look up a package name.
| Word | Plain meaning | In our project |
|---|---|---|
| CLI | Command-line interface: a program we run by typing its name | npm, vite, git |
| package manager | Downloads packages and everything they depend on, records exact versions, runs project scripts | npm (others: pnpm, yarn) |
package.json | The project's description: name, scripts, dependencies | "dev": "vite" is why npm run dev starts Vite |
node_modules | Where installed packages live, per project | React, Vite, Tailwind |
package-lock.json | The exact version of every installed package, so installs repeat | Written by npm install |
| dependencies / devDependencies | Needed by the app / needed only while developing (-D) | react / vite, typescript |
| dev server | Local web server that transforms files on request and reloads on save | npm run dev |
| build tool, bundler, minify | Prepare for publishing: transform, combine modules, shrink | npm run build → dist/ |
| type checker | Checks types without running code | The editor and tsc; never Vite |
The one thing to remember: Vite removes types but never checks them. A type error shows up in the editor and stops npm run build, but the dev server still runs the code.
API, callback, hook, plugin: four words for one arrangement
Read these in order, because each one builds on the one before.
- API: the named functions and objects a program lets other code use, like the buttons on a remote. Examples: the DOM's
document.querySelector, React'suseState, Node'sfs.readFile. We call it. - Callback: a function we hand to other code so that it can call it at the right moment, as in
addEventListener('click', fn),onClick={…}or the function given to.map(…). It calls us. A common mistake isaddEventListener('click', sayHi()): the parentheses callsayHiimmediately and hand over its result instead of the function. - Hook (in tools): a named moment in a host's work where it calls the callbacks plugins gave it. Vite's include
config(reading settings),transform(each file) andbuildEnd. The loop in Frame 4 is thetransformhook being called. - Plugin: a package of callbacks for a host's hooks, which adds a feature without changing the host's code. A Vite plugin is an object with a
nameand hook functions.react()andtailwindcss()invite.config.tsreturn such objects. A plugin can be a host too: Tailwind is a plugin of Vite, and daisyUI is a plugin of Tailwind.
| Hook in Vite | Hook in React | |
|---|---|---|
| Who calls whom | Vite calls our function | We call React's function |
| What it is | A named moment in the tool's work | A use… function that gives a component memory or side effects |
| Example | transform(code, id) | useState(0): stores the value on the component's fiber and returns it with a setter (dispatchSetState) |
The one sentence: a plugin provides callbacks for a host's hooks, through the host's API. React's "hooks" share the word but not the idea.
A class name is a small CSS rule, written only if we use it
Tailwind CSS is a list of ready-made utility classes, each one a small CSS rule. Its Vite plugin reads our files, collects every word that looks like a class name, and writes CSS for exactly those words. utilities.ts registers every family. Here is the padding family, which maps px to padding-inline. The spacing helper turns the number into --spacing(4), and that becomes calc(var(--spacing) * 4). The theme sets --spacing: 0.25rem, so px-4 is 1rem, which is 16px. The multiplier must be a multiple of 0.25, so p-2.5 works and p-0.3 produces nothing.
A variant is a prefix ending in a colon that wraps the rule in a condition. lg: wraps it in @media (width >= 64rem), so it applies from 1024px wide and up, and a class with no prefix applies at every width. This is what "mobile-first" means: we write the phone layout plainly, then change it for bigger screens. hover: holds a detail many tables leave out. It adds :hover and also wraps the rule in @media (hover: hover), so a phone, which cannot hover, does not keep the colour after a tap. Variants stack from left to right: lg:hover:x puts the lg media query on the outside.
Here is the real stylesheet the dev server sent for our navbar. Every class we used has exactly one rule, and nothing else is there, except a few extra words that happen to be class names somewhere in the project's files (transform, filter). Tailwind's scanner reads every file that git does not ignore, so a word in a README can produce CSS too. That is harmless, and it shows that the scanner matches words, not meaning. Our own bg-brand appears because of the @utility rule in index.css, which is how it works with hover: and lg: like a built-in class.
Five rules that read any class
| Rule | Example | The CSS Tailwind writes |
|---|---|---|
| A class is a CSS property plus a value | flex | display: flex |
| Numbers are spacing units of 0.25rem (4px) | px-4, h-16 | padding-inline: calc(var(--spacing) * 4) = 16px; height: …* 16 = 64px |
| Colours are name-shade, 50 (light) to 950 (dark) | text-gray-600 | color: var(--color-gray-600) |
hover: = while a mouse is over it | hover:text-pink-600 | @media (hover: hover) { .…:hover { color: … } } |
sm: md: lg: xl: = from that width up | hidden lg:flex | display: none, and from 64rem display: flex |
Letters: p padding (inside), m margin (outside), x left and right (-inline), y top and bottom (-block), t r b l one side, w width, h height. Breakpoints: sm 40rem (640px), md 48rem (768px), lg 64rem (1024px), xl 80rem (1280px), 2xl 96rem (1536px).
Type classes, see the CSS, and let this browser compute it
Type one or more classes separated by spaces, exactly as in className="…". Each class shows the rule Tailwind writes, what its values work out to, and whether its condition is true in this window right now. Check in this browser applies the declarations to a test box 400px wide and reads back what your browser computes.
What it is. Our model of Tailwind CSS 4's everyday utilities (layout, flexbox and grid, spacing, sizing, position, type, colour, borders, radius, opacity, z-index) and variants (hover, focus, active, disabled, first, last, odd, even, group-hover, dark, print, sm to 2xl and max-*), with the default theme values copied from Tailwind's theme.css.
Checked against. The real Tailwind CSS 4.3.3 compiler on 2,183 distinct classes (hand-picked and seeded random, up to two variants): the same selector, @media wrappers and declarations in the same order, the same theme variables, or rejected by both (verify/compare-tailwind.js). Live: Check in this browser computes the values, and every media condition is tested with your browser's matchMedia.
Not covered. Shadows, rings, transforms, transitions, gradients, arbitrary values such as w-[300px], opacity modifiers such as bg-black/50, space-x-*, daisyUI's classes, and the merging of rules that share a media query in a full stylesheet.
The whole trip in our dev-stack project
Now every word together, in the order things happen when we work on the assignment. Steps 1 to 4 and 7 to 9 run in Node on our machine; steps 5 and 6 run in the browser.
npm create vite@latest dev-stack -- --template react-ts:createisinit, which runs the packagecreate-vite. It writes the project folder and itspackage.json(real output below).npm install: Node runs npm, which asks the registry about every dependency (135 requests in our capture) and unpacks them intonode_modules, recording exact versions inpackage-lock.json.npm install tailwindcss @tailwindcss/vite daisyuiand two lines invite.config.tsandindex.css: Tailwind becomes a Vite plugin, and daisyUI a Tailwind plugin.npm run dev: npm reads"dev": "vite", putsnode_modules/.binon PATH, and Node starts Vite's dev server onlocalhost:5173with thereact()andtailwindcss()plugins loaded.- The browser asks for
index.html, then/src/main.tsx, and Vite transforms each file as it is requested: types removed, JSX turned into calls, imports rewritten to URLs, CSS written for the classes used. - The browser runs it: V8 runs React, React calls our components and builds DOM nodes, and Blink does style, layout and paint (HTML to pixels). A click finds its way back to our
onClick(a click to our listener). - Type checking, alongside: the editor runs TypeScript and underlines errors in red. Vite ignores types.
npm run build:tsc -bstops on any type error, then Vite bundles and minifies intodist/(the real output is in Frame 4).- Deploy: Netlify or Vercel serves
dist/. From then on, only users' browsers run our code.
How to keep all of this in our heads
This page holds about a hundred words, twenty commands and the five Tailwind rules. Reading it again tomorrow will feel productive, and it is one of the least effective things we can do. This is one of the best-replicated findings in learning research. A 2013 review of ten study techniques by Dunlosky and colleagues rated practice testing (recalling from memory) and distributed practice (spacing reviews out) as the two most useful. It rated rereading and highlighting low, because they make material feel familiar without making it retrievable. Below is what each technique is, why it works, and exactly how to apply it to this page.
1 · We forget fast, then slower: the forgetting curve
Ebbinghaus measured his own memory for nonsense syllables in 1885, and Murre and Dros repeated the experiment in 2015. Most of what we learn is lost within the first day, and what survives fades more slowly after that. The useful part is that each successful recall makes the next drop slower. The chart below illustrates the shape. It is not a measurement of us: retention decays as e−t/S, and each review multiplies the stability S by 2.5. Add reviews and watch the curve flatten.
What it is. A drawing of the textbook model: exponential decay whose time constant grows after each successful review.
Checked against. Nothing numerically. The shape (steep at first, flatter after each spaced review) is what Ebbinghaus, Murre and Dros, and the spacing literature report.
Not covered. Real retention depends on the material, sleep, and how hard each recall was. Use the shape, not the numbers.
2 · Recall, don't reread (retrieval practice)
Roediger and Karpicke had students either reread a passage or try to write down what they remembered. After five minutes the rereaders did better. After two days and after a week, the students who had practised recalling remembered clearly more, even though rereading felt more effective to them. The effort of pulling something out of memory is what strengthens it. The honest check is the question "can I produce it with the page closed?", not "does this look familiar?".
Applied here: cover the right-hand column of any table on this page and say the answer out loud first. Use the quizzes in Try it before reading their explanations. The trainer never shows an answer until we have tried.
3 · Space it out (distributed practice)
The same total time spread over several days beats one long session, and the best gap grows with how long we want to remember. Cepeda and colleagues' meta-analysis of hundreds of experiments found this again and again. The simplest schedule is the Leitner system. Cards live in five boxes. A card we recall moves up one box, and a card we miss goes back to box 1. Box 1 is reviewed every day, box 2 every 2 days, box 3 every 4, box 4 every 8 and box 5 every 16. Easy cards drift away; hard ones keep coming back.
| Box | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| Review again after | next session (daily) | 2 days | 4 days | 8 days | 16 days |
Applied here: ten minutes a day with the trainer, rather than an hour on Sunday. It remembers which box each card is in, in this browser only.
4 · Remember the rule, not the list (chunking)
We can hold only a handful of items in mind at once, but one item can be a whole chunk: a pattern that stands for many facts. Experts do not remember more facts; they have better chunks. This page is built around four chunks, and each one replaces a long list:
| Instead of remembering | Remember the chunk | And regenerate |
|---|---|---|
| Hundreds of Tailwind classes | The five rules: property-value, number × 4px, colour-shade, hover:, lg: = from that width up | mt-8 = margin-top 32px; md:grid-cols-2 = two columns from 768px |
| Every npm command shape | Program · command · arguments · options, separated by spaces; -- ends options | Any command we have never seen, read word by word |
| What runs where | The two-sided diagram: Node prepares, the browser runs | "Does Vite run in the browser?" No, it is on the left. |
| API, callback, hook, plugin | "A plugin gives callbacks for a host's hooks, through its API" | What a VS Code extension or a daisyUI theme is |
5 · Make it, don't copy it (the generation effect)
Slamecka and Graf found that words people produce themselves, even from a small cue, are remembered better than words they read. Copying a command from a tutorial teaches the fingers nothing. Typing it from the goal ("add TypeScript as a dev dependency") makes us build it from the grammar.
Applied here: the trainer's command cards give a goal and check whatever we type with the command model, so npm i -D typescript and npm install --save-dev typescript both count, because npm reads them identically. Its class cards give a description and check our class against the Tailwind model.
6 · Mix the kinds (interleaving)
Practising one kind of thing at a time (all the spacing classes, then all the colour classes) feels smooth. Mixing them teaches us to tell them apart, which is the skill we need when facing a blank className. Kornell and Bjork found that interleaved examples taught categories better, even though learners believed the blocked order worked better.
Applied here: the trainer shuffles terms, commands and classes into one queue.
7 · Hooks for the confusions that keep coming back
| Confusion | Memory hook |
|---|---|
The two @s in @tailwindcss/vite@4.3.3 | Front @ is the owner, back @ is the age. The scope comes first, like a family name in a phone book; the version comes last. |
| Library or framework | Hollywood: "don't call us, we'll call you" is the framework speaking. |
-D | D for Dev: --save-dev, a tool we need only while developing. |
npx | x for execute: npx = npm exec (and npm x is a real alias). |
Where -- goes | The double dash is a door: everything after it walks through to the next program. |
| Mobile-first | No prefix = phones. A prefix = "from here up". lg: never means "only laptops". |
| Spacing numbers | Times four: p-4 = 16px, h-16 = 64px, gap-6 = 24px. |
| Hook in Vite vs React | Vite hooks are called on us; React hooks are called by us. |
| Vite and types | Vite strips, tsc checks. |
8 · What not to memorize at all
Memory is for what we need instantly and for the chunks that let us understand. Look the rest up, deliberately and quickly. Knowing where to look is a skill worth practising too.
| Need | Let the tool remember |
|---|---|
| The exact Tailwind class | Tailwind CSS IntelliSense in VS Code: start typing in className, then hover a class to see its CSS. On tailwindcss.com, press Ctrl + K and describe the effect ("rounded corners"). |
| An HTML structure | Emmet: ul>li*5>a then Tab |
| A command's options | npm help install, npm install --help, git commit -h |
| A command typed last week | ↑ in the terminal, or Ctrl + R and a few letters to search the history |
| The project's own commands | npm run with no name lists every script in package.json |
| Which packages a project uses | npm ls --depth=0, or open package.json |
9 · Explain it to someone (or to a rubber duck)
Explaining forces retrieval and shows the gaps immediately: the sentence we cannot finish is the thing we did not understand. After each study session, explain one frame of this page out loud in plain words, without looking, as if to a friend who has never coded. Where we get stuck, reread only that paragraph, then try again.
A plan for the next two weeks
| When | What (about 10 to 15 minutes) |
|---|---|
| Today | Read the 60-second version, then one pass through the trainer (every card is new, so all go to box 1 or 2). Draw the two-sided diagram from memory. |
| Days 1 to 3 | Trainer, due cards only. Then explain one frame out loud. Use the command lab on every command we type for the assignment. |
| Days 4 to 7 | Trainer, due cards. Write a class list for one component from the design before looking anything up, then check it in the class lab. |
| Days 8 to 14 | Trainer, due cards (only a few now). Do the exam questions on paper and mark them honestly. |
| Whenever we stall | Make a new card for the thing we just had to look up. Cards from our own mistakes are the most valuable ones. |
Spaced recall for every word on this page
One card at a time, with the answer hidden until we have tried. For a word card, answer out loud, reveal it, and say honestly whether we got it. Command and class cards are checked by the page's two models, so any form npm or Tailwind would treat the same is accepted. Correct moves a card up a box; a miss sends it back to box 1.
What it is. A Leitner-box scheduler (five boxes, reviewed after 0, 2, 4, 8 and 16 days) over 40 cards drawn from this page, in a shuffled, mixed order.
Checked against. Command answers are checked with the command model and class answers with the class model, the same models the verify scripts compare with npm and Tailwind. The schedule itself is a learning aid, not a measurement.
Not covered. Progress is stored in this browser only (localStorage). Another device, a private window or cleared site data starts fresh, and nothing is sent anywhere.
Predict, then check
See it on our own machine
| What to see | Command (in the project folder) |
|---|---|
| Every request npm makes to the registry | npm install --loglevel http |
| What the registry knows about a package | npm view react-toastify version dist-tags |
| The project's scripts | npm run (no name) |
| Where commands like vite live | Windows: dir node_modules\.bin; macOS/Linux: ls -l node_modules/.bin |
| What the browser receives from Vite | With npm run dev running, open DevTools → Network, click App.tsx and read the Response |
| The CSS Tailwind wrote | DevTools → Network → index.css, or select an element and read the Styles pane |
| A type check without building | npx tsc --noEmit (or npx tsc -b in a Vite project) |
Exercises
- Put
npm create vite@latest dev-stack --template react-ts(no--) in the command lab. Which word ends up where? Predict what create-vite would do withreact-ts, then read create-vite's help to check. - Run
npm view react dist-tags. Which version wouldnpm install react@nextinstall today, and which wouldnpm install reactinstall? Why can they differ? - Write the class list for the Banner's "Learn More" button from the design before looking anything up, then put it in the class lab and compare the CSS with what the design needs.
- In the class lab, type
p-0.3,p-1.25andp-13. Which produce CSS, and which line ofutilities.tsdecides? - Write a tiny Vite plugin with a
transform(code, id)hook that logsid. Add it tovite.config.ts, load the page, and list which files pass through it, in which order.
Exam-style questions, with model answers
Six questions in the style of a web engineering or software tools exam. Write the answer first, then open the model answer and mark it honestly. Answers about commands were produced by the command model and agree with npm 10.9.4; answers about CSS were produced by the class model and agree with Tailwind CSS 4.3.3.
npm i -gD @tailwindcss/vite@latest/daisyui@latest: the command it runs, the settings it sets, and how it reads the package argument (name, scope, spec and spec type). Then fix the command so it does what the author meant.nopt sees -gD. It is not a known option or shorthand, but every letter is a one-letter shorthand, so it expands to --global --save-dev: global = true, save-dev = true. The plain words left are i and the package argument, and deref("i") gives install.
npm-package-arg finds the first @ after position 0. The name is @tailwindcss/vite (scope @tailwindcss) and the spec is everything after that @: latest/daisyui@latest. The spec contains a slash, so it is a directory spec: npm would look for a local folder called latest/daisyui@latest. Also, -g installs for the whole computer, so -D does nothing useful.
Fix: npm install @tailwindcss/vite@latest daisyui@latest (a space between the two specs, and no -g), or -D without -g to record them as dev dependencies.
Marking: 1 for the -gD expansion, 1 for install, 2 for the name, scope and directory spec with the reason, 1 for -g making -D pointless, 1 for the fix.
npm create vite@latest app --template react-ts and gets the wrong template. Using npm's code, explain why, and why npm create vite@latest app -- --template react-ts works.create is an alias of init. init turns vite@latest into the package create-vite@latest (the line req.name.replace(/^(@[^/]+\/)?/, '$1create-')) and runs it through npm exec with the remaining arguments.
Before that, nopt parses npm's own options. --template is not an npm option, and nopt treats an unknown option without = as a switch (typeof argType === 'undefined' && !hadEq), so it sets npm's template = true and leaves react-ts as an ordinary argument. create-vite receives app react-ts and never sees --template.
With --, nopt stops parsing (if (arg.match(/^-{2,}$/))) and pushes every later word into the plain arguments unchanged, so create-vite receives app --template react-ts.
Marking: 1 for create → init → create-vite, 2 for the unknown option becoming npm's setting and react-ts a stray argument, 2 for the -- rule.
px-5 py-2.5 -mt-4 w-1/2 lg:max-w-md, give the CSS Tailwind 4 writes for each class and the computed length in pixels (16px root font size; for w-1/2, a 400px-wide parent).| Class | CSS | Pixels |
|---|---|---|
px-5 | padding-inline: calc(var(--spacing) * 5) | 5 × 4 = 20px left and right |
py-2.5 | padding-block: calc(var(--spacing) * 2.5) | 10px top and bottom |
-mt-4 | margin-top: calc(var(--spacing) * -4) | −16px |
w-1/2 | width: calc(1 / 2 * 100%) | 200px |
lg:max-w-md | @media (width >= 64rem) { .lg\:max-w-md { max-width: var(--container-md); } } | 28rem = 448px, from 1024px-wide windows |
Marking: 1 per class (CSS and value both needed). --spacing is 0.25rem = 4px.
npm run dev to the browser running our App component. Name the program or file that does each step, and say which steps run in Node and which in the browser.1. The shell starts npm, which is node running npm-cli.js. nopt and deref turn run into run-script. (Node)
2. run-script reads "dev": "vite" from package.json, set-path.js puts node_modules/.bin first on PATH, and the shell finds vite, a link to vite/bin/vite.js. (Node)
3. Vite reads vite.config.ts, loads the react() and tailwindcss() plugins and listens on localhost:5173. (Node)
4. The browser requests / and /src/main.tsx. For each file, loadAndTransform loads it and the plugin container calls every plugin's transform hook: types are removed, JSX becomes jsxDEV(…) calls, imports become URLs, and Tailwind writes CSS for the classes it found. (Node)
5. V8 in the browser runs the modules, createRoot(…).render(<App />) schedules a render, React calls App and its children and commits DOM nodes, and Blink styles, lays out and paints them. (Browser)
Marking: 1 for npm as a Node program, 1 for the PATH step, 1 for Vite and plugins, 2 for transform per request, 1 for the browser side.
With a library, our code holds control and calls it (react-toastify's toast()). With a framework, it holds control and calls our code at points it defines (Next.js calls the function exported from app/about/page.tsx when /about is visited). That reversal is inversion of control.
React is in between: we call render, then React calls our components. It is called a library because it covers only the UI. Next.js decides routing, data loading and building, so it is a framework.
A Vite hook is a named moment (transform) at which the host calls a plugin's callback. A React hook is a function we call (useState) to give a component state stored on its fiber.
Marking: 1 for inversion of control, 1 for React, 1 for Next.js, 1 for the hook contrast.
Short daily sessions instead of one long one, because distributed practice beats massed practice for the same total time (Cepeda et al. 2006). Each session starts with recall from memory (cards, covered tables, explaining aloud), not rereading, because retrieval practice beats restudy after days (Roediger and Karpicke 2006) and rereading is rated low utility (Dunlosky et al. 2013).
A Leitner schedule (boxes reviewed after 0, 2, 4, 8, 16 days; a miss goes back to box 1) makes the gaps grow with each success, matching the flattening forgetting curve. Mix terms, commands and classes in one queue (interleaving, Kornell and Bjork 2008). Make commands and class lists from goals rather than copying them (the generation effect, Slamecka and Graf 1978). Learn chunks (the five Tailwind rules, the command grammar) instead of lists (Miller 1956). Add a card for every lookup made during the assignment.
Marking: 1 each for spacing, retrieval, a growing schedule, one more technique (interleaving, generation or chunking), and correct justification throughout.
Everything this page cites, by depth
The numbered markers in the text open the relevant reading right where we need it. Here is all of it in one place: Start here for a first pass, Go deeper for the full treatment, and Primary sources for the documentation, specifications and papers.
Original files and data
- node/src/node_main.cc
- node/src/node.cc
- node/src/node_main_instance.cc
- npm/bin/npx-cli.js
- npm/lib/utils/cmd-list.js
- npm/lib/commands/init.js
- nopt/lib/nopt-lib.js
- npm-package-arg/lib/npa.js
- @npmcli/run-script/lib/set-path.js
- vite/…/server/transformRequest.ts
- vite/…/server/pluginContainer.ts
- @tailwindcss-vite/src/index.ts
- tailwindcss/src/utilities.ts
- tailwindcss/src/variants.ts
- tailwindcss/theme.css
- react-reconciler/src/ReactFiberHooks.js
- Our capture script and its output (data/)
Node.js, Vite, React and Tailwind CSS are MIT-licensed; npm is under the Artistic License 2.0; nopt, npm-package-arg and @npmcli/run-script are ISC-licensed. Quoted for study, with credit to each project.