I'm building a web (react
with webpack
& babel
) and mobile apps (react-native
with expo
) for a project. I therefore created a common library for business logic and redux/api library.
Some code will be slightly different between web and mobile. In my case it's localStorage vs AsyncStorage, which I use for authentication among other things...
I'm trying to pass an environment variable for the build stage to switch import of certain files ex:
if(PLATFORM === 'mobile'){ import StorageModule from './mobile-storage-module` } else { import StorageModule from './mobile-storage-module` } export default StorageModule
Try 1
@babel/preset-env
to say if it's mobile or web so that it imports different libraries depending on build like so:
My .babelrc
has this:
{ "presets": [ [ "@babel/preset-env", { "platform": "mobile" } ] ] }
And then in local storage file I do this:
export default () => { const platform = process.env.platform if (platform === 'mobile') { return import './storage-modules/storage-mobile' } return import './storage-modules/storage-web' }
That didn't work, and this also didn't work for me.
Try 2
I installed react-native-dotenv and created a .env
file with: PLATFORM=mobile
And set the plugin in my .babelrc
:
{ "presets": [ "babel-preset-expo", "react-native-dotenv" ] }
And in my example file, I tried this:
import { PLATFORM } from 'react-native-dotenv' export default PLATFORM === 'mobile' ? import './storage-modules/storage-mobile' : import './storage-modules/storage-web'
But now my build doesn't work. Any idea how I do dynamic imports during the build process that works for babel in react-native app and webpack build (also uses babel)?
2 Answers
Answers 1
First, @babel/preset-env
does not do what you think it does. This is not for specifying your own variables, it is a plugin to automatically use the right target and pollyfills for the browsers you want to support.
The easiest way to get environment variables is with the webpack define plugin (which is part of webpack, so no need to install anything extra)
Just add this to your webpack config.
plugins: [ new webpack.DefinePlugin({ 'process.env': { platform: 'mobile', }, }), ],
Next, you can't use normal import
statements inside of ifs. import
gets resolved before any code runs, either on build by webpack, or in supported environments on script load. To import something on runtime, you need to use dynamic imports.
Here is an example of how this could look like.
export default new Promise(async resolve => { resolve( process.env.platform === 'mobile' ? (await import('./mobile.js')).default : (await import('./desktop.js')).default ); });
You can now import from this file like you normally would, but be aware that the default export is a promise.
Answers 2
As your question's title says "during babel build phase", I assume you would like to make different builds for desktop and mobile (not one build for both and load the needed modules dynamically run-time). So I would go like this:
Define the run scripts in package.json
for desktop and mobile:
"scripts": { "devmobile": "cross-env NODE_ENV=development PLATFORM=mobile webpack --progress", "dev": "cross-env NODE_ENV=development webpack --progress", }
... or you can create two different webpack.config.js
files for desktop and mobile builds but I think the above is easier...
Then npm run devmobile
to build for mobile and npm run dev
for desktop.
Since I'm on Windows I use the cross-env
package but this is the recommended way to be OS independent.
Then I would use Webpack's NormalModuleReplacementPlugin
:
(based on this exmaple)
In your webpack.config.js:
// defining the wanted platform for the build (comfing form the npm run script) const targetPlatform = process.env.PLATFORM || 'desktop'; // then use the plugin like this plugins: [ new webpack.NormalModuleReplacementPlugin(/(.*)-PLATFORM(\.*)/, function(resource) { resource.request = resource.request.replace(/-PLATFORM/, `-${targetPlatform}`); }), ]
...then if you have these two files:
./storage-modules/storage-mobile.js ./storage-modules/storage-desktop.js
import the needed one in your script like this:
import './storage-modules/storage-PLATFORM';
This way the generated build will only contain the needed file for the current PLATFORM used for the build process.
Another possible solution could be the ifdef-loader but I haven't tested it. Maybe worth to try, seems easy.
If you want one build though and import the needed module dynamically, you could do something like this in your app.js
(or whatever):
// this needs to have defined when the app is running const targetPlatform = process.env.PLATFORM || 'desktop'; import( /* webpackChunkName: "[request]" */ `./storage-modules/storage-${targetPlatform}` ).then(storageModule => { // use the loaded module });
or:
(async () => { const storageModule = await import( /* webpackChunkName: "[request]" */ `./storage-modules/storage-${targetPlatform}` ); // use the loaded module })();
For this to work Babel has to be configured.
More on Webpack with dynamic imports here.
0 comments:
Post a Comment