Set of tools to help you build cross-platform libraries from a single codebase
Vite plugin that abstracts out module resolutions so you can build for multiple platforms from a single codebase.
In it's simplest form, it takes module extensions and turns them into separate bundle.
Input | Output |
---|---|
|
|
This allows you to keep your code for multiple platforms in single codebase.
Example, You can create a component library that supports React and Vue both using-
- src
- Button.react.tsx
- Button.vue.tsx
- buttonUtils.ts
- buttonStyles.css
So your framework specific code stays in Button.react.tsx
and Button.vue.tsx
but both of them can import from same buttonStyles.css
and have common logic inside buttonUtils.ts
.
Checkout working examples below for better context.
-
or scaffold locally -
-
Scaffold an example server-edge-library
npx degit saurabhdaware/server-edge-library server-edge
-
Install Dependencies
cd server-edge pnpm install
-
Run the example of the library usage
cd packages/server-edge-app node --conditions=server run.js # Runs the example with server bundle node --conditions=edge run.js # Runs the example with edge bundle
Note
The example is kept simple for better understanding of code. If your usecase requires you to build final bundles of app, you can also pass these conditions from resolve.conditions method in vite.
GitHub | StackBlitz | Local Scaffold | |
---|---|---|---|
Server x Edge Library | saurabhdaware/server-edge-library | Open in StackBlitz | npx degit saurabhdaware/server-edge-library server-edge |
React x Vue Component Library | saurabhdaware/react-vue-component-library | Open in StackBlitz | npx degit saurabhdaware/react-vue-component-library react-vue |
React x React Native Library | saurabhdaware/react-rn-library | Open in StackBlitz | npx degit saurabhdaware/react-rn-library react-rn |
Mobile x Desktop Site | saurabhdaware/desktop-mobile-site | Open in StackBlitz | npx degit saurabhdaware/desktop-mobile-site desktop-mobile |
-
@cross-platform-tools/vite-plugin
package takes care of resolving extensions such as.server.ts
,.client.ts
,.xyz.ts
and creates final bundles such asdist/server/
,dist/client/
, ordist/xyz
. -
It also takes care of resolving extensions for tests when used with
vitest
. So you can define your platform-specific tests with.client.test.ts
,.server.test.ts
, etc. -
You can create build using
--mode development
flag of Vite to generate development build. When used withif (import.meta.env.MODE === 'development)
condition, it removes the development-specific code from production bundle. -
This plugin also takes care of basic things required to build a library such as type file generations for all platforms, base library configuration setup with vite, etc.
This section explains the steps required to set up a library from scratch. Check out the Getting Started for Quick Start guide.
npm i --save-dev @cross-platform-tools/vite-plugin
// vite.config.ts
import { defineConfig } from "vite";
import { viteCrossPlatform } from '@cross-platform-tools/vite-plugin';
export default defineConfig({
plugins: [
viteCrossPlatform({
// We call `vite build` multiple times for each platform
platform: process.env.PLATFORM,
// This can be any string that is used in filenames.
// E.g. something.server.ts and something.client.ts in this example
supportedPlatforms: ['server', 'client'],
lib: {
entryDir: 'src',
outDir: 'dist'
}
}),
]
});
// src/getData.server.ts
export const getData = () => 1234;
// src/getData.client.ts
export const getData = () => 4321;
// src/index.ts
export { getData } from './getData';
{
"scripts": "PLATFORM=server vite build && PLATFORM=client vite build",
"exports": {
"node": {
"default": "./dist/server/production/index.js",
"types": "./dist/server/types/index.d.ts"
},
"default": {
"default": "./dist/client/production/index.js",
"types": "./dist/client/types/index.d.ts"
}
}
}
You can follow the package.json exports documentation of node.js and export as per your use case.
Bundlers and tools have their own defined namespaces in exports from which they pick bundles. Such as React Native has react-native
namespace, while most bundlers use the browser
namespace for client bundling.
You can also define your custom namespace such as "xyz": "./path/to/bundle"
and use it with node --condition=xyz ./file.js
on consumer end or define it in your bundler (e.g. using resolve.conditions
)
Inspired from the setup of Razorpay's Design System Setup - Blade. Checkout The Sorcery of Building a Cross Platform Design System Architecture talk by @kamleshchandnani where he explains the architecture of Razorpay's Design System in the context of React x React Native 🤗
Like my work? You can star this repo or you can sponsor me from GitHub Sponsors @saurabhdaware ⭐️