I'm looking for a way to build my assets using Laravel Mix on my dev machine which will then match the parameters on the production.
For instance, I have a base url for my API which is http://foo.test/api/v1
for local development and https://foo.com/api/v1
on my production server.
So in my client code (e.g. http.js
) I would like to do something like this:
Vue.axios.defaults.baseURL = API_BASE_URL;
Now API_BASE_URL
should get replaced with http://foo.test/api/v1
when I run npm run dev
or with https://foo.com/api/v1
when I run npm run prod
.
I already tried the following approach which does not work.
webpack.mix.js
mix.webpackConfig(webpack => {
return {
plugins: [
new webpack.DefinePlugin({
API_BASE_URL: JSON.stringify('https://foo.com/api/v1'),
})
]
}
});
Then in http.js
Vue.axios.defaults.baseURL = API_BASE_URL;
This compiles to:
Object({"NODE_ENV":"production"}).API_BASE_URL;
I tried this approach with process.env.API_BASE_URL
as well.
It's a bit late, and I don't know if it still applies, but we're probably not the only ones looking for this. I've spend a lot of time on this, trying to figuring out that exact question. I'll share my story here, hoping that other people can avoid the loss of time.
What is passed to your client side application is basically the contents of process.env
.
Laravel Mix tries to protect you from exposing the entire .env
(that gets loaded via dotenv
) by
filtering the variables and allowing only those that start with MIX_
.
This behavior comes from the MixDefinitionsPlugin
.
When the webpack configuration is assembled it places your custom plugins first, followed by the mix plugins.
That means the MixDefinitionsPlugin
will always have the last word.
If you can work with that limitation, then a simple solution would be:
Add the following in your .env
:
MIX_API_URL=http://foo.test/api/v1
When you recompile your assets you'll have a process.env.MIX_API_URL
on the client side.
It's as simple as that.
Btw, since this loads using dotenv-expand
you can even use other variables (ie if your api was already set for the backend):
MIX_API_URL=${API_URL}
If you want to do this for your production variant, the least amount of hassle would be (temporarily) replace your .env
file with a production one.
Just create a shell script to build your production variant:
.env
with your production .env
.npm run production
..env
Done. There is a 2nd solution, it's a lot uglier, but I like it more, read on :)
I wasn't really happy with that as I just wanted to be able to run npm run production
and it would work, so I looked further.
Since only the last defined process.env
is used when the javascript transpiles, the plugin closest to that wins.
As mentioned above, this is the MixDefinitionsPlugin and there is nothing you can do about it.
If looked into several other ways of accomplishing what we want
(Besides the script replacing the .env, this is the only viable solution I could find)
Mix emits a "loading-plugins" event with the plugins as parameter right after it finalised their loading. This means that you'll get a set of very prepared and very pretty plugins that are about to go to webpack.
Initially I tried to push a plugin at the end of the array, but that one is only executed after the transpilation so that gains you nothing. Alternative would be to figure out where the plugin is situated and work with that. If you can modify its contents there you could in theory add values "on the fly" and those will end up in your browser as well. Which is what I did.
Basically this boils down to the following:
if (mix.inProduction()) {
Mix.listen('loading-plugins', plugins => {
const _ = require('lodash');
let define = _.find(plugins, plugin => {return plugin.constructor.name === 'DefinePlugin'});
define.definitions['process.env'].MIX_API_URL = 'http://foo.com/api/v1';
});
}
(Note that we're looking for a DefinePlugin here instead of the MixDefinitionsPlugin, as that already did its work)
Since you can add what you need here without the need for the MIX_
-prefix, you could alternatively set your API_URL here for both:
Mix.listen('loading-plugins', plugins => {
const _ = require('lodash');
let define = _.find(plugins, plugin => {return plugin.constructor.name === 'DefinePlugin'});
define.definitions['process.env'].API_URL = mix.inProduction() ?
'http://foo.com/api/v1' : 'http://foo.test/api/v1';
});
You could also load another .env file here (via dotenv.config()
) and merge it into the definitions, the sky is the limit.
.env
?Something else I thought about.
The MixDefinitionsPlugin constructor allows for this possibility (function MixDefinitionsPlugin(envPath)
), unfortunately the actual implementation does not:
return new webpack.DefinePlugin(
new MixDefinitionsPlugin().getDefinitions(merge)
);
So no such luck, this is unusable.
Next up was checking if I could perhaps create a different folder with a production .env
file.
Mix has a Paths
object that stores the root. So the idea here was to store my alternative .env
in a subdirectory, update the root and presto.
Mix.paths.setRootPath(Mix.paths.root('./resources/assets/production'));
This works for the .env
, but it breaks a lot of other things. The root is used as the context for webpack, as
configuration for the purify-css plugin (css cleaning of vue and blade files) and most importantly it also breaks the url loader.
No go here as well (unless you want to intercept the webpack configuration and restore the root everywhere, not worth it)
So here ends the story of about 5 hours of my life. I'm nowhere near a javascript expert, so perhaps I've made som errors or missed things, but it's my story :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With