Skip to content

Commit

Permalink
feat(strapi): add plugin:watch command (#18462)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuaellis committed Oct 18, 2023
1 parent d4dddeb commit 5d12a35
Show file tree
Hide file tree
Showing 14 changed files with 232 additions and 83 deletions.
38 changes: 38 additions & 0 deletions docs/docs/docs/01-core/strapi/commands/plugin/00-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,41 @@ This is an experimental API that is subject to change at any moment, hence why i
## Available Commands

- [plugin:build](build) - Build a plugin for publishing
- [plugin:watch](watch) - Watch & compile a plugin in local development

## Setting up your package

In order to build/watch/check a plugin you need to have a `package.json` that must contain the following fields:

- `name`
- `version`

In regards to the export keys of your package.json because a plugin _typically_ has both a server and client
side output we recommend doing the following:

```json
{
"name": "@strapi/plugin",
"version": "1.0.0",
"exports": {
"./strapi-admin": {
"types": "./dist/admin/index.d.ts",
"source": "./admin/src/index.ts",
"import": "./dist/admin/index.mjs",
"require": "./dist/admin/index.js",
"default": "./dist/admin/index.js"
},
"./strapi-server": {
"types": "./dist/server/index.d.ts",
"source": "./server/src/index.ts",
"import": "./dist/server/index.mjs",
"require": "./dist/server/index.js",
"default": "./dist/server/index.js"
},
"./package.json": "./package.json"
}
}
```

We don't use `main`, `module` or `types` on the root level of the package.json because of the aforementioned reason (plugins don't have one entry).
If you've not written your plugin in typescript, you can omit the `types` value of an export map. This is the minimum setup required to build a plugin.
80 changes: 7 additions & 73 deletions docs/docs/docs/01-core/strapi/commands/plugin/01-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ tags:
---

The `plugin:build` command is used to build plugins in a CJS/ESM compatible format that can be instantly published to NPM.
This is done by looking at the export fields of a package.json e.g. `main`, `module`, `types` and `exports`. By using the
exports map specifically we can build dual plugins that support a server & client output.
This is done by using `pack-up` underneath and a specific configuration, for this command we _do not_ look for a `packup.config` file.

## Usage

Expand All @@ -24,86 +23,21 @@ strapi plugin:build
Bundle your strapi plugin for publishing.

Options:
-y, --yes Skip all confirmation prompts (default: false)
--force Automatically answer "yes" to all prompts, including potentially destructive requests, and run non-interactively.
-d, --debug Enable debugging mode with verbose logs (default: false)
--silent Don't log anything (default: false)
--sourcemap produce sourcemaps (default: false)
--minify minify the output (default: false)
-h, --help Display help for command
```
## Setting up your package

In order to build a plugin you need to have a `package.json` that must contain the following fields:

- `name`
- `version`

In regards to the export keys of your package.json because a plugin _typically_ has both a server and client
side output we recommend doing the following:

```json
{
"name": "@strapi/plugin",
"version": "1.0.0",
"exports": {
"./strapi-admin": {
"types": "./dist/admin/index.d.ts",
"source": "./admin/src/index.ts",
"import": "./dist/admin/index.mjs",
"require": "./dist/admin/index.js",
"default": "./dist/admin/index.js"
},
"./strapi-server": {
"types": "./dist/server/index.d.ts",
"source": "./server/src/index.ts",
"import": "./dist/server/index.mjs",
"require": "./dist/server/index.js",
"default": "./dist/server/index.js"
},
"./package.json": "./package.json"
}
}
```

We don't use `main`, `module` or `types` on the root level of the package.json because of the aforementioned reason (plugins don't have one entry).
If you've not written your plugin in typescript, you can omit the `types` value of an export map. This is the minimum setup required to build a plugin.

## How it works
The command sequence can be visualised as follows:
- Load package.json
- Validate that package.json against a `yup` schema
- Validate the ordering of an export map if `pkg.exports` is defined
- Create a build context, this holds information like:
- The transpilation target
- The external dependencies (that we don't want to bundle)
- Where the output should go e.g. `dist`
- The exports we're about to use to create build tasks
- Create a list of build tasks based on the `exports` from the build context, these can currently either be `"build:js"` or `"build:dts"`
- Pass the build task to a specific task handler e.g. `vite` or `tsc`
- Create a set of "bundles" to build ignoring the package.json exports map that is _specifically_ set up for strapi-plugins.
- Pass the created config to `pack-up`'s build API.
- Finish

## Transpilation target

There are three different runtimes available for plugins:

- `node` which equates to a `node18` target
- `web` which equates to a `esnext` target
- `*` (universal) which equates to `["last 3 major versions", "Firefox ESR", "last 2 Opera versions", "not dead", "node 18.0.0"]`

The `node` and `web` targets are specifically used for the export maps with they keys `./strapi-server` and `./strapi-admin` respectively.
Any other export map values will be transpiled to the universal target. The universal target can be overwritten by adding the `browserslist`
key to your `package.json` (seen below):

```json
{
"name": "@strapi/plugin",
"version": "1.0.0",
"browserslist": [
"last 3 major versions",
"Firefox ESR",
"last 2 Opera versions",
"not dead",
"node 16.0.0"
]
}
```
40 changes: 40 additions & 0 deletions docs/docs/docs/01-core/strapi/commands/plugin/02-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
title: plugin:build
description: An in depth look at the plugin:build command of the Strapi CLI
tags:
- CLI
- commands
- plugins
- building
---

The `plugin:watch` command is used to watch plugin source files and compile them to production viable assets in real-time.
This is done by using `pack-up` underneath and a specific configuration, for this command we _do not_ look for a `packup.config` file.

## Usage

```bash
strapi plugin:watch
```

### Options

```bash
Watch & compile your strapi plugin for local development.

