@unhead/addons 2.1.8 → 3.0.0-beta.10

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.
@@ -0,0 +1,264 @@
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) && 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))
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
+ if (output === false)
181
+ return;
182
+ const elements = property.value.elements;
183
+ if (!elements.length)
184
+ return;
185
+ const metaTags = elements.map((element) => {
186
+ if (element.type !== "ObjectExpression")
187
+ return ` { ${key}: '${keyValue}', ${valueKey}: ${code.substring(element.start, element.end)} },`;
188
+ return element.properties.map((p) => {
189
+ const propKey = p.key.name;
190
+ const propValue = code.substring(p.value.start, p.value.end);
191
+ return ` { ${key}: '${keyValue}:${propKey}', ${valueKey}: ${propValue} },`;
192
+ }).join("\n");
193
+ });
194
+ output.push(metaTags.join("\n"));
195
+ return;
196
+ } else if (property.value.type === "ObjectExpression") {
197
+ const isStatic = property.value.properties.every((p) => p.value.type === "StringLiteral" && typeof p.value.value === "string");
198
+ if (!isStatic) {
199
+ output = false;
200
+ return;
201
+ }
202
+ const context = createContext({
203
+ resolvePackedMetaObjectValue
204
+ });
205
+ const start = property.value.start;
206
+ const end = property.value.end;
207
+ try {
208
+ value = JSON.stringify(runInContext(`resolvePackedMetaObjectValue(${code.slice(start, end)})`, context));
209
+ } catch {
210
+ output = false;
211
+ return;
212
+ }
213
+ }
214
+ if (valueKey === "charset")
215
+ output.push(` { ${key}: ${value} },`);
216
+ else
217
+ output.push(` { ${key}: '${keyValue}', ${valueKey}: ${value} },`);
218
+ });
219
+ if (output) {
220
+ if (meta.length)
221
+ output.push(" ]");
222
+ output.push("})");
223
+ s.overwrite(node.start, node.end, output.join("\n"));
224
+ if (importDecl) {
225
+ if (!importRewrites.has(importDecl))
226
+ importRewrites.set(importDecl, /* @__PURE__ */ new Set());
227
+ importRewrites.get(importDecl).add(originalName);
228
+ }
229
+ }
230
+ }
231
+ });
232
+ if (options.imports && importRewrites.size > 0) {
233
+ for (const [importNode, transformedNames] of importRewrites) {
234
+ const newSpecifiers = /* @__PURE__ */ new Set();
235
+ for (const spec of importNode.specifiers) {
236
+ if (spec.type !== "ImportSpecifier")
237
+ continue;
238
+ const importedName = spec.imported.name;
239
+ if (transformedNames.has(importedName)) {
240
+ newSpecifiers.add(importedName.includes("Server") ? "useServerHead" : "useHead");
241
+ if (valueReferenced.has(importedName))
242
+ newSpecifiers.add(importedName);
243
+ } else {
244
+ newSpecifiers.add(importedName);
245
+ }
246
+ }
247
+ s.overwrite(
248
+ importNode.specifiers[0].start,
249
+ importNode.specifiers[importNode.specifiers.length - 1].end,
250
+ [...newSpecifiers].join(", ")
251
+ );
252
+ }
253
+ }
254
+ if (s.hasChanged()) {
255
+ return {
256
+ code: s.toString(),
257
+ map: s.generateMap({ includeContent: true, source: id })
258
+ };
259
+ }
260
+ }
261
+ };
262
+ });
263
+
264
+ 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.DAcLivtz.mjs';
1
+ import { T as TreeshakeServerComposables, U as UseSeoMetaTransform } from './shared/addons.CqpZJkhe.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.mjs CHANGED
@@ -1,12 +1,11 @@
1
- import { T as TreeshakeServerComposables, U as UseSeoMetaTransform } from './shared/addons.DAcLivtz.mjs';
1
+ import { T as TreeshakeServerComposables, U as UseSeoMetaTransform } from './shared/addons.CqpZJkhe.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": "2.1.8",
4
+ "version": "3.0.0-beta.10",
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": "2.1.8"
55
+ "unhead": "3.0.0-beta.10"
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
- "mlly": "^1.8.0",
60
+ "oxc-parser": "^0.106.0",
61
+ "oxc-walker": "^0.7.0",
62
62
  "ufo": "^1.6.2",
63
- "unplugin": "^2.3.11",
64
- "unplugin-ast": "^0.15.4"
63
+ "unplugin": "^2.3.11"
65
64
  },
66
65
  "devDependencies": {
67
- "@babel/types": "^7.28.5",
68
- "@types/estree": "^1.0.8",
69
66
  "rollup": "^4.55.1",
70
- "vite": "7.2.2",
71
- "unhead": "2.1.8"
67
+ "vite": "^7.2.2",
68
+ "unhead": "3.0.0-beta.10"
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 };