Extension for html-webpack-plugin to programmatically insert or update <base href="...">
tag in runtime depending on window.location.pathname.
It inserts inline <script>
in your index.html
output which generates (rewrites) <base href="...">
When your application is proxied with many different domains and prefixes you might need to detect publicPath
so you could resolve requests for assets correctly.
Your application is hosted on example.com
and you have 2 known entrypoints (proxies) for this application:
app.test.com/ui/app -> example.com
app2.io/ui/test/entrypoint -> example.com
So you want to open app.test.com/ui/app
and resolve index.js
request to app.test.com/ui/app/index.js
(-> example.com/index.js
)
For this purpose you want to generate different <base>
tag:
<!-- for app.test.com/ui/app/ (/ui/app/) -->
<base href="/ui/app/">
<script src="index.js" /> <!-- /ui/app/index.js -->
<!-- for app2.io/ui/test/entrypoint (/ui/test/entrypoint/) -->
<base href="/ui/test/entrypoint/">
<script src="index.js" /> <!-- /ui/test/entrypoint/index.js -->
Thus <base>
tag for the same index.html
has to be generated in runtime.
For webpack v5 use latest (^1.1.0):
npm i --save-dev base-href-runtime-webpack-plugin
Prepare HtmlWebpackPlugin
, it should generate relative paths for assets.
new HtmlWebpackPlugin({
template: 'public/index.html',
filename: 'index.html',
// ...,
- publicPath: '/',
+ publicPath: 'auto', // assets paths must be relative
+ base: '/' // same as fallbackBaseHref, see https://github.com/jantimon/html-webpack-plugin#base-tag
}),
Init base-href-runtime-webpack-plugin
:
const BaseHrefRuntimeWebpackPlugin = require('base-href-runtime-webpack-plugin');
plugins: [
// ...,
new BaseHrefRuntimeWebpackPlugin({
fallbackBaseHref: '/', // in case when we didn't match location.pathname
publicPaths: [ // availabled prefixes. Order is important!
'/ui/app/', // <base href="/ui/app/">
'/ui/test/entrypoint/', // <base href="/ui/test/entrypoint/">
'/a/b/c/d/e/', // <base href="/a/b/c/d/e/">
],
}),
]
It will inject <script></script>
in your index.html
. This script compares current window.location.pathname
and provided publicPaths
. Then it updates <base href="...">
if we have a match. Otherwise it sets fallbackBaseHref value in your <base href="...">
Plugin leaves your template untouched if fallbackBaseHref
and publicPaths
options are not provided.
You might want to use publicPaths
to prepare your application router (react-router
, vue-router
, etc.)
Example with react-router
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import { publicPaths, fallbackBaseHref } from './lib/constants/config'; // use same variable as publicPaths in you webpack.config.js
const App = ({ basename }) => {
<Router basename={basename}>
{/* ... your app content ... */}
</Router>
}
const getBasename = (pathname) => {
// @NOTE: You may straightaway return baseURI
// return document.baseURI;
const publicPath = publicPaths.find(publicPath => pathname.includes(publicPath.replace(/\/$/, '')));
return publicPath || fallbackBaseHref;
}
ReactDOM.render(<App basename={getBasename(window.location.pathname)} />, document.getElementById('#app'));
Excessive requests (duplicated requests)
https://developer.mozilla.org/en-US/docs/Web/Performance/How_browsers_work#preload_scanner
The preload scanner will parse through the content available and request high priority resources like CSS, JavaScript, and web fonts. <...> It will retrieve resources in the background so that by the time the main HTML parser reaches requested assets, they may possibly already be in flight, or have been downloaded.
It means that a browser requests all page's resources before you execute any <script>
. So if your <base href="...">
tag is being changed by the <script>
then your browser will repeat these requests again.
Example:
<html>
<head>
<base href="/unknown/">
<script type="text/javascript">
console.log('Initial document.baseURI:', document.baseURI);
document.querySelector('base').href = '/'
console.log('New document.baseURI:', document.baseURI);
</script>
<script src="js/index.js"></script>
<!-- @NOTE: I don't know why, but Chrome won't request script below again even after baseURI change -->
<!-- <script src="js/index.js" />-->
<!-- So <script src="..." /> and <script src="..."></script> have different behaviour (WTF?!) -->
</head>
</html>
Chrome's Network tab result (js/index.js
request duplicated):
All modern browsers, even IE7
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#browser_compatibility
https://www.npmjs.com/package/base-href-webpack-plugin
https://github.com/jantimon/html-webpack-plugin#base-tag
Feel free to contribute to this project by submitting issues and/or pull requests.
MIT