One codebase, Multiple outputs
Build your application for multiple devices with Vite and eliminate dead codes with rollup.
This plugin builds your project in two different directories, and after that you can serve these directories based on user-agent header, subdomains or etc.
For ease of development, you can run the vmd
command that runs
multiple vite servers simultaneously and automatically forwards
the request to one of them based on user agent.
- Framework aware (examples added with react, vuejs and svelte)
- Tree shakable code through rollup
- Configurable for adding more devices like tablet
- Separating based on filenames
- Separating based on If/else branches
- Testable code through mocking window.DEVICE
- Dev Server with device auto detection
This plugin only works with Vite >= 2
$ npm install -D vite-plugin-multi-device
// Or
$ yarn add -D vite-plugin-multi-device
Add to your vite.config.js
:
import vue from '@vitejs/plugin-vue';
import multiDevice from 'vite-plugin-multi-device';
export default {
plugins: [vue(), multiDevice()],
};
Now you should define DEVICE
environment variable before running your dev server to specify
which device are you willing to target.
For more ease of use, you can use vmd
command to run a hybrid dev server to automatically detects the device based on user-agent
// package.json
{
"scripts": {
"dev": "vmd", // Run dev server with auto detection feature
"build": "vmd build", // Build project per device
"start": "vmd start" // Run a production-ready node.js server
}
}
There are two ways to code for different devices
- Using if/else branches
- Using separate files
Simply use window.DEVICE.mobile
or window.DEVICE.desktop
variable to detect user device.
You can configure to support more devices in
multidevice.config.js
. or you can change the identifier to something other thanwindow.DEVICE.mobile
(eg: __MOBILE__)
React Example:
// App.jsx
export function App() {
return (
<div>This is { window.DEVICE.desktop ? 'Desktop device' : 'Mobile device' }</div>
)
}
Vuejs Example:
<template>
<!-- inside vue templates -->
<div v-if="DEVICE.mobile">This is mobile</div>
<!-- VNode render branches will be eliminated by rollup and will not exist in final bundle, we explain it later -->
<div v-if="DEVICE.desktop">This is desktop</div>
</template>
<script>
export default {
mounted() {
if (window.DEVICE.mobile) {
console.log('Hi mobile user!')
}
}
}
</script>
Do not use DEVICE directly or dynamically.
window.DEVICE.mobile // ok
const myVar = 'mobile'
window.DEVICE[myVar] // won't work
window.DEVICE['mobile'] // won't work
Object.keys(window.DEVICE) // won't work
All files ending with .{device}.{extension}
will take precedence to be used. eg: MyComponent.mobile.js
or MyStyle.desktop.css
<!-- App.vue -->
<template>
<div>
<AppHeader />
</div>
</template>
<script>
import AppHeader from './AppHeader.vue'
export default {
components: {
AppHeader
}
}
</script>
<!-- AppHeader.vue -->
<template>
<div>
Desktop Header
</div>
</template>
<!-- AppHeader.mobile.vue -->
<template>
<div>
<!-- This takes precendence on mobile build -->
Mobile Header
</div>
</template>
Also works with these packages:
- https://github.com/hannoeru/vite-plugin-pages
- https://github.com/antfu/vite-plugin-components
You can change your package.json build commands to something like the example below.
{
"scripts": {
"build:desktop": "cross-env NODE_ENV=production DEVICE=desktop vite build --outDir=dist/desktop",
"build:mobile": "cross-env NODE_ENV=production DEVICE=mobile vite build --outDir=dist/mobile",
"build": "vmd build", // or simply use vmd command to build all devices.
"start": "vmd start" // production ready node.js server (but you can write your own)
}
}
You could write your own Nodejs http server to serve the outputs to users based on regex, or use a configured nginx server.
There is a simple production ready server based on express, that switches automatically between devices.
To use it, simply build your project with this structure: dist/DEVICE
then run vmd start
command.
// multidevice.config.js
module.exports = {
devices: {
mobile: /Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/,
},
fallback: 'desktop',
// returns the identifiers that should be replaced. by default these two are considered
replacement: (device) => ['DEVICE.' + device, 'window.DEVICE.' + device],
// specifies how to transform a path to the device version path like: /src/app.js -> /src/app.mobile.js
resolvePath: (path, device) => path.replace(/\.([^?/\\]+)(\?.*)?$/, `.${device}.$1$2`),
// Environment variable to detect device (build command)
env: 'DEVICE'
};