@nuxtjs/ionic
Version:
Ionic integration for Nuxt
354 lines (344 loc) • 9.75 kB
JavaScript
import { existsSync, promises } from 'node:fs';
import { addComponent, useNuxt, addImportsSources, useLogger, defineNuxtModule, addTemplate, addPlugin } from '@nuxt/kit';
import { resolve as resolve$1, join } from 'pathe';
import { readPackageJSON } from 'pkg-types';
import { defineUnimportPreset } from 'unimport';
import { fileURLToPath } from 'node:url';
import { resolve } from 'node:path';
import * as _icons from 'ionicons/icons/index.mjs';
const runtimeDir = fileURLToPath(new URL("./runtime", import.meta.url));
const IonicHooks = [
"createAnimation",
"createGesture",
"getIonPageElement",
"getPlatforms",
"getTimeGivenProgression",
"iosTransitionAnimation",
"isPlatform",
"mdTransitionAnimation",
"menuController",
"modalController",
"popoverController",
"alertController",
"actionSheetController",
"loadingController",
"pickerController",
"toastController",
"onIonViewDidEnter",
"onIonViewDidLeave",
"onIonViewWillEnter",
"onIonViewWillLeave",
"openURL",
"useBackButton",
"useIonRouter",
"useKeyboard"
];
const IonicBuiltInComponents = [
"IonAccordion",
"IonAccordionGroup",
"IonActionSheet",
"IonAlert",
"IonApp",
"IonAvatar",
"IonBackButton",
"IonBackdrop",
"IonBadge",
"IonBreadcrumb",
"IonBreadcrumbs",
"IonButton",
"IonButtons",
"IonCard",
"IonCardContent",
"IonCardHeader",
"IonCardSubtitle",
"IonCardTitle",
"IonCheckbox",
"IonChip",
"IonCol",
"IonContent",
"IonDatetime",
"IonDatetimeButton",
"IonFab",
"IonFabButton",
"IonFabList",
"IonFooter",
"IonGrid",
"IonHeader",
"IonIcon",
"IonImg",
"IonInfiniteScroll",
"IonInfiniteScrollContent",
"IonInput",
"IonInputPasswordToggle",
"IonItem",
"IonItemDivider",
"IonItemGroup",
"IonItemOption",
"IonItemOptions",
"IonItemSliding",
"IonLabel",
"IonList",
"IonListHeader",
"IonLoading",
"IonMenu",
"IonMenuButton",
"IonMenuToggle",
"IonModal",
"IonNav",
"IonNavLink",
"IonNote",
"IonPage",
"IonPicker",
"IonPickerColumn",
"IonPickerColumnOption",
"IonPickerLegacy",
"IonPopover",
"IonProgressBar",
"IonRadio",
"IonRadioGroup",
"IonRange",
"IonRefresher",
"IonRefresherContent",
"IonReorder",
"IonReorderGroup",
"IonRippleEffect",
"IonRouterOutlet",
"IonRow",
"IonSearchbar",
"IonSegment",
"IonSegmentButton",
"IonSegmentContent",
"IonSegmentView",
"IonSelect",
"IonSelectModal",
"IonSelectOption",
"IonSkeletonText",
"IonSpinner",
"IonSplitPane",
"IonTab",
"IonTabs",
"IonTabBar",
"IonTabButton",
"IonText",
"IonTextarea",
"IonThumbnail",
"IonTitle",
"IonToast",
"IonToggle",
"IonToolbar"
];
const setupUtilityComponents = () => {
addComponent({
name: "IonAnimation",
filePath: resolve(runtimeDir, "components", "IonAnimation.vue")
});
};
const useCSSSetup = () => {
const nuxt = useNuxt();
const setupCore = () => {
nuxt.options.css.unshift("@ionic/vue/css/core.css");
};
const setupBasic = () => {
nuxt.options.css.unshift(
"@ionic/vue/css/normalize.css",
"@ionic/vue/css/structure.css",
"@ionic/vue/css/typography.css"
);
};
const setupUtilities = () => {
nuxt.options.css.unshift(
"@ionic/vue/css/padding.css",
"@ionic/vue/css/float-elements.css",
"@ionic/vue/css/text-alignment.css",
"@ionic/vue/css/text-transformation.css",
"@ionic/vue/css/flex-utils.css",
"@ionic/vue/css/display.css"
);
};
return { setupCore, setupBasic, setupUtilities };
};
const icons = _icons;
const iconsPreset = defineUnimportPreset({
from: "ionicons/icons",
imports: Object.keys(icons).map((name) => ({
name,
as: "ionicons" + name[0].toUpperCase() + name.slice(1)
}))
});
const setupIcons = () => {
const nuxt = useNuxt();
nuxt.options.build.transpile.push(/ionicons/);
addImportsSources(iconsPreset);
};
const setupMeta = () => {
const nuxt = useNuxt();
const metaDefaults = [
{ name: "color-scheme", content: "light dark" },
{ name: "format-detection", content: "telephone: no" },
{ name: "msapplication-tap-highlight", content: "no" }
];
nuxt.options.app.head.meta = nuxt.options.app.head.meta || [];
for (const meta of metaDefaults) {
if (!nuxt.options.app.head.meta.some((i) => i.name === meta.name)) {
nuxt.options.app.head.meta.unshift(meta);
}
}
const viewport = nuxt.options.app.head.meta.find((i) => i.name === "viewport");
if (viewport?.content === "width=device-width, initial-scale=1") {
viewport.content = "viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no";
}
};
const setupRouter = () => {
const nuxt = useNuxt();
const logger = useLogger();
const pagesDirs = nuxt.options._layers.map(
(layer) => resolve$1(layer.config?.srcDir || layer.cwd, layer.config?.dir?.pages || "pages")
);
if (nuxt.options.pages === false || nuxt.options.pages !== true && !pagesDirs.some((dir) => existsSync(dir))) {
logger.info("Disabling Ionic Router integration as pages dir does not exist.");
return;
}
const ROUTER_PLUGIN_RE = /nuxt(?:3|-nightly)?\/dist\/(?:app\/plugins|pages\/runtime)\/(?:plugins\/)?router/;
const PAGE_USAGE_PLUGIN_RE = /nuxt(?:3|-nightly)?\/dist\/(?:app\/plugins|pages\/runtime)\/(?:plugins\/)?check-if-page-unused/;
const ionicRouterPlugin = {
src: resolve$1(runtimeDir, "plugins/router"),
mode: "all"
};
nuxt.hook("modules:done", () => {
nuxt.hook("app:resolve", (app) => {
app.plugins = app.plugins.filter((p) => !PAGE_USAGE_PLUGIN_RE.test(p.src));
const routerPlugin = app.plugins.findIndex((p) => ROUTER_PLUGIN_RE.test(p.src));
if (routerPlugin !== -1) {
app.plugins.splice(routerPlugin, 1, ionicRouterPlugin);
} else {
app.plugins.unshift(ionicRouterPlugin);
}
});
});
nuxt.hook("app:resolve", (app) => {
if (!app.mainComponent || app.mainComponent.includes("@nuxt/ui-templates") || app.mainComponent.match(/nuxt3?\/dist/)) {
app.mainComponent = join(runtimeDir, "app.vue");
}
});
};
const module = defineNuxtModule({
meta: {
name: "@nuxtjs/ionic",
configKey: "ionic",
compatibility: {
nuxt: ">=3.0.0-rc.12"
}
},
defaults: {
integrations: {
meta: true,
router: true,
icons: true
},
css: {
core: true,
basic: true,
utilities: false
},
config: {}
},
async setup(options, nuxt) {
nuxt.options.build.transpile.push(runtimeDir);
nuxt.options.build.transpile.push(/@ionic/, /@stencil/);
addTemplate({
filename: "ionic/vue-config.mjs",
getContents: () => `export default ${JSON.stringify(options.config)}`
});
const ionicConfigPath = join(nuxt.options.rootDir, "ionic.config.json");
if (!existsSync(ionicConfigPath)) {
await promises.writeFile(
ionicConfigPath,
JSON.stringify(
{
name: await readPackageJSON(nuxt.options.rootDir).then(
({ name }) => name || "nuxt-ionic-project"
),
integrations: {},
type: "vue"
},
null,
2
)
);
}
addPlugin(resolve$1(runtimeDir, "plugins/ionic"));
setupUtilityComponents();
nuxt.options.typescript.hoist ||= [];
nuxt.options.typescript.hoist.push("@ionic/vue");
IonicBuiltInComponents.map(
(name) => addComponent({
name,
export: name,
filePath: "@ionic/vue"
})
);
addImportsSources([
defineUnimportPreset({
from: "@ionic/vue",
imports: [...IonicHooks]
}),
defineUnimportPreset({
from: resolve$1(runtimeDir, "composables/head"),
imports: ["useHead"],
priority: 2
})
]);
if (nuxt.options._generate) {
nuxt.hook("nitro:config", async (config) => {
config.prerender ||= {};
config.prerender.routes ||= [];
config.prerender.routes.push("/200.html");
config.output ||= {};
if (!config.output.publicDir) {
const distDir = resolve$1(nuxt.options.rootDir, "dist");
const stats = await promises.lstat(distDir).catch(() => null);
if (!stats || !stats.isSymbolicLink()) {
config.output.publicDir = distDir;
if (!existsSync(distDir)) {
await promises.mkdir(distDir, { recursive: true });
}
}
}
});
let publicFolder;
nuxt.hook("nitro:init", (nitro) => {
publicFolder = nitro.options.output.publicDir;
});
nuxt.hook("close", async () => {
const indexFile = join(publicFolder, "index.html");
const fallbackFile = join(publicFolder, "200.html");
if (!existsSync(indexFile) && existsSync(fallbackFile)) {
await promises.copyFile(fallbackFile, indexFile);
}
});
}
const { setupBasic, setupCore, setupUtilities } = useCSSSetup();
if (options.css?.core) {
await setupCore();
}
if (options.css?.basic) {
await setupBasic();
}
if (options.css?.utilities) {
await setupUtilities();
}
if (options.integrations?.icons) {
await setupIcons();
}
if (options.integrations?.meta) {
await setupMeta();
}
if (options.integrations?.pwa) {
console.log("PWA integration is has been removed from @nuxtjs/ionic. It is recommended to install and configure @vite-pwa/nuxt instead following the instructions in https://vite-pwa-org.netlify.app/frameworks/nuxt.html.");
}
if (options.integrations?.router) {
await setupRouter();
}
}
});
export { module as default };