diff --git a/rfcs/0054-vite.md b/rfcs/0054-vite.md new file mode 100644 index 0000000..df41e3b --- /dev/null +++ b/rfcs/0054-vite.md @@ -0,0 +1,172 @@ +- Start Date: 2023-09-08 +- RFC PR: https://github.com/strapi/rfcs/pull/54 + +# Summary + +The following RFC compliments POC work already done prior to writing, in the RFC we look at improving the internal / external experience of building packages/plugins and the Strapi admin app via CLI commands. Not only this, but we address user issues around not being able to run the admin panel within their existing web applications nor being able to run a Strapi app with a plug-n-play package manager. All of the above contributes to a move to support Vite in v4 alongside the already existing webpack build option we currently use. + +# Motivation + +Webpack is powerful tool, it’s seen a lot of transformation in the javascript ecosystem and as such it’s architecture is subject to the design decisions of those times. However, in the modern day where _some_ javascript tooling has opted for native binaries e.g. using Rust or GO. Webpack has fallen behind in both speed and simplicity. In addition, webpack configurations are complicated and _extremely extensible_ but don’t provide “good defaults” which results in more developer time to maintain and improve such configurations. + +Introducing Vite (pronounced `veet`). With all applications you have two sides – development and production. The development server is not only valuable to engineers at Strapi but also external developers working on their own Strapi apps/plugins. Vite uses `esbuild` to pre-bundle dependencies which improves typical speed by 10-100x. For more information on the development server see [this article](https://vitejs.dev/guide/why.html#slow-server-start). Regarding production, Vite provides a strong default rollup configuration for both applications and libraries, this however, does not mean we don’t have control over the configuration as it is still 100% configurable should we require it. + +# Detailed Proposal + +This proposal is based of the exploration of the POC found [here](https://github.com/strapi/strapi/pull/17085), and uses code-samples from said POC to illustrate how we _could_ move forward with the ideas proposed. This scope is intentionally kept relatively small focussing primarily on building a foundation for future improvements we can do with Vite such as SSR. + +## Moving building scripts out of admin + +Right now the `@strapi/admin` package exports a series of commands that can be ran to build/watch and clean the admin panel build, this puts all the pressure of the pipeline on the package which isolates a portion of code which could be used more generically. Therefore, we should focus on having no default export for the package & instead using export maps to distinguish between `admin` and `server` allowing us to export FE focussed code from the admin package to be shared where applicable (this _could_ lead to the deprecation of the `admin-test-utils`). The build pipeline would move to the CLI package as explained below. + +## Generic building scripts + +We want to create more plugin orientated CLI commands one in particular would be around “building” and “watching” your plugin files during development, the work has already begun to implement such experimental apis [here](https://github.com/strapi/strapi/pull/17747). It therefore becomes apparent the need to have more “generic” building functions (or tasks) that can be combined and configured to our needs, a simple example of this in relation to vite could look like this: + +```tsx +import { build } from "vite"; + +const viteBuildTask = async (ctx: BuildContext, task: ViteBuildTask) => { + await build({ + root: ctx.cwd, + cacheDir: "node_modules/.strapi/vite", + configFile: false, + ...task, + build: { + outDir: path.resolve(ctx.distDir, "dist"), + ...task.build, + }, + }); +}; +``` + +Each task would receive a build context, whilst the task is dependant on the type being called: + +```tsx +import { ParsedCommandLine } from "typescript"; +import { InlineConfig } from "vite"; + +interface BuildContext { + cwd: string; + distDir: string; + external: string[]; // the external dependencies of the package + pkg: PackageJson; // useful for understanding the expected exports etc. + ts: { + config: Pick; + configPath: string; + }; +} + +interface ViteBuildTask + extends Omit { + build: Omit; +} +``` + +The benefits are we’re applying some standardised formula to the this build task e.g. defining a custom cache directory for vite to avoid any conflicts and a predetermined root / outDir. The rest would most likely be filed in by the local config (outlined in the aforementioned RFC). + +This concept would extend to other tasks such as watching but also to other build tools like webpack, rollup, parcel – whichever we choose to support. It also means we can in the future create experimental support of new builders without conflicting with a stable process. I’d imagine this same system would work for TS compiling of the server and potentially wrapping all the node files into one which avoids developers from importing directly to a path instead of consuming our exposed public API which has previously lead to bugs. + +## Building the admin app + +As described above with the removal of the build scripts from the `admin` package we’re left without a way to access the `index.html` and rendering of the client app. In conjunction a major source of issues for our users comes from a mismatch between the `node_modules` and the strapi-created `.cache` folder, not only this but the way the `.cache` folder is setup causes errors with plug-n-play package managers such as `pnpm` and `yarn@3`. Moving forward we will remove the `.cache` folder completely moving to a more “library” approach. This initially should solve issues with package managers and the hope is to improve the upgrade process for our users. + +When running the `build` command via the Strapi CLI we will create a client folder – `.strapi/client` using the subfolder leaves room to grow the folder should we need it without breaking changes / expectations and isolates the client. The next step is to generate the `index.html` file, in the POC this is done by writing to the FS with string manipulation however, when we have moved to bundling the packages / plugins we will be able to export a `DefaultDocument` react component and use that to render the `index.html`: + +```tsx +import type { DocumentProps } from "@strapi/admin/admin"; + +const getDocumentComponent = () => { + const { DefaultDocument } = require("@strapi/admin/admin"); + + return DefaultDocument; +}; + +interface GetDocumentHTMLArgs { + props: DocumentProps; +} + +const getDocumentHTML = ({ props }: GetDocumentHTMLArgs) => { + const Document = getDocumentComponent(); + + const result = renderToStaticMarkup( + createElement(Document, { ...DEFAULT_PROPS, ...props }) + ); + + return `${result}`; +}; +``` + +The interesting bit of the above with the document is: + +1. we can now give users the ability to render their own document component if they so choose to +2. Using react means we can improve the document experience e.g. supplying a “no javascript” component and even our own error overlay similar to `next.js`. + +Finally, we need our entry JS file, in Vite you reference this directly in the Document component – `