plop
plop copied to clipboard
Support TypeScript usage without compile step
We need help testing this feature: https://github.com/plopjs/plop/issues/297#issuecomment-1707715626
Today, we support:
- ESM
.jsplopfile - ESM
.mjsplopfile - CSM
.jsplopfile - CSM
.cjsplopfile
It would be nice if it also handled:
- TS
.tsplopfile
Without needing to add a compilation step. This would likely be done by:
- Adding in a
tscto output the single JS file to a temporary directory - Read from project's
tsconfig.jsonfile
If the user wants to use TS, would it be safe to assume they have already added tsc to the project? If so, could logic be something like... if plopfile.ts is found and tsc is available, process the plopfile via tsc?
This adds support for TS, without adding more dependencies that are likely not needed.
Thoughts?
Would that work if the plopfile.ts had imports?
Also, projects might have TS loaders hooked up, like when using deno or ts-node instead of plain node, right? 296 looks like he installed the ts-node loader, but wasn't actually registering it? I haven't tried it, but https://github.com/TypeStrong/ts-node#node-flags looks like it.
@amwmedia this would be nice, but unfortunately @Pike is quite right - imports would fail.
What's worse, one of the reasons that ts-node won't work for our usage right now is what appears to be a lack of full ESM support:
https://github.com/TypeStrong/ts-node/issues/1007
What's more - we wouldn't be able to get the return value from ts-node.
Instead, what I might suggest is that we use a Node loader from esbuild to run plop itself, similar to this:
https://github.com/unicorn-utterances/unicorn-utterances/blob/nextjs/package.json#L6
https://www.npmjs.com/package/esbuild-node-loader
This will handle .ts, .tsx, and other files OOTB for us, without having to change much. ESBuild is supported by huge projects like Vite.
I just looked into the viability to do this for real and there's a few minor problems we'll need to sort out first:
node-plop'stsconfigis broken (missing comma) innode_modules🤐 Sorry lol- We get the following error
Could not resolve "#ansi-styles" - To get our E2E tests working, we need something similar to RN's
LogBox.ignoreLogsbut forcli-testing-libraryto ignore theExperimentalWarning, which we expect to see but prints to stderr so as a result, fails
#2 is occurring because of our chalk dep reliance.
Luckily, this is already fixed for us in [email protected]. We'd simply need to make a PR to update esbuild-node-loader to fix this problem
To see the branch I started to POC this idea (just starting with e2e tests of running plop with the ESBuild:
https://github.com/plopjs/plop/tree/ts-no-build-step
I was able to get esbuild-node-loader working really well with the only issue I ran into was the missing comma which is now fixed (https://github.com/plopjs/node-plop/pull/215).
Here's an example repo I made that shows it working: https://github.com/CodyBrouwers/plop-esbuild-example
Happy to help any other way I can!
ts-node's ESM support should cover plop's use-cases, including skipping typechecking and using a native transpiler for speed. If users have configured it for other parts of their project, we'll pick up that config automatically, since it's the same. Let me know if you have questions.
Any updates on this?
tsx presents itself as an esbuild-based alternative to ts-node. It's a lot faster and skips typechecking. I suggest using either tsx or straight esbuild in plop to solve this.
so i've kinda worked around this with: "
yarn add --exact --dev ts-node
with a tsconfig.json of :
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node"
},
// Most ts-node options can be specified here using their programmatic names.
"ts-node": {
"swc": true,
"esm": true,
"pretty": true,
// It is faster to skip typechecking.
// Remove if you want ts-node to do typechecking.
"transpileOnly": true,
"files": true,
"compilerOptions": {
"module": "CommonJS"
// compilerOptions specified here will override those declared below,
// but *only* in ts-node. Useful if you want ts-node and tsc to use
// different options with a single tsconfig.json.
}
}
}
then in my justfile:
...
#
# Generator
#
alias gen := generate
alias g := generate
generate *ARGS:
yarn ts-node \
./node_modules/plop/bin/plop.js {{ARGS}}
...
and then the plopfile.ts:
import type { NodePlopAPI } from 'plop';
module.exports = function Plopfile(plop: NodePlopAPI) {
plop.setGenerator('test', {
prompts: [
{
type: 'confirm',
name: 'wantTacos',
message: 'Do you want tacos?',
},
],
actions: [],
});
};
resulting in:
I played around trying to make this work, and found success with swc compiler, since using tsc one was pretty slow.
With concurrently package in package.json - scripts:
"generate:plop": "concurrently -g --names \"swc plopfile\\,generate api\" -g \"npx swc ./.build/rtkGenerator/plopfile.ts -o ./.build/rtkGenerator/plopfile.js\" \"ts-node --transpileOnly --esm ./.build/rtkGenerator/run.ts\"",
run.ts
#!/usr/bin/env node
/* eslint-disable import/no-extraneous-dependencies */
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import minimist from 'minimist';
import { Plop, run } from 'plop';
const args = process.argv.slice(2);
const argv = minimist(args);
Plop.prepare(
{
cwd: argv?.cwd as string,
configPath: join(
dirname(fileURLToPath(import.meta.url)),
'plopfile.js',
),
},
// eslint-disable-next-line @typescript-eslint/no-misused-promises
(env) => Plop.execute(env, run),
);
Maybe someone here with more expertise can explain why when running plop with plopfile flag command, right after the compilation phase throws Plopfile not found!. Looks like configPath is null, but when i redo the comp + plop script with plopfile.js available before even next compilation starts it works fine. With wrapping plop seems to work fine, buts its an extra step.
I can say with a high degree of certainty, that the following setup works with the following conditions:
Package Versions
❯ pnpm ls plop typescript ts-node
Legend: production dependency, optional only, dev only
devDependencies:
plop 3.1.2
ts-node 10.9.1
typescript 5.2.2
./tsconfig.plop.json
{
"compilerOptions": {
"verbatimModuleSyntax": true,
"isolatedModules": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"module": "CommonJS",
"target": "ES2015",
"moduleResolution": "node",
"strict": true,
"noEmit": true,
"inlineSourceMap": true,
"inlineSources": true
},
"include": [
".plop/**/*.ts"
],
"exclude": [
"node_modules"
],
"ts-node": {
"transpileOnly": true,
"swc": true,
"experimentalSpecifierResolution": "node"
}
}
./plop/plopfile.ts
import type { NodePlopAPI } from 'plop';
module.exports = function (plop: NodePlopAPI) {
plop.setGenerator('test', {
description: 'This is loaded.',
prompts: [{
name: 'name',
message: 'What is your name?',
type: 'input',
}],
actions: [
{
type: 'add',
template: 'foo {{name}}',
path: 'foo-bar',
}
]
});
};
Running:
export TS_NODE_PROJECT=tsconfig.plop.json
export NODE_OPTIONS="--loader ts-node/esm --no-warnings"
plop --plopfile .plop/plopfile.ts
Also, just as an idea for adding support without disrupting the existing solution.
There could be a ts-plop CLI version (naming in the vein with ts-node).
This version would automatically load ts-node correctly, and otherwise would be identical (wrapper) over existing plop CLI.
See: https://typestrong.org/ts-node/docs/usage#programmatic
Would need to load it here (after shebang; line 1):
https://github.com/plopjs/plop/blob/f0742f29bb7084199e85ae430632035e92adfa27/packages/plop/bin/plop.js#L1-L2
Another alternative is to set it via shebang line:
https://typestrong.org/ts-node/docs/usage/#shebang
@moltar I appreciate you suggesting this, but I think the fix might be even "simpler" (conceptually) than that. See, we're using Gulp's Liftoff library to detect configuration files:
https://github.com/plopjs/plop/blob/main/packages/plop/src/plop.js#L5C22-L5C29 https://github.com/plopjs/plop/blob/main/packages/plop/src/plop.js#L18-L24
Which allows you to enable TS support through this mechanism:
https://github.com/gulpjs/liftoff/blob/466e17ba75d213c968caae003eac7d9180ba9cda/README.md?plain=1#L543
@crutchcorn Well, that is amazing! 😁 Going to mark my comment as hidden to avoid confusion.
You'll all be happy to know that I've just implemented this functionality in Plop v4:
https://github.com/plopjs/plop/pull/396
It turns out that it was even easier than anticipated from the easy method outlined in my last comment.
This should be solved in Plop 4.0!
https://github.com/plopjs/plop/releases/tag/plop%404.0.0
@crutchcorn
Thanks for the update! Just tried the example config from the tests and sadly could not get this to work: https://github.com/plopjs/plop/tree/main/packages/plop/tests/examples/typescript
Receiving the following error:
[PLOP] Something went wrong with reading your plop file TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for C:\dev\plopfile.ts
at new NodeError (node:internal/errors:399:5)
at Object.getFileProtocolModuleFormat [as file:] (node:internal/modules/esm/get_format:79:11)
at defaultGetFormat (node:internal/modules/esm/get_format:121:38)
at defaultLoad (node:internal/modules/esm/load:81:20)
at nextLoad (node:internal/modules/esm/loader:163:28)
at ESMLoader.load (node:internal/modules/esm/loader:605:26)
at ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:22)
at new ModuleJob (node:internal/modules/esm/module_job:64:26)
at ESMLoader.#createModuleJob (node:internal/modules/esm/loader:480:17)
at ESMLoader.getModuleJob (node:internal/modules/esm/loader:434:34) {
code: 'ERR_UNKNOWN_FILE_EXTENSION'
}
@nzacca thanks for the heads up. I can confirm that there does appear to be some issues with .ts support. I think this should be a trivial fix though. Investigating and apologies for the pre-emptive celebration - there must've been some mismatch with what I was testing against.
sigh It's a larger lift than I had originally thought. Apologies y'all - I'll still try to get a release out this week that adds TS support, but it's clear my testing wasn't thorough enough. I legit apologize.
For now I'll:
- Remove the mention on our changelog
- Work on Plop 4.1 that adds support
This has high priority given our intention to've launched this with 4.0
Hi all!
First, let me apologize for both:
- Blowing up your emails
- Prematurely celebrating TypeScript support in Plop
I think I've figured out how to get Plop 4.1 out the door with:
- CJS TypeScript support
- ESM TypeScript support
There were some technical challenges with the ESM support, but I believe I have them fixed. Not only do we have automated tests that I'm much more confident in, but I've also gone through and tested support with all 3 of the major package managers (Yarn, NPM, PNPM).
However, the method I've used to support all 4 ecosystem packaging solutions (ESM/CJS, TS ESM/CJS) is a fair amount more hacky than I feel comfortable blindly shipping.
So, I ask this of y'all: Please test the new @crutchcorn/[email protected] release I've just made specifically to test this.
I'm asking everyone to test not only:
- TypeScript ESM support
- TypeScript CJS support
But to even if you aren't using TypeScript in your projects, please install that version and test it against your Plopfile.
The linked PR is here with instructions on how to install with your package manager:
https://github.com/plopjs/plop/pull/397
When I feel comfortable with the number of replies I'll ship out the feature.
Thanks for your patience & support!
Might be worth looking at https://github.com/unjs/jiti - It's what Tailwind used to implement this for their config file CJS/ESM/TS support.
So far my testing hasn't shown any issues. Here's what I did.
- Uninstalled
plop.
npm un plop
- Installed your version.
npm i -D @crutchorn/[email protected]
- Renamed
plopfile.jstoplopfile.ts. - Renamed all other files from
.jsto.ts. - Changed my
plop.load()calls to use.tsextensions. For example:
await plop.load('./plop/generators/create.ts`)
- Running the
plopcommands works great!
Unfortunately I can't share the codebase with you for further inspection, because it's for work. But I'm happy to provide any snippets or information that you're interested in!
Hi @crutchcorn!
First of all, I want to thank you for introducing this amazing feature of building plop configuration files using TypeScript 🚀
I also migrated plopfile in my projects into TypeScript and I can also confirm that it's working fine for me without any issues 👍
I'm looking forward to the release of a stable version with this improvement included 🙂
@bradgarropy @SkrzypMajster thank you for your feedback! One last question before we merge and release - did Plop still work just fine for JS files as well? 😊
@crutchcorn Just tried @crutchcorn/plop with a plopfile.js and everything still works great!
@crutchcorn I also reverted back the plopfile.js file in my project with the @crutchorn/[email protected] package installed and it's working fine 👍
Hi @crutchcorn Works for me, Thx
It appears @crutchorn/plop is no longer available. Is that intended?
Hi @noahgregory-basis Global install worked for me. I use yarn:
yarn global add @crutchcorn/[email protected]
Hi @noahgregory-basis Global install worked for me. I use yarn:
yarn global add @crutchcorn/[email protected]
The package name fails to resolve. It does not appear to be public in the NPM registry (or Yarn registry for that matter).