Options:
-d, --debug Enable debugging mode with verbose logs (default: false)
--silent Don't log anything (default: false)
-h, --help Display help for command
```
## How it works
The command sequence can be visualised as follows:
- Load package.json
- Validate that package.json against a `yup` schema
- Validate the ordering of an export map if `pkg.exports` is defined
- Create a set of "bundles" to build ignoring the package.json exports map that is _specifically_ set up for strapi-plugins.
- Pass the created config to `pack-up`'s watch API.
- Run's indefinitely
2 changes: 2 additions & 0 deletions docs/docs/docs/05-utils/pack-up/01-commands/02-build.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ build();

```ts
interface BuildOptions {
configFile: false;
config?: Config;
cwd?: string;
debug?: boolean;
minify?: boolean;
Expand Down
2 changes: 2 additions & 0 deletions docs/docs/docs/05-utils/pack-up/01-commands/04-watch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ watch();

```ts
interface WatchOptions {
configFile: false;
config?: Config;
cwd?: string;
debug?: boolean;
silent?: boolean;
Expand Down
12 changes: 12 additions & 0 deletions docs/docs/docs/05-utils/pack-up/02-config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ interface Config {
* Whether to minify the output or not.
*/
minify?: boolean;
/**
* Instead of creating as few chunks as possible, this mode
* will create separate chunks for all modules using the original module
* names as file names
*/
preserveModules?: boolean;
/**
* Whether to generate sourcemaps for the output or not.
*/
Expand All @@ -56,13 +62,19 @@ interface Config {
* Node.js workers and you want them to be transpiled for the node environment.
*/
runtime?: Runtime;
/**
* path to the tsconfig file to use for the bundle.
*/
tsconfig?: string;
}

interface ConfigBundle {
source: string;
import?: string;
require?: string;
runtime?: Runtime;
tsconfig?: string;
types?: string;
}

type Runtime = '*' | 'node' | 'web';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default async ({ force, ...opts }: ActionOptions) => {
* Notify users this is an experimental command and get them to approve first
* this can be opted out by setting the argument --yes
*/
await notifyExperimentalCommand({ force });
await notifyExperimentalCommand('plugin:build', { force });

const cwd = process.cwd();

Expand Down
103 changes: 103 additions & 0 deletions packages/core/strapi/src/commands/actions/plugin/watch/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import boxen from 'boxen';
import chalk from 'chalk';
import { ConfigBundle, WatchCLIOptions, watch } from '@strapi/pack-up';
import { notifyExperimentalCommand } from '../../../utils/helpers';
import { createLogger } from '../../../utils/logger';
import { Export, loadPkg, validatePkg } from '../../../utils/pkg';

interface ActionOptions extends WatchCLIOptions {
force?: boolean;
}

export default async ({ force, ...opts }: ActionOptions) => {
const logger = createLogger({ debug: opts.debug, silent: opts.silent, timestamp: false });
try {
/**
* Notify users this is an experimental command and get them to approve first
* this can be opted out by setting the argument --yes
*/
await notifyExperimentalCommand('plugin:watch', { force });

const cwd = process.cwd();

const pkg = await loadPkg({ cwd, logger });
const pkgJson = await validatePkg({ pkg });

if (!pkgJson.exports['./strapi-admin'] && !pkgJson.exports['./strapi-server']) {
throw new Error(
'You need to have either a strapi-admin or strapi-server export in your package.json'
);
}

const bundles: ConfigBundle[] = [];

if (pkgJson.exports['./strapi-admin']) {
const exp = pkgJson.exports['./strapi-admin'] as Export;

const bundle: ConfigBundle = {
source: exp.source,
import: exp.import,
require: exp.require,
runtime: 'web',
};

if (exp.types) {
bundle.types = exp.types;
// TODO: should this be sliced from the source path...?
bundle.tsconfig = './admin/tsconfig.build.json';
}

bundles.push(bundle);
}

if (pkgJson.exports['./strapi-server']) {
const exp = pkgJson.exports['./strapi-server'] as Export;

const bundle: ConfigBundle = {
source: exp.source,
import: exp.import,
require: exp.require,
runtime: 'node',
};

if (exp.types) {
bundle.types = exp.types;
// TODO: should this be sliced from the source path...?
bundle.tsconfig = './server/tsconfig.build.json';
}

bundles.push(bundle);
}

await watch({
cwd,
configFile: false,
config: {
bundles,
dist: './dist',
/**
* ignore the exports map of a plugin, because we're streamlining the
* process and ensuring the server package and admin package are built
* with the correct runtime and their individual tsconfigs
*/
exports: {},
},
...opts,
});
} catch (err) {
logger.error(
'There seems to be an unexpected error, try again with --debug for more information \n'
);
if (err instanceof Error && err.stack) {
console.log(
chalk.red(
boxen(err.stack, {
padding: 1,
align: 'left',
})
)
);
}
process.exit(1);
}
};
17 changes: 17 additions & 0 deletions packages/core/strapi/src/commands/actions/plugin/watch/command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { StrapiCommand } from '../../../types';
import { runAction } from '../../../utils/helpers';
import action from './action';

/**
* `$ strapi plugin:build`
*/
const command: StrapiCommand = ({ command }) => {
command
.command('plugin:watch')
.description('Watch & compile your strapi plugin for local development.')
.option('-d, --debug', 'Enable debugging mode with verbose logs', false)
.option('--silent', "Don't log anything", false)
.action(runAction('plugin:watch', action));
};

export default command;
2 changes: 2 additions & 0 deletions packages/core/strapi/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import uninstallCommand from './actions/uninstall/command';
import versionCommand from './actions/version/command';
import watchAdminCommand from './actions/watch-admin/command';
import buildPluginCommand from './actions/plugin/build-command/command';
import watchPluginCommand from './actions/plugin/watch/command';

const strapiCommands = {
createAdminUser,
Expand Down Expand Up @@ -58,6 +59,7 @@ const strapiCommands = {
versionCommand,
watchAdminCommand,
buildPluginCommand,
watchPluginCommand,
} as const;

const buildStrapiCommand = (argv: string[], command = new Command()) => {
Expand Down

0 comments on commit 5d12a35

Please sign in to comment.