Przeglądaj źródła

refactor(tauri): support for building without environmental variables (#850)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
chip 4 lat temu
rodzic
commit
e02c9419cb
60 zmienionych plików z 1302 dodań i 947 usunięć
  1. 8 0
      .changes/config-refactor.md
  2. 6 1
      .changes/config.json
  3. 0 4
      .changes/tauri-async.md
  4. 0 2
      .changes/tauri-cli.md
  5. 1 0
      Cargo.toml
  6. 30 30
      api/.eslintrc.js
  7. 5 0
      api/.prettierrc.js
  8. 7 7
      api/babel.config.js
  9. 57 57
      api/rollup.config.js
  10. 13 13
      api/src/bundle.ts
  11. 10 10
      api/src/cli.ts
  12. 18 18
      api/src/dialog.ts
  13. 11 11
      api/src/event.ts
  14. 47 47
      api/src/http.ts
  15. 2 2
      api/src/index.ts
  16. 15 15
      api/src/notification.ts
  17. 79 79
      api/src/path.ts
  18. 7 7
      api/src/process.ts
  19. 24 24
      api/src/tauri.ts
  20. 8 8
      api/src/window.ts
  21. 1 1
      cli/tauri.js/CHANGELOG.md
  22. 1 2
      cli/tauri.js/src/api/cli.ts
  23. 4 0
      cli/tauri.js/src/types/config.schema.json
  24. 4 0
      cli/tauri.js/src/types/config.ts
  25. 5 0
      cli/tauri.js/src/types/config.validator.ts
  26. 5 1
      cli/tauri.js/templates/src-tauri/src/main.rs
  27. 0 2
      cli/tauri.js/test/jest/fixtures/app/src-tauri/Cargo.toml
  28. 2 2
      cli/tauri.js/test/jest/fixtures/app/src-tauri/src/cmd.rs
  29. 12 12
      cli/tauri.js/test/jest/fixtures/app/src-tauri/src/main.rs
  30. 1 1
      tauri-api/Cargo.toml
  31. 0 39
      tauri-api/build.rs
  32. 2 3
      tauri-api/src/cli.rs
  33. 15 2
      tauri-api/src/lib.rs
  34. 9 8
      tauri-api/src/notification.rs
  35. 23 0
      tauri-macros/Cargo.toml
  36. 75 0
      tauri-macros/src/error.rs
  37. 120 0
      tauri-macros/src/expand.rs
  38. 164 0
      tauri-macros/src/include_dir.rs
  39. 19 0
      tauri-macros/src/lib.rs
  40. 5 2
      tauri-utils/Cargo.toml
  41. 108 0
      tauri-utils/src/assets.rs
  42. 116 162
      tauri-utils/src/config.rs
  43. 4 0
      tauri-utils/src/lib.rs
  44. 1 3
      tauri/Cargo.toml
  45. 1 121
      tauri/build.rs
  46. 28 30
      tauri/examples/api/src-tauri/Cargo.lock
  47. 9 5
      tauri/examples/api/src-tauri/src/main.rs
  48. 28 30
      tauri/examples/communication/src-tauri/Cargo.lock
  49. 11 6
      tauri/examples/communication/src-tauri/src/main.rs
  50. 38 5
      tauri/src/app.rs
  51. 63 79
      tauri/src/app/runner.rs
  52. 0 1
      tauri/src/assets.rs
  53. 0 9
      tauri/src/cli.rs
  54. 8 15
      tauri/src/endpoints.rs
  55. 35 34
      tauri/src/endpoints/asset.rs
  56. 6 1
      tauri/src/endpoints/notification.rs
  57. 1 7
      tauri/src/lib.rs
  58. 29 29
      tauri/src/server.rs
  59. 0 0
      tauri/test/fixture/dist/__tauri.js
  60. 1 0
      tauri/test/fixture/src-tauri/tauri.conf.json

+ 8 - 0
.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.

+ 6 - 1
.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"]
     }
   }
 }

