@douyinfe/semi-vite-plugin 2.98.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,83 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 DouyinFE
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ The code implementation of the external library is referenced by DouyinFe are:
24
+
25
+ - animate.css
26
+
27
+ The MIT License (MIT)
28
+
29
+ Copyright (c) 2020 Daniel Eden
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining a copy
32
+ of this software and associated documentation files (the "Software"), to deal
33
+ in the Software without restriction, including without limitation the rights
34
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35
+ copies of the Software, and to permit persons to whom the Software is
36
+ furnished to do so, subject to the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be included in all
39
+ copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47
+ SOFTWARE.
48
+
49
+ - Ant Design
50
+ MIT LICENSE
51
+
52
+ Copyright (c) 2015-present Ant UED, https://xtech.antfin.com/
53
+
54
+ Permission is hereby granted, free of charge, to any person obtaining
55
+ a copy of this software and associated documentation files (the
56
+ "Software"), to deal in the Software without restriction, including
57
+ without limitation the rights to use, copy, modify, merge, publish,
58
+ distribute, sublicense, and/or sell copies of the Software, and to
59
+ permit persons to whom the Software is furnished to do so, subject to
60
+ the following conditions:
61
+
62
+ The above copyright notice and this permission notice shall be
63
+ included in all copies or substantial portions of the Software.
64
+
65
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
66
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
67
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
68
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
69
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
70
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
71
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
72
+
73
+ - rc-tree
74
+
75
+ MIT LICENSE
76
+
77
+ Copyright (c) 2015-present Alipay.com, https://www.alipay.com/
78
+
79
+ 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:
80
+
81
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
82
+
83
+ 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.
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ > A vite plugin for Semi Design to custom theme, replace prefix and so on.
2
+
3
+ ## Introduction
4
+
5
+ The plugin is designed for Semi Design with Vite, providing two major abilities:
6
+
7
+ - Custom theme
8
+ - Replace prefix of CSS selector
9
+
10
+ > Note: The plugin detects Semi related dependencies by package path. It supports both
11
+ > `@douyinfe/semi-ui` and version-suffixed packages like `@douyinfe/semi-ui-19` (also for `semi-icons`).
12
+
13
+ ## Usage
14
+
15
+ ### Install
16
+
17
+ Install `@douyinfe/semi-vite-plugin` as a development dependency:
18
+
19
+ ```shell
20
+ npm install --save-dev @douyinfe/semi-vite-plugin
21
+ # or
22
+ yarn add --dev @douyinfe/semi-vite-plugin
23
+ # or
24
+ pnpm add -D @douyinfe/semi-vite-plugin
25
+ ```
26
+
27
+ ### Custom theme
28
+
29
+ Semi Design uses Scss variables to extract thousands of Design Tokens. You can replace
30
+ those tokens through this plugin to achieve theme customization.
31
+ [More info](https://semi.design/dsm/)
32
+
33
+ You can custom theme through three ways:
34
+
35
+ - npm package generated by Semi DSM
36
+ - Local Scss file in your project
37
+ - Pass key-value pair parameters to plugin
38
+
39
+ Priority from low to high.
40
+
41
+ #### Through npm package
42
+
43
+ After finishing the customization on
44
+ [Semi Design System](https://semi.design/dsm/), Semi DSM will generate an npm
45
+ package for you, then use it like this:
46
+
47
+ ```ts
48
+ // vite.config.ts
49
+ import { defineConfig } from 'vite';
50
+ import semiTheming from '@douyinfe/semi-vite-plugin';
51
+
52
+ export default defineConfig({
53
+ plugins: [
54
+ semiTheming({
55
+ theme: '@semi-bot/semi-theme-yours',
56
+ }),
57
+ ],
58
+ });
59
+ ```
60
+
61
+ #### Through local Scss file
62
+
63
+ You can check which tokens can be customized on the
64
+ [Semi Website](https://semi.design/en-US/basic/tokens).
65
+
66
+ Step 1: add a local file
67
+
68
+ ```scss
69
+ // local.scss
70
+ $font-size-small: 16px;
71
+ ```
72
+
73
+ Step 2: config vite
74
+
75
+ ```ts
76
+ // vite.config.ts
77
+ import path from 'path';
78
+ import { defineConfig } from 'vite';
79
+ import semiTheming from '@douyinfe/semi-vite-plugin';
80
+
81
+ export default defineConfig({
82
+ plugins: [
83
+ semiTheming({
84
+ include: path.resolve(__dirname, 'local.scss'),
85
+ }),
86
+ ],
87
+ });
88
+ ```
89
+
90
+ #### Through parameters
91
+
92
+ ```ts
93
+ // vite.config.ts
94
+ import { defineConfig } from 'vite';
95
+ import semiTheming from '@douyinfe/semi-vite-plugin';
96
+
97
+ export default defineConfig({
98
+ plugins: [
99
+ semiTheming({
100
+ variables: {
101
+ '$font-size-small': '16px',
102
+ },
103
+ }),
104
+ ],
105
+ });
106
+ ```
107
+
108
+ ### Replace prefix of CSS selector
109
+
110
+ The CSS selectors used by Semi Design are prefixed with `semi` by default
111
+ (e.g. `.semi-button`). You can replace the prefix through this plugin:
112
+
113
+ ```ts
114
+ // vite.config.ts
115
+ import { defineConfig } from 'vite';
116
+ import semiTheming from '@douyinfe/semi-vite-plugin';
117
+
118
+ export default defineConfig({
119
+ plugins: [
120
+ semiTheming({
121
+ prefixCls: 'custom',
122
+ }),
123
+ ],
124
+ });
125
+ ```
126
+
127
+ Then you get the replaced CSS selectors (e.g. `.custom-button`).
128
+
129
+ ### Wrap output styles with CSS layer
130
+
131
+ ```ts
132
+ // vite.config.ts
133
+ import { defineConfig } from 'vite';
134
+ import semiTheming from '@douyinfe/semi-vite-plugin';
135
+
136
+ export default defineConfig({
137
+ plugins: [
138
+ semiTheming({
139
+ cssLayer: true,
140
+ }),
141
+ ],
142
+ });
143
+ ```
144
+
145
+ ## API
146
+
147
+ ### semiTheming(options)
148
+
149
+ | Property | Type | Default | Description |
150
+ | -------------------- | ----------------------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
151
+ | `theme` | `string \| { name?: string }` | - | Custom theme package name generated by Semi DSM. |
152
+ | `prefixCls` | `string` | `semi` | Prefix of CSS class names. |
153
+ | `variables` | `Record<string, string \| number>` | `{}` | Key-value pair of Scss variables. |
154
+ | `include` | `string` | - | Absolute path of an extra Scss file to inject after theme variables. |
155
+ | `cssLayer` | `boolean` | `false` | When `true`, wrap the final compiled CSS with `@layer semi { ... }`. |
156
+ | `omitCss` | `boolean` | `false` | Comment out `.css` imports inside semi packages. Useful when the integration framework can't import global CSS from `node_modules` directly. |
157
+
158
+ ## How it works
159
+
160
+ 1. **CSS → SCSS rewrite**: Intercepts requests to
161
+ `@douyinfe/semi-(ui|icons|foundation)/lib/**/*.css`, switches the suffix to
162
+ `.scss` and reads the original Sass source.
163
+ 2. **Theme injection**: Injects the theme `index.scss` / `global.scss` /
164
+ `animation.scss` / `local.scss` / user-provided variables and `include`
165
+ files, plus `$prefix` and (optional) `@layer semi { ... }` wrapper.
166
+ 3. **Sass compilation**: Compiles the assembled Sass source with
167
+ `sass.compileString`, resolving `~package/...` imports against the node_modules
168
+ tree of the original `.scss` file.
169
+ 4. **Prefix patch**: For files matching `@douyinfe/semi-*/.../env.js`, rewrites
170
+ `BASE_CLASS_PREFIX` constant to the configured `prefixCls`. To make the same
171
+ replacement work for Vite's dev-mode dep pre-bundle (`.vite/deps`), an
172
+ esbuild plugin is registered via `optimizeDeps.esbuildOptions.plugins` to
173
+ rewrite the env modules at pre-bundle time as well.
174
+ 5. **omitCss**: Optionally comments out `.css` imports inside Semi packages so
175
+ they don't reach the bundler.
176
+
177
+ ## FAQ
178
+
179
+ ### Why are classnames still `semi-*` in `vite dev`?
180
+
181
+ Vite pre-bundles `node_modules` packages into `.vite/deps` using esbuild
182
+ (or Rolldown in Vite 8+). When `prefixCls` is set, this plugin injects an
183
+ esbuild plugin to rewrite the relevant `env.js` files inside the pre-bundle.
184
+ If you previously started `vite` once without the plugin (or with a different
185
+ `prefixCls`), you may have a stale `.vite/deps` cache. Stop the dev server
186
+ and run `rm -rf node_modules/.vite` to force a clean re-bundle.
187
+
188
+ ### Vite 8 prints a deprecation warning about `optimizeDeps.esbuildOptions`
189
+
190
+ Vite 8 switched to Rolldown for dep pre-bundling and now prefers
191
+ `optimizeDeps.rolldownOptions`. The plugin still uses
192
+ `optimizeDeps.esbuildOptions` for broad compatibility with Vite ≥3; Rolldown
193
+ keeps an esbuild-compatible shim, so the warning is harmless and the prefix
194
+ rewrite still works correctly.
195
+
196
+ ## Related
197
+
198
+ - [Semi Design](https://semi.design/)
199
+ - [`@douyinfe/semi-webpack-plugin`](https://www.npmjs.com/package/@douyinfe/semi-webpack-plugin)
200
+ - [`@douyinfe/semi-rspack-plugin`](https://www.npmjs.com/package/@douyinfe/semi-rspack-plugin)
@@ -0,0 +1,2 @@
1
+ declare const componentVariablePathList: string[];
2
+ export default componentVariablePathList;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const componentVariablePathList = [
4
+ // tooltip/variables.scss defines `$horizontal-rate` / `$vertical-rate`
5
+ // which are referenced by popover/variables.scss and other components,
6
+ // so it must be imported before the rest of the alphabetical list.
7
+ '@douyinfe/semi-foundation/tooltip/variables.scss',
8
+ '@douyinfe/semi-foundation/aiChatDialogue/variables.scss',
9
+ '@douyinfe/semi-foundation/aiChatInput/variables.scss',
10
+ '@douyinfe/semi-foundation/anchor/variables.scss',
11
+ '@douyinfe/semi-foundation/audioPlayer/variables.scss',
12
+ '@douyinfe/semi-foundation/autoComplete/variables.scss',
13
+ '@douyinfe/semi-foundation/avatar/variables.scss',
14
+ '@douyinfe/semi-foundation/backtop/variables.scss',
15
+ '@douyinfe/semi-foundation/badge/variables.scss',
16
+ '@douyinfe/semi-foundation/banner/variables.scss',
17
+ '@douyinfe/semi-foundation/breadcrumb/variables.scss',
18
+ '@douyinfe/semi-foundation/button/variables.scss',
19
+ '@douyinfe/semi-foundation/calendar/variables.scss',
20
+ '@douyinfe/semi-foundation/card/variables.scss',
21
+ '@douyinfe/semi-foundation/carousel/variables.scss',
22
+ '@douyinfe/semi-foundation/cascader/variables.scss',
23
+ '@douyinfe/semi-foundation/chat/variables.scss',
24
+ '@douyinfe/semi-foundation/checkbox/variables.scss',
25
+ '@douyinfe/semi-foundation/collapse/variables.scss',
26
+ '@douyinfe/semi-foundation/colorPicker/variables.scss',
27
+ '@douyinfe/semi-foundation/cropper/variables.scss',
28
+ '@douyinfe/semi-foundation/datePicker/variables.scss',
29
+ '@douyinfe/semi-foundation/descriptions/variables.scss',
30
+ '@douyinfe/semi-foundation/divider/variables.scss',
31
+ '@douyinfe/semi-foundation/dropdown/variables.scss',
32
+ '@douyinfe/semi-foundation/empty/variables.scss',
33
+ '@douyinfe/semi-foundation/floatButton/variables.scss',
34
+ '@douyinfe/semi-foundation/form/variables.scss',
35
+ '@douyinfe/semi-foundation/grid/variables.scss',
36
+ '@douyinfe/semi-foundation/highlight/variables.scss',
37
+ '@douyinfe/semi-foundation/hotKeys/variables.scss',
38
+ '@douyinfe/semi-foundation/image/variables.scss',
39
+ '@douyinfe/semi-foundation/input/variables.scss',
40
+ '@douyinfe/semi-foundation/inputNumber/variables.scss',
41
+ '@douyinfe/semi-foundation/jsonViewer/variables.scss',
42
+ '@douyinfe/semi-foundation/list/variables.scss',
43
+ '@douyinfe/semi-foundation/markdownRender/variables.scss',
44
+ '@douyinfe/semi-foundation/modal/variables.scss',
45
+ '@douyinfe/semi-foundation/navigation/variables.scss',
46
+ '@douyinfe/semi-foundation/notification/variables.scss',
47
+ '@douyinfe/semi-foundation/pagination/variables.scss',
48
+ '@douyinfe/semi-foundation/pincode/variables.scss',
49
+ '@douyinfe/semi-foundation/popconfirm/variables.scss',
50
+ '@douyinfe/semi-foundation/popover/variables.scss',
51
+ '@douyinfe/semi-foundation/progress/variables.scss',
52
+ '@douyinfe/semi-foundation/radio/variables.scss',
53
+ '@douyinfe/semi-foundation/rating/variables.scss',
54
+ '@douyinfe/semi-foundation/resizable/variables.scss',
55
+ '@douyinfe/semi-foundation/scrollList/variables.scss',
56
+ '@douyinfe/semi-foundation/select/variables.scss',
57
+ '@douyinfe/semi-foundation/sidebar/variables.scss',
58
+ '@douyinfe/semi-foundation/sideSheet/variables.scss',
59
+ '@douyinfe/semi-foundation/skeleton/variables.scss',
60
+ '@douyinfe/semi-foundation/slider/variables.scss',
61
+ '@douyinfe/semi-foundation/space/variables.scss',
62
+ '@douyinfe/semi-foundation/spin/variables.scss',
63
+ '@douyinfe/semi-foundation/steps/variables.scss',
64
+ '@douyinfe/semi-foundation/switch/variables.scss',
65
+ '@douyinfe/semi-foundation/table/variables.scss',
66
+ '@douyinfe/semi-foundation/tabs/variables.scss',
67
+ '@douyinfe/semi-foundation/tag/variables.scss',
68
+ '@douyinfe/semi-foundation/tagInput/variables.scss',
69
+ '@douyinfe/semi-foundation/timePicker/variables.scss',
70
+ '@douyinfe/semi-foundation/timeline/variables.scss',
71
+ '@douyinfe/semi-foundation/toast/variables.scss',
72
+ '@douyinfe/semi-foundation/tooltip/variables.scss',
73
+ '@douyinfe/semi-foundation/transfer/variables.scss',
74
+ '@douyinfe/semi-foundation/tree/variables.scss',
75
+ '@douyinfe/semi-foundation/treeSelect/variables.scss',
76
+ '@douyinfe/semi-foundation/typography/variables.scss',
77
+ '@douyinfe/semi-foundation/upload/variables.scss',
78
+ '@douyinfe/semi-foundation/userGuide/variables.scss',
79
+ '@douyinfe/semi-foundation/videoPlayer/variables.scss'
80
+ ];
81
+ exports.default = componentVariablePathList;
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { semiTheming } from './plugin';
2
+ import { transformSemiTheme } from './theme-loader';
3
+ export { semiTheming, transformSemiTheme };
4
+ export type { SemiVitePluginOptions, SemiThemeOptions } from './types';
5
+ export default semiTheming;
package/lib/index.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.transformSemiTheme = exports.semiTheming = void 0;
4
+ const plugin_1 = require("./plugin");
5
+ Object.defineProperty(exports, "semiTheming", { enumerable: true, get: function () { return plugin_1.semiTheming; } });
6
+ const theme_loader_1 = require("./theme-loader");
7
+ Object.defineProperty(exports, "transformSemiTheme", { enumerable: true, get: function () { return theme_loader_1.transformSemiTheme; } });
8
+ exports.default = plugin_1.semiTheming;
9
+ const _exports = module.exports;
10
+ const _fn = plugin_1.semiTheming;
11
+ Object.assign(_fn, _exports);
12
+ _fn.default = _fn;
13
+ _fn.semiTheming = _fn;
14
+ _fn.transformSemiTheme = theme_loader_1.transformSemiTheme;
15
+ _fn.__esModule = true;
16
+ module.exports = _fn;
@@ -0,0 +1,23 @@
1
+ import type { Plugin } from 'vite';
2
+ import type { SemiVitePluginOptions } from './types';
3
+ /**
4
+ * Create a Vite plugin that customizes Semi Design themes, replaces selector prefix,
5
+ * and optionally omits CSS imports inside Semi packages.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { defineConfig } from 'vite';
10
+ * import semiTheming from '@douyinfe/semi-vite-plugin';
11
+ *
12
+ * export default defineConfig({
13
+ * plugins: [
14
+ * semiTheming({
15
+ * theme: '@semi-bot/semi-theme-feishu',
16
+ * prefixCls: 'my-semi',
17
+ * }),
18
+ * ],
19
+ * });
20
+ * ```
21
+ */
22
+ export declare function semiTheming(rawOptions?: SemiVitePluginOptions): Plugin;
23
+ export default semiTheming;
package/lib/plugin.js ADDED
@@ -0,0 +1,169 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.semiTheming = void 0;
27
+ const fs_1 = require("fs");
28
+ const url_1 = require("url");
29
+ const utils_1 = require("./utils");
30
+ const theme_loader_1 = require("./theme-loader");
31
+ const SEMI_CSS_RE = /@douyinfe[\\/]+semi-(ui|icons|foundation)(-\d+)?[\\/]+lib[\\/]+.+\.css(\?.*)?$/;
32
+ const SEMI_JS_RE = /@douyinfe[\\/]+semi-(ui|icons)(-\d+)?[\\/]+lib[\\/]+.+\.js(\?.*)?$/;
33
+ const SEMI_ENV_JS_RE = /@douyinfe[\\/]+semi-[^\\/]+[\\/]+.+env\.js(\?.*)?$/;
34
+ const PREFIX_REGEX = /(BASE_CLASS_PREFIX\s*[:=]\s*['"])([^'"]+)(['"])/g;
35
+ const CSS_IMPORT_REGEX = /(import\s+['"][^'"]+\.css['"])/g;
36
+ const CSS_REQUIRE_REGEX = /(require\(['"][^'"]+\.css['"]\))/g;
37
+ /**
38
+ * Create a Vite plugin that customizes Semi Design themes, replaces selector prefix,
39
+ * and optionally omits CSS imports inside Semi packages.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { defineConfig } from 'vite';
44
+ * import semiTheming from '@douyinfe/semi-vite-plugin';
45
+ *
46
+ * export default defineConfig({
47
+ * plugins: [
48
+ * semiTheming({
49
+ * theme: '@semi-bot/semi-theme-feishu',
50
+ * prefixCls: 'my-semi',
51
+ * }),
52
+ * ],
53
+ * });
54
+ * ```
55
+ */
56
+ function semiTheming(rawOptions = {}) {
57
+ const { theme, ...options } = rawOptions;
58
+ const themeOptions = typeof theme === 'object' && theme !== null ? { ...theme } : { name: theme };
59
+ const include = options.include ? (0, utils_1.normalizePath)(options.include) : undefined;
60
+ const variables = (0, utils_1.convertMapToString)(options.variables || {});
61
+ return {
62
+ name: 'semi-theme',
63
+ enforce: 'pre',
64
+ config() {
65
+ // In dev mode Vite pre-bundles `@douyinfe/semi-ui` and friends
66
+ // into `.vite/deps` via esbuild *before* this plugin's
67
+ // `transform` hook can rewrite `BASE_CLASS_PREFIX` in `env.js`.
68
+ // To make the prefix replacement work for the pre-bundled
69
+ // output, we register an esbuild plugin that intercepts the
70
+ // raw `base/env.js` files during pre-bundling and inlines the
71
+ // user-defined prefix. The same `transform` hook continues to
72
+ // cover non-bundled paths (build mode, SSR, lazy imports).
73
+ if (!options.prefixCls) {
74
+ return undefined;
75
+ }
76
+ const prefixCls = options.prefixCls;
77
+ return {
78
+ optimizeDeps: {
79
+ esbuildOptions: {
80
+ plugins: [
81
+ {
82
+ name: 'semi-vite-prefix-env',
83
+ setup(build) {
84
+ build.onLoad({ filter: /@douyinfe[/\\]+semi-[^/\\]+[/\\]+lib[/\\]+(es|cjs)[/\\]+(base[/\\]+)?env\.js$/ }, () => ({
85
+ contents: `export const BASE_CLASS_PREFIX = ${JSON.stringify(prefixCls)};\n`,
86
+ loader: 'js'
87
+ }));
88
+ }
89
+ }
90
+ ]
91
+ }
92
+ }
93
+ };
94
+ },
95
+ async load(id) {
96
+ const filePath = (0, utils_1.normalizePath)(id.split('?')[0]);
97
+ if (!SEMI_CSS_RE.test(filePath)) {
98
+ return null;
99
+ }
100
+ const scssFilePath = filePath.replace(/\.css$/, '.scss');
101
+ if (!(0, fs_1.existsSync)(scssFilePath)) {
102
+ return null;
103
+ }
104
+ const rawSource = (0, fs_1.readFileSync)(scssFilePath, 'utf-8');
105
+ const transformed = (0, theme_loader_1.transformSemiTheme)(rawSource, scssFilePath, {
106
+ name: themeOptions.name,
107
+ prefixCls: options.prefixCls,
108
+ variables,
109
+ include,
110
+ cssLayer: options.cssLayer,
111
+ });
112
+ const { default: sass } = await Promise.resolve().then(() => __importStar(require('sass')));
113
+ const resolverCache = new Map();
114
+ const getResolver = (importer) => {
115
+ let resolver = resolverCache.get(importer);
116
+ if (!resolver) {
117
+ resolver = (0, utils_1.createCssImportResolver)(importer);
118
+ resolverCache.set(importer, resolver);
119
+ }
120
+ return resolver;
121
+ };
122
+ const compileOptions = {
123
+ importers: [
124
+ {
125
+ findFileUrl(url, context) {
126
+ const baseFile = context?.containingUrl
127
+ ? (0, url_1.fileURLToPath)(context.containingUrl)
128
+ : scssFilePath;
129
+ return getResolver(baseFile)(url);
130
+ },
131
+ },
132
+ ],
133
+ logger: sass.Logger.silent,
134
+ };
135
+ try {
136
+ compileOptions.silenceDeprecations = ['import', 'legacy-js-api', 'global-builtin'];
137
+ const result = sass.compileString(transformed, compileOptions);
138
+ return result.css;
139
+ }
140
+ catch (e) {
141
+ delete compileOptions.silenceDeprecations;
142
+ const result = sass.compileString(transformed, compileOptions);
143
+ return result.css;
144
+ }
145
+ },
146
+ transform(code, id) {
147
+ const filePath = (0, utils_1.normalizePath)(id.split('?')[0]);
148
+ let nextCode = code;
149
+ let changed = false;
150
+ if (options.omitCss && SEMI_JS_RE.test(filePath)) {
151
+ const replaced = nextCode.replace(CSS_IMPORT_REGEX, '// $1').replace(CSS_REQUIRE_REGEX, '// $1');
152
+ if (replaced !== nextCode) {
153
+ nextCode = replaced;
154
+ changed = true;
155
+ }
156
+ }
157
+ if (options.prefixCls && SEMI_ENV_JS_RE.test(filePath)) {
158
+ const replaced = nextCode.replace(PREFIX_REGEX, `$1${options.prefixCls}$3`);
159
+ if (replaced !== nextCode) {
160
+ nextCode = replaced;
161
+ changed = true;
162
+ }
163
+ }
164
+ return changed ? { code: nextCode, map: null } : null;
165
+ },
166
+ };
167
+ }
168
+ exports.semiTheming = semiTheming;
169
+ exports.default = semiTheming;
@@ -0,0 +1,18 @@
1
+ export interface SemiThemeLoaderQuery {
2
+ name?: string;
3
+ prefixCls?: string;
4
+ variables?: string;
5
+ include?: string;
6
+ cssLayer?: boolean;
7
+ }
8
+ /**
9
+ * Transform a raw SCSS source (the original `lib/**\/*.scss` of semi-ui/semi-icons/semi-foundation)
10
+ * by injecting theme variables, prefix and (optionally) wrapping with a CSS layer.
11
+ *
12
+ * This is a port of `semi-webpack/src/semi-theme-loader.ts` for sass.compileString environment.
13
+ *
14
+ * @param source the raw scss source code
15
+ * @param importer absolute path of the source file, used as the base directory for resolving `~` imports
16
+ * @param query loader options
17
+ */
18
+ export declare function transformSemiTheme(source: string, importer: string, query: SemiThemeLoaderQuery): string;
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.transformSemiTheme = void 0;
7
+ const fs_1 = require("fs");
8
+ const componentName_1 = __importDefault(require("./componentName"));
9
+ const utils_1 = require("./utils");
10
+ /**
11
+ * Transform a raw SCSS source (the original `lib/**\/*.scss` of semi-ui/semi-icons/semi-foundation)
12
+ * by injecting theme variables, prefix and (optionally) wrapping with a CSS layer.
13
+ *
14
+ * This is a port of `semi-webpack/src/semi-theme-loader.ts` for sass.compileString environment.
15
+ *
16
+ * @param source the raw scss source code
17
+ * @param importer absolute path of the source file, used as the base directory for resolving `~` imports
18
+ * @param query loader options
19
+ */
20
+ function transformSemiTheme(source, importer, query) {
21
+ const theme = query.name || '@douyinfe/semi-theme-default';
22
+ const cssLayer = query.cssLayer ?? false;
23
+ const scssVarStr = `@import "~${theme}/scss/index.scss";\n`;
24
+ const cssVarStr = `@import "~${theme}/scss/global.scss";\n`;
25
+ let animationStr = `@import "~${theme}/scss/animation.scss";\n`;
26
+ if (!(0, utils_1.tryResolve)(importer, `${theme}/scss/animation.scss`)) {
27
+ animationStr = '';
28
+ }
29
+ const shouldInject = source.includes('semi-base');
30
+ let fileStr = source;
31
+ const componentVariables = (0, utils_1.tryResolve)(importer, `${theme}/scss/local.scss`);
32
+ if (query.include || query.variables || componentVariables) {
33
+ let localImport = '';
34
+ if (componentVariables) {
35
+ localImport += `\n@import "~${theme}/scss/local.scss";`;
36
+ }
37
+ if (query.include) {
38
+ localImport += `\n@import "${query.include}";`;
39
+ }
40
+ if (query.variables) {
41
+ localImport += `\n${query.variables}`;
42
+ }
43
+ try {
44
+ const regex = /(@import '.\/variables.scss';?|@import ".\/variables.scss";?)/g;
45
+ const fileSplit = source.split(regex).filter(item => Boolean(item));
46
+ if (fileSplit.length > 1) {
47
+ fileSplit.splice(fileSplit.length - 1, 0, localImport);
48
+ fileStr = fileSplit.join('');
49
+ }
50
+ }
51
+ catch (e) {
52
+ // ignore
53
+ }
54
+ }
55
+ const prefixCls = query.prefixCls || 'semi';
56
+ const prefixClsStr = `$prefix: '${prefixCls}';\n`;
57
+ let finalCSS = '';
58
+ if (shouldInject) {
59
+ const customStr = (() => {
60
+ const resolvedCustom = (0, utils_1.tryResolve)(importer, `${theme}/scss/custom.scss`);
61
+ if (!resolvedCustom) {
62
+ return '';
63
+ }
64
+ let addBodySelector = true;
65
+ try {
66
+ const customFileContent = (0, fs_1.readFileSync)(resolvedCustom, 'utf-8');
67
+ const regex = /body\s*\{/;
68
+ if (regex.test(customFileContent)) {
69
+ addBodySelector = false;
70
+ }
71
+ }
72
+ catch (e) {
73
+ return '';
74
+ }
75
+ const collectAllVariablesPath = [
76
+ ...componentName_1.default,
77
+ ];
78
+ if (componentVariables) {
79
+ collectAllVariablesPath.push(`${theme}/scss/local.scss`);
80
+ }
81
+ collectAllVariablesPath.push(`${theme}/scss/custom.scss`);
82
+ const inner = collectAllVariablesPath.map(p => `@import "~${p}";`).join('\n');
83
+ return addBodySelector ? `body:not(:not(body)){${inner}};` : inner;
84
+ })();
85
+ finalCSS = `${animationStr}${cssVarStr}${scssVarStr}${prefixClsStr}${fileStr}${customStr}`;
86
+ }
87
+ else {
88
+ finalCSS = `${scssVarStr}${prefixClsStr}${fileStr}`;
89
+ }
90
+ if (cssLayer) {
91
+ finalCSS = `@layer semi{${finalCSS}}`;
92
+ }
93
+ return finalCSS;
94
+ }
95
+ exports.transformSemiTheme = transformSemiTheme;
package/lib/types.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ export interface SemiThemeOptions {
2
+ name?: string;
3
+ }
4
+ export interface SemiVitePluginOptions {
5
+ /**
6
+ * Custom theme. Pass the npm package name generated by Semi DSM,
7
+ * or an object with `{ name }`.
8
+ */
9
+ theme?: string | SemiThemeOptions;
10
+ /**
11
+ * Wrap final CSS with `@layer semi { ... }`.
12
+ */
13
+ cssLayer?: boolean;
14
+ /**
15
+ * Prefix of the CSS selector. Default is `semi`.
16
+ */
17
+ prefixCls?: string;
18
+ /**
19
+ * Key-value pair of Scss variables. e.g. `{ '$font-size-small': '16px' }`
20
+ */
21
+ variables?: Record<string, string | number>;
22
+ /**
23
+ * Absolute path of a local Scss file that will be injected after theme variables.
24
+ */
25
+ include?: string;
26
+ /**
27
+ * Omit the `import xxx.css` statements emitted from semi packages.
28
+ * Useful for Next.js/SSR scenarios where global css cannot be imported from node_modules.
29
+ */
30
+ omitCss?: boolean;
31
+ }
package/lib/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/lib/utils.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /// <reference types="node" />
2
+ import { URL } from 'url';
3
+ export declare function normalizePath(p: string): string;
4
+ export declare function convertMapToString(map: Record<string, string | number>): string;
5
+ /**
6
+ * Resolve a `~package/foo` style import to its absolute file URL by walking up node_modules
7
+ * from a given importer. Also supports relative paths.
8
+ */
9
+ export declare function createCssImportResolver(importer: string): (url: string) => URL | null;
10
+ /**
11
+ * Try to resolve a module path relative to a given importer file. Returns absolute file path
12
+ * when found, otherwise `undefined`.
13
+ */
14
+ export declare function tryResolve(importer: string, request: string): string | undefined;
package/lib/utils.js ADDED
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.tryResolve = exports.createCssImportResolver = exports.convertMapToString = exports.normalizePath = void 0;
27
+ const fs_1 = require("fs");
28
+ const os_1 = require("os");
29
+ const path = __importStar(require("path"));
30
+ const module_1 = require("module");
31
+ const url_1 = require("url");
32
+ const isWindows = (0, os_1.platform)() === 'win32';
33
+ function normalizePath(p) {
34
+ return path.posix.normalize(isWindows ? p.replace(/\\/g, '/') : p);
35
+ }
36
+ exports.normalizePath = normalizePath;
37
+ function convertMapToString(map) {
38
+ return Object.keys(map).reduce(function (prev, curr) {
39
+ return prev + `${curr}: ${map[curr]};\n`;
40
+ }, '');
41
+ }
42
+ exports.convertMapToString = convertMapToString;
43
+ /**
44
+ * Resolve a `~package/foo` style import to its absolute file URL by walking up node_modules
45
+ * from a given importer. Also supports relative paths.
46
+ */
47
+ function createCssImportResolver(importer) {
48
+ const req = (0, module_1.createRequire)(importer.startsWith('file://') ? importer : (0, url_1.pathToFileURL)(importer).toString());
49
+ return (id) => {
50
+ if (id.startsWith('~')) {
51
+ const pkgPath = id.substring(1);
52
+ try {
53
+ const resolved = req.resolve(pkgPath);
54
+ if ((0, fs_1.existsSync)(resolved)) {
55
+ return (0, url_1.pathToFileURL)(resolved);
56
+ }
57
+ }
58
+ catch (e) {
59
+ // ignore, fallback to manual walk below
60
+ }
61
+ let currentDir = path.dirname(importer);
62
+ const root = path.parse(currentDir).root;
63
+ while (currentDir && currentDir !== root) {
64
+ const candidate = path.join(currentDir, 'node_modules', pkgPath);
65
+ if ((0, fs_1.existsSync)(candidate)) {
66
+ return (0, url_1.pathToFileURL)(candidate);
67
+ }
68
+ const parent = path.dirname(currentDir);
69
+ if (parent === currentDir)
70
+ break;
71
+ currentDir = parent;
72
+ }
73
+ const rootNodeModulesPath = path.join(process.cwd(), 'node_modules', pkgPath);
74
+ if ((0, fs_1.existsSync)(rootNodeModulesPath)) {
75
+ return (0, url_1.pathToFileURL)(rootNodeModulesPath);
76
+ }
77
+ return null;
78
+ }
79
+ const resolvedFilePath = path.resolve(path.dirname(importer), id);
80
+ if ((0, fs_1.existsSync)(resolvedFilePath)) {
81
+ return (0, url_1.pathToFileURL)(resolvedFilePath);
82
+ }
83
+ return null;
84
+ };
85
+ }
86
+ exports.createCssImportResolver = createCssImportResolver;
87
+ /**
88
+ * Try to resolve a module path relative to a given importer file. Returns absolute file path
89
+ * when found, otherwise `undefined`.
90
+ */
91
+ function tryResolve(importer, request) {
92
+ try {
93
+ const req = (0, module_1.createRequire)(importer.startsWith('file://') ? importer : (0, url_1.pathToFileURL)(importer).toString());
94
+ return req.resolve(request);
95
+ }
96
+ catch (e) {
97
+ return undefined;
98
+ }
99
+ }
100
+ exports.tryResolve = tryResolve;
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@douyinfe/semi-vite-plugin",
3
+ "version": "2.98.0",
4
+ "description": "A vite plugin for Semi Design to custom theme, replace prefix and so on.",
5
+ "homepage": "",
6
+ "license": "MIT",
7
+ "main": "lib/index.js",
8
+ "types": "lib/index.d.ts",
9
+ "directories": {
10
+ "lib": "lib"
11
+ },
12
+ "files": [
13
+ "lib",
14
+ "README.md"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org"
19
+ },
20
+ "scripts": {
21
+ "build:lib": "rimraf lib && tsc",
22
+ "dev": "tsc -w --sourceMap",
23
+ "test": "npm run build:lib && node test/run.mjs",
24
+ "prepublishOnly": "npm run build:lib"
25
+ },
26
+ "dependencies": {
27
+ "sass": "^1.54.9"
28
+ },
29
+ "peerDependencies": {
30
+ "vite": ">=3.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "rimraf": "^3.0.2",
34
+ "typescript": "^4",
35
+ "vite": "^5.0.0"
36
+ },
37
+ "gitHead": "e33a947a4e0745a7ad15d3e773355cc19d23b174"
38
+ }