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