Skip to content

Commit

Permalink
Add build time tooling to bundle stories
Browse files Browse the repository at this point in the history
  • Loading branch information
Etheryte committed Aug 27, 2024
1 parent a0c9ac8 commit 053d67c
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 5 deletions.
99 changes: 99 additions & 0 deletions web/html/src/build/plugins/generate-stories-plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
const fs = require("fs").promises;
const path = require("path");

// eslint-disable-next-line no-unused-vars
const webpack = require("webpack");

/** Automatically gather all imports for story files */
module.exports = class GenerateStoriesPlugin {
didApply = false;
outputFile = undefined;

constructor({ outputFile }) {
if (!outputFile) {
throw new Error("GenerateStoriesPlugin: `outputFile` is not set");
}
this.outputFile = outputFile;
}

/**
* @param {webpack.Compiler} compiler
*/
apply(compiler) {
// See https://webpack.js.org/api/compiler-hooks/#hooks
compiler.hooks.watchRun.tapAsync("GenerateStoriesPlugin", async (params, callback) =>
this.beforeOrWatchRun(params, callback)
);
compiler.hooks.beforeRun.tapAsync("GenerateStoriesPlugin", async (params, callback) =>
this.beforeOrWatchRun(params, callback)
);
}

/**
*
* @param {webpack.Compiler} compiler
* @param {Function} callback
*/
async beforeOrWatchRun(compiler, callback) {
if (this.didApply) {
callback();
return;
}
this.didApply = true;

/** Source directory for the compilation, an absolute path to `/web/html/src` */
const webHtmlSrc = compiler.context;
if (!this.outputFile.startsWith(webHtmlSrc)) {
throw new RangeError("GenerateStoriesPlugin: `outputFile` is outside of the source code directory");
}

const files = await fs.readdir(webHtmlSrc, { recursive: true });
const storyFilePaths = files
.filter(
(item) => !item.startsWith("node_modules") && (item.endsWith(".stories.ts") || item.endsWith(".stories.tsx"))
)
.sort();

const stories = storyFilePaths.map((filePath) => {
const safeName = this.wordify(filePath);
// We use the parent directory name as the group name
const groupName = path.dirname(filePath).split("/").pop() ?? "Unknown";
return storyTemplate(filePath, safeName, groupName);
});

const output = fileTemplate(stories.join(""));
await fs.writeFile(this.outputFile, output, "utf-8");
console.log(`GenerateStoriesPlugin: wrote ${storyFilePaths.length} stories to ${this.outputFile}`);
callback();
}

wordify(input) {
return input.replace(/[\W_]+/g, "_");
}
};

const storyTemplate = (filePath, safeName, groupName) =>
`
import ${safeName}_component from "${filePath}";
import ${safeName}_raw from "${filePath}?raw";
export const ${safeName} = {
path: "${filePath}",
title: "${path.basename(filePath)}",
groupName: "${groupName}",
component: ${safeName}_component,
raw: ${safeName}_raw,
};
`;

const fileTemplate = (content) =>
`
/**
* NB! This is a generated file!
* Any changes you make here will be lost.
* See: web/html/src/build/plugins/generate-stories-plugin.js
*/
/* eslint-disable */
${content}
`.trim();
31 changes: 26 additions & 5 deletions web/html/src/build/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const autoprefixer = require("autoprefixer");

const GenerateStoriesPlugin = require("./plugins/generate-stories-plugin");

const DEVSERVER_WEBSOCKET_PATHNAME = "/ws";

module.exports = (env, argv) => {
Expand Down Expand Up @@ -82,6 +84,10 @@ module.exports = (env, argv) => {
new MiniCssExtractPlugin({
chunkFilename: "css/[name].css",
}),
new GenerateStoriesPlugin({
inputDir: path.resolve(__dirname, "../manager"),
outputFile: path.resolve(__dirname, "../manager/storybook/stories.generated.ts"),
}),
];

if (isProductionMode) {
Expand Down Expand Up @@ -118,15 +124,30 @@ module.exports = (env, argv) => {
publicPath: "/",
hashFunction: "md5",
},
// context: __dirname,
node: {
__filename: true,
__dirname: true,
},
devtool: isProductionMode ? "source-map" : "eval-source-map",
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
},
oneOf: [
{
resourceQuery: /raw/,
type: "asset/source",
},
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: [
{
loader: "babel-loader",
},
],
},
],
},
{
// Stylesheets that are imported directly by components
Expand Down

0 comments on commit 053d67c

Please sign in to comment.