← Root to End Web dev track · 1 of 5 · checked against npm 10.9.4 and Tailwind CSS 4.3.3

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 // ….

Frame 0 · the root

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.

node/src/node_main.cc · main()the UNIX branch, verbatim

    
node/src/node.cc · StartInternal() and node_main_instance.cc · Run()trimmed

    

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.

Frame 1 · engines and runtimes

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.

data/capture.json · Node 22.22.0real output

    
data/capture.json · Chromium 141 (Playwright)real output

    
WordPlain meaningIn our project
source codeThe text we writeApp.tsx, Navbar.tsx
compilerTranslates a whole program into another form before it runs (gcc: C to machine code)Not used directly
transpilerA compiler whose output is source code again (TSX to JavaScript)Inside Vite's React plugin
interpreterExecutes code step by step, with no output fileV8's Ignition
JIT compilerTurns the busiest code into machine code while the program runsV8's Maglev and TurboFan
engineExecutes a language: V8 (Chrome, Edge, Node), SpiderMonkey (Firefox), JavaScriptCore (Safari)V8 runs React in Chrome
runtimeEngine + the APIs and event loop of one environmentThe browser runs our app; Node runs our tools
DOMThe live tree of objects the browser builds from HTML; the screen is drawn from it (HTML to pixels)What React creates and updates
TypeScriptJavaScript plus types, checked on our machine and then deleted, so the browser only ever sees JavaScript.ts and .tsx files
JSX / TSXHTML-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.

Frame 2 · code we didn't write

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.

npm install --loglevel http (in ~/dev-stack)real output, first 8 requests

  
npm view react dist-tagsreal output: tags are names for versions

    
npm-package-arg/lib/npa.js · setName() and fromRegistry()trimmed

    

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".

LibraryFramework
Who calls whomWe call itIt calls us
How much it decidesOne job (show a notification)The shape of the app: files, web addresses, build, server
If we remove itWe replace a few callsWe rewrite the app
Examplesreact-toastify, ReactNext.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.

Frame 3 · commands

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.

  1. npx is npm exec. The npx program is a tiny script: it inserts exec as the first word and puts -- in front of the first word that is not an option. So npx tsc --noEmit becomes npm exec -- tsc --noEmit, and --noEmit goes to tsc, not to npm.
  2. Options are sorted out by nopt. nopt walks the words and looks each option up in npm's definitions. -D is a shorthand for --save-dev, and -gD means -g -D because 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.
  3. The first plain word becomes a command. cmd-list.js holds the 67 commands and the aliases: i is install, run is run-script, and create is init. It also accepts any unambiguous abbreviation (insta) and a few common typos (isntall).
  4. Each package argument is read by npm-package-arg into a name, a scope and a version, range or tag, as in Frame 2.
npm/bin/npx-cli.jstrimmed

    
npm/lib/utils/cmd-list.js · aliasestrimmed: 9 of 56

    

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.

npm/lib/commands/init.js · execCreate()trimmed

    
nopt/lib/nopt-lib.js · parse()trimmed: the -- rule and unknown options

    

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.

@npmcli/run-script/lib/set-path.jstrimmed

    
npm run where · and the links in node_modules/.binreal output

    

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.

Lab · the command reader

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

Frame 4 · the tools

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.

vite/packages/vite/src/node/server/transformRequest.ts · loadAndTransform()trimmed

    
vite/packages/vite/src/node/server/pluginContainer.ts · transform()trimmed

    

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.

src/App.tsx · what we wroteour example (the capture's input)

    
GET /src/App.tsx · what the browser receivedreal output from Vite 8.3.4

    
npm run buildreal output: tsc checks types, then Vite bundles

  
WordPlain meaningIn our project
CLICommand-line interface: a program we run by typing its namenpm, vite, git
package managerDownloads packages and everything they depend on, records exact versions, runs project scriptsnpm (others: pnpm, yarn)
package.jsonThe project's description: name, scripts, dependencies"dev": "vite" is why npm run dev starts Vite
node_modulesWhere installed packages live, per projectReact, Vite, Tailwind
package-lock.jsonThe exact version of every installed package, so installs repeatWritten by npm install
dependencies / devDependenciesNeeded by the app / needed only while developing (-D)react / vite, typescript
dev serverLocal web server that transforms files on request and reloads on savenpm run dev
build tool, bundler, minifyPrepare for publishing: transform, combine modules, shrinknpm run build → dist/
type checkerChecks types without running codeThe 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.

Frame 5 · extending tools

API, callback, hook, plugin: four words for one arrangement

Read these in order, because each one builds on the one before.

  1. 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's useState, Node's fs.readFile. We call it.
  2. 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 is addEventListener('click', sayHi()): the parentheses call sayHi immediately and hand over its result instead of the function.
  3. 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) and buildEnd. The loop in Frame 4 is the transform hook being called.
  4. 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 name and hook functions. react() and tailwindcss() in vite.config.ts return such objects. A plugin can be a host too: Tailwind is a plugin of Vite, and daisyUI is a plugin of Tailwind.
