Skip to content

Commit ed9936d

Browse files
authored
feat: enforce consistent trailing slash (#116)
2 parents e356d72 + 7cf5fda commit ed9936d

File tree

8 files changed

+695
-648
lines changed

8 files changed

+695
-648
lines changed

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"type": "git",
1414
"url": "git+https://github.com/ijkml/nuxt-umami.git"
1515
},
16-
"packageManager": "pnpm@9.9.0",
16+
"packageManager": "pnpm@9.10.0",
1717
"exports": {
1818
".": {
1919
"types": "./dist/types.d.ts",
@@ -42,13 +42,13 @@
4242
"request-ip": "^3.3.0"
4343
},
4444
"devDependencies": {
45-
"@nuxt/eslint-config": "^0.5.6",
46-
"@nuxt/module-builder": "^0.8.3",
45+
"@nuxt/eslint-config": "^0.5.7",
46+
"@nuxt/module-builder": "^0.8.4",
4747
"@nuxt/schema": "^3.13.1",
4848
"@types/node": "^22.5.4",
4949
"@types/request-ip": "^0.0.41",
5050
"bumpp": "^9.5.2",
51-
"eslint": "^9.9.1",
51+
"eslint": "^9.10.0",
5252
"nuxt": "^3.13.1",
5353
"typescript": "latest",
5454
"vue-tsc": "^2.1.6"

playground/app.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
const shareUrl = 'https://savory.vercel.app/share/j2f1spIBFqHJKsXv/Nuxt%20Umami';
2+
const { shareUrl } = useAppConfig();
33
44
function testView() {
55
umTrackView().then(({ ok }) => {

playground/nuxt.config.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,25 @@ export default defineNuxtConfig({
33
compatibilityDate: '2024-08-08',
44

55
modules: ['../src/module'],
6+
67
umami: {
78
enabled: true,
8-
host: 'https://savory.vercel.app/',
9-
id: '84cc2d28-8689-4df0-b575-2202e34a75aa',
9+
host: 'https://savory.ijkml.dev/',
10+
id: '94c6eb8c-646d-41ff-8b5b-a924ce2b4111',
1011
ignoreLocalhost: false,
1112
autoTrack: true,
1213
useDirective: true,
1314
customEndpoint: null,
1415
logErrors: true,
1516
domains: null,
1617
excludeQueryParams: false,
18+
trailingSlash: 'always',
1719
proxy: 'cloak',
1820
},
1921

22+
appConfig: {
23+
shareUrl: 'https://savory.ijkml.dev/share/xj2RHnDuAD8khsui/localhost',
24+
},
25+
2026
css: ['@/assets/reset.css'],
2127
});

pnpm-lock.yaml

Lines changed: 628 additions & 627 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/runtime/composables.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import type {
33
PreflightResult, EventData, FetchResult,
44
} from '../types';
55
import { earlyPromise, flattenObject, isValidString } from './utils';
6-
import { collect, config, logger } from '#build/umami.config.mjs';
6+
import { buildPathUrl, collect, config, logger } from '#build/umami.config.mjs';
77

88
let configChecks: PreflightResult | undefined;
99
let staticPayload: StaticPayload | undefined;
10+
let queryRef: string | undefined;
1011

1112
function runPreflight(): PreflightResult {
1213
if (typeof window === 'undefined')
@@ -54,18 +55,21 @@ function getStaticPayload(): StaticPayload {
5455
return staticPayload;
5556
}
5657

58+
function getQueryRef(): string {
59+
if (typeof queryRef === 'string')
60+
return queryRef;
61+
62+
const params = new URL(window.location.href).searchParams;
63+
queryRef = params.get('referrer') || params.get('ref') || '';
64+
65+
return queryRef;
66+
}
67+
5768
function getPayload(): ViewPayload {
5869
const { referrer, title } = window.document;
59-
const pageUrl = new URL(window.location.href);
60-
61-
const ref = referrer
62-
|| pageUrl.searchParams.get('referrer')
63-
|| pageUrl.searchParams.get('ref')
64-
|| '';
6570

66-
const url = config.excludeQueryParams
67-
? pageUrl.pathname
68-
: pageUrl.pathname + pageUrl.search;
71+
const ref = referrer || getQueryRef();
72+
const url = buildPathUrl();
6973

7074
return {
7175
...getStaticPayload(),

src/runtime/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ function normalizeConfig(options: ModuleOptions = {}): NormalizedModuleOptions {
3434
excludeQueryParams = false,
3535
logErrors = false,
3636
enabled = true,
37+
trailingSlash = 'any',
3738
} = options;
3839

3940
return {
@@ -57,6 +58,14 @@ function normalizeConfig(options: ModuleOptions = {}): NormalizedModuleOptions {
5758
return proxy.trim() as typeof proxy;
5859
return false;
5960
})(),
61+
trailingSlash: (function () {
62+
if (
63+
isValidString(trailingSlash)
64+
&& ['always', 'never'].includes(trailingSlash.trim())
65+
)
66+
return trailingSlash.trim() as typeof trailingSlash;
67+
return 'any';
68+
})(),
6069
ignoreLocalhost: ignoreLocalhost === true,
6170
autoTrack: autoTrack !== false,
6271
useDirective: useDirective === true,

src/template.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,18 @@ const fn_direct = `const { type, payload } = load;
4545

4646
const collectFn: Record<`fn_${ModuleMode}`, string> = { fn_direct, fn_faux, fn_proxy };
4747

48-
function generateTemplate({ options: { mode, config: { logErrors, ...config }, path } }: TemplateOptions) {
48+
function generateTemplate({
49+
options: { mode, path, config: { logErrors, ...config } },
50+
}: TemplateOptions) {
4951
return `// template-generated
5052
import { ofetch } from 'ofetch';
5153
import { ${logErrors ? 'logger' : 'fauxLogger'} as $logger } from "${path.logger}";
5254
5355
/**
5456
* @typedef {import("${path.types}").FetchFn} FetchFn
5557
*
58+
* @typedef {import("${path.types}").BuildPathUrlFn} BuildPathUrlFn
59+
*
5660
* @typedef {import("${path.types}").UmPublicConfig} UmPublicConfig
5761
*/
5862
@@ -84,6 +88,21 @@ function handleSuccess(response) {
8488
return { ok: true };
8589
}
8690
91+
/**
92+
* @type BuildPathUrlFn
93+
*/
94+
export function buildPathUrl() {
95+
const { pathname, search } = new URL(window.location.href);
96+
97+
const path = ${config.trailingSlash === 'always'
98+
? `pathname.endsWith('/') ? pathname : pathname + '/'`
99+
: config.trailingSlash === 'never'
100+
? `pathname.endsWith('/') ? pathname.slice(0, -1) : pathname`
101+
: 'pathname'};
102+
103+
return ${config.excludeQueryParams ? 'path' : 'path + search'};
104+
}
105+
87106
/**
88107
* @type FetchFn
89108
*

src/types.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
type ModuleOptions = Partial<{
2+
/**
3+
* Whether to enable the module
4+
*
5+
* @default true
6+
*/
7+
enabled: boolean;
28
/**
39
* Your umami endpoint. This is where you would
410
* normally load the script from.
@@ -72,11 +78,11 @@ type ModuleOptions = Partial<{
7278
*/
7379
proxy: false | 'direct' | 'cloak';
7480
/**
75-
* Whether to enable the module
81+
* Consistent trailing slash
7682
*
77-
* @default true
83+
* @default 'any'
7884
*/
79-
enabled: boolean;
85+
trailingSlash: 'any' | 'always' | 'never';
8086
}>;
8187

8288
interface NormalizedModuleOptions extends Required<ModuleOptions> {
@@ -135,6 +141,7 @@ interface ServerPayload {
135141

136142
type FetchResult = Promise<{ ok: boolean }>;
137143
type FetchFn = (load: ServerPayload) => FetchResult;
144+
type BuildPathUrlFn = () => string;
138145

139146
export type {
140147
PreflightResult,
@@ -147,6 +154,7 @@ export type {
147154
UmPrivateConfig,
148155
ModuleMode,
149156
FetchFn,
157+
BuildPathUrlFn,
150158
PayloadTypes,
151159
ViewPayload,
152160
EventPayload,

0 commit comments

Comments
 (0)