This is an example how to set up a repository that builds code which can be consumed in Framer. It builds standard JavaScript into ES Modules that you can both use locally for development or to deploy to any http server for distribution across your team or the internet.
yarn run serve
This runs a local web server with the built code.yarn run build
This builds the code indist
.
All the magic is in the esmbuild
folder. Contributions are welcome.
- Built on
esbuild
for speed and to support JavaScript, TypeScript and JSX. - Custom plugin to handle ESM imports and rewrite local import paths.
- CSS Modules plugin to support writing css scoped modules.
- GitHub Workflow to deploy versioned code on GitHub pages.
Start the development server with:
yarn run serve
Open Framer, create a code file and paste the following:
import { addPropertyControls, ControlType } from "framer"
import { Button } from "http://127.0.0.1:8000/index.js"
export default Button as React.ComponentType
addPropertyControls(Button, {
title: {
title: "Title",
type: ControlType.String,
defaultValue: "Title",
},
})
You can now add your component to the canvas and configure it using the defined propertyControls
. This setup assumes you want to define them only on the Framer side (keeping your code clean) but you can also keep them in the original source.
The imported esm gets cached quite agressively, if you want to make sure you see the latest version you can add a random value to the imported url line:
import { Button } from "http://127.0.0.1:8001/index.js?123"
If you forget to start your local server, your component will render with:
Error in <name>.tsx Failed to Fetch
If you want to import a whole library to expose multiple components you can export them like this. Because Framer uses static analysis to recognize components, you will need one line per exported component.
import { addPropertyControls, ControlType } from "framer"
import * as lib from "http://127.0.0.1:8000/index.js"
addPropertyControls(lib.Button, {
title: {
title: "Title",
type: ControlType.String,
defaultValue: "Title",
},
})
export const Button: React.ComponentType = lib.Button
export const Battery: React.ComponentType = lib.Battery
Beware that esm was designed for many small files that the browser can cache and optimize for. If you make your library too big, you'll lose out on these advantages. But it's a trade-off.
Once you are ready to deploy your code, it should be uploaded to an https endpoint with a versioned url. We have set up a Workflow to build the code and deploy to GitHub Pages. To ship a next version you simply type:
yarn run deploy
You'll have to type a new version (let's say 1.0.4
), and soon the code will be deployed to: https://framer.github.io/example-framer-esm-setup/esmbuild@1.0.4/index.js. You can now update your imports to the production url and you'll get the exact same result:
import { Button } from "https://framer.github.io/example-framer-esm-setup/esmbuild@1.0.4/index.js"
It's very important to version your code, so endpoints stay stable. To move to a new version, you simply update the import urls wherever you need.
You can use the default esbuild css importer, or you can use a plugin to use css modules, that optionally auto inserts the css as a <style>
tag (as configured).
The plugin.esm.js
makes sure that your local file imports are translated to esm imports. In the example configurations it works as follows:
import React from "react"
import ReactDOM from "react-dom"
import Framer from "framer"
import motion from "framer-motion"
Externals work like externals, so they can be picked up by an import map. In Framer we defined specific ones in the current import map, so we marked them as externals in the setup here.
import * as _ from "lodash"
node_modules
work like you would expect. You can just install them with yarn
and they'll be inlined in the module that imports them.
import * as _ from "https://ga.jspm.io/npm:lodash@4.17.21/index.js"
Url imports will just be kept intact so you can do non local esm imports.
import { Button } from "./Button"
Local esm imports will be rewritten to include the file extension:
import { Button } from "./Button.js"
- Private code: you should keep your source private, but not your built code. Make sure to enable
minify
in theesmbuild.js
script to minify your code. - Assets: you can host your assets (images, movies) anywhere you like and just use the full urls to use them in your components.
- Auto refresh: you currently have to manually reload your components to see changes in your development code. It should be doable to make an
esbuild
plugin that inserts a snippet to auto reload after changes. Contributions are welcome. - Other build tools: this is just an example setup, but you should be able to use Rollup, Webpack, SWC, etc. as long as you set the output format to
esm
.
https://www.framer.com/docs/render-target/ https://www.framer.com/docs/guides/auto-sizing/ https://www.framer.com/docs/framer-utilities/