@unhead/addons 3.0.0-beta.1 → 3.0.0-beta.11
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/shared/addons.f3glm58r.mjs +262 -0
- package/dist/vite.mjs +4 -5
- package/dist/webpack.d.mts +2 -1
- package/dist/webpack.d.ts +2 -1
- package/dist/webpack.mjs +4 -5
- package/package.json +9 -12
- package/dist/shared/addons.DAcLivtz.mjs +0 -259
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import MagicString from 'magic-string';
|
|
3
|
+
import { parseSync } from 'oxc-parser';
|
|
4
|
+
import { walk, ScopeTracker, ScopeTrackerImport } from 'oxc-walker';
|
|
5
|
+
import { parseURL, parseQuery } from 'ufo';
|
|
6
|
+
import { createUnplugin } from 'unplugin';
|
|
7
|
+
import { createContext, runInContext } from 'node:vm';
|
|
8
|
+
import { resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue } from 'unhead/utils';
|
|
9
|
+
|
|
10
|
+
const functionNames = [
|
|
11
|
+
"useServerHead",
|
|
12
|
+
"useServerHeadSafe",
|
|
13
|
+
"useServerSeoMeta",
|
|
14
|
+
// plugins
|
|
15
|
+
"useSchemaOrg"
|
|
16
|
+
];
|
|
17
|
+
const TreeshakeServerComposables = createUnplugin((options = {}) => {
|
|
18
|
+
options.enabled = options.enabled !== void 0 ? options.enabled : true;
|
|
19
|
+
return {
|
|
20
|
+
name: "unhead:remove-server-composables",
|
|
21
|
+
enforce: "post",
|
|
22
|
+
transformInclude(id) {
|
|
23
|
+
if (!options.enabled)
|
|
24
|
+
return false;
|
|
25
|
+
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
26
|
+
const { type } = parseQuery(search);
|
|
27
|
+
if (pathname.match(/[\\/]node_modules[\\/]/))
|
|
28
|
+
return false;
|
|
29
|
+
if (options.filter?.include?.some((pattern) => id.match(pattern)))
|
|
30
|
+
return true;
|
|
31
|
+
if (options.filter?.exclude?.some((pattern) => id.match(pattern)))
|
|
32
|
+
return false;
|
|
33
|
+
if (pathname.endsWith(".vue") && (type === "script" || !search))
|
|
34
|
+
return true;
|
|
35
|
+
if (pathname.match(/\.((c|m)?j|t)sx?$/g))
|
|
36
|
+
return true;
|
|
37
|
+
return false;
|
|
38
|
+
},
|
|
39
|
+
transform(code, id) {
|
|
40
|
+
if (!code.includes("useServerHead") && !code.includes("useServerHeadSafe") && !code.includes("useServerSeoMeta") && !code.includes("useSchemaOrg")) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const ast = parseSync(id, code);
|
|
44
|
+
const s = new MagicString(code);
|
|
45
|
+
walk(ast.program, {
|
|
46
|
+
enter(node) {
|
|
47
|
+
if (node.type === "ExpressionStatement" && node.expression.type === "CallExpression" && node.expression.callee.type === "Identifier" && functionNames.includes(node.expression.callee.name)) {
|
|
48
|
+
s.remove(node.start, node.end);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
if (s.hasChanged()) {
|
|
53
|
+
return {
|
|
54
|
+
code: s.toString(),
|
|
55
|
+
map: s.generateMap({ includeContent: true, source: id })
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
webpack(ctx) {
|
|
60
|
+
if (ctx.name === "server")
|
|
61
|
+
options.enabled = false;
|
|
62
|
+
},
|
|
63
|
+
vite: {
|
|
64
|
+
apply(config, env) {
|
|
65
|
+
if (env.ssrBuild || env.isSsrBuild) {
|
|
66
|
+
options.enabled = false;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const SEO_META_NAMES = /* @__PURE__ */ new Set(["useSeoMeta", "useServerSeoMeta"]);
|
|
76
|
+
const UseSeoMetaTransform = createUnplugin((options = {}) => {
|
|
77
|
+
options.imports = options.imports || true;
|
|
78
|
+
function isValidPackage(s) {
|
|
79
|
+
if (s === "unhead" || s.startsWith("@unhead")) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return [...options.importPaths || []].includes(s);
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
name: "unhead:use-seo-meta-transform",
|
|
86
|
+
enforce: "post",
|
|
87
|
+
transformInclude(id) {
|
|
88
|
+
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
89
|
+
const { type } = parseQuery(search);
|
|
90
|
+
if (pathname.match(/[\\/]node_modules[\\/]/))
|
|
91
|
+
return false;
|
|
92
|
+
if (options.filter?.include?.some((pattern) => id.match(pattern)))
|
|
93
|
+
return true;
|
|
94
|
+
if (options.filter?.exclude?.some((pattern) => id.match(pattern)))
|
|
95
|
+
return false;
|
|
96
|
+
if (pathname.endsWith(".vue") && (type === "script" || !search))
|
|
97
|
+
return true;
|
|
98
|
+
if (pathname.match(/\.((c|m)?j|t)sx?$/g))
|
|
99
|
+
return true;
|
|
100
|
+
return false;
|
|
101
|
+
},
|
|
102
|
+
async transform(code, id) {
|
|
103
|
+
if (!code.includes("useSeoMeta") && !code.includes("useServerSeoMeta"))
|
|
104
|
+
return;
|
|
105
|
+
const scopeTracker = new ScopeTracker();
|
|
106
|
+
const ast = parseSync(id, code);
|
|
107
|
+
const s = new MagicString(code);
|
|
108
|
+
const importRewrites = /* @__PURE__ */ new Map();
|
|
109
|
+
const valueReferenced = /* @__PURE__ */ new Set();
|
|
110
|
+
walk(ast.program, {
|
|
111
|
+
scopeTracker,
|
|
112
|
+
enter(node, parent) {
|
|
113
|
+
if (node.type === "Identifier" && !(parent?.type === "CallExpression" && parent.callee === node) && parent?.type !== "ImportSpecifier") {
|
|
114
|
+
const decl2 = scopeTracker.getDeclaration(node.name);
|
|
115
|
+
if (decl2 instanceof ScopeTrackerImport && isValidPackage(decl2.importNode.source.value) && decl2.node.type === "ImportSpecifier" && decl2.node.imported.type === "Identifier" && SEO_META_NAMES.has(decl2.node.imported.name)) {
|
|
116
|
+
valueReferenced.add(decl2.node.imported.name);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (node.type !== "CallExpression" || node.callee.type !== "Identifier")
|
|
120
|
+
return;
|
|
121
|
+
const decl = scopeTracker.getDeclaration(node.callee.name);
|
|
122
|
+
let originalName;
|
|
123
|
+
let importDecl = null;
|
|
124
|
+
if (decl instanceof ScopeTrackerImport) {
|
|
125
|
+
if (!isValidPackage(decl.importNode.source.value) || decl.node.type !== "ImportSpecifier" || decl.node.imported.type !== "Identifier")
|
|
126
|
+
return;
|
|
127
|
+
originalName = decl.node.imported.name;
|
|
128
|
+
importDecl = decl.importNode;
|
|
129
|
+
} else if (!decl && SEO_META_NAMES.has(node.callee.name)) {
|
|
130
|
+
originalName = node.callee.name;
|
|
131
|
+
} else {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (!SEO_META_NAMES.has(originalName))
|
|
135
|
+
return;
|
|
136
|
+
const properties = node.arguments[0]?.properties;
|
|
137
|
+
if (!properties)
|
|
138
|
+
return;
|
|
139
|
+
let output = [];
|
|
140
|
+
const title = properties.find((property) => property.key?.name === "title");
|
|
141
|
+
const titleTemplate = properties.find((property) => property.key?.name === "titleTemplate");
|
|
142
|
+
const meta = properties.filter((property) => property.key?.name !== "title" && property.key?.name !== "titleTemplate");
|
|
143
|
+
if (title || titleTemplate || originalName === "useSeoMeta") {
|
|
144
|
+
output.push("useHead({");
|
|
145
|
+
if (title) {
|
|
146
|
+
output.push(` title: ${code.substring(title.value.start, title.value.end)},`);
|
|
147
|
+
}
|
|
148
|
+
if (titleTemplate) {
|
|
149
|
+
output.push(` titleTemplate: ${code.substring(titleTemplate.value.start, titleTemplate.value.end)},`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (originalName === "useServerSeoMeta") {
|
|
153
|
+
if (output.length)
|
|
154
|
+
output.push("});");
|
|
155
|
+
output.push("useServerHead({");
|
|
156
|
+
}
|
|
157
|
+
if (meta.length)
|
|
158
|
+
output.push(" meta: [");
|
|
159
|
+
meta.forEach((property) => {
|
|
160
|
+
if (property.type === "SpreadElement") {
|
|
161
|
+
output = false;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (property.key.type !== "Identifier" || !property.value) {
|
|
165
|
+
output = false;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (output === false)
|
|
169
|
+
return;
|
|
170
|
+
const propertyKey = property.key;
|
|
171
|
+
let key = resolveMetaKeyType(propertyKey.name);
|
|
172
|
+
const keyValue = resolveMetaKeyValue(propertyKey.name);
|
|
173
|
+
let valueKey = "content";
|
|
174
|
+
if (keyValue === "charset") {
|
|
175
|
+
valueKey = "charset";
|
|
176
|
+
key = "charset";
|
|
177
|
+
}
|
|
178
|
+
let value = code.substring(property.value.start, property.value.end);
|
|
179
|
+
if (property.value.type === "ArrayExpression") {
|
|
180
|
+
const elements = property.value.elements;
|
|
181
|
+
if (!elements.length)
|
|
182
|
+
return;
|
|
183
|
+
const metaTags = elements.map((element) => {
|
|
184
|
+
if (element.type !== "ObjectExpression")
|
|
185
|
+
return ` { ${key}: '${keyValue}', ${valueKey}: ${code.substring(element.start, element.end)} },`;
|
|
186
|
+
return element.properties.map((p) => {
|
|
187
|
+
const propKey = p.key.name;
|
|
188
|
+
const propValue = code.substring(p.value.start, p.value.end);
|
|
189
|
+
return ` { ${key}: '${keyValue}:${propKey}', ${valueKey}: ${propValue} },`;
|
|
190
|
+
}).join("\n");
|
|
191
|
+
});
|
|
192
|
+
output.push(metaTags.join("\n"));
|
|
193
|
+
return;
|
|
194
|
+
} else if (property.value.type === "ObjectExpression") {
|
|
195
|
+
const isStatic = property.value.properties.every((p) => p.value.type === "StringLiteral" && typeof p.value.value === "string");
|
|
196
|
+
if (!isStatic) {
|
|
197
|
+
output = false;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const context = createContext({
|
|
201
|
+
resolvePackedMetaObjectValue
|
|
202
|
+
});
|
|
203
|
+
const start = property.value.start;
|
|
204
|
+
const end = property.value.end;
|
|
205
|
+
try {
|
|
206
|
+
value = JSON.stringify(runInContext(`resolvePackedMetaObjectValue(${code.slice(start, end)})`, context));
|
|
207
|
+
} catch {
|
|
208
|
+
output = false;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (valueKey === "charset")
|
|
213
|
+
output.push(` { ${key}: ${value} },`);
|
|
214
|
+
else
|
|
215
|
+
output.push(` { ${key}: '${keyValue}', ${valueKey}: ${value} },`);
|
|
216
|
+
});
|
|
217
|
+
if (output) {
|
|
218
|
+
if (meta.length)
|
|
219
|
+
output.push(" ]");
|
|
220
|
+
output.push("})");
|
|
221
|
+
s.overwrite(node.start, node.end, output.join("\n"));
|
|
222
|
+
if (importDecl) {
|
|
223
|
+
if (!importRewrites.has(importDecl))
|
|
224
|
+
importRewrites.set(importDecl, /* @__PURE__ */ new Set());
|
|
225
|
+
importRewrites.get(importDecl).add(originalName);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
if (options.imports && importRewrites.size > 0) {
|
|
231
|
+
for (const [importNode, transformedNames] of importRewrites) {
|
|
232
|
+
const newSpecifiers = /* @__PURE__ */ new Set();
|
|
233
|
+
for (const spec of importNode.specifiers) {
|
|
234
|
+
if (spec.type !== "ImportSpecifier")
|
|
235
|
+
continue;
|
|
236
|
+
const importedName = spec.imported.name;
|
|
237
|
+
if (transformedNames.has(importedName)) {
|
|
238
|
+
newSpecifiers.add(importedName.includes("Server") ? "useServerHead" : "useHead");
|
|
239
|
+
if (valueReferenced.has(importedName))
|
|
240
|
+
newSpecifiers.add(importedName);
|
|
241
|
+
} else {
|
|
242
|
+
newSpecifiers.add(importedName);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
s.overwrite(
|
|
246
|
+
importNode.specifiers[0].start,
|
|
247
|
+
importNode.specifiers[importNode.specifiers.length - 1].end,
|
|
248
|
+
[...newSpecifiers].join(", ")
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
if (s.hasChanged()) {
|
|
253
|
+
return {
|
|
254
|
+
code: s.toString(),
|
|
255
|
+
map: s.generateMap({ includeContent: true, source: id })
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
export { TreeshakeServerComposables as T, UseSeoMetaTransform as U };
|
package/dist/vite.mjs
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { T as TreeshakeServerComposables, U as UseSeoMetaTransform } from './shared/addons.
|
|
1
|
+
import { T as TreeshakeServerComposables, U as UseSeoMetaTransform } from './shared/addons.f3glm58r.mjs';
|
|
2
2
|
import 'node:url';
|
|
3
|
+
import 'magic-string';
|
|
4
|
+
import 'oxc-parser';
|
|
5
|
+
import 'oxc-walker';
|
|
3
6
|
import 'ufo';
|
|
4
7
|
import 'unplugin';
|
|
5
|
-
import 'unplugin-ast';
|
|
6
8
|
import 'node:vm';
|
|
7
|
-
import 'estree-walker';
|
|
8
|
-
import 'magic-string';
|
|
9
|
-
import 'mlly';
|
|
10
9
|
import 'unhead/utils';
|
|
11
10
|
|
|
12
11
|
const vite = (options = {}) => {
|
package/dist/webpack.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import * as unplugin from 'unplugin';
|
|
1
2
|
import { U as UnpluginOptions } from './shared/addons.51MZ0zeg.mjs';
|
|
2
3
|
|
|
3
|
-
declare const _default: (options?: UnpluginOptions) =>
|
|
4
|
+
declare const _default: (options?: UnpluginOptions) => unplugin.WebpackPluginInstance[];
|
|
4
5
|
|
|
5
6
|
export { UnpluginOptions, _default as default };
|
package/dist/webpack.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import * as unplugin from 'unplugin';
|
|
1
2
|
import { U as UnpluginOptions } from './shared/addons.51MZ0zeg.js';
|
|
2
3
|
|
|
3
|
-
declare const _default: (options?: UnpluginOptions) =>
|
|
4
|
+
declare const _default: (options?: UnpluginOptions) => unplugin.WebpackPluginInstance[];
|
|
4
5
|
|
|
5
6
|
export { UnpluginOptions, _default as default };
|
package/dist/webpack.mjs
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import { T as TreeshakeServerComposables, U as UseSeoMetaTransform } from './shared/addons.
|
|
1
|
+
import { T as TreeshakeServerComposables, U as UseSeoMetaTransform } from './shared/addons.f3glm58r.mjs';
|
|
2
2
|
import 'node:url';
|
|
3
|
+
import 'magic-string';
|
|
4
|
+
import 'oxc-parser';
|
|
5
|
+
import 'oxc-walker';
|
|
3
6
|
import 'ufo';
|
|
4
7
|
import 'unplugin';
|
|
5
|
-
import 'unplugin-ast';
|
|
6
8
|
import 'node:vm';
|
|
7
|
-
import 'estree-walker';
|
|
8
|
-
import 'magic-string';
|
|
9
|
-
import 'mlly';
|
|
10
9
|
import 'unhead/utils';
|
|
11
10
|
|
|
12
11
|
const webpack = (options = {}) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unhead/addons",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.0.0-beta.
|
|
4
|
+
"version": "3.0.0-beta.11",
|
|
5
5
|
"description": "Unhead addons for build tools and bundlers.",
|
|
6
6
|
"author": "Harlan Wilton <harlan@harlanzw.com>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -52,23 +52,20 @@
|
|
|
52
52
|
"dist"
|
|
53
53
|
],
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"unhead": "3.0.0-beta.
|
|
55
|
+
"unhead": "^3.0.0-beta.11"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@rollup/pluginutils": "^5.3.0",
|
|
59
|
-
"estree-walker": "^3.0.3",
|
|
60
59
|
"magic-string": "^0.30.21",
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
"unplugin
|
|
60
|
+
"oxc-parser": "^0.106.0",
|
|
61
|
+
"oxc-walker": "^0.7.0",
|
|
62
|
+
"ufo": "^1.6.2",
|
|
63
|
+
"unplugin": "^2.3.11"
|
|
65
64
|
},
|
|
66
65
|
"devDependencies": {
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"vite": "7.2.2",
|
|
71
|
-
"unhead": "3.0.0-beta.1"
|
|
66
|
+
"rollup": "^4.55.1",
|
|
67
|
+
"vite": "^7.3.1",
|
|
68
|
+
"unhead": "3.0.0-beta.11"
|
|
72
69
|
},
|
|
73
70
|
"scripts": {
|
|
74
71
|
"build": "unbuild",
|
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import { pathToFileURL } from 'node:url';
|
|
2
|
-
import { parseURL, parseQuery } from 'ufo';
|
|
3
|
-
import { createUnplugin } from 'unplugin';
|
|
4
|
-
import { transform } from 'unplugin-ast';
|
|
5
|
-
import { createContext, runInContext } from 'node:vm';
|
|
6
|
-
import { walk } from 'estree-walker';
|
|
7
|
-
import MagicString from 'magic-string';
|
|
8
|
-
import { findStaticImports, parseStaticImport } from 'mlly';
|
|
9
|
-
import { resolveMetaKeyType, resolveMetaKeyValue, resolvePackedMetaObjectValue } from 'unhead/utils';
|
|
10
|
-
|
|
11
|
-
function RemoveFunctions(functionNames) {
|
|
12
|
-
return {
|
|
13
|
-
onNode: (node) => node.type === "CallExpression" && node.callee.type === "Identifier" && functionNames.includes(node.callee.name),
|
|
14
|
-
transform() {
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
const TreeshakeServerComposables = createUnplugin((options = {}) => {
|
|
20
|
-
options.enabled = options.enabled !== void 0 ? options.enabled : true;
|
|
21
|
-
return {
|
|
22
|
-
name: "unhead:remove-server-composables",
|
|
23
|
-
enforce: "post",
|
|
24
|
-
transformInclude(id) {
|
|
25
|
-
if (!options.enabled)
|
|
26
|
-
return false;
|
|
27
|
-
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
28
|
-
const { type } = parseQuery(search);
|
|
29
|
-
if (pathname.match(/[\\/]node_modules[\\/]/))
|
|
30
|
-
return false;
|
|
31
|
-
if (options.filter?.include?.some((pattern) => id.match(pattern)))
|
|
32
|
-
return true;
|
|
33
|
-
if (options.filter?.exclude?.some((pattern) => id.match(pattern)))
|
|
34
|
-
return false;
|
|
35
|
-
if (pathname.endsWith(".vue") && (type === "script" || !search))
|
|
36
|
-
return true;
|
|
37
|
-
if (pathname.match(/\.((c|m)?j|t)sx?$/g))
|
|
38
|
-
return true;
|
|
39
|
-
return false;
|
|
40
|
-
},
|
|
41
|
-
async transform(code, id) {
|
|
42
|
-
if (!code.includes("useServerHead") && !code.includes("useServerHeadSafe") && !code.includes("useServerSeoMeta") && !code.includes("useSchemaOrg")) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
let transformed;
|
|
46
|
-
try {
|
|
47
|
-
transformed = await transform(code, id, {
|
|
48
|
-
parserOptions: {},
|
|
49
|
-
transformer: [
|
|
50
|
-
RemoveFunctions([
|
|
51
|
-
"useServerHead",
|
|
52
|
-
"useServerHeadSafe",
|
|
53
|
-
"useServerSeoMeta",
|
|
54
|
-
// plugins
|
|
55
|
-
"useSchemaOrg"
|
|
56
|
-
])
|
|
57
|
-
]
|
|
58
|
-
});
|
|
59
|
-
} catch {
|
|
60
|
-
}
|
|
61
|
-
return transformed;
|
|
62
|
-
},
|
|
63
|
-
webpack(ctx) {
|
|
64
|
-
if (ctx.name === "server")
|
|
65
|
-
options.enabled = false;
|
|
66
|
-
},
|
|
67
|
-
vite: {
|
|
68
|
-
apply(config, env) {
|
|
69
|
-
if (env.ssrBuild || env.isSsrBuild) {
|
|
70
|
-
options.enabled = false;
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
};
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const UseSeoMetaTransform = createUnplugin((options = {}) => {
|
|
80
|
-
options.imports = options.imports || true;
|
|
81
|
-
function isValidPackage(s) {
|
|
82
|
-
if (s === "unhead" || s.startsWith("@unhead")) {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
return [...options.importPaths || []].includes(s);
|
|
86
|
-
}
|
|
87
|
-
return {
|
|
88
|
-
name: "unhead:use-seo-meta-transform",
|
|
89
|
-
enforce: "post",
|
|
90
|
-
transformInclude(id) {
|
|
91
|
-
const { pathname, search } = parseURL(decodeURIComponent(pathToFileURL(id).href));
|
|
92
|
-
const { type } = parseQuery(search);
|
|
93
|
-
if (pathname.match(/[\\/]node_modules[\\/]/))
|
|
94
|
-
return false;
|
|
95
|
-
if (options.filter?.include?.some((pattern) => id.match(pattern)))
|
|
96
|
-
return true;
|
|
97
|
-
if (options.filter?.exclude?.some((pattern) => id.match(pattern)))
|
|
98
|
-
return false;
|
|
99
|
-
if (pathname.endsWith(".vue") && (type === "script" || !search))
|
|
100
|
-
return true;
|
|
101
|
-
if (pathname.match(/\.((c|m)?j|t)sx?$/g))
|
|
102
|
-
return true;
|
|
103
|
-
return false;
|
|
104
|
-
},
|
|
105
|
-
async transform(code, id) {
|
|
106
|
-
if (!code.includes("useSeoMeta") && !code.includes("useServerSeoMeta"))
|
|
107
|
-
return;
|
|
108
|
-
const statements = findStaticImports(code).filter((i) => isValidPackage(i.specifier));
|
|
109
|
-
const importNames = {};
|
|
110
|
-
for (const i of statements.flatMap((i2) => parseStaticImport(i2))) {
|
|
111
|
-
if (i.namedImports) {
|
|
112
|
-
for (const key in i.namedImports) {
|
|
113
|
-
if (key === "useSeoMeta" || key === "useServerSeoMeta")
|
|
114
|
-
importNames[i.namedImports[key]] = key;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
const ast = this.parse(code);
|
|
119
|
-
const s = new MagicString(code);
|
|
120
|
-
let replacementPayload;
|
|
121
|
-
let replaceCount = 0;
|
|
122
|
-
let totalCount = 0;
|
|
123
|
-
walk(ast, {
|
|
124
|
-
enter(_node) {
|
|
125
|
-
if (options.imports && _node.type === "ImportDeclaration" && isValidPackage(_node.source.value)) {
|
|
126
|
-
const node = _node;
|
|
127
|
-
const hasSeoMeta = node.specifiers.some(
|
|
128
|
-
(s2) => s2.type === "ImportSpecifier" && ["useSeoMeta", "useServerSeoMeta"].includes(s2.imported.name)
|
|
129
|
-
);
|
|
130
|
-
if (!hasSeoMeta) {
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const toImport = /* @__PURE__ */ new Set();
|
|
134
|
-
node.specifiers.forEach((spec) => {
|
|
135
|
-
if (spec.type === "ImportSpecifier" && ["useSeoMeta", "useServerSeoMeta"].includes(spec.imported.name)) {
|
|
136
|
-
toImport.add(spec.imported.name.includes("Server") ? "useServerHead" : "useHead");
|
|
137
|
-
} else {
|
|
138
|
-
toImport.add(spec.imported.name);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
if (toImport.size) {
|
|
142
|
-
replacementPayload = (useSeoMeta = false) => [node.specifiers[0].start, node.specifiers[node.specifiers.length - 1].end, [...toImport, useSeoMeta ? "useSeoMeta" : false].filter(Boolean).join(", ")];
|
|
143
|
-
}
|
|
144
|
-
} else if (_node.type === "CallExpression" && _node.callee.type === "Identifier" && Object.keys({
|
|
145
|
-
useSeoMeta: "useSeoMeta",
|
|
146
|
-
useServerSeoMeta: "useServerSeoMeta",
|
|
147
|
-
...importNames
|
|
148
|
-
}).includes(_node.callee.name)) {
|
|
149
|
-
replaceCount++;
|
|
150
|
-
const node = _node;
|
|
151
|
-
const calleeName = importNames[node.callee.name] || node.callee.name;
|
|
152
|
-
const properties = node.arguments[0].properties;
|
|
153
|
-
if (!properties)
|
|
154
|
-
return;
|
|
155
|
-
let output = [];
|
|
156
|
-
const title = properties.find((property) => property.key?.name === "title");
|
|
157
|
-
const titleTemplate = properties.find((property) => property.key?.name === "titleTemplate");
|
|
158
|
-
const meta = properties.filter((property) => property.key?.name !== "title" && property.key?.name !== "titleTemplate");
|
|
159
|
-
if (title || titleTemplate || calleeName === "useSeoMeta") {
|
|
160
|
-
output.push("useHead({");
|
|
161
|
-
if (title) {
|
|
162
|
-
output.push(` title: ${code.substring(title.value.start, title.value.end)},`);
|
|
163
|
-
}
|
|
164
|
-
if (titleTemplate) {
|
|
165
|
-
output.push(` titleTemplate: ${code.substring(titleTemplate.value.start, titleTemplate.value.end)},`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
if (calleeName === "useServerSeoMeta") {
|
|
169
|
-
if (output.length)
|
|
170
|
-
output.push("});");
|
|
171
|
-
output.push("useServerHead({");
|
|
172
|
-
}
|
|
173
|
-
if (meta.length)
|
|
174
|
-
output.push(" meta: [");
|
|
175
|
-
meta.forEach((property) => {
|
|
176
|
-
if (property.type === "SpreadElement") {
|
|
177
|
-
output = false;
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
if (property.key.type !== "Identifier" || !property.value) {
|
|
181
|
-
output = false;
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
if (output === false)
|
|
185
|
-
return;
|
|
186
|
-
const propertyKey = property.key;
|
|
187
|
-
let key = resolveMetaKeyType(propertyKey.name);
|
|
188
|
-
const keyValue = resolveMetaKeyValue(propertyKey.name);
|
|
189
|
-
let valueKey = "content";
|
|
190
|
-
if (keyValue === "charset") {
|
|
191
|
-
valueKey = "charset";
|
|
192
|
-
key = "charset";
|
|
193
|
-
}
|
|
194
|
-
let value = code.substring(property.value.start, property.value.end);
|
|
195
|
-
if (property.value.type === "ArrayExpression") {
|
|
196
|
-
if (output === false)
|
|
197
|
-
return;
|
|
198
|
-
const elements = property.value.elements;
|
|
199
|
-
if (!elements.length)
|
|
200
|
-
return;
|
|
201
|
-
const metaTags = elements.map((element) => {
|
|
202
|
-
if (element.type !== "ObjectExpression")
|
|
203
|
-
return ` { ${key}: '${keyValue}', ${valueKey}: ${code.substring(element.start, element.end)} },`;
|
|
204
|
-
return element.properties.map((p) => {
|
|
205
|
-
const propKey = p.key.name;
|
|
206
|
-
const propValue = code.substring(p.value.start, p.value.end);
|
|
207
|
-
return ` { ${key}: '${keyValue}:${propKey}', ${valueKey}: ${propValue} },`;
|
|
208
|
-
}).join("\n");
|
|
209
|
-
});
|
|
210
|
-
output.push(metaTags.join("\n"));
|
|
211
|
-
return;
|
|
212
|
-
} else if (property.value.type === "ObjectExpression") {
|
|
213
|
-
const isStatic = property.value.properties.every((p) => p.value.type === "Literal" && typeof p.value.value === "string");
|
|
214
|
-
if (!isStatic) {
|
|
215
|
-
output = false;
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const context = createContext({
|
|
219
|
-
resolvePackedMetaObjectValue
|
|
220
|
-
});
|
|
221
|
-
const start = property.value.start;
|
|
222
|
-
const end = property.value.end;
|
|
223
|
-
try {
|
|
224
|
-
value = JSON.stringify(runInContext(`resolvePackedMetaObjectValue(${code.slice(start, end)})`, context));
|
|
225
|
-
} catch {
|
|
226
|
-
output = false;
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
if (valueKey === "charset")
|
|
231
|
-
output.push(` { ${key}: ${value} },`);
|
|
232
|
-
else
|
|
233
|
-
output.push(` { ${key}: '${keyValue}', ${valueKey}: ${value} },`);
|
|
234
|
-
});
|
|
235
|
-
if (output) {
|
|
236
|
-
if (meta.length)
|
|
237
|
-
output.push(" ]");
|
|
238
|
-
output.push("})");
|
|
239
|
-
s.overwrite(node.start, node.end, output.join("\n"));
|
|
240
|
-
}
|
|
241
|
-
} else if (_node.type === "Identifier" && ["useSeoMeta", "useServerSeoMeta"].includes(_node.name)) {
|
|
242
|
-
totalCount++;
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
if (s.hasChanged()) {
|
|
247
|
-
if (replacementPayload) {
|
|
248
|
-
s.overwrite(...replacementPayload(replaceCount + 3 === totalCount));
|
|
249
|
-
}
|
|
250
|
-
return {
|
|
251
|
-
code: s.toString(),
|
|
252
|
-
map: s.generateMap({ includeContent: true, source: id })
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
export { TreeshakeServerComposables as T, UseSeoMetaTransform as U };
|