✨ Cross Platform Tools ✨
Set of tools to help you build cross-platform libraries from a single codebase
@cross-platform-tools/vite-plugin
Vite plugin that abstracts out module resolutions so you can build for multiple platforms from a single codebase.
🤔 Umm... what is even this? what does it do exactly?
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.
🤝🏼 Getting Started
-
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.
🚀 Examples
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 |
✨ Features
-
Module Resolutions in Build
@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
. -
Module Resolutions in Tests
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. -
Development and Production Builds
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. -
Base Library Setup
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.
📚 Manual Setup
This section explains the steps required to set up a library from scratch. Check out the Getting Started for Quick Start guide.
Step 1: Install the Vite Plugin
npm i --save-dev @cross-platform-tools/vite-plugin
Step 2: Add Vite Plugin to your configuration
// 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'
}
}),
]
});
supportedPlatforms
above
Step 2: Create files with extensions used in // src/getData.server.ts
export const getData = () => 1234;
// src/getData.client.ts
export const getData = () => 4321;
// src/index.ts
export { getData } from './getData';
Step 3: Add build scripts and exports to your package.json
{
"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 ⭐️