feat!: inject style via constructable CSSStyleSheet and remove CSP entry (#847)

* chore: not inject inline css on server

* feat!: remove csp entry

* keep csp title in readme

* chore: switch to rolldown and tsdown

* update

* dedupe

* update according to review

* emphasize "both" in csp section

* load css with unplugin-raw

* change tsdown entry
This commit is contained in:
Yue JIN
2025-08-05 12:15:21 +08:00
committed by Justineo
parent 570a26c262
commit 8ed975e09b
12 changed files with 687 additions and 702 deletions

View File

@ -1,2 +1,3 @@
pnpm-lock.yaml pnpm-lock.yaml
demo/data/*.json demo/data/*.json
src/style.css

View File

@ -411,7 +411,7 @@ Static methods can be accessed from [`echarts` itself](https://echarts.apache.or
## CSP: `style-src` or `style-src-elem` ## CSP: `style-src` or `style-src-elem`
If you are applying a CSP to prevent inline `<style>` injection, you need to use `vue-echarts/csp` instead of `vue-echarts` and include `vue-echarts/csp/style.css` manually. If you are **both** enforcing a strict CSP that prevents inline `<style>` injection and targeting browsers that don't support the [CSSStyleSheet() constructor](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet#browser_compatibility), you need to manually include `vue-echarts/style.css`.
## Migration to v7 ## Migration to v7

View File

@ -411,7 +411,7 @@ Vue-ECharts 允许你通过 Vue 插槽来定义 ECharts 配置中的 [`tooltip.f
## CSP: `style-src` 或 `style-src-elem` ## CSP: `style-src` 或 `style-src-elem`
如果你正在应用 CSP 来防止内联 `<style>` 注入,则需要使用 `vue-echarts/csp` 代替 `vue-echarts`,并手动引入 `vue-echarts/csp/style.css` 如果你执行严格的 CSP 策略来防止内联 `<style>` 注入,**并且**需要兼容不支持 [CSSStyleSheet() 构造函数](https://developer.mozilla.org/en-US/docs/Web/API/CSSStyleSheet/CSSStyleSheet#browser_compatibility) 的浏览器,则需要手动引入 `vue-echarts/style.css`
## 迁移到 v7 ## 迁移到 v7

View File

@ -10,7 +10,7 @@
"author": "GU Yiling <justice360@gmail.com>", "author": "GU Yiling <justice360@gmail.com>",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "pnpm run docs && rimraf dist && rollup -c", "build": "pnpm run docs && tsdown",
"typecheck": "tsc", "typecheck": "tsc",
"lint": "eslint . --fix", "lint": "eslint . --fix",
"format": "prettier . --write", "format": "prettier . --write",
@ -21,7 +21,7 @@
"docs": "node ./scripts/docs.mjs", "docs": "node ./scripts/docs.mjs",
"prepublishOnly": "pnpm run typecheck && pnpm run dev:typecheck && pnpm run build && publint" "prepublishOnly": "pnpm run typecheck && pnpm run dev:typecheck && pnpm run build && publint"
}, },
"packageManager": "pnpm@10.12.4", "packageManager": "pnpm@10.14.0",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
"unpkg": "dist/index.min.js", "unpkg": "dist/index.min.js",
@ -29,8 +29,7 @@
"types": "dist/index.d.ts", "types": "dist/index.d.ts",
"exports": { "exports": {
".": "./dist/index.js", ".": "./dist/index.js",
"./csp": "./dist/csp/index.js", "./style.css": "./dist/style.css"
"./csp/style.css": "./dist/csp/style.css"
}, },
"files": [ "files": [
"dist" "dist"
@ -44,7 +43,7 @@
"@types/node": "^22.15.21", "@types/node": "^22.15.21",
"@typescript-eslint/utils": "^8.32.1", "@typescript-eslint/utils": "^8.32.1",
"@vercel/analytics": "^1.3.1", "@vercel/analytics": "^1.3.1",
"@vitejs/plugin-vue": "^5.2.4", "@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.5.0", "@vue/eslint-config-typescript": "^14.5.0",
"@vue/tsconfig": "^0.7.0", "@vue/tsconfig": "^0.7.0",
@ -62,14 +61,11 @@
"postcss-nested": "^7.0.2", "postcss-nested": "^7.0.2",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"publint": "^0.3.12", "publint": "^0.3.12",
"rimraf": "^6.0.1", "tsdown": "^0.13.2",
"rollup": "^4.41.1",
"rollup-plugin-dts": "^6.2.1",
"rollup-plugin-esbuild": "^6.2.1",
"rollup-plugin-import-css": "^3.5.8",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^6.3.5", "unplugin-raw": "^0.5.0",
"vite": "npm:rolldown-vite@latest",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-tsc": "^2.2.10" "vue-tsc": "^3.0.5"
} }
} }

1195
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,104 +0,0 @@
import esbuild from "rollup-plugin-esbuild";
import { dts } from "rollup-plugin-dts";
import css from "rollup-plugin-import-css";
import { defineConfig } from "rollup";
/**
* Modifies the Rollup options for a build to support strict CSP
* @param {import('rollup').RollupOptions} options the original options
* @param {boolean} csp whether to support strict CSP
* @returns {import('rollup').RollupOptions} the modified options
*/
function configBuild(options, csp) {
const result = { ...options };
const { plugins, output } = result;
result.plugins = [
...plugins,
css({
...(csp ? { output: "style.css" } : { inject: true }),
minify: true,
}),
];
// modify output file names
if (csp && output) {
result.output = (Array.isArray(output) ? output : [output]).map(
(output) => {
return {
...output,
file: output.file.replace(/^dist\//, "dist/csp/"),
assetFileNames: "[name][extname]",
};
},
);
}
return result;
}
/** @type {import('rollup').RollupOptions[]} */
const builds = [
{
input: "src/index.ts",
plugins: [esbuild()],
external: ["vue", /^echarts/],
output: [
{
file: "dist/index.js",
format: "esm",
sourcemap: true,
},
],
},
{
input: "src/global.ts",
plugins: [esbuild({ minify: true })],
external: ["vue", /^echarts/],
output: [
{
file: "dist/index.min.js", // for unpkg/jsdelivr
format: "umd",
name: "VueECharts",
exports: "default",
sourcemap: true,
globals: {
vue: "vue",
echarts: "echarts",
"echarts/core": "echarts",
},
},
],
},
];
export default defineConfig([
...builds.map((options) => configBuild(options, false)),
...builds.map((options) => configBuild(options, true)),
{
input: "src/index.ts",
plugins: [
dts({
// https://github.com/unjs/unbuild/pull/57/files
compilerOptions: {
preserveSymlinks: false,
},
}),
{
load(id) {
if (id.endsWith(".css")) return "";
},
},
],
output: [
{
file: "dist/index.d.ts",
format: "esm",
},
{
file: "dist/csp/index.d.ts",
format: "esm",
},
],
},
]);

View File

@ -41,7 +41,7 @@ import type {
} from "./types"; } from "./types";
import type { EChartsElement } from "./wc"; import type { EChartsElement } from "./wc";
import "./style.css"; import "./style.ts";
const wcRegistered = register(); const wcRegistered = register();

View File

@ -1,6 +1 @@
x-vue-echarts { x-vue-echarts{display:block;width:100%;height:100%;min-width:0;}
display: block;
width: 100%;
height: 100%;
min-width: 0;
}

16
src/style.ts Normal file
View File

@ -0,0 +1,16 @@
import cssRules from "./style.css?raw";
if (typeof document !== "undefined") {
if (
Array.isArray(document.adoptedStyleSheets) &&
"replaceSync" in CSSStyleSheet.prototype
) {
const sheet = new CSSStyleSheet();
sheet.replaceSync(cssRules);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];
} else {
const styleEl = document.createElement("style");
styleEl.textContent = cssRules;
document.head.appendChild(styleEl);
}
}

