Bring an Ink tool over
You'll move a small Ink notes browser into FSR with fsr migrate. It rewrites Node's modules, gray-matter and pino onto FSR's members and turns the props render passed into a loader. The tutorials before this one built a terminal app from scratch. This one starts from a tool that already runs under Node.
Before you start
Install from crates.io. You need no Node and no package manager.
cargo install snapfire_compiler
cargo install snapfire_fsr_cli
fsr --version
Every command and screenshot on this page was captured with fsr 0.x.
The tool#
The notes browser lists the Markdown files in a directory, reads each one's title from its front matter and shows the selected note's text. It has the usual shape of an Ink CLI. An entry works out its settings and calls render. A component does the rest.
#!/usr/bin/env node
;
;
;
;
;
;
;;
;
;
;
;
;
;
;
As it stands, FSR refuses app.tsx: it imports node:fs and node:path and two npm packages the build does not follow.
Migrate it#
fsr migrate takes the tool's directory and a new one to write. It needs no node_modules:
It found Ink in package.json and wrote an FSR project: the files under source/ went to app/src/ and the entry that called render was left behind. ink, react and the packages FSR provides are not reported as dependencies to add. gray-matter and pino are not either, since both were rewritten.
The entry becomes a page and a loader#
app/routes/page.tsx renders the component the entry rendered:
;
;
render passed App a prop, so the statements before it became app/routes/page.loader.ts, with each prop a field of what load returns:
;
;
;
process.env.NOTES_DIR became system.env("NOTES_DIR") and os.homedir() became system.home(). This is the loader from 620: it runs once when the terminal opens and anything after -- on the command line arrives as params.
Node's modules become FSR's members#
app/src/app.tsx keeps its shape. Its imports and the calls that used them are rewritten:
;
;
;
;
;
;
The component below readNotes is unchanged. file is imported as std_file because the file already uses file as a field name. Each rewrite maps one thing onto one member:
| Under Node | In FSR |
|---|---|
fs.existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, statSync | file.exists, read, write, makeDir, list, stat |
path.join and the rest of path | path.join and the rest of path |
os.homedir(), os.platform(), os.tmpdir(), os.EOL | system.home(), platform(), tempDir(), lineEnd() |
process.env.NAME, process.platform | system.env("NAME"), system.platform() |
spawn from child_process | system.spawn |
matter(source, options) from gray-matter | text.frontMatter(source, options) |
matter.stringify({ content }, data, options) | text.withFrontMatter(content, data, options) |
a pino(options) or winston createLogger(options) logger | log, with the message first and the fields second |
The encoding argument to readFileSync is dropped, since file.read always reads text. The logger's options are dropped with a note, because the host's tracing filter decides which lines are written. A file that uses gray-matter or a logger some other way, matter.read(path) or logger.flush() for example, keeps its import and gets a note naming the line.
Run it#
Make a notes directory with two notes:
Then run the migrated app from its project directory:
The header names the directory and counts two notes. The list on the left shows Ideas and Release checklist by their front matter titles and the pane on the right shows the selected note's text. Up and down move between them and q quits. NOTES_DIR=~/work-notes fsr dev app --terminal reads another directory, through the same system.env the loader now calls.