Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/vite #2

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 46 additions & 83 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# base-href-runtime-webpack-plugin
# base-href-runtime-vite-plugin

[![npm version](https://badge.fury.io/js/base-href-runtime-webpack-plugin.svg)](https://badge.fury.io/js/base-href-runtime-webpack-plugin)
[![npm version](https://badge.fury.io/js/base-href-runtime-vite-plugin.svg)](https://badge.fury.io/js/base-href-runtime-vite-plugin)

Extension for [html-webpack-plugin](https://github.com/ampedandwired/html-webpack-plugin) to programmatically insert or update [`<base href="...">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag **in runtime** depending on *window.location.pathname*.
Extension for [html-vite-plugin](https://github.com/ampedandwired/html-vite-plugin) to programmatically insert or update [`<base href="...">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) tag **in runtime** depending on _window.location.pathname_.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут обман


It inserts inline `<script>` in your `index.html` output which generates (rewrites) `<base href="...">`

Expand All @@ -13,6 +13,7 @@ When your application is proxied with many different domains and prefixes you mi
### Example

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
Expand All @@ -21,113 +22,75 @@ 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:

```html
<!-- for app.test.com/ui/app/ (/ui/app/) -->
<base href="/ui/app/">
<script src="index.js" /> <!-- /ui/app/index.js -->
<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 -->
<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**.

# Installation

For webpack v5 use latest (^1.1.0):
`npm i --save-dev base-href-runtime-webpack-plugin`
`npm i --save-dev base-href-runtime-vite-plugin`

# Usage

Prepare `HtmlWebpackPlugin`, it should generate *relative* paths for assets.

```diff
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`:
Init `base-href-runtime-vite-plugin`:

```js
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/">
],
}),
]
// vite.config.js
import { defineConfig } from "vite";
import baseHrefRuntimeVitePlugin from "base-href-runtime-vite-plugin";

export default defineConfig({
plugins: [
baseHrefRuntimeVitePlugin({
fallbackBaseHref: "/", // in case when we didn't match location.pathname
publicPaths: [
// availabled prefixes. Order is important!
"/ui/app/log/", // <base href="/ui/app/log">
"/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="...">`
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.

### Setup application router (optional)
crutch12 marked this conversation as resolved.
Show resolved Hide resolved

You might want to use `publicPaths` to prepare your application router (`react-router`, `vue-router`, etc.)

<details>
<summary>Example with react-router</summary>

```jsx
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'));
```
</details>

# Caveats

<details>
<summary>Excessive requests (duplicated requests)</summary>

https://developer.mozilla.org/en-US/docs/Web/Performance/How_browsers_work#preload_scanner

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.
> 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**.

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:

Example:
```html
```html
<html>
<head>
<base href="/unknown/">
<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);
console.log("Initial document.baseURI:", document.baseURI);
document.querySelector("base").href = "/";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

почему стиль у всех ковычек всех поменялся?
ещё местами потерялись ;

console.log("New document.baseURI:", document.baseURI);
</script>
<script src="js/index.js"></script>

Expand All @@ -136,9 +99,9 @@ You might want to use `publicPaths` to prepare your application router (`react-r
<!-- So <script src="..." /> and <script src="..."></script> have different behaviour (WTF?!) -->
</head>
</html>
```
Chrome's Network tab result (`js/index.js` request duplicated):
```

Chrome's Network tab result (`js/index.js` request duplicated):

<img width="676" alt="chrome_LmDvH3YJzy" src="https://user-images.githubusercontent.com/19373212/152134966-5cd1699b-4951-4a41-bb3a-2a733b1ac754.png">
</details>
Expand All @@ -151,9 +114,9 @@ https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#browser_compatibi

# References
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем убил рефы...


https://www.npmjs.com/package/base-href-webpack-plugin
https://www.npmjs.com/package/base-href-vite-plugin
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну это совсем ложь) И ниже тоже

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lamtev178 не поправил


https://github.com/jantimon/html-webpack-plugin#base-tag
https://github.com/jantimon/html-vite-plugin#base-tag

# Contribution

Expand Down
16 changes: 4 additions & 12 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import { Compiler, WebpackPluginInstance } from 'webpack';

declare interface BaseHrefRuntimeWebpackPluginOptions {
fallbackBaseHref?: string;
publicPaths?: string[];
}

declare class BaseHrefRuntimeWebpackPlugin implements WebpackPluginInstance {
constructor(options: BaseHrefRuntimeWebpackPluginOptions);

apply: (compiler: Compiler) => void;
}
declare interface baseHrefRuntimeVitePlugin {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Типы же с большой буквы всегда пишем

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Чот я туплю видимо
Не могу понять, подсосутся ли эти типы, ты же не экспортишь ничего
Проверял, подсказывает он типы в vite.config.js?

fallbackBaseHref?: string;
publicPaths?: string[];
}
86 changes: 35 additions & 51 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,63 +1,47 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const template = require('lodash.template');
const fs = require('fs');
const { validate } = require('schema-utils');
const schema = require('./schema.json');

const scriptTemplate = fs.readFileSync(__dirname + '/script.ejs', 'utf8');

module.exports = class BaseHrefRuntimeWebpackPlugin {
constructor(options) {
validate(schema, options, {
name: 'BaseHrefRuntimeWebpackPlugin',
baseDataPath: 'options',
});
this.options = options;
}
function baseHrefRuntimeVitePlugin(options) {
const fallbackBaseHref = options?.fallbackBaseHref || "";
const publicPaths = options?.publicPaths || [];

apply(compiler) {
const publicPaths = (this.options.publicPaths || []).filter(Boolean);
const fallbackBaseHref = this.options.fallbackBaseHref;
if (publicPaths.length === 0 && !fallbackBaseHref) {
return;
}

if (publicPaths.length === 0 && !fallbackBaseHref) {
return;
}
const scriptTemplateFunction = `(function () {
var publicPaths = [${publicPaths.map(
(path) => "'" + path + "'"
)}] || [];
var fallbackBaseHref = '${fallbackBaseHref}' ? '${fallbackBaseHref}' : 'undefined';

const logger = compiler.getInfrastructureLogger('BaseHrefRuntimeWebpackPlugin');
const scriptTemplateFunction = template(scriptTemplate);
const base = document.createElement("base")

compiler.hooks.compilation.tap('BaseHrefRuntimeWebpackPlugin', (compilation) => {
HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync('BaseHrefRuntimeWebpackPlugin', (data, callback) => {
if (!data.plugin.options.base) {
logger.warn('You didn\'t specify "base" field in html-webpack-plugin');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning зачем потерял?

}
document.head.append(base)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ты проверял, запросы точно после этого идут верно?
Я думаю так неправильно, т.к. у тебя <base> добавится в конец
И если по пути были ресурсы, или какой-то скрипт, то сначала оно всё выполнится
И только потом уже твой <base> поменяется

Видимо поэтому мы скрипт прямо перед <base> вставляли


const baseTagIndex = data.headTags.findIndex(tag => tag.tagName === 'base');
const targetIndex = baseTagIndex === -1 ? 0 : (baseTagIndex + 1);
document.querySelector('base').href = publicPaths.find(
(path) => window.location.pathname.includes(path)
) || fallbackBaseHref || document.baseURI})();`;

const scriptInnerHTML = scriptTemplateFunction({
publicPaths: publicPaths,
fallbackBaseHref: fallbackBaseHref,
});
return {
name: "base-href-runtime-vite-plugin",

data.headTags = [
...data.headTags.slice(0, targetIndex),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Видно, что мы вставляли скрипт перед тегом <base> (если он уже был), вроде как это не просто так

@santoslos не помнишь случайно?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ой, сразу после , перепутал работу slice
Перед base нельзя, не найдёт его в дереве же)

// @NOTE: Insert our script
transformIndexHtml(html) {
return {
html: html,
tags: [
{
tagName: 'script',
tag: "script",
voidTag: false,
meta: { plugin: 'base-href-runtime-webpack-plugin' },
attributes: {
type: 'text/javascript',
'data-name': 'base-href-runtime-webpack-plugin',
injectTo: "head",
meta: { plugin: "base-href-runtime-webpack-plugin" },
attrs: {
type: "text/javascript",
"data-name": "base-href-runtime-webpack-plugin",
},
innerHTML: scriptInnerHTML,
children: scriptTemplateFunction,
},
...data.headTags.slice(targetIndex)
];

callback(null, data);
});
});
}
],
};
},
};
}

module.exports = baseHrefRuntimeVitePlugin;
20 changes: 5 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,33 +1,23 @@
{
"name": "base-href-runtime-webpack-plugin",
"name": "base-href-runtime-vite-plugin",
"version": "1.1.0",
"main": "index.js",
"types": "index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/crutch12/base-href-runtime-webpack-plugin.git"
"url": "https://github.com/web-bee-ru/base-href-runtime-vite-plugin"
},
"keywords": [
"base",
"href",
"webpack",
"vite",
"plugin"
],
"scripts": {
"test": "cd ./test && webpack"
"test": "cd ./test && npm install && npm run dev"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

npm i тут лишний

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А, ну не лишний в текущей реализации)
Но я бы убил второй package.json

},
"license": "MIT",
"peerDependencies": {
"html-webpack-plugin": ">=4.0.0",
"webpack": ">=4.0.0"
},
"devDependencies": {
"html-webpack-plugin": "5.5",
"webpack": "^5.23.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А зачем всё перенёс в test/package.json?
Я бы оставил, как было
Это же devDeps, они не будут устанавливаться у потребителей

"webpack-cli": "^4.9.2"
},
"dependencies": {
"lodash.template": "^4.5.0",
"schema-utils": "^4.0.0"
"vite": "^4.3.2"
}
}
9 changes: 0 additions & 9 deletions script.ejs

This file was deleted.

12 changes: 12 additions & 0 deletions test/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Пример то репрезентативен? Надо такой пример, чтобы я в network увидел, что у меня запрос идёт в соответствии с base

<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
1 change: 0 additions & 1 deletion test/index.js

This file was deleted.

Loading