From e02c9419cb8c66f4e43ed598d2fc74d4b19384ec Mon Sep 17 00:00:00 2001 From: chip Date: Tue, 9 Feb 2021 10:22:04 -0800 Subject: [PATCH] refactor(tauri): support for building without environmental variables (#850) Co-authored-by: Lucas Nogueira --- .changes/config-refactor.md | 8 + .changes/config.json | 7 +- .changes/tauri-async.md | 4 - .changes/tauri-cli.md | 2 - Cargo.toml | 1 + api/.eslintrc.js | 60 ++-- api/.prettierrc.js | 5 + api/babel.config.js | 14 +- api/rollup.config.js | 114 +++---- api/src/bundle.ts | 26 +- api/src/cli.ts | 20 +- api/src/dialog.ts | 36 +-- api/src/event.ts | 22 +- api/src/http.ts | 94 +++--- api/src/index.ts | 4 +- api/src/notification.ts | 30 +- api/src/path.ts | 158 +++++----- api/src/process.ts | 14 +- api/src/tauri.ts | 48 +-- api/src/window.ts | 16 +- cli/tauri.js/CHANGELOG.md | 2 +- cli/tauri.js/src/api/cli.ts | 3 +- cli/tauri.js/src/types/config.schema.json | 4 + cli/tauri.js/src/types/config.ts | 4 + cli/tauri.js/src/types/config.validator.ts | 5 + cli/tauri.js/templates/src-tauri/src/main.rs | 6 +- .../jest/fixtures/app/src-tauri/Cargo.toml | 2 - .../jest/fixtures/app/src-tauri/src/cmd.rs | 4 +- .../jest/fixtures/app/src-tauri/src/main.rs | 24 +- tauri-api/Cargo.toml | 2 +- tauri-api/build.rs | 39 --- tauri-api/src/cli.rs | 5 +- tauri-api/src/lib.rs | 17 +- tauri-api/src/notification.rs | 17 +- tauri-macros/Cargo.toml | 23 ++ tauri-macros/src/error.rs | 75 +++++ tauri-macros/src/expand.rs | 120 ++++++++ tauri-macros/src/include_dir.rs | 164 +++++++++++ tauri-macros/src/lib.rs | 19 ++ tauri-utils/Cargo.toml | 7 +- tauri-utils/src/assets.rs | 108 +++++++ {tauri-api => tauri-utils}/src/config.rs | 278 ++++++++---------- tauri-utils/src/lib.rs | 4 + tauri/Cargo.toml | 4 +- tauri/build.rs | 122 +------- tauri/examples/api/src-tauri/Cargo.lock | 66 ++--- tauri/examples/api/src-tauri/src/main.rs | 14 +- .../communication/src-tauri/Cargo.lock | 66 ++--- .../communication/src-tauri/src/main.rs | 17 +- tauri/src/app.rs | 43 ++- tauri/src/app/runner.rs | 142 ++++----- tauri/src/assets.rs | 1 - tauri/src/cli.rs | 9 - tauri/src/endpoints.rs | 23 +- tauri/src/endpoints/asset.rs | 69 ++--- tauri/src/endpoints/notification.rs | 7 +- tauri/src/lib.rs | 8 +- tauri/src/server.rs | 58 ++-- tauri/test/fixture/dist/__tauri.js | 0 tauri/test/fixture/src-tauri/tauri.conf.json | 1 + 60 files changed, 1310 insertions(+), 955 deletions(-) create mode 100644 .changes/config-refactor.md create mode 100644 api/.prettierrc.js delete mode 100644 tauri-api/build.rs create mode 100644 tauri-macros/Cargo.toml create mode 100644 tauri-macros/src/error.rs create mode 100644 tauri-macros/src/expand.rs create mode 100644 tauri-macros/src/include_dir.rs create mode 100644 tauri-macros/src/lib.rs create mode 100644 tauri-utils/src/assets.rs rename {tauri-api => tauri-utils}/src/config.rs (71%) delete mode 100644 tauri/src/assets.rs delete mode 100644 tauri/src/cli.rs create mode 100644 tauri/test/fixture/dist/__tauri.js diff --git a/.changes/config-refactor.md b/.changes/config-refactor.md new file mode 100644 index 000000000..7092b97d4 --- /dev/null +++ b/.changes/config-refactor.md @@ -0,0 +1,8 @@ +--- +"tauri-utils": minor +"tauri-api": minor +"tauri": minor +--- + +The Tauri files are now read on the app space instead of the `tauri` create. +Also, the `AppBuilder` `build` function now returns a Result. diff --git a/.changes/config.json b/.changes/config.json index 9de6fa6d8..1ae0cbc6f 100644 --- a/.changes/config.json +++ b/.changes/config.json @@ -178,6 +178,11 @@ "manager": "rust", "dependencies": ["tauri-utils"] }, + "tauri-macros": { + "path": "./tauri-macros", + "manager": "rust", + "dependencies": ["tauri-utils"] + }, "tauri-updater": { "path": "./tauri-updater", "manager": "rust", @@ -186,7 +191,7 @@ "tauri": { "path": "./tauri", "manager": "rust", - "dependencies": ["api", "tauri-api", "tauri-updater"] + "dependencies": ["api", "tauri-api", "tauri-macros", "tauri-updater"] } } } diff --git a/.changes/tauri-async.md b/.changes/tauri-async.md index df4e60d77..4c1014fd4 100644 --- a/.changes/tauri-async.md +++ b/.changes/tauri-async.md @@ -3,7 +3,3 @@ --- Added `async` support to the Tauri Rust core on commit [#a169b67](https://github.com/tauri-apps/tauri/commit/a169b67ef0277b958bdac97e33c6e4c41b6844c3). -This is a breaking change: -- Change `.setup(|webview, source| {` to `.setup(|webview, _source| async move {`. -- Change `.invoke_handler(|_webview, arg| {` to `.invoke_handler(|_webview, arg| async move {`. -- Add `.await` after `tauri::execute_promise()` calls. diff --git a/.changes/tauri-cli.md b/.changes/tauri-cli.md index 69b6a206e..df8a1fb80 100644 --- a/.changes/tauri-cli.md +++ b/.changes/tauri-cli.md @@ -3,5 +3,3 @@ --- The Tauri Node.js CLI package is now `@tauri-apps/cli`. -To use the new CLI, delete the old `tauri` from your `package.json` and install the new package: -`$ yarn remove tauri && yarn add --dev @tauri-apps/cli` or `$ npm uninstall tauri && npm install --save-dev @tauri-apps/cli`. diff --git a/Cargo.toml b/Cargo.toml index 745a3c16a..9d8263191 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "tauri", "tauri-api", + "tauri-macros", "tauri-utils", ] exclude = [ diff --git a/api/.eslintrc.js b/api/.eslintrc.js index 43835de52..3f0a5dbf0 100644 --- a/api/.eslintrc.js +++ b/api/.eslintrc.js @@ -3,54 +3,54 @@ module.exports = { env: { node: true, - jest: true, + jest: true }, - parser: "@typescript-eslint/parser", + parser: '@typescript-eslint/parser', extends: [ - "standard-with-typescript", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "plugin:lodash-template/recommended", + 'standard-with-typescript', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:lodash-template/recommended', // TODO: make this work with typescript // 'plugin:node/recommended' - "prettier", - "prettier/@typescript-eslint", + 'prettier', + 'prettier/@typescript-eslint' ], - plugins: ["@typescript-eslint", "node", "security"], + plugins: ['@typescript-eslint', 'node', 'security'], parserOptions: { tsconfigRootDir: __dirname, - project: "./tsconfig.json", + project: './tsconfig.json' }, globals: { __statics: true, - process: true, + process: true }, // add your custom rules here rules: { // allow console.log during development only - "no-console": process.env.NODE_ENV === "production" ? "error" : "off", + 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', // allow debugger during development only - "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off", - "no-process-exit": "off", - "security/detect-non-literal-fs-filename": "warn", - "security/detect-unsafe-regex": "error", - "security/detect-buffer-noassert": "error", - "security/detect-child-process": "warn", - "security/detect-disable-mustache-escape": "error", - "security/detect-eval-with-expression": "error", - "security/detect-no-csrf-before-method-override": "error", - "security/detect-non-literal-regexp": "error", - "security/detect-non-literal-require": "warn", - "security/detect-object-injection": "warn", - "security/detect-possible-timing-attacks": "error", - "security/detect-pseudoRandomBytes": "error", - "space-before-function-paren": "off", - "@typescript-eslint/default-param-last": "off", - "@typescript-eslint/strict-boolean-expressions": 0, - }, -}; + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'no-process-exit': 'off', + 'security/detect-non-literal-fs-filename': 'warn', + 'security/detect-unsafe-regex': 'error', + 'security/detect-buffer-noassert': 'error', + 'security/detect-child-process': 'warn', + 'security/detect-disable-mustache-escape': 'error', + 'security/detect-eval-with-expression': 'error', + 'security/detect-no-csrf-before-method-override': 'error', + 'security/detect-non-literal-regexp': 'error', + 'security/detect-non-literal-require': 'warn', + 'security/detect-object-injection': 'warn', + 'security/detect-possible-timing-attacks': 'error', + 'security/detect-pseudoRandomBytes': 'error', + 'space-before-function-paren': 'off', + '@typescript-eslint/default-param-last': 'off', + '@typescript-eslint/strict-boolean-expressions': 0 + } +} diff --git a/api/.prettierrc.js b/api/.prettierrc.js new file mode 100644 index 000000000..2be2f9327 --- /dev/null +++ b/api/.prettierrc.js @@ -0,0 +1,5 @@ +module.exports = { + singleQuote: true, + semi: false, + trailingComma: 'none' +} diff --git a/api/babel.config.js b/api/babel.config.js index 46b5fd765..454f6976a 100644 --- a/api/babel.config.js +++ b/api/babel.config.js @@ -1,14 +1,14 @@ module.exports = { presets: [ [ - "@babel/preset-env", + '@babel/preset-env', { targets: { - node: "current", + node: 'current' }, - modules: "commonjs", - }, + modules: 'commonjs' + } ], - "@babel/preset-typescript", - ], -}; + '@babel/preset-typescript' + ] +} diff --git a/api/rollup.config.js b/api/rollup.config.js index 659ae206d..5f8cf90c6 100644 --- a/api/rollup.config.js +++ b/api/rollup.config.js @@ -1,107 +1,107 @@ // rollup.config.js -import { terser } from "rollup-plugin-terser"; -import resolve from "@rollup/plugin-node-resolve"; -import commonjs from "@rollup/plugin-commonjs"; -import sucrase from "@rollup/plugin-sucrase"; -import babel, { getBabelOutputPlugin } from "@rollup/plugin-babel"; -import typescript from "@rollup/plugin-typescript"; -import pkg from "./package.json"; +import { terser } from 'rollup-plugin-terser' +import resolve from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs' +import sucrase from '@rollup/plugin-sucrase' +import babel, { getBabelOutputPlugin } from '@rollup/plugin-babel' +import typescript from '@rollup/plugin-typescript' +import pkg from './package.json' export default [ { input: { - fs: "./src/fs.ts", - path: "./src/path.ts", - dialog: "./src/dialog.ts", - event: "./src/event.ts", - http: "./src/http.ts", - index: "./src/index.ts", - process: "./src/process.ts", - tauri: "./src/tauri.ts", - window: "./src/window.ts", - cli: "./src/cli.ts", - notification: "./src/notification.ts", + fs: './src/fs.ts', + path: './src/path.ts', + dialog: './src/dialog.ts', + event: './src/event.ts', + http: './src/http.ts', + index: './src/index.ts', + process: './src/process.ts', + tauri: './src/tauri.ts', + window: './src/window.ts', + cli: './src/cli.ts', + notification: './src/notification.ts' }, treeshake: true, perf: true, output: [ { - dir: "dist/", - entryFileNames: "[name].js", - format: "cjs", - exports: "named", - globals: {}, + dir: 'dist/', + entryFileNames: '[name].js', + format: 'cjs', + exports: 'named', + globals: {} }, { - dir: "dist/", - entryFileNames: "[name].mjs", - format: "esm", - exports: "named", - globals: {}, - }, + dir: 'dist/', + entryFileNames: '[name].mjs', + format: 'esm', + exports: 'named', + globals: {} + } ], plugins: [ commonjs({}), resolve({ // pass custom options to the resolve plugin customResolveOptions: { - moduleDirectory: "node_modules", - }, + moduleDirectory: 'node_modules' + } }), typescript({ - tsconfig: "./tsconfig.json", + tsconfig: './tsconfig.json' }), babel({ configFile: false, - presets: [["@babel/preset-env"], ["@babel/preset-typescript"]], + presets: [['@babel/preset-env'], ['@babel/preset-typescript']] }), - terser(), + terser() ], external: [ ...Object.keys(pkg.dependencies || {}), - ...Object.keys(pkg.peerDependencies || {}), + ...Object.keys(pkg.peerDependencies || {}) ], watch: { chokidar: true, - include: "src/**", - exclude: "node_modules/**", - }, + include: 'src/**', + exclude: 'node_modules/**' + } }, { input: { - bundle: "./src/bundle.ts", + bundle: './src/bundle.ts' }, output: [ { - name: "__TAURI__", - dir: "dist/", // if it needs to run in the browser - entryFileNames: "tauri.bundle.umd.js", - format: "umd", + name: '__TAURI__', + dir: 'dist/', // if it needs to run in the browser + entryFileNames: 'tauri.bundle.umd.js', + format: 'umd', plugins: [ getBabelOutputPlugin({ - presets: [["@babel/preset-env", { modules: "umd" }]], - allowAllFormats: true, + presets: [['@babel/preset-env', { modules: 'umd' }]], + allowAllFormats: true }), - terser(), + terser() ], - globals: {}, - }, + globals: {} + } ], plugins: [ sucrase({ - exclude: ["node_modules"], - transforms: ["typescript"], + exclude: ['node_modules'], + transforms: ['typescript'] }), resolve({ // pass custom options to the resolve plugin customResolveOptions: { - moduleDirectory: "node_modules", - }, - }), + moduleDirectory: 'node_modules' + } + }) ], external: [ ...Object.keys(pkg.dependencies || {}), - ...Object.keys(pkg.peerDependencies || {}), - ], - }, -]; + ...Object.keys(pkg.peerDependencies || {}) + ] + } +] diff --git a/api/src/bundle.ts b/api/src/bundle.ts index dca9cf416..d26d1e329 100644 --- a/api/src/bundle.ts +++ b/api/src/bundle.ts @@ -1,14 +1,14 @@ -import "regenerator-runtime/runtime"; -import * as cli from "./cli"; -import * as dialog from "./dialog"; -import * as event from "./event"; -import * as fs from "./fs"; -import * as path from "./path"; -import http from "./http"; -import * as process from "./process"; -import * as tauri from "./tauri"; -import * as window from "./window"; -import * as notification from "./notification"; +import 'regenerator-runtime/runtime' +import * as cli from './cli' +import * as dialog from './dialog' +import * as event from './event' +import * as fs from './fs' +import * as path from './path' +import http from './http' +import * as process from './process' +import * as tauri from './tauri' +import * as window from './window' +import * as notification from './notification' export { cli, @@ -20,5 +20,5 @@ export { process, tauri, window, - notification, -}; + notification +} diff --git a/api/src/cli.ts b/api/src/cli.ts index 40956e15c..af72bfae9 100644 --- a/api/src/cli.ts +++ b/api/src/cli.ts @@ -1,4 +1,4 @@ -import { promisified } from "./tauri"; +import { promisified } from './tauri' export interface ArgMatch { /** @@ -6,21 +6,21 @@ export interface ArgMatch { * boolean if flag * string[] or null if takes multiple values */ - value: string | boolean | string[] | null; + value: string | boolean | string[] | null /** * number of occurrences */ - occurrences: number; + occurrences: number } export interface SubcommandMatch { - name: string; - matches: CliMatches; + name: string + matches: CliMatches } export interface CliMatches { - args: { [name: string]: ArgMatch }; - subcommand: SubcommandMatch | null; + args: { [name: string]: ArgMatch } + subcommand: SubcommandMatch | null } /** @@ -28,8 +28,8 @@ export interface CliMatches { */ async function getMatches(): Promise { return await promisified({ - cmd: "cliMatches", - }); + cmd: 'cliMatches' + }) } -export { getMatches }; +export { getMatches } diff --git a/api/src/dialog.ts b/api/src/dialog.ts index 5a35873b0..3dee01cef 100644 --- a/api/src/dialog.ts +++ b/api/src/dialog.ts @@ -1,16 +1,16 @@ -import { promisified } from "./tauri"; +import { promisified } from './tauri' export interface OpenDialogOptions { - filter?: string; - defaultPath?: string; - multiple?: boolean; - directory?: boolean; + filter?: string + defaultPath?: string + multiple?: boolean + directory?: boolean } export type SaveDialogOptions = Pick< OpenDialogOptions, - "filter" | "defaultPath" ->; + 'filter' | 'defaultPath' +> /** * @name openDialog @@ -25,14 +25,14 @@ export type SaveDialogOptions = Pick< async function open( options: OpenDialogOptions = {} ): Promise { - if (typeof options === "object") { - Object.freeze(options); + if (typeof options === 'object') { + Object.freeze(options) } return await promisified({ - cmd: "openDialog", - options, - }); + cmd: 'openDialog', + options + }) } /** @@ -44,14 +44,14 @@ async function open( * @returns {Promise} Promise resolving to the select path */ async function save(options: SaveDialogOptions = {}): Promise { - if (typeof options === "object") { - Object.freeze(options); + if (typeof options === 'object') { + Object.freeze(options) } return await promisified({ - cmd: "saveDialog", - options, - }); + cmd: 'saveDialog', + options + }) } -export { open, save }; +export { open, save } diff --git a/api/src/event.ts b/api/src/event.ts index bb05c98e1..b395507d8 100644 --- a/api/src/event.ts +++ b/api/src/event.ts @@ -1,11 +1,11 @@ -import { invoke, transformCallback } from "./tauri"; +import { invoke, transformCallback } from './tauri' export interface Event { - type: string; - payload: T; + type: string + payload: T } -export type EventCallback = (event: Event) => void; +export type EventCallback = (event: Event) => void /** * listen to an event from the backend @@ -19,11 +19,11 @@ function listen( once = false ): void { invoke({ - cmd: "listen", + cmd: 'listen', event, handler: transformCallback(handler, once), - once, - }); + once + }) } /** @@ -34,10 +34,10 @@ function listen( */ function emit(event: string, payload?: string): void { invoke({ - cmd: "emit", + cmd: 'emit', event, - payload, - }); + payload + }) } -export { listen, emit }; +export { listen, emit } diff --git a/api/src/http.ts b/api/src/http.ts index bf1f461f5..92d681273 100644 --- a/api/src/http.ts +++ b/api/src/http.ts @@ -1,47 +1,47 @@ -import { promisified } from "./tauri"; +import { promisified } from './tauri' export enum ResponseType { JSON = 1, Text = 2, - Binary = 3, + Binary = 3 } export enum BodyType { Form = 1, File = 2, - Auto = 3, + Auto = 3 } -export type Body = object | string | BinaryType; +export type Body = object | string | BinaryType export type HttpVerb = - | "GET" - | "POST" - | "PUT" - | "DELETE" - | "PATCH" - | "HEAD" - | "OPTIONS" - | "CONNECT" - | "TRACE"; + | 'GET' + | 'POST' + | 'PUT' + | 'DELETE' + | 'PATCH' + | 'HEAD' + | 'OPTIONS' + | 'CONNECT' + | 'TRACE' export interface HttpOptions { - method: HttpVerb; - url: string; - headers?: Record; - params?: Record; - body?: Body; - followRedirects: boolean; - maxRedirections: boolean; - connectTimeout: number; - readTimeout: number; - timeout: number; - allowCompression: boolean; - responseType?: ResponseType; - bodyType: BodyType; + method: HttpVerb + url: string + headers?: Record + params?: Record + body?: Body + followRedirects: boolean + maxRedirections: boolean + connectTimeout: number + readTimeout: number + timeout: number + allowCompression: boolean + responseType?: ResponseType + bodyType: BodyType } -export type PartialOptions = Omit; +export type PartialOptions = Omit /** * makes a HTTP request @@ -52,9 +52,9 @@ export type PartialOptions = Omit; */ async function request(options: HttpOptions): Promise { return await promisified({ - cmd: "httpRequest", - options: options, - }); + cmd: 'httpRequest', + options: options + }) } /** @@ -67,10 +67,10 @@ async function request(options: HttpOptions): Promise { */ async function get(url: string, options: PartialOptions): Promise { return await request({ - method: "GET", + method: 'GET', url, - ...options, - }); + ...options + }) } /** @@ -88,11 +88,11 @@ async function post( options: PartialOptions ): Promise { return await request({ - method: "POST", + method: 'POST', url, body, - ...options, - }); + ...options + }) } /** @@ -110,11 +110,11 @@ async function put( options: PartialOptions ): Promise { return await request({ - method: "PUT", + method: 'PUT', url, body, - ...options, - }); + ...options + }) } /** @@ -127,10 +127,10 @@ async function put( */ async function patch(url: string, options: PartialOptions): Promise { return await request({ - method: "PATCH", + method: 'PATCH', url, - ...options, - }); + ...options + }) } /** @@ -146,10 +146,10 @@ async function deleteRequest( options: PartialOptions ): Promise { return await request({ - method: "DELETE", + method: 'DELETE', url, - ...options, - }); + ...options + }) } export default { @@ -160,5 +160,5 @@ export default { patch, delete: deleteRequest, ResponseType, - BodyType, -}; + BodyType +} diff --git a/api/src/index.ts b/api/src/index.ts index f6a3fc9af..b42a238b9 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,2 +1,2 @@ -import * as api from "./bundle"; -export default api; +import * as api from './bundle' +export default api diff --git a/api/src/notification.ts b/api/src/notification.ts index dd5b7b384..cb56caecc 100644 --- a/api/src/notification.ts +++ b/api/src/notification.ts @@ -1,35 +1,35 @@ -import { promisified } from "./tauri"; +import { promisified } from './tauri' export interface Options { - title: string; - body?: string; - icon?: string; + title: string + body?: string + icon?: string } -export type PartialOptions = Omit; -export type Permission = "granted" | "denied" | "default"; +export type PartialOptions = Omit +export type Permission = 'granted' | 'denied' | 'default' async function isPermissionGranted(): Promise { - if (window.Notification.permission !== "default") { - return await Promise.resolve(window.Notification.permission === "granted"); + if (window.Notification.permission !== 'default') { + return await Promise.resolve(window.Notification.permission === 'granted') } return await promisified({ - cmd: "isNotificationPermissionGranted", - }); + cmd: 'isNotificationPermissionGranted' + }) } async function requestPermission(): Promise { - return await window.Notification.requestPermission(); + return await window.Notification.requestPermission() } function sendNotification(options: Options | string): void { - if (typeof options === "string") { + if (typeof options === 'string') { // eslint-disable-next-line no-new - new window.Notification(options); + new window.Notification(options) } else { // eslint-disable-next-line no-new - new window.Notification(options.title, options); + new window.Notification(options.title, options) } } -export { sendNotification, requestPermission, isPermissionGranted }; +export { sendNotification, requestPermission, isPermissionGranted } diff --git a/api/src/path.ts b/api/src/path.ts index a1a414153..4ff39db91 100644 --- a/api/src/path.ts +++ b/api/src/path.ts @@ -1,5 +1,5 @@ -import { promisified } from "./tauri"; -import { BaseDirectory } from "./fs"; +import { promisified } from './tauri' +import { BaseDirectory } from './fs' /** * @name appDir @@ -8,10 +8,10 @@ import { BaseDirectory } from "./fs"; */ async function appDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.App, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.App + }) } /** @@ -21,10 +21,10 @@ async function appDir(): Promise { */ async function audioDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Audio, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Audio + }) } /** @@ -34,10 +34,10 @@ async function audioDir(): Promise { */ async function cacheDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Cache, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Cache + }) } /** @@ -47,10 +47,10 @@ async function cacheDir(): Promise { */ async function configDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Config, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Config + }) } /** @@ -60,10 +60,10 @@ async function configDir(): Promise { */ async function dataDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Data, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Data + }) } /** @@ -73,10 +73,10 @@ async function dataDir(): Promise { */ async function desktopDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Desktop, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Desktop + }) } /** @@ -86,10 +86,10 @@ async function desktopDir(): Promise { */ async function documentDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Document, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Document + }) } /** @@ -99,10 +99,10 @@ async function documentDir(): Promise { */ async function downloadDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Download, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Download + }) } /** @@ -112,10 +112,10 @@ async function downloadDir(): Promise { */ async function executableDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Executable, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Executable + }) } /** @@ -125,10 +125,10 @@ async function executableDir(): Promise { */ async function fontDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Font, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Font + }) } /** @@ -138,10 +138,10 @@ async function fontDir(): Promise { */ async function homeDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Home, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Home + }) } /** @@ -151,10 +151,10 @@ async function homeDir(): Promise { */ async function localDataDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.LocalData, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.LocalData + }) } /** @@ -164,10 +164,10 @@ async function localDataDir(): Promise { */ async function pictureDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Picture, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Picture + }) } /** @@ -177,10 +177,10 @@ async function pictureDir(): Promise { */ async function publicDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Public, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Public + }) } /** @@ -190,10 +190,10 @@ async function publicDir(): Promise { */ async function resourceDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Resource, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Resource + }) } /** @@ -203,10 +203,10 @@ async function resourceDir(): Promise { */ async function runtimeDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Runtime, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Runtime + }) } /** @@ -216,10 +216,10 @@ async function runtimeDir(): Promise { */ async function templateDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Template, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Template + }) } /** @@ -229,10 +229,10 @@ async function templateDir(): Promise { */ async function videoDir(): Promise { return await promisified({ - cmd: "resolvePath", - path: "", - directory: BaseDirectory.Video, - }); + cmd: 'resolvePath', + path: '', + directory: BaseDirectory.Video + }) } /** @@ -245,10 +245,10 @@ async function resolvePath( directory: BaseDirectory ): Promise { return await promisified({ - cmd: "resolvePath", + cmd: 'resolvePath', path, - directory, - }); + directory + }) } export { @@ -270,5 +270,5 @@ export { runtimeDir, templateDir, videoDir, - resolvePath, -}; + resolvePath +} diff --git a/api/src/process.ts b/api/src/process.ts index a8250881b..14b7f69f6 100644 --- a/api/src/process.ts +++ b/api/src/process.ts @@ -1,4 +1,4 @@ -import { promisified } from "./tauri"; +import { promisified } from './tauri' /** * spawns a process @@ -11,15 +11,15 @@ async function execute( command: string, args?: string | string[] ): Promise { - if (typeof args === "object") { - Object.freeze(args); + if (typeof args === 'object') { + Object.freeze(args) } return await promisified({ - cmd: "execute", + cmd: 'execute', command, - args: typeof args === "string" ? [args] : args, - }); + args: typeof args === 'string' ? [args] : args + }) } -export { execute }; +export { execute } diff --git a/api/src/tauri.ts b/api/src/tauri.ts index 1fe131143..ed5ac6e97 100644 --- a/api/src/tauri.ts +++ b/api/src/tauri.ts @@ -1,31 +1,31 @@ declare global { // eslint-disable-next-line @typescript-eslint/no-unused-vars interface Window { - __TAURI_INVOKE_HANDLER__: (command: string) => void; + __TAURI_INVOKE_HANDLER__: (command: string) => void } } function s4(): string { return Math.floor((1 + Math.random()) * 0x10000) .toString(16) - .substring(1); + .substring(1) } function uid(): string { return ( s4() + s4() + - "-" + + '-' + s4() + - "-" + + '-' + s4() + - "-" + + '-' + s4() + - "-" + + '-' + s4() + s4() + s4() - ); + ) } /** @@ -34,28 +34,28 @@ function uid(): string { * @param args */ function invoke(args: any): void { - window.__TAURI_INVOKE_HANDLER__(JSON.stringify(args)); + window.__TAURI_INVOKE_HANDLER__(JSON.stringify(args)) } function transformCallback( callback?: (response: any) => void, once = false ): string { - const identifier = uid(); + const identifier = uid() Object.defineProperty(window, identifier, { value: (result: any) => { if (once) { - Reflect.deleteProperty(window, identifier); + Reflect.deleteProperty(window, identifier) } - return callback?.(result); + return callback?.(result) }, writable: false, - configurable: true, - }); + configurable: true + }) - return identifier; + return identifier } /** @@ -68,20 +68,20 @@ function transformCallback( async function promisified(args: any): Promise { return await new Promise((resolve, reject) => { const callback = transformCallback((e) => { - resolve(e); - Reflect.deleteProperty(window, error); - }, true); + resolve(e) + Reflect.deleteProperty(window, error) + }, true) const error = transformCallback((e) => { - reject(e); - Reflect.deleteProperty(window, callback); - }, true); + reject(e) + Reflect.deleteProperty(window, callback) + }, true) invoke({ callback, error, - ...args, - }); - }); + ...args + }) + }) } -export { invoke, transformCallback, promisified }; +export { invoke, transformCallback, promisified } diff --git a/api/src/window.ts b/api/src/window.ts index 9f4dfa69c..9428bf07a 100644 --- a/api/src/window.ts +++ b/api/src/window.ts @@ -1,4 +1,4 @@ -import { invoke } from "./tauri"; +import { invoke } from './tauri' /** * sets the window title @@ -7,9 +7,9 @@ import { invoke } from "./tauri"; */ function setTitle(title: string): void { invoke({ - cmd: "setTitle", - title, - }); + cmd: 'setTitle', + title + }) } /** @@ -19,9 +19,9 @@ function setTitle(title: string): void { */ function open(url: string): void { invoke({ - cmd: "open", - uri: url, - }); + cmd: 'open', + uri: url + }) } -export { setTitle, open }; +export { setTitle, open } diff --git a/cli/tauri.js/CHANGELOG.md b/cli/tauri.js/CHANGELOG.md index 54b294652..9cecac015 100644 --- a/cli/tauri.js/CHANGELOG.md +++ b/cli/tauri.js/CHANGELOG.md @@ -130,4 +130,4 @@ - Create UMD, ESM and CJS artifacts for the JavaScript API entry point from TS source using rollup. - Renaming window.tauri to window.\_\_TAURI\_\_, closing #435. The **Tauri** object now follows the TypeScript API structure (e.g. window.tauri.readTextFile is now window.\_\_TAURI\_\_.fs.readTextFile). - If you want to keep the `window.tauri` object for a while, you can add a [mapping object](https://gist.github.com/lucasfernog/8f7b29cadd91d92ee2cf816a20c2ef01) to your code. + If you want to keep the `window.tauri` object for a while, you can add a [mapping object](https://gist.github.com/lucasfernog/8f7b29cadd91d92ee2cf816a20c2ef01) to your code. \ No newline at end of file diff --git a/cli/tauri.js/src/api/cli.ts b/cli/tauri.js/src/api/cli.ts index d7b9a335e..ebc36e1ed 100644 --- a/cli/tauri.js/src/api/cli.ts +++ b/cli/tauri.js/src/api/cli.ts @@ -9,8 +9,7 @@ function runCliCommand( args: Args ): { pid: number; promise: Promise } { const argsArray = [] - for (const argName in args) { - const argValue = args[argName] + for (const [argName, argValue] of Object.entries(args)) { if (argValue === false) { continue } diff --git a/cli/tauri.js/src/types/config.schema.json b/cli/tauri.js/src/types/config.schema.json index 98718392b..656583d97 100644 --- a/cli/tauri.js/src/types/config.schema.json +++ b/cli/tauri.js/src/types/config.schema.json @@ -406,6 +406,10 @@ } ], "description": "the embedded server port number or the 'random' string to generate one at runtime" + }, + "publicPath": { + "description": "The base path for all the assets within your application", + "type": "string" } }, "type": "object" diff --git a/cli/tauri.js/src/types/config.ts b/cli/tauri.js/src/types/config.ts index 477bd9858..ce081f5c3 100644 --- a/cli/tauri.js/src/types/config.ts +++ b/cli/tauri.js/src/types/config.ts @@ -229,6 +229,10 @@ export interface TauriConfig { * the embedded server port number or the 'random' string to generate one at runtime */ port?: number | 'random' | undefined + /** + * The base path for all the assets within your application + */ + publicPath?: string } /** * tauri bundler configuration diff --git a/cli/tauri.js/src/types/config.validator.ts b/cli/tauri.js/src/types/config.validator.ts index a9a51f70c..3a01d4402 100644 --- a/cli/tauri.js/src/types/config.validator.ts +++ b/cli/tauri.js/src/types/config.validator.ts @@ -458,6 +458,11 @@ export const TauriConfigSchema = { ], description: "the embedded server port number or the 'random' string to generate one at runtime" + }, + publicPath: { + description: + 'The base path for all the assets within your application.', + type: 'string' } }, type: 'object' diff --git a/cli/tauri.js/templates/src-tauri/src/main.rs b/cli/tauri.js/templates/src-tauri/src/main.rs index b1b0ce7aa..f0d1db8ad 100755 --- a/cli/tauri.js/templates/src-tauri/src/main.rs +++ b/cli/tauri.js/templates/src-tauri/src/main.rs @@ -5,8 +5,11 @@ mod cmd; +#[derive(tauri::FromTauriContext)] +struct Context; + fn main() { - tauri::AppBuilder::::new() + tauri::AppBuilder::::new() .invoke_handler(|_webview, arg| async move { use cmd::Cmd::*; match serde_json::from_str(&arg) { @@ -24,5 +27,6 @@ fn main() { } }) .build() + .unwrap() .run(); } diff --git a/cli/tauri.js/test/jest/fixtures/app/src-tauri/Cargo.toml b/cli/tauri.js/test/jest/fixtures/app/src-tauri/Cargo.toml index 4083590d9..a95c0119d 100644 --- a/cli/tauri.js/test/jest/fixtures/app/src-tauri/Cargo.toml +++ b/cli/tauri.js/test/jest/fixtures/app/src-tauri/Cargo.toml @@ -24,8 +24,6 @@ icon = [ serde_json = "1.0.61" serde = "1.0" serde_derive = "1.0" -phf = "0.8.0" -includedir = "0.6.0" tauri = { path = "../../../../../../../tauri", features =["all-api"]} [features] diff --git a/cli/tauri.js/test/jest/fixtures/app/src-tauri/src/cmd.rs b/cli/tauri.js/test/jest/fixtures/app/src-tauri/src/cmd.rs index eda189ca8..c61062070 100644 --- a/cli/tauri.js/test/jest/fixtures/app/src-tauri/src/cmd.rs +++ b/cli/tauri.js/test/jest/fixtures/app/src-tauri/src/cmd.rs @@ -1,8 +1,8 @@ -#[derive(Deserialize)] +#[derive(serde::Deserialize)] #[serde(tag = "cmd", rename_all = "camelCase")] pub enum Cmd { // your custom commands // multiple arguments are allowed // note that rename_all = "camelCase": you need to use "myCustomCommand" on JS - Exit { }, + Exit {}, } diff --git a/cli/tauri.js/test/jest/fixtures/app/src-tauri/src/main.rs b/cli/tauri.js/test/jest/fixtures/app/src-tauri/src/main.rs index ee05d1d72..3f7bf429b 100644 --- a/cli/tauri.js/test/jest/fixtures/app/src-tauri/src/main.rs +++ b/cli/tauri.js/test/jest/fixtures/app/src-tauri/src/main.rs @@ -1,26 +1,25 @@ mod cmd; -#[macro_use] -extern crate serde_derive; -extern crate serde_json; +use tauri::ApplicationDispatcherExt; + +#[derive(tauri::FromTauriContext)] +struct Context; fn main() { - tauri::AppBuilder::new() - .setup(|webview, _| async move { - let mut webview_ = webview.as_mut(); + tauri::AppBuilder::::new() + .setup(|dispatcher, _| async move { + let mut dispatcher_ = dispatcher.clone(); tauri::event::listen(String::from("hello"), move |_| { tauri::event::emit( - &mut webview_, + &mut dispatcher_, String::from("reply"), Some("{ msg: 'TEST' }".to_string()), ) .unwrap(); }); - let _ = webview.dispatch(|w| { - w.eval("window.onTauriInit && window.onTauriInit()"); - }); + dispatcher.eval("window.onTauriInit && window.onTauriInit()"); }) - .invoke_handler(|webview, arg| async move { + .invoke_handler(|dispatcher, arg| async move { use cmd::Cmd::*; match serde_json::from_str(&arg) { Err(e) => Err(e.to_string()), @@ -28,7 +27,7 @@ fn main() { match command { // definitions for your custom commands from Cmd here Exit {} => { - webview.terminate(); + // TODO dispatcher.terminate(); } } Ok(()) @@ -36,5 +35,6 @@ fn main() { } }) .build() + .unwrap() .run(); } diff --git a/tauri-api/Cargo.toml b/tauri-api/Cargo.toml index b19139826..882216dd2 100644 --- a/tauri-api/Cargo.toml +++ b/tauri-api/Cargo.toml @@ -24,8 +24,8 @@ semver = "0.11" tempfile = "3" either = "1.6.1" tar = "0.4" -flate2 = "1" anyhow = "1.0.38" +flate2 = "1.0" thiserror = "1.0.23" rand = "0.8" nfd = "0.0.4" diff --git a/tauri-api/build.rs b/tauri-api/build.rs deleted file mode 100644 index 60d48fb5c..000000000 --- a/tauri-api/build.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::{ - env, - error::Error, - fs::{read_to_string, File}, - io::{BufWriter, Write}, - path::Path, -}; - -pub fn main() -> Result<(), Box> { - let out_dir = env::var("OUT_DIR")?; - - let dest_config_path = Path::new(&out_dir).join("tauri.conf.json"); - let mut config_file = BufWriter::new(File::create(&dest_config_path)?); - - match env::var_os("TAURI_CONFIG") { - Some(tauri_config) => { - println!("cargo:rerun-if-env-changed=TAURI_CONFIG"); - let tauri_config_string = tauri_config.into_string().unwrap(); - write!(config_file, "{}", tauri_config_string)?; - } - None => match env::var_os("TAURI_DIR") { - Some(tauri_dir) => { - let tauri_dir_string = tauri_dir.into_string().unwrap(); - - println!("cargo:rerun-if-changed={}", tauri_dir_string); - - let original_config_path = Path::new(&tauri_dir_string).join("tauri.conf.json"); - let original_config = read_to_string(original_config_path)?; - - write!(config_file, "{}", original_config)?; - } - None => { - write!(config_file, "{{}}")?; - println!("Build error: Couldn't find ENV: TAURI_CONFIG or TAURI_DIR"); - } - }, - } - Ok(()) -} diff --git a/tauri-api/src/cli.rs b/tauri-api/src/cli.rs index 98f395858..4b1af5c0a 100644 --- a/tauri-api/src/cli.rs +++ b/tauri-api/src/cli.rs @@ -1,4 +1,4 @@ -use crate::config::{get as get_config, CliArg, CliConfig}; +use crate::config::{CliArg, CliConfig, Config}; use clap::{App, Arg, ArgMatches}; use serde::Serialize; @@ -53,8 +53,7 @@ impl Matches { } /// Gets the arg matches of the CLI definition. -pub fn get_matches() -> crate::Result { - let config = get_config()?; +pub fn get_matches(config: &Config) -> crate::Result { let cli = config .tauri .cli diff --git a/tauri-api/src/lib.rs b/tauri-api/src/lib.rs index 55a30ac9b..efffd0a01 100644 --- a/tauri-api/src/lib.rs +++ b/tauri-api/src/lib.rs @@ -3,8 +3,6 @@ /// The Command API module allows you to manage child processes. pub mod command; -/// The Config module allows you to read the configuration from `tauri.conf.json`. -pub mod config; /// The Dialog API module allows you to show messages and prompt for file paths. pub mod dialog; /// The Dir module is a helper for file system directory management. @@ -22,6 +20,9 @@ pub mod tcp; /// The semver API. pub mod version; +/// The Tauri config definition. +pub use tauri_utils::config; + /// The CLI args interface. #[cfg(feature = "cli")] pub mod cli; @@ -61,3 +62,15 @@ pub enum Error { #[error("Network Error:{0}")] Network(attohttpc::StatusCode), } + +// Not public API +#[doc(hidden)] +pub mod private { + pub trait AsTauriContext { + fn config_path() -> &'static std::path::Path; + fn raw_config() -> &'static str; + fn assets() -> &'static crate::assets::Assets; + fn raw_index() -> &'static str; + fn raw_tauri_script() -> &'static str; + } +} diff --git a/tauri-api/src/notification.rs b/tauri-api/src/notification.rs index 32edf0806..5e97c3142 100644 --- a/tauri-api/src/notification.rs +++ b/tauri-api/src/notification.rs @@ -1,6 +1,4 @@ #[cfg(windows)] -use crate::config::get as get_config; -#[cfg(windows)] use std::path::MAIN_SEPARATOR; /// The Notification definition. @@ -10,7 +8,7 @@ use std::path::MAIN_SEPARATOR; /// ``` /// use tauri_api::notification::Notification; /// // shows a notification with the given title and body -/// Notification::new() +/// Notification::new("studio.tauri.example") /// .title("New message") /// .body("You've got a new message.") /// .show(); @@ -24,12 +22,17 @@ pub struct Notification { title: Option, /// The notification icon. icon: Option, + /// The notification identifier + identifier: String, } impl Notification { /// Initializes a instance of a Notification. - pub fn new() -> Self { - Default::default() + pub fn new(identifier: impl Into) -> Self { + Self { + identifier: identifier.into(), + ..Default::default() + } } /// Sets the notification body. @@ -71,9 +74,7 @@ impl Notification { if !(curr_dir.ends_with(format!("{S}target{S}debug", S = MAIN_SEPARATOR).as_str()) || curr_dir.ends_with(format!("{S}target{S}release", S = MAIN_SEPARATOR).as_str())) { - let config = get_config()?; - let identifier = config.tauri.bundle.identifier.clone(); - notification.app_id(&identifier); + notification.app_id(&self.identifier); } } notification diff --git a/tauri-macros/Cargo.toml b/tauri-macros/Cargo.toml new file mode 100644 index 000000000..5dde03d21 --- /dev/null +++ b/tauri-macros/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "tauri-macros" +version = "0.1.0" +authors = [ "Tauri Community" ] +categories = [ "gui", "os", "filesystem", "web-programming" ] +license = "MIT" +homepage = "https://tauri.studio" +repository = "https://github.com/tauri-apps/tauri" +description = "Macros for the tauri crate." +edition = "2018" + +[lib] +proc-macro = true + +[dependencies] +flate2 = "1" +proc-macro2 = "1" +quote = "1" +serde = {version = "1", features = ["derive"]} +serde_json = "1" +syn = { version = "1", features = ["extra-traits"] } +tauri-utils = { version = "0.5", path = "../tauri-utils" } +walkdir = "2" diff --git a/tauri-macros/src/error.rs b/tauri-macros/src/error.rs new file mode 100644 index 000000000..0577a3a38 --- /dev/null +++ b/tauri-macros/src/error.rs @@ -0,0 +1,75 @@ +use proc_macro2::{Ident, TokenStream}; +use quote::quote; +use std::io::Error as IoError; +use std::path::PathBuf; +use Error::*; + +pub(crate) enum Error { + EnvOutDir, + EnvCargoManifestDir, + IncludeDirPrefix, + IncludeDirCacheDir, + IncludeDirEmptyFilename, + ConfigDir, + Serde(PathBuf, serde_json::Error), + Io(PathBuf, IoError), +} + +impl Error { + /// Output a compiler error to the ast being transformed + pub(crate) fn into_compile_error(self, struct_: &Ident) -> TokenStream { + let error: String = match self { + EnvOutDir => "Unable to find OUT_DIR environmental variable from tauri-macros".into(), + EnvCargoManifestDir => { + "Unable to find CARGO_MANIFEST_DIR environmental variable from tauri-macros".into() + } + IncludeDirPrefix => "Invalid directory prefix encountered while including assets".into(), + IncludeDirCacheDir => { + "Unable to find cache directory to compress assets into during tauri-macros".into() + } + IncludeDirEmptyFilename => "Asset included during tauri-macros has empty filename".into(), + ConfigDir => { + "Unable to get the directory the config file was found in during tauri-macros".into() + } + Serde(path, error) => format!( + "{:?} encountered for {} during tauri-macros", + error, + path.display() + ), + Io(path, error) => format!( + "{:?} encountered for {} during tauri-macros", + error.kind(), + path.display() + ), + }; + + quote! { + compile_error!(#error); + + impl ::tauri::api::private::AsTauriContext for #struct_ { + fn config_path() -> &'static std::path::Path { + unimplemented!() + } + + /// Make the file a dependency for the compiler + fn raw_config() -> &'static str { + unimplemented!() + } + + fn assets() -> &'static ::tauri::api::assets::Assets { + unimplemented!() + } + + /// Make the index.tauri.html a dependency for the compiler + fn raw_index() -> &'static str { + unimplemented!() + } + + /// Make the __tauri.js a dependency for the compiler + fn raw_tauri_script() -> &'static str { + unimplemented!() + } + } + } + } +} diff --git a/tauri-macros/src/expand.rs b/tauri-macros/src/expand.rs new file mode 100644 index 000000000..cb0ebd767 --- /dev/null +++ b/tauri-macros/src/expand.rs @@ -0,0 +1,120 @@ +use crate::error::Error; +use crate::include_dir::IncludeDir; +use crate::DEFAULT_CONFIG_FILE; +use proc_macro2::TokenStream; +use quote::quote; +use std::collections::HashSet; +use std::env::var; +use std::fs::File; +use std::io::BufReader; +use std::path::{Path, PathBuf}; +use syn::{DeriveInput, Lit::Str, Meta::NameValue, MetaNameValue}; +use tauri_utils::{assets::AssetCompression, config::Config}; + +pub(crate) fn load_context(input: DeriveInput) -> Result { + let name = input.ident; + + // quick way of parsing #[config_path = "path_goes_here"] + let mut config_file_path = DEFAULT_CONFIG_FILE.into(); + let config_path_attr = input + .attrs + .iter() + .find(|attr| attr.path.is_ident("config_path")); + if let Some(attr) = config_path_attr { + if let Ok(meta) = attr.parse_meta() { + if let NameValue(MetaNameValue { lit: Str(path), .. }) = meta { + config_file_path = path.value() + } + } + } + + // grab the manifest of the application the macro is in + let manifest = var("CARGO_MANIFEST_DIR") + .map(PathBuf::from) + .map_err(|_| Error::EnvCargoManifestDir)?; + + let full_config_path = Path::new(&manifest).join(config_file_path); + let config = get_config(&full_config_path)?; + let config_dir = full_config_path.parent().ok_or(Error::ConfigDir)?; + let dist_dir = config_dir.join(config.build.dist_dir); + + // generate the assets into a perfect hash function + let assets = generate_asset_map(&dist_dir)?; + + // should be possible to do the index.tauri.hmtl manipulations during this macro too in the future + let tauri_index_html_path = dist_dir.join("index.tauri.html"); + let tauri_script_path = dist_dir.join("__tauri.js"); + + // format paths into a string to use them in quote! + let tauri_config_path = full_config_path.display().to_string(); + let tauri_index_html_path = tauri_index_html_path.display().to_string(); + let tauri_script_path = tauri_script_path.display().to_string(); + + Ok(quote! { + impl ::tauri::api::private::AsTauriContext for #name { + fn config_path() -> &'static std::path::Path { + std::path::Path::new(#tauri_config_path) + } + + /// Make the file a dependency for the compiler + fn raw_config() -> &'static str { + include_str!(#tauri_config_path) + } + + fn assets() -> &'static ::tauri::api::assets::Assets { + use ::tauri::api::assets::{Assets, AssetCompression, phf, phf::phf_map}; + static ASSETS: Assets = Assets::new(#assets); + &ASSETS + } + + /// Make the index.tauri.html a dependency for the compiler + fn raw_index() -> &'static str { + include_str!(#tauri_index_html_path) + } + + /// Make the __tauri.js a dependency for the compiler + fn raw_tauri_script() -> &'static str { + include_str!(#tauri_script_path) + } + } + }) +} + +fn get_config(path: &Path) -> Result { + match var("TAURI_CONFIG") { + Ok(custom_config) => { + serde_json::from_str(&custom_config).map_err(|e| Error::Serde("TAURI_CONFIG".into(), e)) + } + Err(_) => { + let file = File::open(&path).map_err(|e| Error::Io(path.into(), e))?; + let reader = BufReader::new(file); + serde_json::from_reader(reader).map_err(|e| Error::Serde(path.into(), e)) + } + } +} + +/// Generates a perfect hash function from `phf` of the assets in dist directory +/// +/// The `TokenStream` produced by this function expects to have `phf` and +/// `phf_map` paths available. Make sure to `use` these so the macro has access to them. +/// It also expects `AssetCompression` to be in path. +fn generate_asset_map(dist: &Path) -> Result { + let mut inline_assets = HashSet::new(); + if let Ok(assets) = std::env::var("TAURI_INLINED_ASSETS") { + assets + .split('|') + .filter(|&s| !s.trim().is_empty()) + .map(PathBuf::from) + .for_each(|path| { + inline_assets.insert(path); + }) + } + + // the index.html is parsed so we always ignore it + inline_assets.insert("/index.html".into()); + + IncludeDir::new(&dist) + .dir(&dist, AssetCompression::Gzip)? + .set_filter(inline_assets)? + .build() +} diff --git a/tauri-macros/src/include_dir.rs b/tauri-macros/src/include_dir.rs new file mode 100644 index 000000000..762977575 --- /dev/null +++ b/tauri-macros/src/include_dir.rs @@ -0,0 +1,164 @@ +use crate::error::Error; +use flate2::bufread::GzEncoder; +use proc_macro2::TokenStream; +use quote::quote; +use quote::TokenStreamExt; +use std::collections::{HashMap, HashSet}; +use std::env::var; +use std::fs::{canonicalize, create_dir_all, File}; +use std::io::{BufReader, BufWriter}; +use std::path::{Path, PathBuf}; +use tauri_utils::assets::{AssetCompression, Assets}; +use walkdir::WalkDir; + +enum Asset { + Identity(PathBuf), + Compressed(PathBuf, PathBuf), +} + +pub(crate) struct IncludeDir { + assets: HashMap, + filter: HashSet, + prefix: PathBuf, +} + +impl IncludeDir { + pub fn new(prefix: impl Into) -> Self { + Self { + assets: HashMap::new(), + filter: HashSet::new(), + prefix: prefix.into(), + } + } + + /// get a relative path based on the `IncludeDir`'s prefix + fn relative<'p>(&self, path: &'p Path) -> Result<&'p Path, Error> { + path + .strip_prefix(&self.prefix) + .map_err(|_| Error::IncludeDirPrefix) + } + + pub fn file(mut self, path: impl Into, comp: AssetCompression) -> Result { + let path = path.into(); + let relative = self.relative(&path)?; + let key = Assets::format_key(&relative); + + let asset = match comp { + AssetCompression::None => Asset::Identity(path), + AssetCompression::Gzip => { + let cache = var("OUT_DIR") + .map_err(|_| Error::EnvOutDir) + .and_then(|out| canonicalize(&out).map_err(|e| Error::Io(PathBuf::from(out), e))) + .map(|out| out.join(".tauri-assets"))?; + + // normalize path separators + let relative: PathBuf = relative.components().collect(); + let cache = cache.join(relative); + + // append .br extension to filename + let filename = cache.file_name().ok_or(Error::IncludeDirEmptyFilename)?; + let filename = format!("{}.br", filename.to_string_lossy()); + + // remove filename from cache + let cache = cache.parent().ok_or(Error::IncludeDirCacheDir)?; + + // append the filename to the canonical path + let cache_file = cache.join(filename); + + // make sure the cache directory is created + create_dir_all(&cache).map_err(|e| Error::Io(cache.to_path_buf(), e))?; + + // open original asset path + let reader = File::open(&path).map_err(|e| Error::Io(path.to_path_buf(), e))?; + let reader = BufReader::new(reader); + let mut reader = GzEncoder::new(reader, flate2::Compression::best()); + + // open cache path + let writer = + File::create(&cache_file).map_err(|e| Error::Io(cache_file.to_path_buf(), e))?; + let mut writer = BufWriter::new(writer); + + std::io::copy(&mut reader, &mut writer).map_err(|e| Error::Io(path.to_path_buf(), e))?; + + Asset::Compressed(path, cache_file) + } + }; + + self.assets.insert(key, asset); + Ok(self) + } + + pub fn dir(mut self, path: impl AsRef, comp: AssetCompression) -> Result { + let path = path.as_ref(); + let walker = WalkDir::new(&path).follow_links(true); + for entry in walker.into_iter() { + match entry { + Ok(e) => { + if !e.file_type().is_dir() { + self = self.file(e.path(), comp)? + } + } + Err(e) => return Err(Error::Io(path.into(), e.into())), + } + } + Ok(self) + } + + /// Set list of files to not embed. Paths should be relative to the dist dir + pub fn set_filter(mut self, filter: HashSet) -> Result { + self.filter = filter + .iter() + .map(|path| { + let path = if path.starts_with(&self.prefix) { + self.relative(path)? + } else { + &path + }; + Ok(Assets::format_key(path)) + }) + .collect::>()?; + + Ok(self) + } + + pub fn build(self) -> Result { + let mut matches = TokenStream::new(); + for (key, asset) in self.assets { + if self.filter.contains(&key) { + continue; + } + + let value = match asset { + Asset::Identity(path) => { + let path = path.display().to_string(); + quote! { + (AssetCompression::None, include_bytes!(#path)) + } + } + Asset::Compressed(path, cache) => { + let path = path.display().to_string(); + let cache = cache.display().to_string(); + quote! { + { + // make compiler check asset file for re-run. + // rely on dead code elimination to remove it from target binary + const _: &[u8] = include_bytes!(#path); + + (AssetCompression::Gzip, include_bytes!(#cache)) + } + } + } + }; + + matches.append_all(quote! { + #key => #value, + }) + } + + Ok(quote! { + phf_map! { + #matches + } + }) + } +} diff --git a/tauri-macros/src/lib.rs b/tauri-macros/src/lib.rs new file mode 100644 index 000000000..d8ed29afd --- /dev/null +++ b/tauri-macros/src/lib.rs @@ -0,0 +1,19 @@ +extern crate proc_macro; +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +mod error; +mod expand; +mod include_dir; + +const DEFAULT_CONFIG_FILE: &str = "tauri.conf.json"; + +#[proc_macro_derive(FromTauriContext, attributes(config_path))] +pub fn load_context(ast: TokenStream) -> TokenStream { + let input = parse_macro_input!(ast as DeriveInput); + let name = input.ident.clone(); + + expand::load_context(input) + .unwrap_or_else(|e| e.into_compile_error(&name)) + .into() +} diff --git a/tauri-utils/Cargo.toml b/tauri-utils/Cargo.toml index ef05627a6..2aa1bf431 100644 --- a/tauri-utils/Cargo.toml +++ b/tauri-utils/Cargo.toml @@ -8,8 +8,11 @@ repository = "https://github.com/tauri-apps/tauri" description = "Utilities for Tauri" edition = "2018" - [dependencies] +serde = "1.0" +serde_json = "1.0" sysinfo = "0.10" anyhow = "1.0.31" -thiserror = "1.0.19" \ No newline at end of file +thiserror = "1.0.19" +phf = { version = "0.8", features = ["macros"] } +flate2 = "1" diff --git a/tauri-utils/src/assets.rs b/tauri-utils/src/assets.rs new file mode 100644 index 000000000..483aced4f --- /dev/null +++ b/tauri-utils/src/assets.rs @@ -0,0 +1,108 @@ +//! Assets handled by Tauri during compile time and runtime. + +use flate2::read::{GzDecoder, GzEncoder}; +pub use phf; +use std::io::Read; +use std::path::{Component, Path, PathBuf}; + +/// Type of compression applied to an asset +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum AssetCompression { + /// No compression applied + None, + + /// Compressed with (gzip)[https://crates.io/crates/flate2] + Gzip, +} + +/// How the embedded asset should be fetched from `Assets` +pub enum AssetFetch { + /// Do not modify the compression + Identity, + + /// Ensure asset is decompressed + Decompress, + + /// Ensure asset is compressed + Compress, +} + +/// Runtime access to the included files +pub struct Assets { + inner: phf::Map<&'static str, (AssetCompression, &'static [u8])>, +} + +impl Assets { + /// Create `Assets` container from `phf::Map` + pub const fn new(map: phf::Map<&'static str, (AssetCompression, &'static [u8])>) -> Self { + Self { inner: map } + } + + /// Format a key used to identify a file embedded in `Assets`. + /// + /// Output should use unix path separators and have a root directory to mimic + /// server urls. + pub fn format_key(path: impl Into) -> String { + let path = path.into(); + + // add in root to mimic how it is used from a server url + let path = if path.has_root() { + path + } else { + Path::new(&Component::RootDir).join(path) + }; + + if cfg!(windows) { + let mut buf = String::new(); + for component in path.components() { + match component { + Component::RootDir => buf.push('/'), + Component::CurDir => buf.push_str("./"), + Component::ParentDir => buf.push_str("../"), + Component::Prefix(prefix) => buf.push_str(&prefix.as_os_str().to_string_lossy()), + Component::Normal(s) => { + buf.push_str(&s.to_string_lossy()); + buf.push('/') + } + } + } + + // remove the last slash + if buf != "/" { + buf.pop(); + } + + buf + } else { + path.to_string_lossy().to_string() + } + } + + /// Get embedded asset, automatically handling compression. + pub fn get( + &self, + path: impl Into, + fetch: AssetFetch, + ) -> Option<(Box, AssetCompression)> { + use self::{AssetCompression::*, AssetFetch::*}; + + let key = Self::format_key(path); + let &(compression, content) = self.inner.get(&*key)?; + Some(match (compression, fetch) { + // content is already in compression format expected + (_, Identity) | (None, Decompress) | (Gzip, Compress) => (Box::new(content), compression), + + // content is uncompressed, but fetched with compression + (None, Compress) => { + let compressor = GzEncoder::new(content, flate2::Compression::new(6)); + (Box::new(compressor), Gzip) + } + + // content is compressed, but fetched with decompression + (Gzip, Decompress) => { + let decompressor = GzDecoder::new(content); + (Box::new(decompressor), None) + } + }) + } +} diff --git a/tauri-api/src/config.rs b/tauri-utils/src/config.rs similarity index 71% rename from tauri-api/src/config.rs rename to tauri-utils/src/config.rs index b9e548b1d..6811ad74f 100644 --- a/tauri-api/src/config.rs +++ b/tauri-utils/src/config.rs @@ -2,11 +2,8 @@ use serde::de::{Deserializer, Error as DeError, Visitor}; use serde::Deserialize; use serde_json::Value as JsonValue; -use once_cell::sync::OnceCell; use std::collections::HashMap; -static CONFIG: OnceCell = OnceCell::new(); - /// The window configuration object. #[derive(PartialEq, Deserialize, Debug)] #[serde(tag = "window", rename_all = "camelCase")] @@ -44,13 +41,15 @@ fn default_title() -> String { "Tauri App".to_string() } -fn default_window() -> WindowConfig { - WindowConfig { - width: default_width(), - height: default_height(), - resizable: default_resizable(), - title: default_title(), - fullscreen: false, +impl Default for WindowConfig { + fn default() -> Self { + Self { + width: default_width(), + height: default_height(), + resizable: default_resizable(), + title: default_title(), + fullscreen: false, + } } } @@ -74,12 +73,38 @@ pub struct EmbeddedServerConfig { /// If it's `random`, we'll generate one at runtime. #[serde(default = "default_port", deserialize_with = "port_deserializer")] pub port: Port, + + /// The base path of the embedded server. + /// The path should always start and end in a forward slash, which the deserializer will ensure + #[serde( + default = "default_public_path", + deserialize_with = "public_path_deserializer" + )] + pub public_path: String, } fn default_host() -> String { "http://127.0.0.1".to_string() } +fn default_port() -> Port { + Port::Random +} + +fn default_public_path() -> String { + "/".to_string() +} + +impl Default for EmbeddedServerConfig { + fn default() -> Self { + Self { + host: default_host(), + port: default_port(), + public_path: default_public_path(), + } + } +} + fn port_deserializer<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -116,15 +141,45 @@ where deserializer.deserialize_any(PortDeserializer {}) } -fn default_port() -> Port { - Port::Random -} +fn public_path_deserializer<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + struct PublicPathDeserializer; -fn default_embedded_server() -> EmbeddedServerConfig { - EmbeddedServerConfig { - host: default_host(), - port: default_port(), + impl<'de> Visitor<'de> for PublicPathDeserializer { + type Value = String; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a string starting and ending in a forward slash /") + } + + fn visit_str(self, value: &str) -> Result + where + E: DeError, + { + match value.len() { + 0 => return Ok("/".into()), + 1 if value == "/" => return Ok("/".into()), + 1 => return Ok(format!("/{}/", value)), + _ => {} + } + + // we know there are at least 2 characters in the string + let mut chars = value.chars(); + let first = chars.next().unwrap(); + let last = chars.last().unwrap(); + + match (first == '/', last == '/') { + (true, true) => Ok(value.into()), + (true, false) => Ok(format!("{}/", value)), + (false, true) => Ok(format!("/{}", value)), + _ => Ok(format!("/{}/", value)), + } + } } + + deserializer.deserialize_any(PublicPathDeserializer {}) } /// A CLI argument definition @@ -270,9 +325,11 @@ pub struct BundleConfig { pub identifier: String, } -fn default_bundle() -> BundleConfig { - BundleConfig { - identifier: String::from(""), +impl Default for BundleConfig { + fn default() -> Self { + Self { + identifier: String::from(""), + } } } @@ -281,19 +338,30 @@ fn default_bundle() -> BundleConfig { #[serde(tag = "tauri", rename_all = "camelCase")] pub struct TauriConfig { /// The window configuration. - #[serde(default = "default_window")] + #[serde(default)] pub window: WindowConfig, /// The embeddedServer configuration. - #[serde(default = "default_embedded_server")] + #[serde(default)] pub embedded_server: EmbeddedServerConfig, /// The CLI configuration. #[serde(default)] pub cli: Option, /// The bundler configuration. - #[serde(default = "default_bundle")] + #[serde(default)] pub bundle: BundleConfig, } +impl Default for TauriConfig { + fn default() -> Self { + Self { + window: WindowConfig::default(), + embedded_server: EmbeddedServerConfig::default(), + cli: None, + bundle: BundleConfig::default(), + } + } +} + /// The Build configuration object. #[derive(PartialEq, Deserialize, Debug)] #[serde(tag = "build", rename_all = "camelCase")] @@ -301,11 +369,26 @@ pub struct BuildConfig { /// the devPath config. #[serde(default = "default_dev_path")] pub dev_path: String, + /// the dist config. + #[serde(default = "default_dist_path")] + pub dist_dir: String, } fn default_dev_path() -> String { "".to_string() } +fn default_dist_path() -> String { + "../dist".to_string() +} + +impl Default for BuildConfig { + fn default() -> Self { + Self { + dev_path: default_dev_path(), + dist_dir: default_dist_path(), + } + } +} type JsonObject = HashMap; @@ -314,10 +397,10 @@ type JsonObject = HashMap; #[serde(rename_all = "camelCase")] pub struct Config { /// The Tauri configuration. - #[serde(default = "default_tauri")] + #[serde(default)] pub tauri: TauriConfig, /// The build configuration. - #[serde(default = "default_build")] + #[serde(default)] pub build: BuildConfig, /// The plugins config. #[serde(default)] @@ -331,160 +414,29 @@ impl Config { } } -fn default_tauri() -> TauriConfig { - TauriConfig { - window: default_window(), - embedded_server: default_embedded_server(), - cli: None, - bundle: default_bundle(), - } -} - -fn default_build() -> BuildConfig { - BuildConfig { - dev_path: default_dev_path(), - } -} - -/// Gets the static parsed config from `tauri.conf.json`. -pub fn get() -> crate::Result<&'static Config> { - if let Some(config) = CONFIG.get() { - return Ok(config); - } - let config: Config = match option_env!("TAURI_CONFIG") { - Some(config) => serde_json::from_str(config).expect("failed to parse TAURI_CONFIG env"), - None => { - let config = include_str!(concat!(env!("OUT_DIR"), "/tauri.conf.json")); - serde_json::from_str(&config).expect("failed to read tauri.conf.json") - } - }; - - CONFIG - .set(config) - .map_err(|_| anyhow::anyhow!("failed to set CONFIG"))?; - - let config = CONFIG.get().unwrap(); - Ok(config) -} - #[cfg(test)] mod test { use super::*; - // generate a test_config based on the test fixture - fn create_test_config() -> Config { - let mut subcommands = std::collections::HashMap::new(); - subcommands.insert( - "update".to_string(), - CliConfig { - description: Some("Updates the app".to_string()), - long_description: None, - before_help: None, - after_help: None, - args: Some(vec![CliArg { - short: Some('b'), - name: "background".to_string(), - description: Some("Update in background".to_string()), - ..Default::default() - }]), - subcommands: None, - }, - ); - Config { - tauri: TauriConfig { - window: WindowConfig { - width: 800, - height: 600, - resizable: true, - title: String::from("Tauri API Validation"), - fullscreen: false, - }, - embedded_server: EmbeddedServerConfig { - host: String::from("http://127.0.0.1"), - port: Port::Random, - }, - bundle: BundleConfig { - identifier: String::from("com.tauri.communication"), - }, - cli: Some(CliConfig { - description: Some("Tauri communication example".to_string()), - long_description: None, - before_help: None, - after_help: None, - args: Some(vec![ - CliArg { - short: Some('c'), - name: "config".to_string(), - takes_value: Some(true), - description: Some("Config path".to_string()), - ..Default::default() - }, - CliArg { - short: Some('t'), - name: "theme".to_string(), - takes_value: Some(true), - description: Some("App theme".to_string()), - possible_values: Some(vec![ - "light".to_string(), - "dark".to_string(), - "system".to_string(), - ]), - ..Default::default() - }, - CliArg { - short: Some('v'), - name: "verbose".to_string(), - multiple_occurrences: Some(true), - description: Some("Verbosity level".to_string()), - ..Default::default() - }, - ]), - subcommands: Some(subcommands), - }), - }, - build: BuildConfig { - dev_path: String::from("../dist"), - }, - plugins: Default::default(), - } - } - #[test] - // test the get function. Will only resolve to true if the TAURI_CONFIG variable is set properly to the fixture. - fn test_get() { - // get test_config - let test_config = create_test_config(); - - // call get(); - let config = get(); - - // check to see if there is an OK or Err, on Err fail test. - match config { - // On Ok, check that the config is the same as the test config. - Ok(c) => { - println!("{:?}", c); - assert_eq!(c, &test_config) - } - Err(e) => panic!("get config failed: {:?}", e.to_string()), - } - } + // TODO: create a test that compares a config to a json config #[test] // test all of the default functions fn test_defaults() { // get default tauri config - let t_config = default_tauri(); + let t_config = TauriConfig::default(); // get default build config - let b_config = default_build(); + let b_config = BuildConfig::default(); // get default dev path let d_path = default_dev_path(); // get default embedded server - let de_server = default_embedded_server(); + let de_server = EmbeddedServerConfig::default(); // get default window - let d_window = default_window(); + let d_window = WindowConfig::default(); // get default title let d_title = default_title(); // get default bundle - let d_bundle = default_bundle(); + let d_bundle = BundleConfig::default(); // create a tauri config. let tauri = TauriConfig { @@ -498,6 +450,7 @@ mod test { embedded_server: EmbeddedServerConfig { host: String::from("http://127.0.0.1"), port: Port::Random, + public_path: "/".into(), }, bundle: BundleConfig { identifier: String::from(""), @@ -508,6 +461,7 @@ mod test { // create a build config let build = BuildConfig { dev_path: String::from(""), + dist_dir: String::from("../dist"), }; // test the configs diff --git a/tauri-utils/src/lib.rs b/tauri-utils/src/lib.rs index e0930934f..b4568fa20 100644 --- a/tauri-utils/src/lib.rs +++ b/tauri-utils/src/lib.rs @@ -1,6 +1,10 @@ //! Tauri utility helpers #![warn(missing_docs, rust_2018_idioms)] +/// The Assets module allows you to read files that have been bundled by tauri +pub mod assets; +/// Tauri config definition. +pub mod config; /// Platform helpers pub mod platform; /// Process helpers diff --git a/tauri/Cargo.toml b/tauri/Cargo.toml index f1f7b06b7..50095b961 100644 --- a/tauri/Cargo.toml +++ b/tauri/Cargo.toml @@ -20,8 +20,6 @@ features = [ "all-api" ] [dependencies] serde_json = "1.0" serde = { version = "1.0", features = [ "derive" ] } -tauri_includedir = "0.6.0" -phf = "0.8.0" base64 = "0.13.0" webbrowser = "0.5.5" lazy_static = "1.4.0" @@ -34,6 +32,7 @@ anyhow = "1.0.38" thiserror = "1.0.23" once_cell = "1.5.2" tauri-api = { version = "0.7.5", path = "../tauri-api" } +tauri-macros = { version = "0.1", path = "../tauri-macros" } urlencoding = "1.1.1" wry = { git = "https://github.com/tauri-apps/wry", rev = "42f4f2133f7921ed5adc47908787094da8abeac5" } @@ -41,7 +40,6 @@ wry = { git = "https://github.com/tauri-apps/wry", rev = "42f4f2133f7921ed5adc47 runas = "0.2" [build-dependencies] -tauri_includedir_codegen = "0.6.2" cfg_aliases = "0.1.1" [dev-dependencies] diff --git a/tauri/build.rs b/tauri/build.rs index 107e9aaf4..084be3369 100644 --- a/tauri/build.rs +++ b/tauri/build.rs @@ -1,126 +1,6 @@ use cfg_aliases::cfg_aliases; -use std::{ - env, - error::Error, - fs::{read_to_string, File}, - io::{BufWriter, Write}, - path::Path, -}; - -#[cfg(any(feature = "embedded-server", feature = "no-server"))] -pub fn main() -> Result<(), Box> { - shared()?; - - let out_dir = env::var("OUT_DIR")?; - - let dest_index_html_path = Path::new(&out_dir).join("index.tauri.html"); - let mut index_html_file = BufWriter::new(File::create(&dest_index_html_path)?); - - match env::var_os("TAURI_DIST_DIR") { - Some(dist_path) => { - let dist_path_string = dist_path.into_string().unwrap(); - let dist_path = Path::new(&dist_path_string); - - println!("cargo:rerun-if-changed={}", dist_path_string); - - let mut inlined_assets = match std::env::var_os("TAURI_INLINED_ASSETS") { - Some(assets) => assets - .into_string() - .unwrap() - .split('|') - .map(|s| s.to_string()) - .filter(|s| !s.is_empty()) - .collect(), - None => Vec::new(), - }; - - // the index.html is parsed so we always ignore it - inlined_assets.push( - dist_path - .join("index.html") - .into_os_string() - .into_string() - .expect("failed to convert dist path to string"), - ); - if cfg!(feature = "no-server") { - // on no-server we include_str() the index.tauri.html on the runner - inlined_assets.push( - dist_path - .join("index.tauri.html") - .into_os_string() - .into_string() - .expect("failed to convert dist path to string"), - ); - } - - // include assets - tauri_includedir_codegen::start("ASSETS") - .dir( - dist_path_string.clone(), - tauri_includedir_codegen::Compression::None, - ) - .build("data.rs", inlined_assets) - .expect("failed to build data.rs"); - - write!( - index_html_file, - "{}", - read_to_string(dist_path.join("index.tauri.html"))? - )?; - } - None => { - // dummy assets - tauri_includedir_codegen::start("ASSETS") - .dir("".to_string(), tauri_includedir_codegen::Compression::None) - .build("data.rs", vec![]) - .expect("failed to build data.rs"); - write!( - index_html_file, - "Build error: Couldn't find ENV: TAURI_DIST_DIR" - )?; - println!("Build error: Couldn't find ENV: TAURI_DIST_DIR"); - } - } - Ok(()) -} - -#[cfg(not(any(feature = "embedded-server", feature = "no-server")))] -pub fn main() -> Result<(), Box> { - shared() -} - -fn shared() -> Result<(), Box> { - let out_dir = env::var("OUT_DIR")?; - let dest_tauri_script_path = Path::new(&out_dir).join("__tauri.js"); - let mut tauri_script_file = BufWriter::new(File::create(&dest_tauri_script_path)?); - - match env::var_os("TAURI_DIST_DIR") { - Some(dist_path) => { - let dist_path_string = dist_path.into_string().unwrap(); - let dist_path = Path::new(&dist_path_string); - - write!( - tauri_script_file, - "{}", - read_to_string(dist_path.join("__tauri.js"))? - )?; - } - None => { - write!( - tauri_script_file, - r#"console.warning("Couldn't find ENV: TAURI_DIST_DIR, the Tauri API won't work. Please rebuild with the Tauri CLI.")"#, - )?; - } - } - - setup_env_aliases(); - - Ok(()) -} - -#[allow(clippy::cognitive_complexity)] -fn setup_env_aliases() { +fn main() { cfg_aliases! { embedded_server: { feature = "embedded-server" }, no_server: { feature = "no-server" }, diff --git a/tauri/examples/api/src-tauri/Cargo.lock b/tauri/examples/api/src-tauri/Cargo.lock index 4aa9f38b6..0fefc722e 100644 --- a/tauri/examples/api/src-tauri/Cargo.lock +++ b/tauri/examples/api/src-tauri/Cargo.lock @@ -1717,17 +1717,9 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ + "phf_macros", "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator", - "phf_shared", + "proc-macro-hack", ] [[package]] @@ -1740,6 +1732,20 @@ dependencies = [ "rand 0.7.3", ] +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote 1.0.8", + "syn 1.0.60", +] + [[package]] name = "phf_shared" version = "0.8.0" @@ -2375,13 +2381,11 @@ dependencies = [ "futures", "lazy_static", "once_cell", - "phf", "runas", "serde", "serde_json", "tauri-api", - "tauri_includedir", - "tauri_includedir_codegen", + "tauri-macros", "thiserror", "tiny_http", "tokio", @@ -2405,6 +2409,7 @@ dependencies = [ "nfd", "notify-rust", "once_cell", + "phf", "rand 0.8.3", "semver", "serde", @@ -2437,6 +2442,20 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "tauri-macros" +version = "0.1.0" +dependencies = [ + "flate2", + "proc-macro2", + "quote 1.0.8", + "serde", + "serde_json", + "syn 1.0.60", + "tauri-api", + "walkdir", +] + [[package]] name = "tauri-utils" version = "0.5.1" @@ -2446,27 +2465,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "tauri_includedir" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a3644510b11203f42cf6e6384d5c46e0d20e426aa6e05b9110f5e4583a1032" -dependencies = [ - "flate2", - "phf", -] - -[[package]] -name = "tauri_includedir_codegen" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afccce834cf6062a1a19d2d0018fd2a5a2d13d75f547ed0dac2cc3d9813b6ae7" -dependencies = [ - "flate2", - "phf_codegen", - "walkdir", -] - [[package]] name = "tempfile" version = "3.2.0" diff --git a/tauri/examples/api/src-tauri/src/main.rs b/tauri/examples/api/src-tauri/src/main.rs index 6323d76e7..451aa1225 100644 --- a/tauri/examples/api/src-tauri/src/main.rs +++ b/tauri/examples/api/src-tauri/src/main.rs @@ -12,21 +12,24 @@ struct Reply { data: String, } +#[derive(tauri::FromTauriContext)] +struct Context; + fn main() { - tauri::AppBuilder::::new() - .setup(|webview, _source| async move { - let mut webview = webview.clone(); + tauri::AppBuilder::::new() + .setup(|dispatcher, _source| async move { + let mut dispatcher = dispatcher.clone(); tauri::event::listen(String::from("js-event"), move |msg| { println!("got js-event with message '{:?}'", msg); let reply = Reply { data: "something else".to_string(), }; - tauri::event::emit(&mut webview, String::from("rust-event"), Some(reply)) + tauri::event::emit(&mut dispatcher, String::from("rust-event"), Some(reply)) .expect("failed to emit"); }); }) - .invoke_handler(|mut webview, arg| async move { + .invoke_handler(|mut dispatcher, arg| async move { use cmd::Cmd::*; match serde_json::from_str(&arg) { Err(e) => Err(e.to_string()), @@ -64,5 +67,6 @@ fn main() { } }) .build() + .unwrap() .run(); } diff --git a/tauri/examples/communication/src-tauri/Cargo.lock b/tauri/examples/communication/src-tauri/Cargo.lock index 4aa9f38b6..0fefc722e 100644 --- a/tauri/examples/communication/src-tauri/Cargo.lock +++ b/tauri/examples/communication/src-tauri/Cargo.lock @@ -1717,17 +1717,9 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" dependencies = [ + "phf_macros", "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator", - "phf_shared", + "proc-macro-hack", ] [[package]] @@ -1740,6 +1732,20 @@ dependencies = [ "rand 0.7.3", ] +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro-hack", + "proc-macro2", + "quote 1.0.8", + "syn 1.0.60", +] + [[package]] name = "phf_shared" version = "0.8.0" @@ -2375,13 +2381,11 @@ dependencies = [ "futures", "lazy_static", "once_cell", - "phf", "runas", "serde", "serde_json", "tauri-api", - "tauri_includedir", - "tauri_includedir_codegen", + "tauri-macros", "thiserror", "tiny_http", "tokio", @@ -2405,6 +2409,7 @@ dependencies = [ "nfd", "notify-rust", "once_cell", + "phf", "rand 0.8.3", "semver", "serde", @@ -2437,6 +2442,20 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "tauri-macros" +version = "0.1.0" +dependencies = [ + "flate2", + "proc-macro2", + "quote 1.0.8", + "serde", + "serde_json", + "syn 1.0.60", + "tauri-api", + "walkdir", +] + [[package]] name = "tauri-utils" version = "0.5.1" @@ -2446,27 +2465,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "tauri_includedir" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7a3644510b11203f42cf6e6384d5c46e0d20e426aa6e05b9110f5e4583a1032" -dependencies = [ - "flate2", - "phf", -] - -[[package]] -name = "tauri_includedir_codegen" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afccce834cf6062a1a19d2d0018fd2a5a2d13d75f547ed0dac2cc3d9813b6ae7" -dependencies = [ - "flate2", - "phf_codegen", - "walkdir", -] - [[package]] name = "tempfile" version = "3.2.0" diff --git a/tauri/examples/communication/src-tauri/src/main.rs b/tauri/examples/communication/src-tauri/src/main.rs index 6323d76e7..16d8a6d6e 100644 --- a/tauri/examples/communication/src-tauri/src/main.rs +++ b/tauri/examples/communication/src-tauri/src/main.rs @@ -12,21 +12,25 @@ struct Reply { data: String, } +#[derive(tauri::FromTauriContext)] +#[config_path = "examples/communication/src-tauri/tauri.conf.json"] +struct Context; + fn main() { - tauri::AppBuilder::::new() - .setup(|webview, _source| async move { - let mut webview = webview.clone(); + tauri::AppBuilder::::new() + .setup(|dispatcher, _source| async move { + let mut dispatcher = dispatcher.clone(); tauri::event::listen(String::from("js-event"), move |msg| { println!("got js-event with message '{:?}'", msg); let reply = Reply { data: "something else".to_string(), }; - tauri::event::emit(&mut webview, String::from("rust-event"), Some(reply)) + tauri::event::emit(&mut dispatcher, String::from("rust-event"), Some(reply)) .expect("failed to emit"); }); }) - .invoke_handler(|mut webview, arg| async move { + .invoke_handler(|mut dispatcher, arg| async move { use cmd::Cmd::*; match serde_json::from_str(&arg) { Err(e) => Err(e.to_string()), @@ -44,7 +48,7 @@ fn main() { // tauri::execute_promise is a helper for APIs that uses the tauri.promisified JS function // so you can easily communicate between JS and Rust with promises tauri::execute_promise( - &mut webview, + &mut dispatcher, async move { println!("{} {:?}", endpoint, body); // perform an async operation here @@ -64,5 +68,6 @@ fn main() { } }) .build() + .unwrap() .run(); } diff --git a/tauri/src/app.rs b/tauri/src/app.rs index 4e211bf80..d4b4712c8 100644 --- a/tauri/src/app.rs +++ b/tauri/src/app.rs @@ -1,11 +1,38 @@ use crate::ApplicationExt; use futures::future::BoxFuture; +use std::marker::PhantomData; +use tauri_api::config::Config; +use tauri_api::private::AsTauriContext; mod runner; type InvokeHandler = dyn Fn(W, String) -> BoxFuture<'static, Result<(), String>> + Send + Sync; type Setup = dyn Fn(W, String) -> BoxFuture<'static, ()> + Send + Sync; +/// `App` runtime information. +pub struct Context { + pub(crate) config: Config, + pub(crate) tauri_script: &'static str, + #[cfg(assets)] + pub(crate) assets: &'static tauri_api::assets::Assets, + #[cfg(any(dev, no_server))] + #[allow(dead_code)] + pub(crate) index: &'static str, +} + +impl Context { + pub(crate) fn new() -> crate::Result { + Ok(Self { + config: serde_json::from_str(Context::raw_config())?, + tauri_script: Context::raw_tauri_script(), + #[cfg(assets)] + assets: Context::assets(), + #[cfg(any(dev, no_server))] + index: Context::raw_index(), + }) + } +} + /// The application runner. pub struct App { /// The JS message handler. @@ -14,6 +41,8 @@ pub struct App { setup: Option>>, /// The HTML of the splashscreen to render. splashscreen_html: Option, + /// The context the App was created with + pub(crate) context: Context, } impl App { @@ -54,22 +83,25 @@ impl App { /// The App builder. #[derive(Default)] -pub struct AppBuilder { +pub struct AppBuilder { /// The JS message handler. invoke_handler: Option>>, /// The setup callback, invoked when the webview is ready. setup: Option>>, /// The HTML of the splashscreen to render. splashscreen_html: Option, + /// The configuration used + config: PhantomData, } -impl AppBuilder { +impl AppBuilder { /// Creates a new App builder. pub fn new() -> Self { Self { invoke_handler: None, setup: None, splashscreen_html: None, + config: Default::default(), } } @@ -117,11 +149,12 @@ impl AppBuilder { } /// Builds the App. - pub fn build(self) -> App { - App { + pub fn build(self) -> crate::Result> { + Ok(App { invoke_handler: self.invoke_handler, setup: self.setup, splashscreen_html: self.splashscreen_html, - } + context: Context::new::()?, + }) } } diff --git a/tauri/src/app/runner.rs b/tauri/src/app/runner.rs index 02d7b0e7c..9fddd8153 100644 --- a/tauri/src/app/runner.rs +++ b/tauri/src/app/runner.rs @@ -8,7 +8,7 @@ use crate::{ApplicationDispatcherExt, ApplicationExt, WebviewBuilderExt, WindowB use super::App; #[cfg(embedded_server)] use crate::api::tcp::{get_available_port, port_is_available}; -use tauri_api::config::get; +use crate::app::Context; #[allow(dead_code)] enum Content { @@ -19,17 +19,21 @@ enum Content { /// Main entry point for running the Webview pub(crate) fn run(application: App) -> crate::Result<()> { // setup the content using the config struct depending on the compile target - let main_content = setup_content()?; + let main_content = setup_content(&application.context)?; - // setup the server url for the embedded-server #[cfg(embedded_server)] - let server_url = { - if let Content::Url(ref url) = &main_content { + { + // setup the server url for the embedded-server + let server_url = if let Content::Url(url) = &main_content { String::from(url) } else { String::from("") - } - }; + }; + + // spawn the embedded server on our server url + #[cfg(embedded_server)] + spawn_server(server_url, &application.context)?; + } let splashscreen_content = if application.splashscreen_html().is_some() { Some(Content::Html( @@ -50,10 +54,6 @@ pub(crate) fn run(application: App) -> crate::Re crate::plugin::created(A::plugin_store(), &mut dispatcher).await }); - // spawn the embedded server on our server url - #[cfg(embedded_server)] - spawn_server(server_url); - // spin up the updater process #[cfg(feature = "updater")] spawn_updater(); @@ -65,14 +65,14 @@ pub(crate) fn run(application: App) -> crate::Re } #[cfg(all(embedded_server, no_server))] -fn setup_content() -> crate::Result> { +fn setup_content(_: &Context) -> crate::Result> { panic!("only one of `embedded-server` and `no-server` is allowed") } // setup content for dev-server #[cfg(dev)] -fn setup_content() -> crate::Result> { - let config = get()?; +fn setup_content(context: &Context) -> crate::Result> { + let config = &context.config; if config.build.dev_path.starts_with("http") { #[cfg(windows)] { @@ -99,27 +99,19 @@ fn setup_content() -> crate::Result> { } Ok(Content::Url(config.build.dev_path.clone())) } else { - let dev_dir = &config.build.dev_path; - let dev_path = std::path::Path::new(dev_dir).join("index.tauri.html"); - if !dev_path.exists() { - panic!( - "Couldn't find 'index.tauri.html' inside {}; did you forget to run 'tauri dev'?", - dev_dir - ); - } Ok(Content::Html(format!( "data:text/html,{}", - urlencoding::encode(&std::fs::read_to_string(dev_path)?) + urlencoding::encode(context.index) ))) } } // setup content for embedded server #[cfg(all(embedded_server, not(no_server)))] -fn setup_content() -> crate::Result> { - let (port, valid) = setup_port()?; +fn setup_content(context: &Context) -> crate::Result> { + let (port, valid) = setup_port(&context)?; let url = (if valid { - setup_server_url(port) + setup_server_url(port, &context) } else { Err(anyhow::anyhow!("invalid port")) }) @@ -130,19 +122,18 @@ fn setup_content() -> crate::Result> { // setup content for no-server #[cfg(all(no_server, not(embedded_server)))] -fn setup_content() -> crate::Result> { - let html = include_str!(concat!(env!("OUT_DIR"), "/index.tauri.html")); +fn setup_content(context: &Context) -> crate::Result> { Ok(Content::Html(format!( "data:text/html,{}", - urlencoding::encode(html) + urlencoding::encode(context.index) ))) } // get the port for the embedded server #[cfg(embedded_server)] #[allow(dead_code)] -fn setup_port() -> crate::Result<(String, bool)> { - let config = get()?; +fn setup_port(context: &Context) -> crate::Result<(String, bool)> { + let config = &context.config; match config.tauri.embedded_server.port { tauri_api::config::Port::Random => match get_available_port() { Some(available_port) => Ok((available_port.to_string(), true)), @@ -158,8 +149,8 @@ fn setup_port() -> crate::Result<(String, bool)> { // setup the server url for embedded server #[cfg(embedded_server)] #[allow(dead_code)] -fn setup_server_url(port: String) -> crate::Result { - let config = get()?; +fn setup_server_url(port: String, context: &Context) -> crate::Result { + let config = &context.config; let mut url = format!("{}:{}", config.tauri.embedded_server.host, port); if !url.starts_with("http") { url = format!("http://{}", url); @@ -169,21 +160,35 @@ fn setup_server_url(port: String) -> crate::Result { // spawn the embedded server #[cfg(embedded_server)] -fn spawn_server(server_url: String) { +fn spawn_server(server_url: String, context: &Context) -> crate::Result<()> { + let assets = context.assets; + let public_path = context.config.tauri.embedded_server.public_path.clone(); std::thread::spawn(move || { let server = tiny_http::Server::http(server_url.replace("http://", "").replace("https://", "")) .expect("Unable to spawn server"); for request in server.incoming_requests() { - let url = match request.url() { + let url = request.url().replace(&server_url, ""); + let url = match url.as_str() { "/" => "/index.tauri.html", - url => url, + url => { + if url.starts_with(&public_path) { + &url[public_path.len() - 1..] + } else { + eprintln!( + "found url not matching public path.\nurl: {}\npublic path: {}", + url, public_path + ); + url + } + } } .to_string(); request - .respond(crate::server::asset_response(&url)) + .respond(crate::server::asset_response(&url, assets)) .expect("unable to setup response"); } }); + Ok(()) } // spawn an updater process. @@ -242,7 +247,7 @@ fn build_webview( content: Content, splashscreen_content: Option>, ) -> crate::Result<(A, A::Dispatcher)> { - let config = get()?; + let config = &application.context.config; // TODO let debug = cfg!(debug_assertions); // get properties from config struct // TODO let width = config.tauri.window.width; @@ -276,7 +281,7 @@ fn build_webview( }} {plugin_init} "#, - tauri_init = include_str!(concat!(env!("OUT_DIR"), "/__tauri.js")), + tauri_init = application.context.tauri_script, event_init = init(), plugin_init = crate::async_runtime::block_on(crate::plugin::init_script(A::plugin_store())) ); @@ -316,9 +321,10 @@ fn build_webview( } else if arg == r#"{"cmd":"closeSplashscreen"}"# { dispatcher.eval(&format!(r#"window.location.href = "{}""#, content_url)); } else { - let mut endpoint_handle = crate::endpoints::handle(&mut dispatcher, &arg) - .await - .map_err(|e| e.to_string()); + let mut endpoint_handle = + crate::endpoints::handle(&mut dispatcher, &arg, &application.context) + .await + .map_err(|e| e.to_string()); if let Err(ref tauri_handle_error) = endpoint_handle { if tauri_handle_error.contains("unknown variant") { let error = match application.run_invoke_handler(&mut dispatcher, &arg).await { @@ -388,21 +394,18 @@ fn get_api_error_message(arg: &str, handler_error_message: String) -> String { #[cfg(test)] mod test { use super::Content; + use crate::Context; + use crate::FromTauriContext; use proptest::prelude::*; - use std::env; + + #[derive(FromTauriContext)] + #[config_path = "test/fixture/src-tauri/tauri.conf.json"] + struct TauriContext; #[test] fn check_setup_content() { - let tauri_dir = match option_env!("TAURI_DIR") { - Some(d) => d.to_string(), - None => env::current_dir() - .unwrap() - .into_os_string() - .into_string() - .expect("Unable to convert to normal String"), - }; - env::set_current_dir(tauri_dir).expect("failed to change cwd"); - let res = super::setup_content(); + let context = Context::new::().unwrap(); + let res = super::setup_content(&context); #[cfg(embedded_server)] match res { @@ -413,23 +416,9 @@ mod test { #[cfg(no_server)] match res { Ok(Content::Html(s)) => { - let dist_dir = match option_env!("TAURI_DIST_DIR") { - Some(d) => d.to_string(), - None => env::current_dir() - .unwrap() - .into_os_string() - .into_string() - .expect("Unable to convert to normal String"), - }; assert_eq!( s, - format!( - "data:text/html,{}", - urlencoding::encode( - &std::fs::read_to_string(std::path::Path::new(&dist_dir).join("index.tauri.html")) - .unwrap() - ) - ) + format!("data:text/html,{}", urlencoding::encode(context.index)) ); } _ => panic!("setup content failed"), @@ -437,20 +426,13 @@ mod test { #[cfg(dev)] { - let config = tauri_api::config::get().expect("unable to setup default config"); + let config = &context.config; match res { Ok(Content::Url(dp)) => assert_eq!(dp, config.build.dev_path), Ok(Content::Html(s)) => { - let dev_dir = &config.build.dev_path; - let dev_path = std::path::Path::new(dev_dir).join("index.tauri.html"); assert_eq!( s, - format!( - "data:text/html,{}", - urlencoding::encode( - &std::fs::read_to_string(dev_path).expect("failed to read dev path") - ) - ) + format!("data:text/html,{}", urlencoding::encode(context.index)) ); } _ => panic!("setup content failed"), @@ -461,7 +443,8 @@ mod test { #[cfg(embedded_server)] #[test] fn check_setup_port() { - let res = super::setup_port(); + let context = Context::new::().unwrap(); + let res = super::setup_port(&context); match res { Ok((_s, _b)) => {} _ => panic!("setup port failed"), @@ -474,8 +457,9 @@ mod test { #[test] fn check_server_url(port in (any::().prop_map(|v| v.to_string()))) { let p = port.clone(); + let context = Context::new::().unwrap(); - let res = super::setup_server_url(port); + let res = super::setup_server_url(port, &context); match res { Ok(url) => assert!(url.contains(&p)), diff --git a/tauri/src/assets.rs b/tauri/src/assets.rs deleted file mode 100644 index 7ce7221b3..000000000 --- a/tauri/src/assets.rs +++ /dev/null @@ -1 +0,0 @@ -include!(concat!(env!("OUT_DIR"), "/data.rs")); diff --git a/tauri/src/cli.rs b/tauri/src/cli.rs deleted file mode 100644 index 60d0d1937..000000000 --- a/tauri/src/cli.rs +++ /dev/null @@ -1,9 +0,0 @@ -use once_cell::sync::Lazy; -use tauri_api::cli::Matches; - -/// Gets the CLI arg matches. -pub fn get_matches() -> &'static Option { - static MATCHES: Lazy> = Lazy::new(|| tauri_api::cli::get_matches().ok()); - - &MATCHES -} diff --git a/tauri/src/endpoints.rs b/tauri/src/endpoints.rs index 6b997c8c0..cc2465c65 100644 --- a/tauri/src/endpoints.rs +++ b/tauri/src/endpoints.rs @@ -16,12 +16,13 @@ mod http; #[cfg(notification)] mod notification; -use crate::{ApplicationDispatcherExt, Event}; +use crate::{app::Context, ApplicationDispatcherExt, Event}; #[allow(unused_variables)] pub(crate) async fn handle( dispatcher: &mut D, arg: &str, + context: &Context, ) -> crate::Result<()> { use cmd::Cmd::*; match serde_json::from_str(arg) { @@ -277,22 +278,14 @@ pub(crate) async fn handle( callback, error, } => { - asset::load(dispatcher, asset, asset_type, callback, error).await; + asset::load(dispatcher, asset, asset_type, callback, error, &context).await; } CliMatches { callback, error } => { #[cfg(cli)] - crate::execute_promise( - dispatcher, - async move { - match crate::cli::get_matches() { - Some(matches) => Ok(matches), - None => Err(anyhow::anyhow!(r#""failed to get matches""#)), - } - }, - callback, - error, - ) - .await; + { + let matches = tauri_api::cli::get_matches(&context.config); + crate::execute_promise(dispatcher, async move { matches }, callback, error).await; + } #[cfg(not(cli))] api_error( dispatcher, @@ -306,7 +299,7 @@ pub(crate) async fn handle( error, } => { #[cfg(notification)] - notification::send(dispatcher, options, callback, error).await; + notification::send(dispatcher, options, callback, error, &context.config).await; #[cfg(not(notification))] allowlist_error(dispatcher, error, "notification"); } diff --git a/tauri/src/endpoints/asset.rs b/tauri/src/endpoints/asset.rs index fc3e47e09..e3bf215d9 100644 --- a/tauri/src/endpoints/asset.rs +++ b/tauri/src/endpoints/asset.rs @@ -1,5 +1,6 @@ -use crate::ApplicationDispatcherExt; -use std::path::PathBuf; +use crate::{ApplicationDispatcherExt, Context}; +use std::io::Read; +use tauri_api::assets::{AssetFetch, Assets}; #[allow(clippy::option_env_unwrap)] pub async fn load( @@ -8,42 +9,43 @@ pub async fn load( asset_type: String, callback: String, error: String, + ctx: &Context, ) { let mut dispatcher_ = dispatcher.clone(); + let assets = ctx.assets; + let public_path = ctx.config.tauri.embedded_server.public_path.clone(); crate::execute_promise( dispatcher, async move { - let mut path = PathBuf::from(if asset.starts_with('/') { - asset.replacen("/", "", 1) + // strip "about:" uri scheme if it exists + let asset = if asset.starts_with("about:") { + &asset[6..] } else { - asset.clone() - }); - let mut read_asset; - loop { - read_asset = crate::assets::ASSETS.get(&format!( - "{}/{}", - option_env!("TAURI_DIST_DIR") - .expect("tauri apps should be built with the TAURI_DIST_DIR environment variable"), - path.to_string_lossy() - )); - if read_asset.is_err() { - match path.iter().next() { - Some(component) => { - let first_component = component.to_str().expect("failed to read path component"); - path = PathBuf::from(path.to_string_lossy().replacen( - format!("{}/", first_component).as_str(), - "", - 1, - )); - } - None => { - return Err(anyhow::anyhow!("Asset '{}' not found", asset)); - } - } - } else { - break; - } + &asset + }; + + // handle public path setting from tauri.conf > tauri > embeddedServer > publicPath + let asset = if asset.starts_with(&public_path) { + &asset[public_path.len() - 1..] + } else { + eprintln!( + "found url not matching public path.\nasset url: {}\npublic path: {}", + asset, public_path + ); + asset } + .to_string(); + + // how should that condition be handled now? + let asset_bytes = assets + .get(&Assets::format_key(&asset), AssetFetch::Decompress) + .ok_or_else(|| anyhow::anyhow!("Asset '{}' not found", asset)) + .and_then(|(read, _)| { + read + .bytes() + .collect::, _>>() + .map_err(Into::into) + })?; if asset_type == "image" { let mime_type = if asset.ends_with("gif") { @@ -62,12 +64,11 @@ pub async fn load( "jpeg" }; Ok(format!( - r#"data:image/{};base64,{}"#, + r#""data:image/{};base64,{}""#, mime_type, - base64::encode(&read_asset.expect("Failed to read asset type").into_owned()) + base64::encode(&asset_bytes) )) } else { - let asset_bytes = read_asset.expect("Failed to read asset type"); let asset_str = std::str::from_utf8(&asset_bytes).expect("failed to convert asset bytes to u8 slice"); if asset_type == "stylesheet" { diff --git a/tauri/src/endpoints/notification.rs b/tauri/src/endpoints/notification.rs index cbe3c2498..5cbbeb990 100644 --- a/tauri/src/endpoints/notification.rs +++ b/tauri/src/endpoints/notification.rs @@ -1,17 +1,22 @@ use super::cmd::NotificationOptions; use crate::ApplicationDispatcherExt; use serde_json::Value as JsonValue; +use tauri_api::config::Config; +use tauri_api::notification::Notification; pub async fn send( dispatcher: &mut D, options: NotificationOptions, callback: String, error: String, + config: &Config, ) { + let identifier = config.tauri.bundle.identifier.clone(); + crate::execute_promise( dispatcher, async move { - let mut notification = tauri_api::notification::Notification::new().title(options.title); + let mut notification = Notification::new(identifier).title(options.title); if let Some(body) = options.body { notification = notification.body(body); } diff --git a/tauri/src/lib.rs b/tauri/src/lib.rs index e2447355d..3cde69fe1 100644 --- a/tauri/src/lib.rs +++ b/tauri/src/lib.rs @@ -6,9 +6,6 @@ //! Tauri uses (and contributes to) the MIT licensed project that you can find at [webview](https://github.com/webview/webview). #![warn(missing_docs, rust_2018_idioms)] -/// The asset management module. -#[cfg(assets)] -pub mod assets; /// The event system module. pub mod event; /// The embedded server helpers. @@ -17,10 +14,6 @@ pub mod server; /// The Tauri-specific settings for your app e.g. notification permission status. pub mod settings; -/// The CLI args interface. -#[cfg(cli)] -pub mod cli; - /// The webview application entry. mod app; /// The Tauri API endpoints. @@ -41,6 +34,7 @@ pub type SyncTask = Box; pub use anyhow::Result; pub use app::*; pub use tauri_api as api; +pub use tauri_macros::FromTauriContext; pub use webview::{ ApplicationDispatcherExt, ApplicationExt, Callback, Event, WebviewBuilderExt, WindowBuilderExt, }; diff --git a/tauri/src/server.rs b/tauri/src/server.rs index cc2685b7d..639f129a4 100644 --- a/tauri/src/server.rs +++ b/tauri/src/server.rs @@ -1,39 +1,39 @@ -use tiny_http::{Header, Response}; +use std::io::Read; +use tauri_api::assets::{AssetFetch, Assets}; +use tiny_http::{Response, StatusCode}; /// Returns the HTTP response of the given asset path. -#[allow(clippy::option_env_unwrap)] -pub fn asset_response(path: &str) -> Response>> { - let asset_path = &format!( - "{}{}", - option_env!("TAURI_DIST_DIR") - .expect("tauri apps should be built with the TAURI_DIST_DIR environment variable"), - path - ); - let asset = crate::assets::ASSETS - .get(asset_path) - .unwrap_or_else(|_| panic!("Could not read asset {}", asset_path)) - .into_owned(); - let mut response = Response::from_data(asset); - let header; +pub fn asset_response(path: &str, assets: &'static Assets) -> Response { + let (asset, _) = assets + .get(path, AssetFetch::Compress) + .unwrap_or_else(|| panic!("Could not read asset {}", path)); - if path.ends_with(".svg") { - header = Header::from_bytes(&b"Content-Type"[..], &b"image/svg+xml"[..]) - .expect("Could not add svg+xml header"); + let mut headers = Vec::new(); + + // Content-Encoding + const CONTENT_ENCODING: &str = "Content-Encoding: gzip"; + let content_encoding = CONTENT_ENCODING + .parse() + .unwrap_or_else(|_| panic!("Could not add {} header", CONTENT_ENCODING)); + headers.push(content_encoding); + + // Content-Type + let mime = if path.ends_with(".svg") { + "Content-Type: image/svg+xml" } else if path.ends_with(".css") { - header = - Header::from_bytes(&b"Content-Type"[..], &b"text/css"[..]).expect("Could not add css header"); + "Content-Type: text/css" } else if path.ends_with(".html") { - header = Header::from_bytes(&b"Content-Type"[..], &b"text/html"[..]) - .expect("Could not add html header"); + "Content-Type: text/html" } else if path.ends_with(".js") { - header = Header::from_bytes(&b"Content-Type"[..], &b"text/javascript"[..]) - .expect("Could not add Javascript header"); + "Content-Type: text/javascript" } else { - header = Header::from_bytes(&b"Content-Type"[..], &b"application/octet-stream"[..]) - .expect("Could not add octet-stream header"); - } + "Content-Type: application/octet-stream" + }; - response.add_header(header); + let content_type = mime + .parse() + .unwrap_or_else(|_| panic!("Could not add {} header", mime)); + headers.push(content_type); - response + Response::new(StatusCode(200), headers, asset, None, None) } diff --git a/tauri/test/fixture/dist/__tauri.js b/tauri/test/fixture/dist/__tauri.js new file mode 100644 index 000000000..e69de29bb diff --git a/tauri/test/fixture/src-tauri/tauri.conf.json b/tauri/test/fixture/src-tauri/tauri.conf.json index 190ae1b81..3fba2cc88 100644 --- a/tauri/test/fixture/src-tauri/tauri.conf.json +++ b/tauri/test/fixture/src-tauri/tauri.conf.json @@ -9,6 +9,7 @@ "active": true }, "bundle": { + "identifier": "studio.tauri.example", "active": true }, "allowlist": {