runner.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. const
  2. chokidar = require('chokidar')
  3. const debounce = require('lodash.debounce')
  4. const path = require('path')
  5. const { readFileSync, writeFileSync } = require('fs-extra')
  6. const
  7. { spawn } = require('./helpers/spawn')
  8. const onShutdown = require('./helpers/on-shutdown')
  9. const generator = require('./generator')
  10. const entry = require('./entry')
  11. const { appDir, tauriDir } = require('./helpers/app-paths')
  12. const logger = require('./helpers/logger')
  13. const log = logger('app:tauri', 'green')
  14. const warn = logger('app:tauri (template)', 'red')
  15. class Runner {
  16. constructor () {
  17. this.pid = 0
  18. this.tauriWatcher = null
  19. onShutdown(() => {
  20. this.stop()
  21. })
  22. }
  23. async run (cfg) {
  24. const devPath = cfg.build.devPath
  25. if (this.pid) {
  26. if (this.devPath !== devPath) {
  27. await this.stop()
  28. } else {
  29. return
  30. }
  31. }
  32. this.__manipulateToml(toml => {
  33. this.__whitelistApi(cfg, toml)
  34. })
  35. const runningDevServer = devPath.startsWith('http')
  36. let inlinedAssets = []
  37. if (!runningDevServer) {
  38. inlinedAssets = await this.__parseHtml(path.resolve(appDir, devPath))
  39. }
  40. generator.generate({
  41. devPath: runningDevServer ? devPath : path.resolve(appDir, devPath),
  42. inlinedAssets,
  43. ...cfg.tauri
  44. })
  45. entry.generate(tauriDir, cfg)
  46. this.devPath = devPath
  47. const features = runningDevServer ? ['dev-server'] : []
  48. const startDevTauri = () => {
  49. return this.__runCargoCommand({
  50. cargoArgs: ['run'].concat(features.length ? ['--features', ...features] : []),
  51. dev: true
  52. })
  53. }
  54. // Start watching for tauri app changes
  55. this.tauriWatcher = chokidar
  56. .watch([
  57. path.join(tauriDir, 'src'),
  58. path.join(tauriDir, 'Cargo.toml'),
  59. path.join(tauriDir, 'build.rs'),
  60. path.join(appDir, 'tauri.conf.js')
  61. ], {
  62. watchers: {
  63. chokidar: {
  64. ignoreInitial: true
  65. }
  66. }
  67. })
  68. .on('change', debounce(async (path) => {
  69. await this.__stopCargo()
  70. if (path.includes('tauri.conf.js')) {
  71. this.run(require('./helpers/tauri-config')({ ctx: cfg.ctx }))
  72. } else {
  73. startDevTauri()
  74. }
  75. }, 1000))
  76. return startDevTauri()
  77. }
  78. async build (cfg) {
  79. this.__manipulateToml(toml => {
  80. this.__whitelistApi(cfg, toml)
  81. })
  82. const inlinedAssets = await this.__parseHtml(cfg.build.distDir)
  83. generator.generate({
  84. inlinedAssets,
  85. ...cfg.tauri
  86. })
  87. entry.generate(tauriDir, cfg)
  88. const features = [
  89. cfg.tauri.embeddedServer.active ? 'embedded-server' : 'no-server'
  90. ]
  91. const buildFn = target => this.__runCargoCommand({
  92. cargoArgs: [cfg.tauri.bundle.active ? 'tauri-cli' : 'build', '--features', ...features]
  93. .concat(cfg.ctx.debug ? [] : ['--release'])
  94. .concat(target ? ['--target', target] : [])
  95. })
  96. if (cfg.ctx.debug || !cfg.ctx.targetName) {
  97. // on debug mode or if no target specified,
  98. // build only for the current platform
  99. await buildFn()
  100. } else {
  101. const targets = cfg.ctx.target.split(',')
  102. for (const target of targets) {
  103. await buildFn(target)
  104. }
  105. }
  106. }
  107. __parseHtml (indexDir) {
  108. const Inliner = require('@tauri-apps/tauri-inliner')
  109. const jsdom = require('jsdom')
  110. const { JSDOM } = jsdom
  111. const inlinedAssets = []
  112. return new Promise((resolve, reject) => {
  113. new Inliner(path.join(indexDir, 'index.html'), (err, html) => {
  114. if (err) {
  115. reject(err)
  116. } else {
  117. const dom = new JSDOM(html)
  118. const document = dom.window.document
  119. document.querySelectorAll('link').forEach(link => {
  120. link.removeAttribute('rel')
  121. link.removeAttribute('as')
  122. })
  123. const tauriScript = document.createElement('script')
  124. tauriScript.text = readFileSync(path.join(tauriDir, 'tauri.js'))
  125. document.body.insertBefore(tauriScript, document.body.firstChild)
  126. writeFileSync(path.join(indexDir, 'index.tauri.html'), dom.serialize())
  127. resolve(inlinedAssets)
  128. }
  129. }).on('progress', event => {
  130. const match = event.match(/([\S\d]+)\.([\S\d]+)/g)
  131. match && inlinedAssets.push(match[0])
  132. })
  133. })
  134. }
  135. stop () {
  136. return new Promise((resolve, reject) => {
  137. this.tauriWatcher && this.tauriWatcher.close()
  138. this.__stopCargo().then(resolve)
  139. })
  140. }
  141. __runCargoCommand ({
  142. cargoArgs,
  143. extraArgs,
  144. dev = false
  145. }) {
  146. return new Promise(resolve => {
  147. this.pid = spawn(
  148. 'cargo',
  149. extraArgs
  150. ? cargoArgs.concat(['--']).concat(extraArgs)
  151. : cargoArgs,
  152. tauriDir,
  153. code => {
  154. if (code) {
  155. warn()
  156. warn('⚠️ [FAIL] Cargo CLI has failed')
  157. warn()
  158. process.exit(1)
  159. }
  160. if (this.killPromise) {
  161. this.killPromise()
  162. this.killPromise = null
  163. } else if (dev) {
  164. warn()
  165. warn('Cargo process was killed. Exiting...')
  166. warn()
  167. process.exit(0)
  168. }
  169. }
  170. )
  171. resolve()
  172. })
  173. }
  174. __stopCargo () {
  175. const pid = this.pid
  176. if (!pid) {
  177. return Promise.resolve()
  178. }
  179. log('Shutting down tauri process...')
  180. this.pid = 0
  181. return new Promise((resolve, reject) => {
  182. this.killPromise = resolve
  183. process.kill(pid)
  184. })
  185. }
  186. __manipulateToml (callback) {
  187. const toml = require('@tauri-apps/toml')
  188. const tomlPath = path.join(tauriDir, 'Cargo.toml')
  189. const tomlFile = readFileSync(tomlPath)
  190. const tomlContents = toml.parse(tomlFile)
  191. callback(tomlContents)
  192. const output = toml.stringify(tomlContents)
  193. writeFileSync(tomlPath, output)
  194. }
  195. __whitelistApi (cfg, tomlContents) {
  196. const tomlFeatures = []
  197. if (cfg.tauri.whitelist.all) {
  198. tomlFeatures.push('all-api')
  199. } else {
  200. const whitelist = Object.keys(cfg.tauri.whitelist).filter(w => cfg.tauri.whitelist[w] === true)
  201. tomlFeatures.push(...whitelist)
  202. }
  203. if (cfg.tauri.edge.active) {
  204. tomlFeatures.push('edge')
  205. }
  206. tomlContents.dependencies.tauri.features = tomlFeatures
  207. }
  208. }
  209. module.exports = Runner