+ 0 - 4
.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.

+ 0 - 2
.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`.

+ 1 - 0
Cargo.toml

@@ -2,6 +2,7 @@
 members = [
   "tauri",
   "tauri-api",
+  "tauri-macros",
   "tauri-utils",
 ]
 exclude = [

+ 30 - 30
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
+  }
+}

+ 5 - 0
api/.prettierrc.js

@@ -0,0 +1,5 @@
+module.exports = {
+  singleQuote: true,
+  semi: false,
+  trailingComma: 'none'
+}

+ 7 - 7
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'
+  ]
+}

+ 57 - 57
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 || {})
+    ]
+  }
+]

+ 13 - 13
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
+}

+ 10 - 10
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<CliMatches> {
   return await promisified<CliMatches>({
-    cmd: "cliMatches",
-  });
+    cmd: 'cliMatches'
+  })
 }
 
-export { getMatches };
+export { getMatches }

+ 18 - 18
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<string | string[]> {
-  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<string>} Promise resolving to the select path
  */
 async function save(options: SaveDialogOptions = {}): Promise<string> {
-  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 }

+ 11 - 11
api/src/event.ts

@@ -1,11 +1,11 @@
-import { invoke, transformCallback } from "./tauri";
+import { invoke, transformCallback } from './tauri'
 
 export interface Event<T> {
-  type: string;
-  payload: T;
+  type: string
+  payload: T
 }
 
-export type EventCallback<T> = (event: Event<T>) => void;
+export type EventCallback<T> = (event: Event<T>) => void
 
 /**
  * listen to an event from the backend
@@ -19,11 +19,11 @@ function listen<T>(
   once = false
 ): void {
   invoke({
-    cmd: "listen",
+    cmd: 'listen',
     event,
     handler: transformCallback(handler, once),
-    once,
-  });
+    once
+  })
 }
 
 /**
@@ -34,10 +34,10 @@ function listen<T>(
  */
 function emit(event: string, payload?: string): void {
   invoke({
-    cmd: "emit",
+    cmd: 'emit',
     event,
-    payload,
-  });
+    payload
+  })
 }
 
-export { listen, emit };
+export { listen, emit }

+ 47 - 47
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<string, any>;
-  params?: Record<string, any>;
-  body?: Body;
-  followRedirects: boolean;
-  maxRedirections: boolean;
-  connectTimeout: number;
-  readTimeout: number;
-  timeout: number;
-  allowCompression: boolean;
-  responseType?: ResponseType;
-  bodyType: BodyType;
+  method: HttpVerb
+  url: string
+  headers?: Record<string, any>
+  params?: Record<string, any>
+  body?: Body
+  followRedirects: boolean
+  maxRedirections: boolean
+  connectTimeout: number
+  readTimeout: number
+  timeout: number
+  allowCompression: boolean
+  responseType?: ResponseType
+  bodyType: BodyType
 }
 
-export type PartialOptions = Omit<HttpOptions, "method" | "url">;
+export type PartialOptions = Omit<HttpOptions, 'method' | 'url'>
 
 /**
  * makes a HTTP request
@@ -52,9 +52,9 @@ export type PartialOptions = Omit<HttpOptions, "method" | "url">;
  */
 async function request<T>(options: HttpOptions): Promise<T> {
   return await promisified({
-    cmd: "httpRequest",
-    options: options,
-  });
+    cmd: 'httpRequest',
+    options: options
+  })
 }
 
 /**
@@ -67,10 +67,10 @@ async function request<T>(options: HttpOptions): Promise<T> {
  */
 async function get<T>(url: string, options: PartialOptions): Promise<T> {
   return await request({
-    method: "GET",
+    method: 'GET',
     url,
-    ...options,
-  });
+    ...options
+  })
 }
 
 /**
@@ -88,11 +88,11 @@ async function post<T>(
   options: PartialOptions
 ): Promise<T> {
   return await request({
-    method: "POST",
+    method: 'POST',
     url,
     body,
-    ...options,
-  });
+    ...options
+  })
 }
 
 /**
@@ -110,11 +110,11 @@ async function put<T>(
   options: PartialOptions
 ): Promise<T> {
   return await request({
-    method: "PUT",
+    method: 'PUT',
     url,
     body,
-    ...options,
-  });
+    ...options
+  })
 }
 
 /**
@@ -127,10 +127,10 @@ async function put<T>(
  */
 async function patch<T>(url: string, options: PartialOptions): Promise<T> {
   return await request({
-    method: "PATCH",
+    method: 'PATCH',
     url,
-    ...options,
-  });
+    ...options
+  })
 }
 
 /**
@@ -146,10 +146,10 @@ async function deleteRequest<T>(
   options: PartialOptions
 ): Promise<T> {
   return await request({
-    method: "DELETE",
+    method: 'DELETE',
     url,
-    ...options,
-  });
+    ...options
+  })
 }
 
 export default {
@@ -160,5 +160,5 @@ export default {
   patch,
   delete: deleteRequest,
   ResponseType,
-  BodyType,
-};
+  BodyType
+}

+ 2 - 2
api/src/index.ts

@@ -1,2 +1,2 @@
-import * as api from "./bundle";
-export default api;
+import * as api from './bundle'
+export default api

+ 15 - 15
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<Options, "title">;
-export type Permission = "granted" | "denied" | "default";
+export type PartialOptions = Omit<Options, 'title'>
+export type Permission = 'granted' | 'denied' | 'default'
 
 async function isPermissionGranted(): Promise<boolean | null> {
-  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<Permission> {
-  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 }

+ 79 - 79
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<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.App,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.App
+  })
 }
 
 /**
@@ -21,10 +21,10 @@ async function appDir(): Promise<string> {
  */
 async function audioDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Audio,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Audio
+  })
 }
 
 /**
@@ -34,10 +34,10 @@ async function audioDir(): Promise<string> {
  */
 async function cacheDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Cache,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Cache
+  })
 }
 
 /**
@@ -47,10 +47,10 @@ async function cacheDir(): Promise<string> {
  */
 async function configDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Config,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Config
+  })
 }
 
 /**
@@ -60,10 +60,10 @@ async function configDir(): Promise<string> {
  */
 async function dataDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Data,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Data
+  })
 }
 
 /**
@@ -73,10 +73,10 @@ async function dataDir(): Promise<string> {
  */
 async function desktopDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Desktop,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Desktop
+  })
 }
 
 /**
@@ -86,10 +86,10 @@ async function desktopDir(): Promise<string> {
  */
 async function documentDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Document,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Document
+  })
 }
 
 /**
@@ -99,10 +99,10 @@ async function documentDir(): Promise<string> {
  */
 async function downloadDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Download,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Download
+  })
 }
 
 /**
@@ -112,10 +112,10 @@ async function downloadDir(): Promise<string> {
  */
 async function executableDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Executable,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Executable
+  })
 }
 
 /**
@@ -125,10 +125,10 @@ async function executableDir(): Promise<string> {
  */
 async function fontDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Font,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Font
+  })
 }
 
 /**
@@ -138,10 +138,10 @@ async function fontDir(): Promise<string> {
  */
 async function homeDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Home,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Home
+  })
 }
 
 /**
@@ -151,10 +151,10 @@ async function homeDir(): Promise<string> {
  */
 async function localDataDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.LocalData,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.LocalData
+  })
 }
 
 /**
@@ -164,10 +164,10 @@ async function localDataDir(): Promise<string> {
  */
 async function pictureDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Picture,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Picture
+  })
 }
 
 /**
@@ -177,10 +177,10 @@ async function pictureDir(): Promise<string> {
  */
 async function publicDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Public,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Public
+  })
 }
 
 /**
@@ -190,10 +190,10 @@ async function publicDir(): Promise<string> {
  */
 async function resourceDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Resource,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Resource
+  })
 }
 
 /**
@@ -203,10 +203,10 @@ async function resourceDir(): Promise<string> {
  */
 async function runtimeDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Runtime,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Runtime
+  })
 }
 
 /**
@@ -216,10 +216,10 @@ async function runtimeDir(): Promise<string> {
  */
 async function templateDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Template,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Template
+  })
 }
 
 /**
@@ -229,10 +229,10 @@ async function templateDir(): Promise<string> {
  */
 async function videoDir(): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
-    path: "",
-    directory: BaseDirectory.Video,
-  });
+    cmd: 'resolvePath',
+    path: '',
+    directory: BaseDirectory.Video
+  })
 }
 
 /**
@@ -245,10 +245,10 @@ async function resolvePath(
   directory: BaseDirectory
 ): Promise<string> {
   return await promisified<string>({
-    cmd: "resolvePath",
+    cmd: 'resolvePath',
     path,
-    directory,
-  });
+    directory
+  })
 }
 
 export {
@@ -270,5 +270,5 @@ export {
   runtimeDir,
   templateDir,
   videoDir,
-  resolvePath,
-};
+  resolvePath
+}

+ 7 - 7
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<string> {
-  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 }

+ 24 - 24
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<T>(args: any): Promise<T> {
   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 }

+ 8 - 8
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 }

+ 1 - 1
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.

+ 1 - 2
cli/tauri.js/src/api/cli.ts

@@ -9,8 +9,7 @@ function runCliCommand(
   args: Args
 ): { pid: number; promise: Promise<void> } {
   const argsArray = []
-  for (const argName in args) {
-    const argValue = args[argName]
+  for (const [argName, argValue] of Object.entries(args)) {
     if (argValue === false) {
       continue
     }

+ 4 - 0
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"

+ 4 - 0
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

+ 5 - 0
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'

+ 5 - 1
cli/tauri.js/templates/src-tauri/src/main.rs

@@ -5,8 +5,11 @@
 
 mod cmd;
 
+#[derive(tauri::FromTauriContext)]
+struct Context;
+
 fn main() {
-  tauri::AppBuilder::<tauri::flavors::Wry>::new()
+  tauri::AppBuilder::<tauri::flavors::Wry, Context>::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();
 }

+ 0 - 2
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]

+ 2 - 2
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 {},
 }

+ 12 - 12
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::<tauri::flavors::Wry, Context>::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();
 }

+ 1 - 1
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"

+ 0 - 39
tauri-api/build.rs

@@ -1,39 +0,0 @@
-use std::{
-  env,
-  error::Error,
-  fs::{read_to_string, File},
-  io::{BufWriter, Write},
-  path::Path,
-};
-
-pub fn main() -> Result<(), Box<dyn Error>> {
-  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(())
-}

+ 2 - 3
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<Matches> {
-  let config = get_config()?;
+pub fn get_matches(config: &Config) -> crate::Result<Matches> {
   let cli = config
     .tauri
     .cli

+ 15 - 2
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;
+  }
+}

+ 9 - 8
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<String>,
   /// The notification icon.
   icon: Option<String>,
+  /// 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<String>) -> 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

+ 23 - 0
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"

+ 75 - 0
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!()
+        }
+      }
+    }
+  }
+}

+ 120 - 0
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<TokenStream, Error> {
+  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<Config, Error> {
+  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<TokenStream, Error> {
+  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()
+}

+ 164 - 0
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<String, Asset>,
+  filter: HashSet<String>,
+  prefix: PathBuf,
+}
+
+impl IncludeDir {
+  pub fn new(prefix: impl Into<PathBuf>) -> 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<PathBuf>, comp: AssetCompression) -> Result<Self, Error> {
+    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<Path>, comp: AssetCompression) -> Result<Self, Error> {
+    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<PathBuf>) -> Result<Self, Error> {
+    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::<Result<_, _>>()?;
+
+    Ok(self)
+  }
+
+  pub fn build(self) -> Result<TokenStream, Error> {
+    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
+      }
+    })
+  }
+}

+ 19 - 0
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()
+}

+ 5 - 2
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"
+thiserror = "1.0.19"
+phf = { version = "0.8", features = ["macros"] }
+flate2 = "1"

+ 108 - 0
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<PathBuf>) -> 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<PathBuf>,
+    fetch: AssetFetch,
+  ) -> Option<(Box<dyn Read>, 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)
+      }
+    })
+  }
+}

+ 116 - 162
tauri-api/src/config.rs → 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<Config> = 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<Port, D::Error>
 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<String, D::Error>
+where
+  D: Deserializer<'de>,
+{
+  struct PublicPathDeserializer;
+
+  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 default_embedded_server() -> EmbeddedServerConfig {
-  EmbeddedServerConfig {
-    host: default_host(),
-    port: default_port(),
+    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
+    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<CliConfig>,
   /// 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<String, JsonValue>;
 
@@ -314,10 +397,10 @@ type JsonObject = HashMap<String, JsonValue>;
 #[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

+ 4 - 0
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

+ 1 - 3
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]

+ 1 - 121
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<dyn Error>> {
-  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,
-        "<html><body>Build error: Couldn't find ENV: TAURI_DIST_DIR</body></html>"
-      )?;
-      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<dyn Error>> {
-  shared()
-}
-
-fn shared() -> Result<(), Box<dyn Error>> {
-  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" },

+ 28 - 30
tauri/examples/api/src-tauri/Cargo.lock

@@ -1717,27 +1717,33 @@ version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
 dependencies = [
+ "phf_macros",
  "phf_shared",
+ "proc-macro-hack",
 ]
 
 [[package]]
-name = "phf_codegen"
+name = "phf_generator"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
 dependencies = [
- "phf_generator",
  "phf_shared",
+ "rand 0.7.3",
 ]
 
 [[package]]
-name = "phf_generator"
+name = "phf_macros"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
 dependencies = [
+ "phf_generator",
  "phf_shared",
- "rand 0.7.3",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote 1.0.8",
+ "syn 1.0.60",
 ]
 
 [[package]]
@@ -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",
@@ -2438,33 +2443,26 @@ dependencies = [
 ]
 
 [[package]]
-name = "tauri-utils"
-version = "0.5.1"
-dependencies = [
- "anyhow",
- "sysinfo",
- "thiserror",
-]
-
-[[package]]
-name = "tauri_includedir"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7a3644510b11203f42cf6e6384d5c46e0d20e426aa6e05b9110f5e4583a1032"
+name = "tauri-macros"
+version = "0.1.0"
 dependencies = [
  "flate2",
- "phf",
+ "proc-macro2",
+ "quote 1.0.8",
+ "serde",
+ "serde_json",
+ "syn 1.0.60",
+ "tauri-api",
+ "walkdir",
 ]
 
 [[package]]
-name = "tauri_includedir_codegen"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afccce834cf6062a1a19d2d0018fd2a5a2d13d75f547ed0dac2cc3d9813b6ae7"
+name = "tauri-utils"
+version = "0.5.1"
 dependencies = [
- "flate2",
- "phf_codegen",
- "walkdir",
+ "anyhow",
+ "sysinfo",
+ "thiserror",
 ]
 
 [[package]]

+ 9 - 5
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::<tauri::flavors::Wry>::new()
-    .setup(|webview, _source| async move {
-      let mut webview = webview.clone();
+  tauri::AppBuilder::<tauri::flavors::Wry, Context>::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();
 }

+ 28 - 30
tauri/examples/communication/src-tauri/Cargo.lock

@@ -1717,27 +1717,33 @@ version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
 dependencies = [
+ "phf_macros",
  "phf_shared",
+ "proc-macro-hack",
 ]
 
 [[package]]
-name = "phf_codegen"
+name = "phf_generator"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
+checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
 dependencies = [
- "phf_generator",
  "phf_shared",
+ "rand 0.7.3",
 ]
 
 [[package]]
-name = "phf_generator"
+name = "phf_macros"
 version = "0.8.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
+checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c"
 dependencies = [
+ "phf_generator",
  "phf_shared",
- "rand 0.7.3",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote 1.0.8",
+ "syn 1.0.60",
 ]
 
 [[package]]
@@ -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",
@@ -2438,33 +2443,26 @@ dependencies = [
 ]
 
 [[package]]
-name = "tauri-utils"
-version = "0.5.1"
-dependencies = [
- "anyhow",
- "sysinfo",
- "thiserror",
-]
-
-[[package]]
-name = "tauri_includedir"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7a3644510b11203f42cf6e6384d5c46e0d20e426aa6e05b9110f5e4583a1032"
+name = "tauri-macros"
+version = "0.1.0"
 dependencies = [
  "flate2",
- "phf",
+ "proc-macro2",
+ "quote 1.0.8",
+ "serde",
+ "serde_json",
+ "syn 1.0.60",
+ "tauri-api",
+ "walkdir",
 ]
 
 [[package]]
-name = "tauri_includedir_codegen"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "afccce834cf6062a1a19d2d0018fd2a5a2d13d75f547ed0dac2cc3d9813b6ae7"
+name = "tauri-utils"
+version = "0.5.1"
 dependencies = [
- "flate2",
- "phf_codegen",
- "walkdir",
+ "anyhow",
+ "sysinfo",
+ "thiserror",
 ]
 
 [[package]]

+ 11 - 6
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::<tauri::flavors::Wry>::new()
-    .setup(|webview, _source| async move {
-      let mut webview = webview.clone();
+  tauri::AppBuilder::<tauri::flavors::Wry, Context>::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();
 }

+ 38 - 5
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<W> = dyn Fn(W, String) -> BoxFuture<'static, Result<(), String>> + Send + Sync;
 type Setup<W> = 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<Context: AsTauriContext>() -> crate::Result<Self> {
+    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<A: ApplicationExt> {
   /// The JS message handler.
@@ -14,6 +41,8 @@ pub struct App<A: ApplicationExt> {
   setup: Option<Box<Setup<A::Dispatcher>>>,
   /// The HTML of the splashscreen to render.
   splashscreen_html: Option<String>,
+  /// The context the App was created with
+  pub(crate) context: Context,
 }
 
 impl<A: ApplicationExt + 'static> App<A> {
@@ -54,22 +83,25 @@ impl<A: ApplicationExt + 'static> App<A> {
 
 /// The App builder.
 #[derive(Default)]
-pub struct AppBuilder<A: ApplicationExt> {
+pub struct AppBuilder<A: ApplicationExt, C: AsTauriContext> {
   /// The JS message handler.
   invoke_handler: Option<Box<InvokeHandler<A::Dispatcher>>>,
   /// The setup callback, invoked when the webview is ready.
   setup: Option<Box<Setup<A::Dispatcher>>>,
   /// The HTML of the splashscreen to render.
   splashscreen_html: Option<String>,
+  /// The configuration used
+  config: PhantomData<C>,
 }
 
-impl<A: ApplicationExt + 'static> AppBuilder<A> {
+impl<A: ApplicationExt + 'static, C: AsTauriContext> AppBuilder<A, C> {
   /// 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<A: ApplicationExt + 'static> AppBuilder<A> {
   }
 
   /// Builds the App.
-  pub fn build(self) -> App<A> {
-    App {
+  pub fn build(self) -> crate::Result<App<A>> {
+    Ok(App {
       invoke_handler: self.invoke_handler,
       setup: self.setup,
       splashscreen_html: self.splashscreen_html,
-    }
+      context: Context::new::<C>()?,
+    })
   }
 }

+ 63 - 79
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<T> {
@@ -19,17 +19,21 @@ enum Content<T> {
 /// Main entry point for running the Webview
 pub(crate) fn run<A: ApplicationExt + 'static>(application: App<A>) -> 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<A: ApplicationExt + 'static>(application: App<A>) -> 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<A: ApplicationExt + 'static>(application: App<A>) -> crate::Re
 }
 
 #[cfg(all(embedded_server, no_server))]
-fn setup_content() -> crate::Result<Content<String>> {
+fn setup_content(_: &Context) -> crate::Result<Content<String>> {
   panic!("only one of `embedded-server` and `no-server` is allowed")
 }
 
 // setup content for dev-server
 #[cfg(dev)]
-fn setup_content() -> crate::Result<Content<String>> {
-  let config = get()?;
+fn setup_content(context: &Context) -> crate::Result<Content<String>> {
+  let config = &context.config;
   if config.build.dev_path.starts_with("http") {
     #[cfg(windows)]
     {
@@ -99,27 +99,19 @@ fn setup_content() -> crate::Result<Content<String>> {
     }
     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<Content<String>> {
-  let (port, valid) = setup_port()?;
+fn setup_content(context: &Context) -> crate::Result<Content<String>> {
+  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<Content<String>> {
 
 // setup content for no-server
 #[cfg(all(no_server, not(embedded_server)))]
-fn setup_content() -> crate::Result<Content<String>> {
-  let html = include_str!(concat!(env!("OUT_DIR"), "/index.tauri.html"));
+fn setup_content(context: &Context) -> crate::Result<Content<String>> {
   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<String> {
-  let config = get()?;
+fn setup_server_url(port: String, context: &Context) -> crate::Result<String> {
+  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<String> {
 
 // 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<A: ApplicationExt + 'static>(
   content: Content<String>,
   splashscreen_content: Option<Content<String>>,
 ) -> 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<A: ApplicationExt + 'static>(
       }}
       {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<A: ApplicationExt + 'static>(
         } 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::<TauriContext>().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::<TauriContext>().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::<u32>().prop_map(|v| v.to_string()))) {
       let p = port.clone();
+      let context = Context::new::<TauriContext>().unwrap();
 
-      let res = super::setup_server_url(port);
+      let res = super::setup_server_url(port, &context);
 
       match res {
         Ok(url) => assert!(url.contains(&p)),

+ 0 - 1
tauri/src/assets.rs

@@ -1 +0,0 @@
-include!(concat!(env!("OUT_DIR"), "/data.rs"));

+ 0 - 9
tauri/src/cli.rs

@@ -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<Matches> {
-  static MATCHES: Lazy<Option<Matches>> = Lazy::new(|| tauri_api::cli::get_matches().ok());
-
-  &MATCHES
-}

+ 8 - 15
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<D: ApplicationDispatcherExt + 'static>(
   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<D: ApplicationDispatcherExt + 'static>(
           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<D: ApplicationDispatcherExt + 'static>(
           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");
         }

+ 35 - 34
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<D: ApplicationDispatcherExt + 'static>(
@@ -8,42 +9,43 @@ pub async fn load<D: ApplicationDispatcherExt + 'static>(
   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::<Result<Vec<u8>, _>>()
+            .map_err(Into::into)
+        })?;
 
       if asset_type == "image" {
         let mime_type = if asset.ends_with("gif") {
@@ -62,12 +64,11 @@ pub async fn load<D: ApplicationDispatcherExt + 'static>(
           "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" {

+ 6 - 1
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<D: ApplicationDispatcherExt>(
   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);
       }

+ 1 - 7
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<dyn FnOnce() + Send>;
 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,
 };

+ 29 - 29
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<std::io::Cursor<Vec<u8>>> {
-  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<impl Read> {
+  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)
 }

+ 0 - 0
tauri/test/fixture/dist/__tauri.js


+ 1 - 0
tauri/test/fixture/src-tauri/tauri.conf.json

@@ -9,6 +9,7 @@
       "active": true
     },
     "bundle": {
+      "identifier": "studio.tauri.example",
       "active": true
     },
     "allowlist": {