@webinex/vite 0.0.1-beta0
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/dist/external-plugin/index.cjs +135 -0
- package/dist/external-plugin/index.d.cts +13 -0
- package/dist/external-plugin/index.d.mts +13 -0
- package/dist/external-plugin/index.d.ts +13 -0
- package/dist/external-plugin/index.mjs +129 -0
- package/dist/i18next-resources-plugin/client.d.ts +7 -0
- package/dist/i18next-resources-plugin/index.cjs +77 -0
- package/dist/i18next-resources-plugin/index.d.cts +9 -0
- package/dist/i18next-resources-plugin/index.d.mts +9 -0
- package/dist/i18next-resources-plugin/index.d.ts +9 -0
- package/dist/i18next-resources-plugin/index.mjs +75 -0
- package/dist/scss-path-plugin/index.cjs +28 -0
- package/dist/scss-path-plugin/index.d.cts +12 -0
- package/dist/scss-path-plugin/index.d.mts +12 -0
- package/dist/scss-path-plugin/index.d.ts +12 -0
- package/dist/scss-path-plugin/index.mjs +22 -0
- package/package.json +70 -0
- package/src/external-plugin/index.ts +176 -0
- package/src/i18next-resources-plugin/client.d.ts +7 -0
- package/src/i18next-resources-plugin/index.ts +97 -0
- package/src/scss-path-plugin/index.ts +31 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const node_fs = require('node:fs');
|
|
4
|
+
const promises = require('node:fs/promises');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
8
|
+
|
|
9
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
10
|
+
|
|
11
|
+
const MODULE_PREFIX = "\0external:";
|
|
12
|
+
const PATH_SPLIT = "::";
|
|
13
|
+
async function resolveModule(id, location) {
|
|
14
|
+
const packageJson = JSON.parse(
|
|
15
|
+
await promises.readFile(path__default.resolve(location, "package.json"), { encoding: "utf-8" })
|
|
16
|
+
);
|
|
17
|
+
const main = packageJson.module ?? packageJson.main;
|
|
18
|
+
if (!main) throw new Error(`Module ${id} does not have a main or module field in its package.json`);
|
|
19
|
+
return {
|
|
20
|
+
id,
|
|
21
|
+
location,
|
|
22
|
+
exports: {
|
|
23
|
+
".": path__default.resolve(location, main)
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
class Resolver {
|
|
28
|
+
_moduleById;
|
|
29
|
+
constructor(modules) {
|
|
30
|
+
this._moduleById = new Map(modules.map((x) => [x.id, x]));
|
|
31
|
+
}
|
|
32
|
+
async resolveId(id, importer) {
|
|
33
|
+
if (this.has(id)) {
|
|
34
|
+
return this.resolveBase(id);
|
|
35
|
+
}
|
|
36
|
+
const isExternalModuleInternalImport = importer?.startsWith(MODULE_PREFIX) && id.startsWith(".");
|
|
37
|
+
if (isExternalModuleInternalImport) {
|
|
38
|
+
return await this.resolveInternalImport(id, importer);
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
async path(id) {
|
|
43
|
+
const [moduleId, relativePath] = id.slice(MODULE_PREFIX.length).split(PATH_SPLIT);
|
|
44
|
+
return path.resolve(this.module(moduleId).location, relativePath);
|
|
45
|
+
}
|
|
46
|
+
async resolveBase(id) {
|
|
47
|
+
const filePath = path.relative(this.module(id).location, this.module(id).exports["."]).replaceAll(
|
|
48
|
+
path__default.sep,
|
|
49
|
+
"/"
|
|
50
|
+
);
|
|
51
|
+
return MODULE_PREFIX + id + PATH_SPLIT + filePath;
|
|
52
|
+
}
|
|
53
|
+
async resolveInternalImport(id, importer) {
|
|
54
|
+
const [module, filePath] = await this.resolveImporter(importer);
|
|
55
|
+
const importedPath = path.resolve(module.location, filePath, "..", id);
|
|
56
|
+
if (!node_fs.existsSync(importedPath)) {
|
|
57
|
+
throw new Error(`Module ${module.id} does not have a file at ${importedPath}`);
|
|
58
|
+
}
|
|
59
|
+
const resolvedPath = path__default.relative(module.location, importedPath).replaceAll(path__default.sep, "/");
|
|
60
|
+
return MODULE_PREFIX + module.id + PATH_SPLIT + resolvedPath;
|
|
61
|
+
}
|
|
62
|
+
async resolveImporter(importer) {
|
|
63
|
+
const [moduleId, relativePath] = importer.slice(MODULE_PREFIX.length).split(PATH_SPLIT);
|
|
64
|
+
return [this.module(moduleId), relativePath];
|
|
65
|
+
}
|
|
66
|
+
has(id) {
|
|
67
|
+
return this._moduleById.has(id);
|
|
68
|
+
}
|
|
69
|
+
module(id) {
|
|
70
|
+
const module = this._moduleById.get(id);
|
|
71
|
+
if (!module) throw new Error(`Module ${id} not found`);
|
|
72
|
+
return module;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function viteExternalPlugin(options) {
|
|
76
|
+
let resolver;
|
|
77
|
+
return {
|
|
78
|
+
name: "@webinex/vite-external-plugin",
|
|
79
|
+
enforce: "pre",
|
|
80
|
+
async configResolved(config) {
|
|
81
|
+
const modules = await Promise.all(
|
|
82
|
+
options.entries.map((x) => resolveModule(x.id, path__default.resolve(config.root, x.to)))
|
|
83
|
+
);
|
|
84
|
+
resolver = new Resolver(modules);
|
|
85
|
+
},
|
|
86
|
+
async resolveId(id, importer) {
|
|
87
|
+
if (id.endsWith("map")) console.log(`Loading ${id}`);
|
|
88
|
+
return resolver.resolveId(id, importer);
|
|
89
|
+
},
|
|
90
|
+
/**
|
|
91
|
+
* Loading dynamically loaded source maps
|
|
92
|
+
*/
|
|
93
|
+
async transform(code, id) {
|
|
94
|
+
if (!id.startsWith(MODULE_PREFIX)) return;
|
|
95
|
+
const match = code.match(/\/\/# sourceMappingURL=(.+)$/m);
|
|
96
|
+
if (!match) {
|
|
97
|
+
if (id.endsWith(".js")) {
|
|
98
|
+
console.log(`No sourceMappingURL found in ${id}
|
|
99
|
+
${code}`);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const url = match[1];
|
|
104
|
+
let map;
|
|
105
|
+
if (url.startsWith("data:application/json;base64,")) {
|
|
106
|
+
const base64 = url.slice("data:application/json;base64,".length);
|
|
107
|
+
const json = Buffer.from(base64, "base64").toString("utf-8");
|
|
108
|
+
map = JSON.parse(json);
|
|
109
|
+
} else {
|
|
110
|
+
const jsPath = await resolver.path(id);
|
|
111
|
+
const mapPath = path__default.resolve(path__default.dirname(jsPath), url);
|
|
112
|
+
if (!node_fs.existsSync(mapPath)) {
|
|
113
|
+
this.warn(`Sourcemap not found: ${mapPath}`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const mapRaw = await promises.readFile(mapPath, "utf-8");
|
|
117
|
+
map = JSON.parse(mapRaw);
|
|
118
|
+
}
|
|
119
|
+
code = code.replaceAll(match[0], "");
|
|
120
|
+
return { code, map };
|
|
121
|
+
},
|
|
122
|
+
async load(id) {
|
|
123
|
+
if (id.endsWith("map")) console.log(`Loading ${id}`);
|
|
124
|
+
if (!id.startsWith(MODULE_PREFIX)) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
const path2 = await resolver.path(id);
|
|
128
|
+
this.debug(`load ${id} to ${path2}`);
|
|
129
|
+
this.addWatchFile(path2);
|
|
130
|
+
return promises.readFile(path2, { encoding: "utf-8" });
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = viteExternalPlugin;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface Entry {
|
|
4
|
+
id: string;
|
|
5
|
+
to: string;
|
|
6
|
+
}
|
|
7
|
+
interface ViteExternalPluginOptions {
|
|
8
|
+
entries: Entry[];
|
|
9
|
+
}
|
|
10
|
+
declare function viteExternalPlugin(options: ViteExternalPluginOptions): Plugin;
|
|
11
|
+
|
|
12
|
+
export = viteExternalPlugin;
|
|
13
|
+
export type { Entry, ViteExternalPluginOptions };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface Entry {
|
|
4
|
+
id: string;
|
|
5
|
+
to: string;
|
|
6
|
+
}
|
|
7
|
+
interface ViteExternalPluginOptions {
|
|
8
|
+
entries: Entry[];
|
|
9
|
+
}
|
|
10
|
+
declare function viteExternalPlugin(options: ViteExternalPluginOptions): Plugin;
|
|
11
|
+
|
|
12
|
+
export { viteExternalPlugin as default };
|
|
13
|
+
export type { Entry, ViteExternalPluginOptions };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface Entry {
|
|
4
|
+
id: string;
|
|
5
|
+
to: string;
|
|
6
|
+
}
|
|
7
|
+
interface ViteExternalPluginOptions {
|
|
8
|
+
entries: Entry[];
|
|
9
|
+
}
|
|
10
|
+
declare function viteExternalPlugin(options: ViteExternalPluginOptions): Plugin;
|
|
11
|
+
|
|
12
|
+
export = viteExternalPlugin;
|
|
13
|
+
export type { Entry, ViteExternalPluginOptions };
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path, { resolve, relative } from 'node:path';
|
|
4
|
+
|
|
5
|
+
const MODULE_PREFIX = "\0external:";
|
|
6
|
+
const PATH_SPLIT = "::";
|
|
7
|
+
async function resolveModule(id, location) {
|
|
8
|
+
const packageJson = JSON.parse(
|
|
9
|
+
await readFile(path.resolve(location, "package.json"), { encoding: "utf-8" })
|
|
10
|
+
);
|
|
11
|
+
const main = packageJson.module ?? packageJson.main;
|
|
12
|
+
if (!main) throw new Error(`Module ${id} does not have a main or module field in its package.json`);
|
|
13
|
+
return {
|
|
14
|
+
id,
|
|
15
|
+
location,
|
|
16
|
+
exports: {
|
|
17
|
+
".": path.resolve(location, main)
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
class Resolver {
|
|
22
|
+
_moduleById;
|
|
23
|
+
constructor(modules) {
|
|
24
|
+
this._moduleById = new Map(modules.map((x) => [x.id, x]));
|
|
25
|
+
}
|
|
26
|
+
async resolveId(id, importer) {
|
|
27
|
+
if (this.has(id)) {
|
|
28
|
+
return this.resolveBase(id);
|
|
29
|
+
}
|
|
30
|
+
const isExternalModuleInternalImport = importer?.startsWith(MODULE_PREFIX) && id.startsWith(".");
|
|
31
|
+
if (isExternalModuleInternalImport) {
|
|
32
|
+
return await this.resolveInternalImport(id, importer);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
async path(id) {
|
|
37
|
+
const [moduleId, relativePath] = id.slice(MODULE_PREFIX.length).split(PATH_SPLIT);
|
|
38
|
+
return resolve(this.module(moduleId).location, relativePath);
|
|
39
|
+
}
|
|
40
|
+
async resolveBase(id) {
|
|
41
|
+
const filePath = relative(this.module(id).location, this.module(id).exports["."]).replaceAll(
|
|
42
|
+
path.sep,
|
|
43
|
+
"/"
|
|
44
|
+
);
|
|
45
|
+
return MODULE_PREFIX + id + PATH_SPLIT + filePath;
|
|
46
|
+
}
|
|
47
|
+
async resolveInternalImport(id, importer) {
|
|
48
|
+
const [module, filePath] = await this.resolveImporter(importer);
|
|
49
|
+
const importedPath = resolve(module.location, filePath, "..", id);
|
|
50
|
+
if (!existsSync(importedPath)) {
|
|
51
|
+
throw new Error(`Module ${module.id} does not have a file at ${importedPath}`);
|
|
52
|
+
}
|
|
53
|
+
const resolvedPath = path.relative(module.location, importedPath).replaceAll(path.sep, "/");
|
|
54
|
+
return MODULE_PREFIX + module.id + PATH_SPLIT + resolvedPath;
|
|
55
|
+
}
|
|
56
|
+
async resolveImporter(importer) {
|
|
57
|
+
const [moduleId, relativePath] = importer.slice(MODULE_PREFIX.length).split(PATH_SPLIT);
|
|
58
|
+
return [this.module(moduleId), relativePath];
|
|
59
|
+
}
|
|
60
|
+
has(id) {
|
|
61
|
+
return this._moduleById.has(id);
|
|
62
|
+
}
|
|
63
|
+
module(id) {
|
|
64
|
+
const module = this._moduleById.get(id);
|
|
65
|
+
if (!module) throw new Error(`Module ${id} not found`);
|
|
66
|
+
return module;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function viteExternalPlugin(options) {
|
|
70
|
+
let resolver;
|
|
71
|
+
return {
|
|
72
|
+
name: "@webinex/vite-external-plugin",
|
|
73
|
+
enforce: "pre",
|
|
74
|
+
async configResolved(config) {
|
|
75
|
+
const modules = await Promise.all(
|
|
76
|
+
options.entries.map((x) => resolveModule(x.id, path.resolve(config.root, x.to)))
|
|
77
|
+
);
|
|
78
|
+
resolver = new Resolver(modules);
|
|
79
|
+
},
|
|
80
|
+
async resolveId(id, importer) {
|
|
81
|
+
if (id.endsWith("map")) console.log(`Loading ${id}`);
|
|
82
|
+
return resolver.resolveId(id, importer);
|
|
83
|
+
},
|
|
84
|
+
/**
|
|
85
|
+
* Loading dynamically loaded source maps
|
|
86
|
+
*/
|
|
87
|
+
async transform(code, id) {
|
|
88
|
+
if (!id.startsWith(MODULE_PREFIX)) return;
|
|
89
|
+
const match = code.match(/\/\/# sourceMappingURL=(.+)$/m);
|
|
90
|
+
if (!match) {
|
|
91
|
+
if (id.endsWith(".js")) {
|
|
92
|
+
console.log(`No sourceMappingURL found in ${id}
|
|
93
|
+
${code}`);
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const url = match[1];
|
|
98
|
+
let map;
|
|
99
|
+
if (url.startsWith("data:application/json;base64,")) {
|
|
100
|
+
const base64 = url.slice("data:application/json;base64,".length);
|
|
101
|
+
const json = Buffer.from(base64, "base64").toString("utf-8");
|
|
102
|
+
map = JSON.parse(json);
|
|
103
|
+
} else {
|
|
104
|
+
const jsPath = await resolver.path(id);
|
|
105
|
+
const mapPath = path.resolve(path.dirname(jsPath), url);
|
|
106
|
+
if (!existsSync(mapPath)) {
|
|
107
|
+
this.warn(`Sourcemap not found: ${mapPath}`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const mapRaw = await readFile(mapPath, "utf-8");
|
|
111
|
+
map = JSON.parse(mapRaw);
|
|
112
|
+
}
|
|
113
|
+
code = code.replaceAll(match[0], "");
|
|
114
|
+
return { code, map };
|
|
115
|
+
},
|
|
116
|
+
async load(id) {
|
|
117
|
+
if (id.endsWith("map")) console.log(`Loading ${id}`);
|
|
118
|
+
if (!id.startsWith(MODULE_PREFIX)) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
const path2 = await resolver.path(id);
|
|
122
|
+
this.debug(`load ${id} to ${path2}`);
|
|
123
|
+
this.addWatchFile(path2);
|
|
124
|
+
return readFile(path2, { encoding: "utf-8" });
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export { viteExternalPlugin as default };
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const glob = require('glob');
|
|
4
|
+
const yaml = require('yaml');
|
|
5
|
+
const lodash = require('lodash');
|
|
6
|
+
const promises = require('fs/promises');
|
|
7
|
+
|
|
8
|
+
const MODULE_ID = "virtual:resources";
|
|
9
|
+
const RESOLVED_MODULE_ID = "\0" + MODULE_ID;
|
|
10
|
+
function i18nextPlugin(options) {
|
|
11
|
+
const { resources } = options;
|
|
12
|
+
const touched = /* @__PURE__ */ new Set();
|
|
13
|
+
const readLocale = async (addWatch, locale) => {
|
|
14
|
+
const processFile = async (file) => {
|
|
15
|
+
addWatch(file);
|
|
16
|
+
touched.add(file);
|
|
17
|
+
const content = await promises.readFile(file, { encoding: "utf-8" });
|
|
18
|
+
return yaml.parse(content);
|
|
19
|
+
};
|
|
20
|
+
const processGlob = async (path) => {
|
|
21
|
+
let result2 = {};
|
|
22
|
+
const files = await glob.glob(path, { absolute: true, maxDepth: 15 });
|
|
23
|
+
const contents = await Promise.all(files.map(async (file) => await processFile(file)));
|
|
24
|
+
for (const content of contents) {
|
|
25
|
+
result2 = lodash.merge(result2, content);
|
|
26
|
+
}
|
|
27
|
+
return result2;
|
|
28
|
+
};
|
|
29
|
+
const results = await Promise.all(resources[locale].map((x) => processGlob(x)));
|
|
30
|
+
let result = {};
|
|
31
|
+
for (const content of results) {
|
|
32
|
+
result = lodash.merge(result, content);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
const plugin = {
|
|
37
|
+
name: "@custom/i18next-plugin",
|
|
38
|
+
enforce: "pre",
|
|
39
|
+
resolveId: (id) => {
|
|
40
|
+
if (id === MODULE_ID) {
|
|
41
|
+
return RESOLVED_MODULE_ID;
|
|
42
|
+
}
|
|
43
|
+
if (id.startsWith(MODULE_ID + "/")) {
|
|
44
|
+
return "\0" + id;
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
handleHotUpdate: ({ server, file }) => {
|
|
48
|
+
const filePath = file.replaceAll("/", "\\");
|
|
49
|
+
if (touched.has(filePath)) {
|
|
50
|
+
const root = server.moduleGraph.getModuleById(RESOLVED_MODULE_ID);
|
|
51
|
+
server.moduleGraph.invalidateModule(root);
|
|
52
|
+
}
|
|
53
|
+
server.ws.send({ type: "full-reload" });
|
|
54
|
+
},
|
|
55
|
+
async load(id) {
|
|
56
|
+
if (id === RESOLVED_MODULE_ID) {
|
|
57
|
+
return {
|
|
58
|
+
code: `export const RESOURCES = {
|
|
59
|
+
${Object.entries(resources).map(([locale]) => `'${locale}': () => import('virtual:resources/${locale}')`).join("\n,")}
|
|
60
|
+
}`,
|
|
61
|
+
map: null
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
if (id.startsWith(RESOLVED_MODULE_ID + "/")) {
|
|
65
|
+
const content = await readLocale(
|
|
66
|
+
(id2) => this.addWatchFile(id2),
|
|
67
|
+
id.substring((RESOLVED_MODULE_ID + "/").length)
|
|
68
|
+
);
|
|
69
|
+
const json = JSON.stringify(content);
|
|
70
|
+
return `export default ${json}`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return plugin;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
exports.i18nextPlugin = i18nextPlugin;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { parse } from 'yaml';
|
|
3
|
+
import { merge } from 'lodash';
|
|
4
|
+
import { readFile } from 'fs/promises';
|
|
5
|
+
|
|
6
|
+
const MODULE_ID = "virtual:resources";
|
|
7
|
+
const RESOLVED_MODULE_ID = "\0" + MODULE_ID;
|
|
8
|
+
function i18nextPlugin(options) {
|
|
9
|
+
const { resources } = options;
|
|
10
|
+
const touched = /* @__PURE__ */ new Set();
|
|
11
|
+
const readLocale = async (addWatch, locale) => {
|
|
12
|
+
const processFile = async (file) => {
|
|
13
|
+
addWatch(file);
|
|
14
|
+
touched.add(file);
|
|
15
|
+
const content = await readFile(file, { encoding: "utf-8" });
|
|
16
|
+
return parse(content);
|
|
17
|
+
};
|
|
18
|
+
const processGlob = async (path) => {
|
|
19
|
+
let result2 = {};
|
|
20
|
+
const files = await glob(path, { absolute: true, maxDepth: 15 });
|
|
21
|
+
const contents = await Promise.all(files.map(async (file) => await processFile(file)));
|
|
22
|
+
for (const content of contents) {
|
|
23
|
+
result2 = merge(result2, content);
|
|
24
|
+
}
|
|
25
|
+
return result2;
|
|
26
|
+
};
|
|
27
|
+
const results = await Promise.all(resources[locale].map((x) => processGlob(x)));
|
|
28
|
+
let result = {};
|
|
29
|
+
for (const content of results) {
|
|
30
|
+
result = merge(result, content);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
const plugin = {
|
|
35
|
+
name: "@custom/i18next-plugin",
|
|
36
|
+
enforce: "pre",
|
|
37
|
+
resolveId: (id) => {
|
|
38
|
+
if (id === MODULE_ID) {
|
|
39
|
+
return RESOLVED_MODULE_ID;
|
|
40
|
+
}
|
|
41
|
+
if (id.startsWith(MODULE_ID + "/")) {
|
|
42
|
+
return "\0" + id;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
handleHotUpdate: ({ server, file }) => {
|
|
46
|
+
const filePath = file.replaceAll("/", "\\");
|
|
47
|
+
if (touched.has(filePath)) {
|
|
48
|
+
const root = server.moduleGraph.getModuleById(RESOLVED_MODULE_ID);
|
|
49
|
+
server.moduleGraph.invalidateModule(root);
|
|
50
|
+
}
|
|
51
|
+
server.ws.send({ type: "full-reload" });
|
|
52
|
+
},
|
|
53
|
+
async load(id) {
|
|
54
|
+
if (id === RESOLVED_MODULE_ID) {
|
|
55
|
+
return {
|
|
56
|
+
code: `export const RESOURCES = {
|
|
57
|
+
${Object.entries(resources).map(([locale]) => `'${locale}': () => import('virtual:resources/${locale}')`).join("\n,")}
|
|
58
|
+
}`,
|
|
59
|
+
map: null
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (id.startsWith(RESOLVED_MODULE_ID + "/")) {
|
|
63
|
+
const content = await readLocale(
|
|
64
|
+
(id2) => this.addWatchFile(id2),
|
|
65
|
+
id.substring((RESOLVED_MODULE_ID + "/").length)
|
|
66
|
+
);
|
|
67
|
+
const json = JSON.stringify(content);
|
|
68
|
+
return `export default ${json}`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return plugin;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export { i18nextPlugin };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
6
|
+
|
|
7
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
8
|
+
|
|
9
|
+
function scssPathPlugin(options) {
|
|
10
|
+
const { replace } = options;
|
|
11
|
+
const regExp = new RegExp(`@use '${replace.key}/`, "g");
|
|
12
|
+
const plugin = {
|
|
13
|
+
name: "@custom/scss-path-plugin",
|
|
14
|
+
transform: {
|
|
15
|
+
order: "pre",
|
|
16
|
+
handler(src, filePath) {
|
|
17
|
+
if (!filePath.endsWith(".scss") && !filePath.endsWith(".sass")) {
|
|
18
|
+
return src;
|
|
19
|
+
}
|
|
20
|
+
const relative = path__default.relative(path__default.dirname(filePath), replace.path).replace(/\\/g, "/");
|
|
21
|
+
return src.replace(regExp, `@use '${relative}/`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
return plugin;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
exports.scssPathPlugin = scssPathPlugin;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface ScssPathPluginOptions {
|
|
4
|
+
replace: {
|
|
5
|
+
key: string;
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
declare function scssPathPlugin(options: ScssPathPluginOptions): Plugin<any>;
|
|
10
|
+
|
|
11
|
+
export { scssPathPlugin };
|
|
12
|
+
export type { ScssPathPluginOptions };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface ScssPathPluginOptions {
|
|
4
|
+
replace: {
|
|
5
|
+
key: string;
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
declare function scssPathPlugin(options: ScssPathPluginOptions): Plugin<any>;
|
|
10
|
+
|
|
11
|
+
export { scssPathPlugin };
|
|
12
|
+
export type { ScssPathPluginOptions };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
|
|
3
|
+
interface ScssPathPluginOptions {
|
|
4
|
+
replace: {
|
|
5
|
+
key: string;
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
declare function scssPathPlugin(options: ScssPathPluginOptions): Plugin<any>;
|
|
10
|
+
|
|
11
|
+
export { scssPathPlugin };
|
|
12
|
+
export type { ScssPathPluginOptions };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
function scssPathPlugin(options) {
|
|
4
|
+
const { replace } = options;
|
|
5
|
+
const regExp = new RegExp(`@use '${replace.key}/`, "g");
|
|
6
|
+
const plugin = {
|
|
7
|
+
name: "@custom/scss-path-plugin",
|
|
8
|
+
transform: {
|
|
9
|
+
order: "pre",
|
|
10
|
+
handler(src, filePath) {
|
|
11
|
+
if (!filePath.endsWith(".scss") && !filePath.endsWith(".sass")) {
|
|
12
|
+
return src;
|
|
13
|
+
}
|
|
14
|
+
const relative = path.relative(path.dirname(filePath), replace.path).replace(/\\/g, "/");
|
|
15
|
+
return src.replace(regExp, `@use '${relative}/`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
return plugin;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { scssPathPlugin };
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webinex/vite",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.0.1-beta0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./external-plugin": {
|
|
8
|
+
"types": "./dist/external-plugin/index.d.ts",
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/external-plugin/index.d.ts",
|
|
11
|
+
"default": "./dist/external-plugin/index.mjs"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/external-plugin/index.d.ts",
|
|
15
|
+
"default": "./dist/external-plugin/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"./i18next-resources-plugin": {
|
|
19
|
+
"types": "./dist/i18next-resources-plugin/index.d.ts",
|
|
20
|
+
"import": {
|
|
21
|
+
"types": "./dist/i18next-resources-plugin/index.d.ts",
|
|
22
|
+
"default": "./dist/i18next-resources-plugin/index.mjs"
|
|
23
|
+
},
|
|
24
|
+
"require": {
|
|
25
|
+
"types": "./dist/i18next-resources-plugin/index.d.ts",
|
|
26
|
+
"default": "./dist/i18next-resources-plugin/index.cjs"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"./i18next-resources-plugin/client": {
|
|
30
|
+
"types": "./dist/i18next-resources-plugin/client.d.ts",
|
|
31
|
+
"default": "./dist/i18next-resources-plugin/client.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./scss-path-plugin": {
|
|
34
|
+
"types": "./dist/scss-path-plugin/index.d.ts",
|
|
35
|
+
"import": {
|
|
36
|
+
"types": "./dist/scss-path-plugin/index.d.ts",
|
|
37
|
+
"default": "./dist/scss-path-plugin/index.mjs"
|
|
38
|
+
},
|
|
39
|
+
"require": {
|
|
40
|
+
"types": "./dist/scss-path-plugin/index.d.ts",
|
|
41
|
+
"default": "./dist/scss-path-plugin/index.cjs"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"lib",
|
|
48
|
+
"src"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "unbuild"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@eslint/js": "^9.13.0",
|
|
55
|
+
"@types/lodash-es": "^4.17.12",
|
|
56
|
+
"@types/node": "^22.7.9",
|
|
57
|
+
"eslint": "^9.13.0",
|
|
58
|
+
"prettier": "^3.4.2",
|
|
59
|
+
"typescript": "~5.6.2",
|
|
60
|
+
"typescript-eslint": "^8.10.0",
|
|
61
|
+
"unbuild": "^3.5.0",
|
|
62
|
+
"vite": "^5.4.9",
|
|
63
|
+
"vite-plugin-dts": "^4.3.0"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"glob": "^11.0.2",
|
|
67
|
+
"lodash": "^4.17.21",
|
|
68
|
+
"yaml": "^2.8.0"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path, { relative, resolve } from 'node:path';
|
|
4
|
+
import { Plugin } from 'vite';
|
|
5
|
+
|
|
6
|
+
export interface Entry {
|
|
7
|
+
id: string;
|
|
8
|
+
to: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const MODULE_PREFIX = '\0external:';
|
|
12
|
+
const PATH_SPLIT = '::';
|
|
13
|
+
|
|
14
|
+
export interface ViteExternalPluginOptions {
|
|
15
|
+
entries: Entry[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface Module {
|
|
19
|
+
id: string;
|
|
20
|
+
location: string;
|
|
21
|
+
exports: Record<string, string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function resolveModule(id: string, location: string): Promise<Module> {
|
|
25
|
+
const packageJson = JSON.parse(
|
|
26
|
+
await readFile(path.resolve(location, 'package.json'), { encoding: 'utf-8' }),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const main = packageJson.module ?? packageJson.main;
|
|
30
|
+
if (!main) throw new Error(`Module ${id} does not have a main or module field in its package.json`);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
id,
|
|
34
|
+
location,
|
|
35
|
+
exports: {
|
|
36
|
+
'.': path.resolve(location, main),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
class Resolver {
|
|
42
|
+
private _moduleById: Map<string, Module>;
|
|
43
|
+
|
|
44
|
+
constructor(modules: Module[]) {
|
|
45
|
+
this._moduleById = new Map(modules.map((x) => [x.id, x]));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public async resolveId(id: string, importer: string | undefined): Promise<string | null> {
|
|
49
|
+
if (this.has(id)) {
|
|
50
|
+
return this.resolveBase(id);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const isExternalModuleInternalImport = importer?.startsWith(MODULE_PREFIX) && id.startsWith('.');
|
|
54
|
+
|
|
55
|
+
if (isExternalModuleInternalImport) {
|
|
56
|
+
return await this.resolveInternalImport(id, importer!);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
public async path(id: string) {
|
|
63
|
+
const [moduleId, relativePath] = id.slice(MODULE_PREFIX.length).split(PATH_SPLIT);
|
|
64
|
+
return resolve(this.module(moduleId).location, relativePath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private async resolveBase(id: string) {
|
|
68
|
+
const filePath = relative(this.module(id).location, this.module(id).exports['.']).replaceAll(
|
|
69
|
+
path.sep,
|
|
70
|
+
'/',
|
|
71
|
+
);
|
|
72
|
+
return MODULE_PREFIX + id + PATH_SPLIT + filePath;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private async resolveInternalImport(id: string, importer: string): Promise<string> {
|
|
76
|
+
const [module, filePath] = await this.resolveImporter(importer);
|
|
77
|
+
const importedPath = resolve(module.location, filePath, '..', id);
|
|
78
|
+
|
|
79
|
+
if (!existsSync(importedPath)) {
|
|
80
|
+
throw new Error(`Module ${module.id} does not have a file at ${importedPath}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const resolvedPath = path.relative(module.location, importedPath).replaceAll(path.sep, '/');
|
|
84
|
+
return MODULE_PREFIX + module.id + PATH_SPLIT + resolvedPath;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private async resolveImporter(importer: string) {
|
|
88
|
+
const [moduleId, relativePath] = importer.slice(MODULE_PREFIX.length).split(PATH_SPLIT);
|
|
89
|
+
return [this.module(moduleId), relativePath] as const;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private has(id: string) {
|
|
93
|
+
return this._moduleById.has(id);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private module(id: string) {
|
|
97
|
+
const module = this._moduleById.get(id);
|
|
98
|
+
if (!module) throw new Error(`Module ${id} not found`);
|
|
99
|
+
return module;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export default function viteExternalPlugin(options: ViteExternalPluginOptions): Plugin {
|
|
104
|
+
let resolver: Resolver;
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
name: '@webinex/vite-external-plugin',
|
|
108
|
+
enforce: 'pre',
|
|
109
|
+
|
|
110
|
+
async configResolved(config) {
|
|
111
|
+
const modules = await Promise.all(
|
|
112
|
+
options.entries.map((x) => resolveModule(x.id, path.resolve(config.root, x.to))),
|
|
113
|
+
);
|
|
114
|
+
resolver = new Resolver(modules);
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
async resolveId(id, importer) {
|
|
118
|
+
if (id.endsWith('map')) console.log(`Loading ${id}`);
|
|
119
|
+
return resolver.resolveId(id, importer);
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Loading dynamically loaded source maps
|
|
124
|
+
*/
|
|
125
|
+
async transform(code, id) {
|
|
126
|
+
if (!id.startsWith(MODULE_PREFIX)) return;
|
|
127
|
+
|
|
128
|
+
const match = code.match(/\/\/# sourceMappingURL=(.+)$/m);
|
|
129
|
+
if (!match) {
|
|
130
|
+
if (id.endsWith('.js')) {
|
|
131
|
+
console.log(`No sourceMappingURL found in ${id}\n${code}`);
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const url = match[1];
|
|
137
|
+
|
|
138
|
+
let map;
|
|
139
|
+
|
|
140
|
+
if (url.startsWith('data:application/json;base64,')) {
|
|
141
|
+
const base64 = url.slice('data:application/json;base64,'.length);
|
|
142
|
+
const json = Buffer.from(base64, 'base64').toString('utf-8');
|
|
143
|
+
map = JSON.parse(json);
|
|
144
|
+
} else {
|
|
145
|
+
const jsPath = await resolver.path(id);
|
|
146
|
+
const mapPath = path.resolve(path.dirname(jsPath), url);
|
|
147
|
+
|
|
148
|
+
if (!existsSync(mapPath)) {
|
|
149
|
+
this.warn(`Sourcemap not found: ${mapPath}`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const mapRaw = await readFile(mapPath, 'utf-8');
|
|
154
|
+
map = JSON.parse(mapRaw);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// we remove the sourceMappingURL comment from the code to avoid default browser behavior
|
|
158
|
+
code = code.replaceAll(match[0], '');
|
|
159
|
+
return { code, map };
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
async load(id) {
|
|
163
|
+
if (id.endsWith('map')) console.log(`Loading ${id}`);
|
|
164
|
+
|
|
165
|
+
if (!id.startsWith(MODULE_PREFIX)) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const path = await resolver.path(id);
|
|
170
|
+
this.debug(`load ${id} to ${path}`);
|
|
171
|
+
|
|
172
|
+
this.addWatchFile(path);
|
|
173
|
+
return readFile(path, { encoding: 'utf-8' });
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { glob } from 'glob';
|
|
2
|
+
import { Plugin } from 'vite';
|
|
3
|
+
import { parse as parseYaml } from 'yaml';
|
|
4
|
+
import { merge } from 'lodash';
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
6
|
+
|
|
7
|
+
export interface I18nextPluginOptions {
|
|
8
|
+
resources: Record<string, string[]>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const MODULE_ID = 'virtual:resources';
|
|
12
|
+
const RESOLVED_MODULE_ID = '\0' + MODULE_ID;
|
|
13
|
+
|
|
14
|
+
export function i18nextPlugin(options: I18nextPluginOptions) {
|
|
15
|
+
const { resources } = options;
|
|
16
|
+
const touched = new Set<string>();
|
|
17
|
+
|
|
18
|
+
const readLocale = async (addWatch: (id: string) => void, locale: string) => {
|
|
19
|
+
const processFile = async (file: string) => {
|
|
20
|
+
addWatch(file);
|
|
21
|
+
touched.add(file);
|
|
22
|
+
const content = await readFile(file, { encoding: 'utf-8' });
|
|
23
|
+
return parseYaml(content);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const processGlob = async (path: string) => {
|
|
27
|
+
let result: Record<string, any> = {};
|
|
28
|
+
const files = await glob(path, { absolute: true, maxDepth: 15 });
|
|
29
|
+
const contents = await Promise.all(files.map(async (file) => await processFile(file)));
|
|
30
|
+
|
|
31
|
+
for (const content of contents) {
|
|
32
|
+
result = merge(result, content);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const results = await Promise.all(resources[locale].map((x) => processGlob(x)));
|
|
39
|
+
let result: Record<string, any> = {};
|
|
40
|
+
|
|
41
|
+
for (const content of results) {
|
|
42
|
+
result = merge(result, content);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const plugin: Plugin = {
|
|
49
|
+
name: '@custom/i18next-plugin',
|
|
50
|
+
enforce: 'pre',
|
|
51
|
+
|
|
52
|
+
resolveId: (id) => {
|
|
53
|
+
if (id === MODULE_ID) {
|
|
54
|
+
return RESOLVED_MODULE_ID;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (id.startsWith(MODULE_ID + '/')) {
|
|
58
|
+
return '\0' + id;
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
handleHotUpdate: ({ server, file }) => {
|
|
63
|
+
const filePath = file.replaceAll('/', '\\');
|
|
64
|
+
|
|
65
|
+
if (touched.has(filePath)) {
|
|
66
|
+
const root = server.moduleGraph.getModuleById(RESOLVED_MODULE_ID)!;
|
|
67
|
+
server.moduleGraph.invalidateModule(root);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
server.ws.send({ type: 'full-reload' });
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async load(this, id) {
|
|
74
|
+
if (id === RESOLVED_MODULE_ID) {
|
|
75
|
+
return {
|
|
76
|
+
code: `export const RESOURCES = {
|
|
77
|
+
${Object.entries(resources)
|
|
78
|
+
.map(([locale]) => `'${locale}': () => import('virtual:resources/${locale}')`)
|
|
79
|
+
.join('\n,')}
|
|
80
|
+
}`,
|
|
81
|
+
map: null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (id.startsWith(RESOLVED_MODULE_ID + '/')) {
|
|
86
|
+
const content = await readLocale(
|
|
87
|
+
(id) => this.addWatchFile(id),
|
|
88
|
+
id.substring((RESOLVED_MODULE_ID + '/').length),
|
|
89
|
+
);
|
|
90
|
+
const json = JSON.stringify(content);
|
|
91
|
+
return `export default ${json}`;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return plugin;
|
|
97
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { Plugin } from 'vite';
|
|
3
|
+
|
|
4
|
+
export interface ScssPathPluginOptions {
|
|
5
|
+
replace: {
|
|
6
|
+
key: string;
|
|
7
|
+
path: string;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function scssPathPlugin(options: ScssPathPluginOptions) {
|
|
12
|
+
const { replace } = options;
|
|
13
|
+
const regExp = new RegExp(`@use '${replace.key}/`, 'g');
|
|
14
|
+
|
|
15
|
+
const plugin: Plugin = {
|
|
16
|
+
name: '@custom/scss-path-plugin',
|
|
17
|
+
transform: {
|
|
18
|
+
order: 'pre',
|
|
19
|
+
handler(this, src, filePath) {
|
|
20
|
+
if (!filePath.endsWith('.scss') && !filePath.endsWith('.sass')) {
|
|
21
|
+
return src;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const relative = path.relative(path.dirname(filePath), replace.path).replace(/\\/g, '/');
|
|
25
|
+
return src.replace(regExp, `@use '${relative}/`);
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return plugin;
|
|
31
|
+
}
|