View File

@ -12,6 +12,7 @@
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"noEmit": true, "noEmit": true,
"baseUrl": ".", "baseUrl": ".",
"types": ["vite/client"],
"lib": ["ESNext", "DOM", "DOM.Iterable", "ScriptHost"] "lib": ["ESNext", "DOM", "DOM.Iterable", "ScriptHost"]
}, },
"include": ["src/**/*.ts", "src/**/*.tsx"], "include": ["src/**/*.ts", "src/**/*.tsx"],

View File

@ -1,10 +1,5 @@
{ {
"include": [ "include": ["*.config.*"],
"vite.config.*",
"vitest.config.*",
"eslint.config.*",
"rollup.config.*"
],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"lib": ["ESNext"], "lib": ["ESNext"],

32
tsdown.config.ts Normal file
View File

@ -0,0 +1,32 @@
import { defineConfig } from "tsdown";
import raw from "unplugin-raw/rollup";
export default defineConfig([
{
entry: "src/index.ts",
platform: "browser",
sourcemap: true,
copy: ["src/style.css"],
plugins: [raw()],
},
{
entry: "src/global.ts",
outputOptions: {
file: "dist/index.min.js", // for unpkg/jsdelivr
dir: undefined,
format: "umd",
name: "VueECharts",
exports: "default",
globals: {
vue: "Vue",
echarts: "echarts",
"echarts/core": "echarts",
},
},
platform: "browser",
sourcemap: true,
minify: true,
dts: false,
plugins: [raw()],
},
]);