Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how to build a library for SolidJS #97

Open
ChristophP opened this issue Mar 27, 2023 · 3 comments
Open

Document how to build a library for SolidJS #97

ChristophP opened this issue Mar 27, 2023 · 3 comments

Comments

@ChristophP
Copy link

My team had difficulties in building a component library for SolidJS because we couldn't find any documentation for it.

We have a monorepo where one package is an application which should use components from another package in the repo.
Each has their own build. We use Vite and vite-plugin-solid to build.

vite-plugin-solid seems to use the package.json exports field with a custom import condition solid.

We couldn't find any documentation about this behavior (if there is one please let me know). Only after scouring through this plugin's source code and the NodeJs docs and looking at existing SolidJS component libs we got some idea about how this works.

IMO it the documentation should answer the following questions:

  • How to build a library for solid JS components?
  • How to use package.json exports to export SolidJS code
  • What's the difference in libraries compared to VDOM Frameworks like React, Vue etc (build done by the application vs build done in each package)
  • What's the recommended approach for building in combination with Typescript and/or some CSS preprocessing.
@itsyoboieltr
Copy link

It would be great to have some documentation on this, because I cannot get my solidjs component library to work when bundled. If I copy the source code into my project, it works, if I import the bundled version, it shows very weird behaviour.

@itsyoboieltr
Copy link

I could figure out how to bundle my library, but it turned out to be pretty complicated imo:

import { defineConfig } from 'vite';
import solid from 'vite-plugin-solid';
import dts from 'vite-plugin-dts';
import { glob } from 'glob';
import { readFile, writeFile } from 'fs/promises';
import { sep } from 'path';

const getEntries = async () => {
  const entries = (await glob('src/**/index.@(ts|tsx)')).reduce(
    (acc, entry) => {
      const name = entry
        .substring(0, entry.lastIndexOf('.'))
        .replace(`src${sep}`, '')
        .replace(`stories${sep}`, '');
      return { ...acc, [name]: entry };
    },
    {} as Record<string, string>,
  );
  return entries;
};

interface Metadata {
  main: string;
  module: string;
  types: string;
  browser: {};
  exports: {
    [key: string]: {
      import: {
        types: string;
        default: string;
      };
    };
  };
  typesVersions: {
    '*': {
      [key: string]: string[];
    };
  };
}

const entries = await getEntries();

export default defineConfig({
  plugins: [
    solid(),
    dts({
      include: 'src/**/index.@(ts|tsx)',
      beforeWriteFile: (filePath, content) => {
        return { filePath: filePath.replace('stories/', ''), content };
      },
      afterBuild: async () => {
        const metadata: Metadata = {
          main: './dist/index.js',
          module: './dist/index.js',
          types: './dist/index.d.ts',
          browser: {},
          exports: {
            '.': {
              import: {
                types: './dist/index.d.ts',
                default: './dist/index.js',
              },
            },
          },
          typesVersions: { '*': {} },
        };
        for (const key of Object.keys(entries)) {
          if (key === 'index') continue;
          const name = key.replace(`${sep}index`, '');
          metadata.exports[`./${name}`] = {
            import: {
              types: `./dist/${name}/index.d.ts`,
              default: `./dist/${name}/index.js`,
            },
          };
          metadata.typesVersions['*'][name] = [`./dist/${name}/index.d.ts`];
        }
        const packageJson = JSON.parse(await readFile('package.json', 'utf-8'));
        await writeFile(
          'package.json',
          `${JSON.stringify({ ...packageJson, ...metadata }, null, 2)}\n`,
          'utf-8',
        );
      },
    }),
  ],
  server: { port: 3000 },
  build: {
    target: 'esnext',
    lib: { formats: ['es'], entry: entries },
    rollupOptions: { external: ['solid-js'] },
  },
});

This includes everything, presenting a fully end-to-end build solution:

  1. Collect the paths of all index.ts and index.tsx files using a glob (because I only want to expose these files in the bundle). Please keep in mind that I used storybook for the development of my component library, so there is some storybook specific stuff stories/ which you normally would probably not need.
  2. Generate types for the index.ts and index.tsx files using vite-plugin-dts.
  3. After the build, write the entrypoints to the package.json.

@ChristophP
Copy link
Author

I had the same experience at work. The amount of work you have to do to get the build right including typings, CJS/ESM options and client/server bundles is quite heavy.
This helped some but still there's quite a bit if configuration to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants