@stagewise/plugin-builder
Version:
Build stagewise plugins with ease.
132 lines (128 loc) • 4.82 kB
JavaScript
// We're just building a default build script for plugins.
// People can extend or adapt the build process of the main plugin by simply passing in a vite config as a paremter
import { build } from 'vite';
import react from '@vitejs/plugin-react-swc';
import preserveDirectives from 'rollup-preserve-directives';
import { resolve } from 'node:path';
import fs from 'node:fs';
const mode = process.argv[2] || 'production';
export default async function buildPlugin(projectDir, config) {
const __dirname = projectDir;
// In the first build step, we're just bundling up the plugin itself to get one file that exports the plugin.
// This build step can be adapted by passing in a vite config as a parameter.
// We deep merge both configs and then build the plugin.
// This kind of config can be overridden by the user by passing in a config object.
const baseConfig = {
mode: mode,
resolve: {
alias: {
'@': resolve(__dirname, 'src'),
},
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json'],
},
esbuild: {
minifyIdentifiers: mode === 'production',
treeShaking: mode === 'production',
},
build: {
rollupOptions: {
treeshake: mode === 'production',
},
minify: mode === 'production',
cssMinify: mode === 'production',
},
};
// These parts of the config are forced by the plugin builder.
const forcedConfig = {
plugins: [react(), preserveDirectives()], // Cast to workaround Rollup version compatibility
resolve: {
mainFields: ['module', 'main'],
},
build: {
outDir: 'tmp/plugin',
lib: {
entry: resolve(process.cwd(), 'src/index.tsx'),
name: 'StagewisePluginExample',
fileName: (format) => `index.${format}.js`,
formats: ['es', 'cjs'],
},
rollupOptions: {
output: {
manualChunks: undefined,
preserveModules: false,
},
external: [
'react',
'react-dom',
'react-dom/client',
'react/jsx-runtime',
'@stagewise/toolbar',
'@stagewise/toolbar/plugin-ui',
],
},
},
optimizeDeps: {
esbuildOptions: {
mainFields: ['module', 'main'],
},
},
};
const mergedConfig = config
? mergeDeep(config, baseConfig, forcedConfig)
: mergeDeep(baseConfig, forcedConfig);
await build(mergedConfig);
// Read the resulting file
const pluginContent = fs.readFileSync(resolve(__dirname, 'tmp/plugin/index.es.js'), 'utf8');
if (!fs.existsSync(resolve(__dirname, 'dist'))) {
fs.mkdirSync(resolve(__dirname, 'dist'), { recursive: true });
}
// create .js and .d.ts files in the output directory
// Write CJS file
fs.writeFileSync(resolve(__dirname, 'dist/index.cjs.js'), getLoaderContentCjs(pluginContent), 'utf8');
// Write ESM file
fs.writeFileSync(resolve(__dirname, 'dist/index.es.js'), getLoaderContentEs(pluginContent), 'utf8');
// Write type definitions file
fs.writeFileSync(resolve(__dirname, 'dist/index.d.ts'), loaderTypeContent, 'utf8');
}
function isObject(item) {
return item && typeof item === 'object' && !Array.isArray(item);
}
function mergeDeep(target, ...sources) {
if (!sources.length)
return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (Object.hasOwn(source, key)) {
if (isObject(source[key])) {
if (!target[key])
Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
}
else {
Object.assign(target, { [key]: source[key] });
}
}
}
}
return mergeDeep(target, ...sources);
}
const getLoaderContentEs = (pluginContent) => `'use client'
const plugin = {
mainPlugin: ${JSON.stringify(pluginContent)},
loader: true
}
export default plugin;
`;
const getLoaderContentCjs = (pluginContent) => `"use client";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const plugin = {
mainPlugin: ${JSON.stringify(pluginContent)},
loader: true
}
exports.default = plugin;
`;
const loaderTypeContent = `import type { ToolbarPluginLoader } from '@stagewise/toolbar'
declare const plugin: ToolbarPluginLoader;
export default plugin;
`;