tailwindcss/packages/@tailwindcss-vite/src/index.tstrimmed: a plugin is a list of named hook objects

    
react/packages/react-reconciler/src/ReactFiberHooks.js · mountState()verbatim: the other meaning of "hook"

    
Hook in ViteHook in React
Who calls whomVite calls our functionWe call React's function
What it isA named moment in the tool's workA use… function that gives a component memory or side effects
Exampletransform(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.

Frame 6 · Tailwind

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.

tailwindcss/packages/tailwindcss/src/utilities.tstrimmed

    
tailwindcss/packages/tailwindcss/src/variants.ts and theme.csstrimmed

    

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.

GET /src/index.css?direct · the utilities layerreal output from Tailwind 4.3.3 through Vite

  

Five rules that read any class

RuleExampleThe CSS Tailwind writes
A class is a CSS property plus a valueflexdisplay: flex
Numbers are spacing units of 0.25rem (4px)px-4, h-16padding-inline: calc(var(--spacing) * 4) = 16px; height: …* 16 = 64px
Colours are name-shade, 50 (light) to 950 (dark)text-gray-600color: var(--color-gray-600)
hover: = while a mouse is over ithover:text-pink-600@media (hover: hover) { .…:hover { color: … } }
sm: md: lg: xl: = from that width uphidden lg:flexdisplay: 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).

Lab · the class reader

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.

Frame 7 · our code

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.

  1. npm create vite@latest dev-stack -- --template react-ts: create is init, which runs the package create-vite. It writes the project folder and its package.json (real output below).
  2. npm install: Node runs npm, which asks the registry about every dependency (135 requests in our capture) and unpacks them into node_modules, recording exact versions in package-lock.json.
  3. npm install tailwindcss @tailwindcss/vite daisyui and two lines in vite.config.ts and index.css: Tailwind becomes a Vite plugin, and daisyUI a Tailwind plugin.
  4. npm run dev: npm reads "dev": "vite", puts node_modules/.bin on PATH, and Node starts Vite's dev server on localhost:5173 with the react() and tailwindcss() plugins loaded.
  5. 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.
  6. 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).
  7. Type checking, alongside: the editor runs TypeScript and underlines errors in red. Vite ignores types.
  8. npm run build: tsc -b stops on any type error, then Vite bundles and minifies into dist/ (the real output is in Frame 4).
  9. Deploy: Netlify or Vercel serves dist/. From then on, only users' browsers run our code.
npm create vite@9.2.1 dev-stack -- --template react-tsreal output

    
npm ls --depth=0 · after adding Tailwind and daisyUIreal output

    
Remembering it

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.

Box12345
Review again afternext session (daily)2 days4 days8 days16 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 rememberingRemember the chunkAnd regenerate
Hundreds of Tailwind classesThe five rules: property-value, number × 4px, colour-shade, hover:, lg: = from that width upmt-8 = margin-top 32px; md:grid-cols-2 = two columns from 768px
Every npm command shapeProgram · command · arguments · options, separated by spaces; -- ends optionsAny command we have never seen, read word by word
What runs whereThe 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

ConfusionMemory hook
The two @s in @tailwindcss/vite@4.3.3Front @ 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 frameworkHollywood: "don't call us, we'll call you" is the framework speaking.
-DD for Dev: --save-dev, a tool we need only while developing.
npxx for execute: npx = npm exec (and npm x is a real alias).
Where -- goesThe double dash is a door: everything after it walks through to the next program.
Mobile-firstNo prefix = phones. A prefix = "from here up". lg: never means "only laptops".
Spacing numbersTimes four: p-4 = 16px, h-16 = 64px, gap-6 = 24px.
Hook in Vite vs ReactVite hooks are called on us; React hooks are called by us.
Vite and typesVite 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.

NeedLet the tool remember
The exact Tailwind classTailwind 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 structureEmmet: ul>li*5>a then Tab
A command's optionsnpm 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 commandsnpm run with no name lists every script in package.json
Which packages a project usesnpm 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

WhenWhat (about 10 to 15 minutes)
TodayRead 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 3Trainer, due cards only. Then explain one frame out loud. Use the command lab on every command we type for the assignment.
Days 4 to 7Trainer, 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 14Trainer, due cards (only a few now). Do the exam questions on paper and mark them honestly.
Whenever we stallMake a new card for the thing we just had to look up. Cards from our own mistakes are the most valuable ones.
Lab · the recall trainer

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.

Practice

Predict, then check

See it on our own machine

What to seeCommand (in the project folder)
Every request npm makes to the registrynpm install --loglevel http
What the registry knows about a packagenpm view react-toastify version dist-tags
The project's scriptsnpm run (no name)
Where commands like vite liveWindows: dir node_modules\.bin; macOS/Linux: ls -l node_modules/.bin
What the browser receives from ViteWith npm run dev running, open DevTools → Network, click App.tsx and read the Response
The CSS Tailwind wroteDevTools → Network → index.css, or select an element and read the Styles pane
A type check without buildingnpx tsc --noEmit (or npx tsc -b in a Vite project)

Exercises

  1. 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 with react-ts, then read create-vite's help to check.
  2. Run npm view react dist-tags. Which version would npm install react@next install today, and which would npm install react install? Why can they differ?
  3. 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.
  4. In the class lab, type p-0.3, p-1.25 and p-13. Which produce CSS, and which line of utilities.ts decides?
  5. Write a tiny Vite plugin with a transform(code, id) hook that logs id. Add it to vite.config.ts, load the page, and list which files pass through it, in which order.
Exam practice

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.

Self-marks stay on this page only; refreshing clears them.
Show how npm reads 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.

A student runs 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.

For 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).
ClassCSSPixels
px-5padding-inline: calc(var(--spacing) * 5)5 × 4 = 20px left and right
py-2.5padding-block: calc(var(--spacing) * 2.5)10px top and bottom
-mt-4margin-top: calc(var(--spacing) * -4)−16px
w-1/2width: 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.

Trace what happens from typing 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.

Compare a library and a framework using inversion of control, place React and Next.js, and contrast "hook" in Vite with "hook" in React.

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.

Design a two-week review plan for learning this vocabulary, and justify each choice with evidence from learning research.

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.

Reading list

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.

Sources

Original files and 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.