By writing a small adapter to choose the import path (styled-components
vs styled-components/native
), we should be able to import web UI components into a React Native context. This is accomplished by:
- Writing the adapter
- Configuring each project (web vs native) to import the right path for
styled-components
The web project can easily set up a package alias in the next.config.js
. The working setup I have is this:
const path = require("path");
module.exports = {
webpack: config => {
// Fixes npm packages that depend on `fs` module
config.node = {
fs: "empty"
};
config.resolve = {
alias: {
"styled-components-path": path.join(
__dirname,
"./node_modules/styled-components"
)
}
};
return config;
}
};
The only thing I added was the path
requirement and the config.resolve
object. Now, when webpack sees "styled-components-path"
, it looks up the registered full path alias.
React Native does not use webpack at all since files are build statically before runtime. It uses babel to transform syntax, so we can add package aliases there with the babel-plugin-module-resolver
. I added a plugins
property to the scaffolded babel.config.js
file like this:
module.exports = function(api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: [
[
"module-resolver",
{
root: ["./"],
alias: {
"styled-components-path": "./node_modules/styled-components/native"
}
}
]
]
};
};
The same mapping occurs here when babel sees "styled-components-path"
.
To define a reusable styled component, the API is very similar to the underlying styled-components interface. A file would look like this:
import React from "react";
import getStyledElement from "styled-elements-adapter";
const { element, platform } = getStyledElement("p"); // <-- since RN components are a smaller set, we ask it for the HTML tag
const SometimesRedBlock = element` // <-- really a reference to "styled.p"
width: 200px;
height: 200px;
background-color: ${platform === "web" ? "red" : "blue"};
`;
export default SometimesRedBlock;
This component is defined using a p
tag, but in a React Native context getStyledElement
will return styled.Text
instead.
The adapter code sits between the styled component definitions and the actual styled-components
package. A rough version could look like this:
import styled from 'styled-components-path' . // <-- where the aliasing magic counts
const webToMobileMap = { // these can be added as needed
p: 'Text', //
div: 'View',
button: 'Button'
}
const getStyledElement = tagName => {
const isWeb = Boolean(styled[tagName]) // tries to find e.g. the 'div' method in 'styled'; if it's not there, we're in React Native land
const elementKey = isWeb ? tagName : webToMobileMap[tagName]
return {
element: styled[elementKey],
platform: isWeb ? 'web' : 'native'
}
}
export default getStyledElement