Initial commit

This commit is contained in:
yuding
2025-12-03 12:00:46 +08:00
commit 5763b764a3
5365 changed files with 1483113 additions and 0 deletions

21
node_modules/vite-plugin-css-injected-by-js/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Marco Prontera
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

366
node_modules/vite-plugin-css-injected-by-js/README.md generated vendored Normal file
View File

@@ -0,0 +1,366 @@
# vite-plugin-css-injected-by-js 🤯
A Vite plugin that takes the CSS and adds it to the page through the JS. For those who want a single JS file.
The plugin can be configured to execute the CSS injection before or after your app code, and you can also provide a
custom id for the injected style element and other configurations that fulfill some particular cases, even for libs.
## How does it work
Essentially what it does is take all the CSS generated by the build process and add it via JavaScript. The CSS file is
therefore not generated and the declaration in the generated HTML file is also removed. You can also configure when the
CSS injection will be executed (before or after your app code).
## Installation
```
npm i vite-plugin-css-injected-by-js --save
```
## Usage
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin(),
]
}
```
### Configurations
When you add the plugin, you can provide a configuration object. Below you can find all configuration parameters
available.
#### cssAssetsFilterFunction (function)
The `cssAssetsFilterFunction` parameter allows you to specify a filter function that will enable you to exclude some
output css assets.
**This option is not applied to `relativeCSSInjection` logic.**
Here is an example of how to use the `cssAssetsFilterFunction`:
```javascript
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({
cssAssetsFilterFunction: function customCssAssetsFilterFunction(outputAsset) {
return outputAsset.fileName == 'font.css';
}
}),
]
}
```
#### dev (object)
**EXPERIMENTAL**
Why experimental? Because it uses a non-conventional solution.
Previously, the plugin strictly applied logic solely during the build phase. Now, we have the capability to experiment
with it in the development environment.
To activate the plugin in the development environment as well, you need to configure a dev object and set the enableDev
parameter to true.
Here's an example:
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({
dev: {
enableDev: true,
removeStyleCodeFunction: function removeStyleCode(id: string) {
// The 'id' corresponds to the value of the 'data-vite-dev-id' attribute found on the style element. This attribute is visible even when the development mode of this plugin is not activated.
}
}
}),
]
}
```
This approach should serve its purpose effectively unless you're employing custom injection code to insert styles where
necessary. Since the development environment involves the concept of "updating" styles in the Document Object Model (
DOM), this plugin requires code to remove the injected style from the DOM.
Due to these factors, if you're utilizing custom injection code (via `injectCode` or `injectCodeFunction`), the plugin
cannot automatically discern how to delete the injected style. Therefore, all you need to do is configure
either `removeStyleCode` or `removeStyleCodeFunction` within the `dev` object as demonstrated above.
**NOTE:** The `injectCode` and `injectCodeFunction` parameters now also include the `attributes`, and in `dev` mode,
the `attributes` object encompasses the `data-vite-dev-id` as well. Refer to the `injectCodeFunction` example below for
further details.
#### injectCode (function)
You can provide also a function for `injectCode` param to customize the injection code used. The `injectCode` callback
must return a `string` (with valid JS code) and it's called with two arguments:
- cssCode (the `string` that contains all the css code that need to be injected via JavaScript)
- options (an object with `styleId`, `useStrictCSP` and `attributes` the last is an object that represent the attributes
of the style element that should have)
This is an example:
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({
injectCode: (cssCode: string, options: InjectCodeOptions) => {
return `try{if(typeof document != 'undefined'){var elementStyle = document.createElement('style');elementStyle.appendChild(document.createTextNode(${cssCode}));document.head.appendChild(elementStyle);}}catch(e){console.error('vite-plugin-css-injected-by-js', e);}`
}
}),
]
}
```
#### injectCodeFunction (function)
If you prefer to specify the injectCode as a plain function you can use the `injectCodeFunction` param.
The `injectCodeFunction` function is a void function that will be called at runtime application with two arguments:
- cssCode (the `string` that contains all the css code that need to be injected via JavaScript)
- options (an object with `styleId`, `useStrictCSP` and `attributes` the last is an object that represent the attributes
of the style element that should have)
This is an example:
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({
injectCodeFunction: function injectCodeCustomRunTimeFunction(cssCode: string, options: InjectCodeOptions) {
try {
if (typeof document != 'undefined') {
var elementStyle = document.createElement('style');
// SET ALL ATTRIBUTES
for (const attribute in options.attributes) {
elementStyle.setAttribute(attribute, options.attributes[attribute]);
}
elementStyle.appendChild(document.createTextNode(${cssCode}));
document.head.appendChild(elementStyle);
}
} catch (e) {
console.error('vite-plugin-css-injected-by-js', e);
}
}
}),
]
}
```
#### injectionCodeFormat (ModuleFormat)
You can specify the format of the injection code, by default is `iife`.
#### jsAssetsFilterFunction (function)
The `jsAssetsFilterFunction` parameter allows you to specify which JavaScript file(s) the CSS injection code should be
added to. This is useful when using a Vite configuration that exports multiple entry points in the building process. The
function takes in an OutputChunk object and should return true for the file(s) you wish to use as the host of the CSS
injection. If multiple files are specified, the CSS injection code will be added to all files returned as true.
Here is an example of how to use the `jsAssetsFilterFunction`:
```javascript
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({
jsAssetsFilterFunction: function customJsAssetsfilterFunction(outputChunk) {
return outputChunk.fileName == 'index.js';
}
}),
]
}
```
In this example, the CSS injection code will only be added to the `index.js` file. If you wish to add the code to
multiple files, you can specify them in the function:
```javascript
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({
jsAssetsFilterFunction: function customJsAssetsfilterFunction(outputChunk) {
return outputChunk.fileName == 'index.js' || outputChunk.fileName == 'subdir/main.js';
}
}),
]
}
```
This code will add the injection code to both index.js and main.js files.
**Be aware that if you specified multiple files that the CSS can be doubled.**
#### preRenderCSSCode (function)
You can use the `preRenderCSSCode` parameter to make specific changes to your CSS before it is printed in the output JS
file. This parameter takes the CSS code extracted from the build process and allows you to return the modified CSS code
to be used within the injection code.
This way, you can customize the CSS code without having to write additional code that runs during the execution of your
application.
This is an example:
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({preRenderCSSCode: (cssCode) => cssCode}), // The return will be used as the CSS that will be injected during execution.
]
}
```
#### relativeCSSInjection (boolean)
_This feature is based on information provided by Vite. Since we can't control how Vite handles this information this
means that there may be problems that may not be possible to fix them in this plugin._
The default behavior of this plugin takes all the CSS code of your application directly to the entrypoint generated.
The `relativeCSSInjection` if configured to `true` will inject the CSS code of every entrypoint to the relative
importer.
**Set this option to `true` if you are using the multiple entry point option of Rollup.**
**For this feature to work, `build.cssCodeSplit` must be set to `true`**
_Future release can have an advanced behavior where this options will be configured to true automatically by sniffing
user configurations._
If a CSS chunk is generated that's not imported by any JS chunk, a warning will be shown. To disable this warning
set `suppressUnusedCssWarning` to `true`.
#### styleId (string | function)
If you provide a `string` for `styleId` param, the code of injection will set the `id` attribute of the `style` element
with the value of the parameter provided. This is an example:
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({styleId: "foo"}),
]
}
```
The output injected into the DOM will look like this example:
```html
<head>
<style id="foo">/* Generated CSS rules */</style>
</head>
```
If you provide a `function` for `styleId` param, it will run that function and return a string. It's especially useful
if you use `relativeCSSInjection` and want unique styleIds for each file.
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({styleId: () => `foo-${Math.random() * 100}`}),
]
}
```
```html
<head>
<style id="foo-1234">/* Generated CSS rules */</style>
<style id="foo-4321">/* Generated CSS rules */</style>
</head>
```
#### topExecutionPriority (boolean)
The default behavior adds the injection of CSS before your bundle code. If you provide `topExecutionPriority` equal
to: `false` the code of injection will be added after the bundle code. This is an example:
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({topExecutionPriority: false}),
]
}
```
#### useStrictCSP (boolean)
The `useStrictCSP` configuration option adds a nonce to style tags based
on `<meta property="csp-nonce" content={{ nonce }} />`. See the following [link](https://cssinjs.org/csp/?v=v10.9.2) for
more information.
This is an example:
```ts
import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js'
export default {
plugins: [
cssInjectedByJsPlugin({useStrictCSP: true}),
]
}
```
The tag `<meta property="csp-nonce" content={{ nonce }} />` (nonce should be replaced with the value) must be present in
your html page. The `content` value of that tag will be provided to the `nonce` property of the `style` element that
will be injected by our default injection code.
## Contributing
When you make changes to plugin locally, you may want to build the js from the typescript file of the plugin.
Here the guidelines:
### Install
```
npm install
```
### Testing
```
npm run test
```
### Build plugin
```
npm run build
```
See [CONTRIBUTING.md](CONTRIBUTING.md) for more information.
### A note for plugin-legacy users
At first the plugin supported generating the CSS injection code also in the legacy files generated by
the [plugin-legacy](https://github.com/vitejs/vite/tree/main/packages/plugin-legacy). Since the plugin-legacy injects
the CSS code for [different reasons](https://github.com/vitejs/vite/issues/2062), this plugin no longer has the
plugin-legacy support code. If the code of the plugin-legacy changes an update to this plugin may occur.

View File

@@ -0,0 +1,8 @@
import type { Plugin } from 'vite';
import type { PluginConfiguration } from './interface';
/**
* Inject the CSS compiled with JS.
*
* @return {Plugin}
*/
export default function cssInjectedByJsPlugin({ cssAssetsFilterFunction, dev: { enableDev, removeStyleCode, removeStyleCodeFunction }, injectCode, injectCodeFunction, injectionCodeFormat, jsAssetsFilterFunction, preRenderCSSCode, relativeCSSInjection, styleId, suppressUnusedCssWarning, topExecutionPriority, useStrictCSP, }?: PluginConfiguration | undefined): Plugin[];

View File

@@ -0,0 +1,31 @@
import type { InjectCode, InjectCodeFunction } from './utils';
import type { OutputAsset, OutputChunk } from 'rollup';
import type { BuildOptions } from 'vite';
import type { ModuleFormat } from 'rollup';
export interface DevOptions {
enableDev?: boolean;
removeStyleCode?: (id: string) => string;
removeStyleCodeFunction?: (id: string) => void;
}
export interface BaseOptions {
dev?: DevOptions;
injectCode?: InjectCode;
injectCodeFunction?: InjectCodeFunction;
injectionCodeFormat?: ModuleFormat;
styleId?: string | (() => string);
topExecutionPriority?: boolean;
useStrictCSP?: boolean;
}
export interface PluginConfiguration extends BaseOptions {
cssAssetsFilterFunction?: (chunk: OutputAsset) => boolean;
jsAssetsFilterFunction?: (chunk: OutputChunk) => boolean;
preRenderCSSCode?: (cssCode: string) => string;
relativeCSSInjection?: boolean;
suppressUnusedCssWarning?: boolean;
}
export interface CSSInjectionConfiguration extends BaseOptions {
cssToInject: string;
}
export interface BuildCSSInjectionConfiguration extends CSSInjectionConfiguration {
buildOptions: BuildOptions;
}

View File

@@ -0,0 +1,26 @@
import type { OutputBundle, OutputChunk } from 'rollup';
import type { BuildCSSInjectionConfiguration, PluginConfiguration } from './interface';
interface InjectCodeOptions {
styleId?: string | (() => string);
useStrictCSP?: boolean;
attributes?: {
[key: string]: string;
} | undefined;
}
export type InjectCode = (cssCode: string, options: InjectCodeOptions) => string;
export type InjectCodeFunction = (cssCode: string, options: InjectCodeOptions) => void;
export declare function buildCSSInjectionCode({ buildOptions, cssToInject, injectCode, injectCodeFunction, injectionCodeFormat, styleId, useStrictCSP, }: BuildCSSInjectionConfiguration): Promise<OutputChunk | null>;
export declare function resolveInjectionCode(cssCode: string, injectCode: ((cssCode: string, options: InjectCodeOptions) => string) | undefined, injectCodeFunction: ((cssCode: string, options: InjectCodeOptions) => void) | undefined, { styleId, useStrictCSP, attributes }: InjectCodeOptions): string;
export declare function removeLinkStyleSheets(html: string, cssFileName: string): string;
export declare function warnLog(msg: string): void;
export declare function debugLog(msg: string): void;
export declare function extractCss(bundle: OutputBundle, cssName: string): string;
export declare function concatCssAndDeleteFromBundle(bundle: OutputBundle, cssAssets: string[]): string;
export declare function buildJsCssMap(bundle: OutputBundle, jsAssetsFilterFunction?: PluginConfiguration['jsAssetsFilterFunction']): Record<string, string[]>;
export declare function getJsTargetBundleKeys(bundle: OutputBundle, jsAssetsFilterFunction?: PluginConfiguration['jsAssetsFilterFunction']): string[];
export declare function relativeCssInjection(bundle: OutputBundle, assetsWithCss: Record<string, string[]>, buildCssCode: (css: string) => Promise<OutputChunk | null>, topExecutionPriorityFlag: boolean): Promise<void>;
export declare function globalCssInjection(bundle: OutputBundle, cssAssets: string[], buildCssCode: (css: string) => Promise<OutputChunk | null>, jsAssetsFilterFunction: PluginConfiguration['jsAssetsFilterFunction'], topExecutionPriorityFlag: boolean): Promise<void>;
export declare function buildOutputChunkWithCssInjectionCode(jsAssetCode: string, cssInjectionCode: string, topExecutionPriorityFlag: boolean): string;
export declare function clearImportedCssViteMetadataFromBundle(bundle: OutputBundle, unusedCssAssets: string[]): void;
export declare function isCSSRequest(request: string): boolean;
export {};

View File

@@ -0,0 +1,136 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const utils_js_1 = require("./utils.js");
/**
* Inject the CSS compiled with JS.
*
* @return {Plugin}
*/
function cssInjectedByJsPlugin({ cssAssetsFilterFunction, dev: { enableDev, removeStyleCode, removeStyleCodeFunction } = {}, injectCode, injectCodeFunction, injectionCodeFormat, jsAssetsFilterFunction, preRenderCSSCode, relativeCSSInjection, styleId, suppressUnusedCssWarning, topExecutionPriority, useStrictCSP, } = {}) {
let config;
const topExecutionPriorityFlag = typeof topExecutionPriority == 'boolean' ? topExecutionPriority : true;
const plugins = [
{
apply: 'build',
enforce: 'post',
name: 'vite-plugin-css-injected-by-js',
config(config, env) {
if (env.command === 'build') {
if (!config.build) {
config.build = {};
}
if (relativeCSSInjection == true) {
if (!config.build.cssCodeSplit) {
config.build.cssCodeSplit = true;
(0, utils_js_1.warnLog)(`[vite-plugin-css-injected-by-js] Override of 'build.cssCodeSplit' option to true, it must be true when 'relativeCSSInjection' is enabled.`);
}
}
}
},
configResolved(_config) {
config = _config;
},
generateBundle(opts, bundle) {
return __awaiter(this, void 0, void 0, function* () {
if (config.build.ssr) {
return;
}
const buildCssCode = (cssToInject) => (0, utils_js_1.buildCSSInjectionCode)({
buildOptions: config.build,
cssToInject: typeof preRenderCSSCode == 'function' ? preRenderCSSCode(cssToInject) : cssToInject,
injectCode,
injectCodeFunction,
injectionCodeFormat,
styleId,
useStrictCSP,
});
const cssAssetsFilter = (asset) => {
return typeof cssAssetsFilterFunction == 'function' ? cssAssetsFilterFunction(asset) : true;
};
const cssAssets = Object.keys(bundle).filter((i) => bundle[i].type == 'asset' &&
bundle[i].fileName.endsWith('.css') &&
cssAssetsFilter(bundle[i]));
let unusedCssAssets = [];
if (relativeCSSInjection) {
const assetsWithCss = (0, utils_js_1.buildJsCssMap)(bundle, jsAssetsFilterFunction);
yield (0, utils_js_1.relativeCssInjection)(bundle, assetsWithCss, buildCssCode, topExecutionPriorityFlag);
unusedCssAssets = cssAssets.filter((cssAsset) => !!bundle[cssAsset]);
if (!suppressUnusedCssWarning) {
// With all used CSS assets now being removed from the bundle, navigate any that have not been linked and output
const unusedCssAssetsString = unusedCssAssets.join(',');
unusedCssAssetsString.length > 0 &&
(0, utils_js_1.warnLog)(`[vite-plugin-css-injected-by-js] Some CSS assets were not included in any known JS: ${unusedCssAssetsString}`);
}
}
else {
const allCssAssets = Object.keys(bundle).filter((i) => bundle[i].type == 'asset' &&
bundle[i].fileName.endsWith('.css'));
unusedCssAssets = allCssAssets.filter(cssAsset => !cssAssets.includes(cssAsset));
yield (0, utils_js_1.globalCssInjection)(bundle, cssAssets, buildCssCode, jsAssetsFilterFunction, topExecutionPriorityFlag);
}
(0, utils_js_1.clearImportedCssViteMetadataFromBundle)(bundle, unusedCssAssets);
const htmlFiles = Object.keys(bundle).filter((i) => i.endsWith('.html'));
for (const name of htmlFiles) {
const htmlChunk = bundle[name];
let replacedHtml = htmlChunk.source instanceof Uint8Array
? new TextDecoder().decode(htmlChunk.source)
: `${htmlChunk.source}`;
cssAssets.forEach(function replaceLinkedStylesheetsHtml(cssName) {
if (!unusedCssAssets.includes(cssName)) {
replacedHtml = (0, utils_js_1.removeLinkStyleSheets)(replacedHtml, cssName);
htmlChunk.source = replacedHtml;
}
});
}
});
},
},
];
if (enableDev) {
(0, utils_js_1.warnLog)('[vite-plugin-css-injected-by-js] Experimental dev mode activated! Please, for any error open a issue.');
plugins.push({
name: 'vite-plugin-css-injected-by-js-dev',
apply: 'serve',
enforce: 'post',
transform(src, id) {
if ((0, utils_js_1.isCSSRequest)(id)) {
const defaultRemoveStyleCode = (devId) => `{
(function removeStyleInjected() {
const elementsToRemove = document.querySelectorAll("style[data-vite-dev-id='${devId}']");
elementsToRemove.forEach(element => {
element.remove();
});
})()
}`;
let removeStyleFunction = removeStyleCode || defaultRemoveStyleCode;
if (removeStyleCodeFunction) {
removeStyleFunction = (id) => `(${removeStyleCodeFunction})("${id}")`;
}
// removeStyleFunction is called before since the function that inject the CSS doesn't handle the update case required in dev mode.
let injectionCode = src.replace('__vite__updateStyle(__vite__id, __vite__css)', ';\n' +
removeStyleFunction(id) +
';\n' +
(0, utils_js_1.resolveInjectionCode)('__vite__css', injectCode, injectCodeFunction, {
attributes: { type: 'text/css', ['data-vite-dev-id']: id },
}));
injectionCode = injectionCode.replace('__vite__removeStyle(__vite__id)', removeStyleFunction(id));
return {
code: injectionCode,
map: null,
};
}
},
});
}
return plugins;
}
exports.default = cssInjectedByJsPlugin;

View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View File

@@ -0,0 +1,3 @@
{
"type": "commonjs"
}

View File

@@ -0,0 +1,265 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isCSSRequest = exports.clearImportedCssViteMetadataFromBundle = exports.buildOutputChunkWithCssInjectionCode = exports.globalCssInjection = exports.relativeCssInjection = exports.getJsTargetBundleKeys = exports.buildJsCssMap = exports.concatCssAndDeleteFromBundle = exports.extractCss = exports.debugLog = exports.warnLog = exports.removeLinkStyleSheets = exports.resolveInjectionCode = exports.buildCSSInjectionCode = void 0;
const vite_1 = require("vite");
const cssInjectedByJsId = '\0vite/all-css';
const defaultInjectCode = (cssCode, { styleId, useStrictCSP, attributes }) => {
let attributesInjection = '';
for (const attribute in attributes) {
attributesInjection += `elementStyle.setAttribute('${attribute}', '${attributes[attribute]}');`;
}
return `try{if(typeof document != 'undefined'){var elementStyle = document.createElement('style');${typeof styleId == 'string' && styleId.length > 0 ? `elementStyle.id = '${styleId}';` : ''}${useStrictCSP ? `elementStyle.nonce = document.head.querySelector('meta[property=csp-nonce]')?.content;` : ''}${attributesInjection}elementStyle.appendChild(document.createTextNode(${cssCode}));document.head.appendChild(elementStyle);}}catch(e){console.error('vite-plugin-css-injected-by-js', e);}`;
};
function buildCSSInjectionCode({ buildOptions, cssToInject, injectCode, injectCodeFunction, injectionCodeFormat = 'iife', styleId, useStrictCSP, }) {
return __awaiter(this, void 0, void 0, function* () {
let { minify, target } = buildOptions;
const generatedStyleId = typeof styleId === 'function' ? styleId() : styleId;
const res = yield (0, vite_1.build)({
root: '',
configFile: false,
logLevel: 'error',
plugins: [
injectionCSSCodePlugin({
cssToInject,
styleId: generatedStyleId,
injectCode,
injectCodeFunction,
useStrictCSP,
}),
],
build: {
write: false,
target,
minify,
assetsDir: '',
rollupOptions: {
input: {
['all-css']: cssInjectedByJsId,
},
output: {
format: injectionCodeFormat,
manualChunks: undefined,
},
},
},
});
const _cssChunk = Array.isArray(res) ? res[0] : res;
if (!('output' in _cssChunk))
return null;
return _cssChunk.output[0];
});
}
exports.buildCSSInjectionCode = buildCSSInjectionCode;
function resolveInjectionCode(cssCode, injectCode, injectCodeFunction, { styleId, useStrictCSP, attributes }) {
const injectionOptions = { styleId, useStrictCSP, attributes };
if (injectCodeFunction) {
return `(${injectCodeFunction})(${cssCode}, ${JSON.stringify(injectionOptions)})`;
}
const injectFunction = injectCode || defaultInjectCode;
return injectFunction(cssCode, injectionOptions);
}
exports.resolveInjectionCode = resolveInjectionCode;
function injectionCSSCodePlugin({ cssToInject, injectCode, injectCodeFunction, styleId, useStrictCSP, }) {
return {
name: 'vite:injection-css-code-plugin',
resolveId(id) {
if (id == cssInjectedByJsId) {
return id;
}
},
load(id) {
if (id == cssInjectedByJsId) {
const cssCode = JSON.stringify(cssToInject.trim());
return resolveInjectionCode(cssCode, injectCode, injectCodeFunction, { styleId, useStrictCSP });
}
},
};
}
function removeLinkStyleSheets(html, cssFileName) {
const removeCSS = new RegExp(`<link rel=".*"[^>]*?href=".*/?${cssFileName}"[^>]*?>`);
return html.replace(removeCSS, '');
}
exports.removeLinkStyleSheets = removeLinkStyleSheets;
/* istanbul ignore next -- @preserve */
function warnLog(msg) {
console.warn(`\x1b[33m \n${msg} \x1b[39m`);
}
exports.warnLog = warnLog;
/* istanbul ignore next -- @preserve */
function debugLog(msg) {
console.debug(`\x1b[34m \n${msg} \x1b[39m`);
}
exports.debugLog = debugLog;
function isJsOutputChunk(chunk) {
return chunk.type == 'chunk' && chunk.fileName.match(/.[cm]?js(?:\?.+)?$/) != null;
}
function defaultJsAssetsFilter(chunk) {
return chunk.isEntry && !chunk.fileName.includes('polyfill');
}
// The cache must be global since execution context is different every entry
const cssSourceCache = {};
function extractCss(bundle, cssName) {
var _a;
const cssAsset = bundle[cssName];
if (cssAsset !== undefined && cssAsset.source) {
const cssSource = cssAsset.source;
// We treat these as strings and coerce them implicitly to strings, explicitly handle conversion
cssSourceCache[cssName] =
cssSource instanceof Uint8Array ? new TextDecoder().decode(cssSource) : `${cssSource}`;
}
return (_a = cssSourceCache[cssName]) !== null && _a !== void 0 ? _a : '';
}
exports.extractCss = extractCss;
function concatCssAndDeleteFromBundle(bundle, cssAssets) {
return cssAssets.reduce(function extractCssAndDeleteFromBundle(previous, cssName) {
const cssSource = extractCss(bundle, cssName);
delete bundle[cssName];
return previous + cssSource;
}, '');
}
exports.concatCssAndDeleteFromBundle = concatCssAndDeleteFromBundle;
function buildJsCssMap(bundle, jsAssetsFilterFunction) {
const chunksWithCss = {};
const bundleKeys = getJsTargetBundleKeys(bundle, typeof jsAssetsFilterFunction == 'function' ? jsAssetsFilterFunction : () => true);
if (bundleKeys.length === 0) {
throw new Error('Unable to locate the JavaScript asset for adding the CSS injection code. It is recommended to review your configurations.');
}
for (const key of bundleKeys) {
const chunk = bundle[key];
if (chunk.type === 'asset' || !chunk.viteMetadata || chunk.viteMetadata.importedCss.size === 0) {
continue;
}
const chunkStyles = chunksWithCss[key] || [];
chunkStyles.push(...chunk.viteMetadata.importedCss.values());
chunksWithCss[key] = chunkStyles;
}
return chunksWithCss;
}
exports.buildJsCssMap = buildJsCssMap;
function getJsTargetBundleKeys(bundle, jsAssetsFilterFunction) {
if (typeof jsAssetsFilterFunction != 'function') {
const jsAssets = Object.keys(bundle).filter((i) => {
const asset = bundle[i];
return isJsOutputChunk(asset) && defaultJsAssetsFilter(asset);
});
if (jsAssets.length == 0) {
return [];
}
const jsTargetFileName = jsAssets[jsAssets.length - 1];
if (jsAssets.length > 1) {
warnLog(`[vite-plugin-css-injected-by-js] has identified "${jsTargetFileName}" as one of the multiple output files marked as "entry" to put the CSS injection code.` +
'However, if this is not the intended file to add the CSS injection code, you can use the "jsAssetsFilterFunction" parameter to specify the desired output file (read docs).');
if (process.env.VITE_CSS_INJECTED_BY_JS_DEBUG) {
const jsAssetsStr = jsAssets.join(', ');
debugLog(`[vite-plugin-css-injected-by-js] identified js file targets: ${jsAssetsStr}. Selected "${jsTargetFileName}".\n`);
}
}
// This should be always the root of the application
return [jsTargetFileName];
}
const chunkFilter = ([_key, chunk]) => isJsOutputChunk(chunk) && jsAssetsFilterFunction(chunk);
return Object.entries(bundle)
.filter(chunkFilter)
.map(function extractAssetKeyFromBundleEntry([key]) {
return key;
});
}
exports.getJsTargetBundleKeys = getJsTargetBundleKeys;
function relativeCssInjection(bundle, assetsWithCss, buildCssCode, topExecutionPriorityFlag) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
for (const [jsAssetName, cssAssets] of Object.entries(assetsWithCss)) {
process.env.VITE_CSS_INJECTED_BY_JS_DEBUG &&
debugLog(`[vite-plugin-css-injected-by-js] Relative CSS: ${jsAssetName}: [ ${cssAssets.join(',')} ]`);
const assetCss = concatCssAndDeleteFromBundle(bundle, cssAssets);
const cssInjectionCode = assetCss.length > 0 ? (_a = (yield buildCssCode(assetCss))) === null || _a === void 0 ? void 0 : _a.code : '';
// We have already filtered these chunks to be RenderedChunks
const jsAsset = bundle[jsAssetName];
jsAsset.code = buildOutputChunkWithCssInjectionCode(jsAsset.code, cssInjectionCode !== null && cssInjectionCode !== void 0 ? cssInjectionCode : '', topExecutionPriorityFlag);
}
});
}
exports.relativeCssInjection = relativeCssInjection;
const globalCSSCodeEntryCache = new Map();
let previousFacadeModuleId = '';
function globalCssInjection(bundle, cssAssets, buildCssCode, jsAssetsFilterFunction, topExecutionPriorityFlag) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const jsTargetBundleKeys = getJsTargetBundleKeys(bundle, jsAssetsFilterFunction);
if (jsTargetBundleKeys.length == 0) {
throw new Error('Unable to locate the JavaScript asset for adding the CSS injection code. It is recommended to review your configurations.');
}
process.env.VITE_CSS_INJECTED_BY_JS_DEBUG &&
debugLog(`[vite-plugin-css-injected-by-js] Global CSS Assets: [${cssAssets.join(',')}]`);
const allCssCode = concatCssAndDeleteFromBundle(bundle, cssAssets);
let cssInjectionCode = '';
if (allCssCode.length > 0) {
const cssCode = (_a = (yield buildCssCode(allCssCode))) === null || _a === void 0 ? void 0 : _a.code;
if (typeof cssCode == 'string') {
cssInjectionCode = cssCode;
}
}
for (const jsTargetKey of jsTargetBundleKeys) {
const jsAsset = bundle[jsTargetKey];
/**
* Since it creates the assets once sequential builds for the same entry point
* (for example when multiple formats of same entry point are built),
* we need to reuse the same CSS created the first time.
*/
if (jsAsset.facadeModuleId != null && jsAsset.isEntry && cssInjectionCode != '') {
if (jsAsset.facadeModuleId != previousFacadeModuleId) {
globalCSSCodeEntryCache.clear();
}
previousFacadeModuleId = jsAsset.facadeModuleId;
globalCSSCodeEntryCache.set(jsAsset.facadeModuleId, cssInjectionCode);
}
if (cssInjectionCode == '' &&
jsAsset.isEntry &&
jsAsset.facadeModuleId != null &&
typeof globalCSSCodeEntryCache.get(jsAsset.facadeModuleId) == 'string') {
cssInjectionCode = globalCSSCodeEntryCache.get(jsAsset.facadeModuleId);
}
process.env.VITE_CSS_INJECTED_BY_JS_DEBUG &&
debugLog(`[vite-plugin-css-injected-by-js] Global CSS inject: ${jsAsset.fileName}`);
jsAsset.code = buildOutputChunkWithCssInjectionCode(jsAsset.code, cssInjectionCode !== null && cssInjectionCode !== void 0 ? cssInjectionCode : '', topExecutionPriorityFlag);
}
});
}
exports.globalCssInjection = globalCssInjection;
function buildOutputChunkWithCssInjectionCode(jsAssetCode, cssInjectionCode, topExecutionPriorityFlag) {
const appCode = jsAssetCode.replace(/\/\*\s*empty css\s*\*\//g, '');
jsAssetCode = topExecutionPriorityFlag ? '' : appCode;
jsAssetCode += cssInjectionCode;
jsAssetCode += !topExecutionPriorityFlag ? '' : appCode;
return jsAssetCode;
}
exports.buildOutputChunkWithCssInjectionCode = buildOutputChunkWithCssInjectionCode;
function clearImportedCssViteMetadataFromBundle(bundle, unusedCssAssets) {
// Required to exclude removed files from manifest.json
for (const key in bundle) {
const chunk = bundle[key];
if (chunk.viteMetadata && chunk.viteMetadata.importedCss.size > 0) {
const importedCssFileNames = chunk.viteMetadata.importedCss;
importedCssFileNames.forEach((importedCssFileName) => {
if (!unusedCssAssets.includes(importedCssFileName) && chunk.viteMetadata) {
chunk.viteMetadata.importedCss = new Set();
}
});
}
}
}
exports.clearImportedCssViteMetadataFromBundle = clearImportedCssViteMetadataFromBundle;
function isCSSRequest(request) {
const CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
return CSS_LANGS_RE.test(request);
}
exports.isCSSRequest = isCSSRequest;

View File

@@ -0,0 +1,8 @@
import type { Plugin } from 'vite';
import type { PluginConfiguration } from './interface';
/**
* Inject the CSS compiled with JS.
*
* @return {Plugin}
*/
export default function cssInjectedByJsPlugin({ cssAssetsFilterFunction, dev: { enableDev, removeStyleCode, removeStyleCodeFunction }, injectCode, injectCodeFunction, injectionCodeFormat, jsAssetsFilterFunction, preRenderCSSCode, relativeCSSInjection, styleId, suppressUnusedCssWarning, topExecutionPriority, useStrictCSP, }?: PluginConfiguration | undefined): Plugin[];

View File

@@ -0,0 +1,31 @@
import type { InjectCode, InjectCodeFunction } from './utils';
import type { OutputAsset, OutputChunk } from 'rollup';
import type { BuildOptions } from 'vite';
import type { ModuleFormat } from 'rollup';
export interface DevOptions {
enableDev?: boolean;
removeStyleCode?: (id: string) => string;
removeStyleCodeFunction?: (id: string) => void;
}
export interface BaseOptions {
dev?: DevOptions;
injectCode?: InjectCode;
injectCodeFunction?: InjectCodeFunction;
injectionCodeFormat?: ModuleFormat;
styleId?: string | (() => string);
topExecutionPriority?: boolean;
useStrictCSP?: boolean;
}
export interface PluginConfiguration extends BaseOptions {
cssAssetsFilterFunction?: (chunk: OutputAsset) => boolean;
jsAssetsFilterFunction?: (chunk: OutputChunk) => boolean;
preRenderCSSCode?: (cssCode: string) => string;
relativeCSSInjection?: boolean;
suppressUnusedCssWarning?: boolean;
}
export interface CSSInjectionConfiguration extends BaseOptions {
cssToInject: string;
}
export interface BuildCSSInjectionConfiguration extends CSSInjectionConfiguration {
buildOptions: BuildOptions;
}

View File

@@ -0,0 +1,26 @@
import type { OutputBundle, OutputChunk } from 'rollup';
import type { BuildCSSInjectionConfiguration, PluginConfiguration } from './interface';
interface InjectCodeOptions {
styleId?: string | (() => string);
useStrictCSP?: boolean;
attributes?: {
[key: string]: string;
} | undefined;
}
export type InjectCode = (cssCode: string, options: InjectCodeOptions) => string;
export type InjectCodeFunction = (cssCode: string, options: InjectCodeOptions) => void;
export declare function buildCSSInjectionCode({ buildOptions, cssToInject, injectCode, injectCodeFunction, injectionCodeFormat, styleId, useStrictCSP, }: BuildCSSInjectionConfiguration): Promise<OutputChunk | null>;
export declare function resolveInjectionCode(cssCode: string, injectCode: ((cssCode: string, options: InjectCodeOptions) => string) | undefined, injectCodeFunction: ((cssCode: string, options: InjectCodeOptions) => void) | undefined, { styleId, useStrictCSP, attributes }: InjectCodeOptions): string;
export declare function removeLinkStyleSheets(html: string, cssFileName: string): string;
export declare function warnLog(msg: string): void;
export declare function debugLog(msg: string): void;
export declare function extractCss(bundle: OutputBundle, cssName: string): string;
export declare function concatCssAndDeleteFromBundle(bundle: OutputBundle, cssAssets: string[]): string;
export declare function buildJsCssMap(bundle: OutputBundle, jsAssetsFilterFunction?: PluginConfiguration['jsAssetsFilterFunction']): Record<string, string[]>;
export declare function getJsTargetBundleKeys(bundle: OutputBundle, jsAssetsFilterFunction?: PluginConfiguration['jsAssetsFilterFunction']): string[];
export declare function relativeCssInjection(bundle: OutputBundle, assetsWithCss: Record<string, string[]>, buildCssCode: (css: string) => Promise<OutputChunk | null>, topExecutionPriorityFlag: boolean): Promise<void>;
export declare function globalCssInjection(bundle: OutputBundle, cssAssets: string[], buildCssCode: (css: string) => Promise<OutputChunk | null>, jsAssetsFilterFunction: PluginConfiguration['jsAssetsFilterFunction'], topExecutionPriorityFlag: boolean): Promise<void>;
export declare function buildOutputChunkWithCssInjectionCode(jsAssetCode: string, cssInjectionCode: string, topExecutionPriorityFlag: boolean): string;
export declare function clearImportedCssViteMetadataFromBundle(bundle: OutputBundle, unusedCssAssets: string[]): void;
export declare function isCSSRequest(request: string): boolean;
export {};

View File

@@ -0,0 +1,122 @@
import { buildCSSInjectionCode, buildJsCssMap, clearImportedCssViteMetadataFromBundle, globalCssInjection, isCSSRequest, relativeCssInjection, removeLinkStyleSheets, resolveInjectionCode, warnLog, } from './utils.js';
/**
* Inject the CSS compiled with JS.
*
* @return {Plugin}
*/
export default function cssInjectedByJsPlugin({ cssAssetsFilterFunction, dev: { enableDev, removeStyleCode, removeStyleCodeFunction } = {}, injectCode, injectCodeFunction, injectionCodeFormat, jsAssetsFilterFunction, preRenderCSSCode, relativeCSSInjection, styleId, suppressUnusedCssWarning, topExecutionPriority, useStrictCSP, } = {}) {
let config;
const topExecutionPriorityFlag = typeof topExecutionPriority == 'boolean' ? topExecutionPriority : true;
const plugins = [
{
apply: 'build',
enforce: 'post',
name: 'vite-plugin-css-injected-by-js',
config(config, env) {
if (env.command === 'build') {
if (!config.build) {
config.build = {};
}
if (relativeCSSInjection == true) {
if (!config.build.cssCodeSplit) {
config.build.cssCodeSplit = true;
warnLog(`[vite-plugin-css-injected-by-js] Override of 'build.cssCodeSplit' option to true, it must be true when 'relativeCSSInjection' is enabled.`);
}
}
}
},
configResolved(_config) {
config = _config;
},
async generateBundle(opts, bundle) {
if (config.build.ssr) {
return;
}
const buildCssCode = (cssToInject) => buildCSSInjectionCode({
buildOptions: config.build,
cssToInject: typeof preRenderCSSCode == 'function' ? preRenderCSSCode(cssToInject) : cssToInject,
injectCode,
injectCodeFunction,
injectionCodeFormat,
styleId,
useStrictCSP,
});
const cssAssetsFilter = (asset) => {
return typeof cssAssetsFilterFunction == 'function' ? cssAssetsFilterFunction(asset) : true;
};
const cssAssets = Object.keys(bundle).filter((i) => bundle[i].type == 'asset' &&
bundle[i].fileName.endsWith('.css') &&
cssAssetsFilter(bundle[i]));
let unusedCssAssets = [];
if (relativeCSSInjection) {
const assetsWithCss = buildJsCssMap(bundle, jsAssetsFilterFunction);
await relativeCssInjection(bundle, assetsWithCss, buildCssCode, topExecutionPriorityFlag);
unusedCssAssets = cssAssets.filter((cssAsset) => !!bundle[cssAsset]);
if (!suppressUnusedCssWarning) {
// With all used CSS assets now being removed from the bundle, navigate any that have not been linked and output
const unusedCssAssetsString = unusedCssAssets.join(',');
unusedCssAssetsString.length > 0 &&
warnLog(`[vite-plugin-css-injected-by-js] Some CSS assets were not included in any known JS: ${unusedCssAssetsString}`);
}
}
else {
const allCssAssets = Object.keys(bundle).filter((i) => bundle[i].type == 'asset' &&
bundle[i].fileName.endsWith('.css'));
unusedCssAssets = allCssAssets.filter(cssAsset => !cssAssets.includes(cssAsset));
await globalCssInjection(bundle, cssAssets, buildCssCode, jsAssetsFilterFunction, topExecutionPriorityFlag);
}
clearImportedCssViteMetadataFromBundle(bundle, unusedCssAssets);
const htmlFiles = Object.keys(bundle).filter((i) => i.endsWith('.html'));
for (const name of htmlFiles) {
const htmlChunk = bundle[name];
let replacedHtml = htmlChunk.source instanceof Uint8Array
? new TextDecoder().decode(htmlChunk.source)
: `${htmlChunk.source}`;
cssAssets.forEach(function replaceLinkedStylesheetsHtml(cssName) {
if (!unusedCssAssets.includes(cssName)) {
replacedHtml = removeLinkStyleSheets(replacedHtml, cssName);
htmlChunk.source = replacedHtml;
}
});
}
},
},
];
if (enableDev) {
warnLog('[vite-plugin-css-injected-by-js] Experimental dev mode activated! Please, for any error open a issue.');
plugins.push({
name: 'vite-plugin-css-injected-by-js-dev',
apply: 'serve',
enforce: 'post',
transform(src, id) {
if (isCSSRequest(id)) {
const defaultRemoveStyleCode = (devId) => `{
(function removeStyleInjected() {
const elementsToRemove = document.querySelectorAll("style[data-vite-dev-id='${devId}']");
elementsToRemove.forEach(element => {
element.remove();
});
})()
}`;
let removeStyleFunction = removeStyleCode || defaultRemoveStyleCode;
if (removeStyleCodeFunction) {
removeStyleFunction = (id) => `(${removeStyleCodeFunction})("${id}")`;
}
// removeStyleFunction is called before since the function that inject the CSS doesn't handle the update case required in dev mode.
let injectionCode = src.replace('__vite__updateStyle(__vite__id, __vite__css)', ';\n' +
removeStyleFunction(id) +
';\n' +
resolveInjectionCode('__vite__css', injectCode, injectCodeFunction, {
attributes: { type: 'text/css', ['data-vite-dev-id']: id },
}));
injectionCode = injectionCode.replace('__vite__removeStyle(__vite__id)', removeStyleFunction(id));
return {
code: injectionCode,
map: null,
};
}
},
});
}
return plugins;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,3 @@
{
"type": "module"
}

View File

@@ -0,0 +1,230 @@
import { build } from 'vite';
const cssInjectedByJsId = '\0vite/all-css';
const defaultInjectCode = (cssCode, { styleId, useStrictCSP, attributes }) => {
let attributesInjection = '';
for (const attribute in attributes) {
attributesInjection += `elementStyle.setAttribute('${attribute}', '${attributes[attribute]}');`;
}
return `try{if(typeof document != 'undefined'){var elementStyle = document.createElement('style');${typeof styleId == 'string' && styleId.length > 0 ? `elementStyle.id = '${styleId}';` : ''}${useStrictCSP ? `elementStyle.nonce = document.head.querySelector('meta[property=csp-nonce]')?.content;` : ''}${attributesInjection}elementStyle.appendChild(document.createTextNode(${cssCode}));document.head.appendChild(elementStyle);}}catch(e){console.error('vite-plugin-css-injected-by-js', e);}`;
};
export async function buildCSSInjectionCode({ buildOptions, cssToInject, injectCode, injectCodeFunction, injectionCodeFormat = 'iife', styleId, useStrictCSP, }) {
let { minify, target } = buildOptions;
const generatedStyleId = typeof styleId === 'function' ? styleId() : styleId;
const res = await build({
root: '',
configFile: false,
logLevel: 'error',
plugins: [
injectionCSSCodePlugin({
cssToInject,
styleId: generatedStyleId,
injectCode,
injectCodeFunction,
useStrictCSP,
}),
],
build: {
write: false,
target,
minify,
assetsDir: '',
rollupOptions: {
input: {
['all-css']: cssInjectedByJsId,
},
output: {
format: injectionCodeFormat,
manualChunks: undefined,
},
},
},
});
const _cssChunk = Array.isArray(res) ? res[0] : res;
if (!('output' in _cssChunk))
return null;
return _cssChunk.output[0];
}
export function resolveInjectionCode(cssCode, injectCode, injectCodeFunction, { styleId, useStrictCSP, attributes }) {
const injectionOptions = { styleId, useStrictCSP, attributes };
if (injectCodeFunction) {
return `(${injectCodeFunction})(${cssCode}, ${JSON.stringify(injectionOptions)})`;
}
const injectFunction = injectCode || defaultInjectCode;
return injectFunction(cssCode, injectionOptions);
}
function injectionCSSCodePlugin({ cssToInject, injectCode, injectCodeFunction, styleId, useStrictCSP, }) {
return {
name: 'vite:injection-css-code-plugin',
resolveId(id) {
if (id == cssInjectedByJsId) {
return id;
}
},
load(id) {
if (id == cssInjectedByJsId) {
const cssCode = JSON.stringify(cssToInject.trim());
return resolveInjectionCode(cssCode, injectCode, injectCodeFunction, { styleId, useStrictCSP });
}
},
};
}
export function removeLinkStyleSheets(html, cssFileName) {
const removeCSS = new RegExp(`<link rel=".*"[^>]*?href=".*/?${cssFileName}"[^>]*?>`);
return html.replace(removeCSS, '');
}
/* istanbul ignore next -- @preserve */
export function warnLog(msg) {
console.warn(`\x1b[33m \n${msg} \x1b[39m`);
}
/* istanbul ignore next -- @preserve */
export function debugLog(msg) {
console.debug(`\x1b[34m \n${msg} \x1b[39m`);
}
function isJsOutputChunk(chunk) {
return chunk.type == 'chunk' && chunk.fileName.match(/.[cm]?js(?:\?.+)?$/) != null;
}
function defaultJsAssetsFilter(chunk) {
return chunk.isEntry && !chunk.fileName.includes('polyfill');
}
// The cache must be global since execution context is different every entry
const cssSourceCache = {};
export function extractCss(bundle, cssName) {
const cssAsset = bundle[cssName];
if (cssAsset !== undefined && cssAsset.source) {
const cssSource = cssAsset.source;
// We treat these as strings and coerce them implicitly to strings, explicitly handle conversion
cssSourceCache[cssName] =
cssSource instanceof Uint8Array ? new TextDecoder().decode(cssSource) : `${cssSource}`;
}
return cssSourceCache[cssName] ?? '';
}
export function concatCssAndDeleteFromBundle(bundle, cssAssets) {
return cssAssets.reduce(function extractCssAndDeleteFromBundle(previous, cssName) {
const cssSource = extractCss(bundle, cssName);
delete bundle[cssName];
return previous + cssSource;
}, '');
}
export function buildJsCssMap(bundle, jsAssetsFilterFunction) {
const chunksWithCss = {};
const bundleKeys = getJsTargetBundleKeys(bundle, typeof jsAssetsFilterFunction == 'function' ? jsAssetsFilterFunction : () => true);
if (bundleKeys.length === 0) {
throw new Error('Unable to locate the JavaScript asset for adding the CSS injection code. It is recommended to review your configurations.');
}
for (const key of bundleKeys) {
const chunk = bundle[key];
if (chunk.type === 'asset' || !chunk.viteMetadata || chunk.viteMetadata.importedCss.size === 0) {
continue;
}
const chunkStyles = chunksWithCss[key] || [];
chunkStyles.push(...chunk.viteMetadata.importedCss.values());
chunksWithCss[key] = chunkStyles;
}
return chunksWithCss;
}
export function getJsTargetBundleKeys(bundle, jsAssetsFilterFunction) {
if (typeof jsAssetsFilterFunction != 'function') {
const jsAssets = Object.keys(bundle).filter((i) => {
const asset = bundle[i];
return isJsOutputChunk(asset) && defaultJsAssetsFilter(asset);
});
if (jsAssets.length == 0) {
return [];
}
const jsTargetFileName = jsAssets[jsAssets.length - 1];
if (jsAssets.length > 1) {
warnLog(`[vite-plugin-css-injected-by-js] has identified "${jsTargetFileName}" as one of the multiple output files marked as "entry" to put the CSS injection code.` +
'However, if this is not the intended file to add the CSS injection code, you can use the "jsAssetsFilterFunction" parameter to specify the desired output file (read docs).');
if (process.env.VITE_CSS_INJECTED_BY_JS_DEBUG) {
const jsAssetsStr = jsAssets.join(', ');
debugLog(`[vite-plugin-css-injected-by-js] identified js file targets: ${jsAssetsStr}. Selected "${jsTargetFileName}".\n`);
}
}
// This should be always the root of the application
return [jsTargetFileName];
}
const chunkFilter = ([_key, chunk]) => isJsOutputChunk(chunk) && jsAssetsFilterFunction(chunk);
return Object.entries(bundle)
.filter(chunkFilter)
.map(function extractAssetKeyFromBundleEntry([key]) {
return key;
});
}
export async function relativeCssInjection(bundle, assetsWithCss, buildCssCode, topExecutionPriorityFlag) {
for (const [jsAssetName, cssAssets] of Object.entries(assetsWithCss)) {
process.env.VITE_CSS_INJECTED_BY_JS_DEBUG &&
debugLog(`[vite-plugin-css-injected-by-js] Relative CSS: ${jsAssetName}: [ ${cssAssets.join(',')} ]`);
const assetCss = concatCssAndDeleteFromBundle(bundle, cssAssets);
const cssInjectionCode = assetCss.length > 0 ? (await buildCssCode(assetCss))?.code : '';
// We have already filtered these chunks to be RenderedChunks
const jsAsset = bundle[jsAssetName];
jsAsset.code = buildOutputChunkWithCssInjectionCode(jsAsset.code, cssInjectionCode ?? '', topExecutionPriorityFlag);
}
}
const globalCSSCodeEntryCache = new Map();
let previousFacadeModuleId = '';
export async function globalCssInjection(bundle, cssAssets, buildCssCode, jsAssetsFilterFunction, topExecutionPriorityFlag) {
const jsTargetBundleKeys = getJsTargetBundleKeys(bundle, jsAssetsFilterFunction);
if (jsTargetBundleKeys.length == 0) {
throw new Error('Unable to locate the JavaScript asset for adding the CSS injection code. It is recommended to review your configurations.');
}
process.env.VITE_CSS_INJECTED_BY_JS_DEBUG &&
debugLog(`[vite-plugin-css-injected-by-js] Global CSS Assets: [${cssAssets.join(',')}]`);
const allCssCode = concatCssAndDeleteFromBundle(bundle, cssAssets);
let cssInjectionCode = '';
if (allCssCode.length > 0) {
const cssCode = (await buildCssCode(allCssCode))?.code;
if (typeof cssCode == 'string') {
cssInjectionCode = cssCode;
}
}
for (const jsTargetKey of jsTargetBundleKeys) {
const jsAsset = bundle[jsTargetKey];
/**
* Since it creates the assets once sequential builds for the same entry point
* (for example when multiple formats of same entry point are built),
* we need to reuse the same CSS created the first time.
*/
if (jsAsset.facadeModuleId != null && jsAsset.isEntry && cssInjectionCode != '') {
if (jsAsset.facadeModuleId != previousFacadeModuleId) {
globalCSSCodeEntryCache.clear();
}
previousFacadeModuleId = jsAsset.facadeModuleId;
globalCSSCodeEntryCache.set(jsAsset.facadeModuleId, cssInjectionCode);
}
if (cssInjectionCode == '' &&
jsAsset.isEntry &&
jsAsset.facadeModuleId != null &&
typeof globalCSSCodeEntryCache.get(jsAsset.facadeModuleId) == 'string') {
cssInjectionCode = globalCSSCodeEntryCache.get(jsAsset.facadeModuleId);
}
process.env.VITE_CSS_INJECTED_BY_JS_DEBUG &&
debugLog(`[vite-plugin-css-injected-by-js] Global CSS inject: ${jsAsset.fileName}`);
jsAsset.code = buildOutputChunkWithCssInjectionCode(jsAsset.code, cssInjectionCode ?? '', topExecutionPriorityFlag);
}
}
export function buildOutputChunkWithCssInjectionCode(jsAssetCode, cssInjectionCode, topExecutionPriorityFlag) {
const appCode = jsAssetCode.replace(/\/\*\s*empty css\s*\*\//g, '');
jsAssetCode = topExecutionPriorityFlag ? '' : appCode;
jsAssetCode += cssInjectionCode;
jsAssetCode += !topExecutionPriorityFlag ? '' : appCode;
return jsAssetCode;
}
export function clearImportedCssViteMetadataFromBundle(bundle, unusedCssAssets) {
// Required to exclude removed files from manifest.json
for (const key in bundle) {
const chunk = bundle[key];
if (chunk.viteMetadata && chunk.viteMetadata.importedCss.size > 0) {
const importedCssFileNames = chunk.viteMetadata.importedCss;
importedCssFileNames.forEach((importedCssFileName) => {
if (!unusedCssAssets.includes(importedCssFileName) && chunk.viteMetadata) {
chunk.viteMetadata.importedCss = new Set();
}
});
}
}
}
export function isCSSRequest(request) {
const CSS_LANGS_RE = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(?:$|\?)/;
return CSS_LANGS_RE.test(request);
}

View File

@@ -0,0 +1,58 @@
{
"name": "vite-plugin-css-injected-by-js",
"version": "3.5.2",
"description": "A Vite plugin that takes the CSS and adds it to the page through the JS. For those who want a single JS file.",
"main": "dist/cjs/index.js",
"module": "dist/esm/index.js",
"exports": {
".": {
"types": "./dist/esm/declarations/index.d.ts",
"import": "./dist/esm/index.js",
"require": "./dist/cjs/index.js"
}
},
"typings": "dist/esm/declarations/index.d.ts",
"files": [
"dist"
],
"scripts": {
"test": "vitest",
"coverage": "rimraf coverage && vitest run --coverage",
"ci-test": "vitest run",
"build": "rimraf dist && tsc -p tsconfig.json && tsc -p tsconfig-cjs.json && ./cjs-esm-fixup",
"format": "prettier '{src,test}/*.ts' --write"
},
"repository": {
"type": "git",
"url": "git+https://github.com/marco-prontera/vite-plugin-css-injected-by-js.git"
},
"keywords": [
"vite",
"vite-plugin",
"plugin",
"typescript",
"css-injected-by-js",
"single-js-file",
"css",
"js"
],
"peerDependencies": {
"vite": ">2.0.0-0"
},
"devDependencies": {
"@types/node": "^18.11.15",
"@vitest/coverage-istanbul": "^0.34.6",
"happy-dom": "^8.1.3",
"prettier": "^2.8.1",
"rimraf": "^5.0.5",
"typescript": "^4.9.4",
"vite": "^4.5.0",
"vitest": "^0.34.6"
},
"author": "Marco Prontera",
"license": "MIT",
"bugs": {
"url": "https://github.com/marco-prontera/vite-plugin-css-injected-by-js/issues"
},
"homepage": "https://github.com/marco-prontera/vite-plugin-css-injected-by-js#readme"
}