Skip to content

Instantly share code, notes, and snippets.

@hfossli
Last active January 2, 2024 00:27
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hfossli/c0818637d67beda27f9767a811dc76df to your computer and use it in GitHub Desktop.
Save hfossli/c0818637d67beda27f9767a811dc76df to your computer and use it in GitHub Desktop.
Start a node.js server using NSTask in Swift.
let mainBundle = NSBundle.mainBundle()
let pathToNode = mainBundle.pathForResource("node", ofType: "")! as String
let pathToNodeApp = mainBundle.pathForResource("server", ofType: "js", inDirectory: "Server")! as String
let pathToAppFolder = (pathToNodeApp as NSString).stringByDeletingLastPathComponent
task = NodeTask(nodeJSPath: pathToNode, appPath: pathToNodeApp, currentDirectoryPath: pathToAppFolder)
task.launch()
import Foundation
import Cocoa
class NodeTask: NSObject {
private let processIdentifier = NSProcessInfo.processInfo().processIdentifier
private let nodeTask = NSTask()
private let readPipe = NSPipe()
private let errorPipe = NSPipe()
private let queue = dispatch_queue_create("NodeTask.output.queue", DISPATCH_QUEUE_SERIAL)
private var running = false
init(nodeJSPath: String, appPath: String, currentDirectoryPath: String) {
super.init()
readPipe.fileHandleForReading.readabilityHandler = { [unowned self] (handler: NSFileHandle!) in
dispatch_async(self.queue) {
if self.running {
let data = handler.readDataToEndOfFile()
self.onRead(data)
}
}
}
errorPipe.fileHandleForReading.readabilityHandler = { [unowned self] (handler: NSFileHandle!) in
dispatch_async(self.queue) {
if self.running {
let data = handler.readDataToEndOfFile()
self.onError(data)
}
}
}
nodeTask.currentDirectoryPath = currentDirectoryPath
nodeTask.launchPath = nodeJSPath
nodeTask.arguments = [appPath, "\(processIdentifier)"]
nodeTask.qualityOfService = .UserInitiated
nodeTask.standardOutput = readPipe
nodeTask.standardError = errorPipe
}
deinit {
self.quit()
}
func launch() {
if !running {
print("------------------------------ Node launch ------------------------------")
running = true
nodeTask.launch()
}
}
func quit() {
dispatch_sync(self.queue) {
self.terminate()
}
}
private func terminate() {
if running {
print("------------------------------ Node quit ------------------------------")
running = false
readPipe.fileHandleForReading.closeFile()
errorPipe.fileHandleForReading.closeFile()
nodeTask.terminate()
}
}
private func stringFromData(data: NSData) -> String {
return NSString(data: data, encoding: NSUTF8StringEncoding)!.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet()) as String
}
private func onRead(data: NSData) {
let text = stringFromData(data)
if text != "" {
print("Node: \(text)")
}
}
private func onError(data: NSData) {
let text = stringFromData(data)
if text != "" {
print("------------------------------ Node fatal error ------------------------------")
print(text)
self.terminate()
}
}
}
@MrLoh
Copy link

MrLoh commented Jul 16, 2018

Hi, I'm trying to start a node server from a swift mac app and came across your example here. It seems that Bundle.main.path(forResource: "node", ofType: "") is null though, is there anythng special I have to do to make node available in my app?

@hfossli
Copy link
Author

hfossli commented Jul 16, 2018

‪Thanks for asking! I bundled the node executable in the app by copying it and adding it to the app target in xcode. Did the same with the js code. ‬

@zackshapiro
Copy link

@hfossli, thank you so much for this! I'm trying to adapt it to run a Next.js project that's inside of my Xcode project at ./web. I'm trying to run yarn dev in applicationDidFinishLaunching. I'd love any tips you might have on how to do that given this code, if you have a moment. It's a brand new, Next.js project without any additional configuration. I usually just cd into its root and run yarn dev to start a local server.

@hfossli
Copy link
Author

hfossli commented Jan 8, 2021

I would suggest you bundle it all to one file (main.jsbundle) and try to run that.

@hfossli
Copy link
Author

hfossli commented Jan 8, 2021

What's your goal? Embedding next js in app and render it in a webview? In that case you don't need this. Then a simple main.jsbundle and index.html is all you need for the webview.

@elmcapp
Copy link

elmcapp commented May 27, 2022

Is there a way to do this in objective-c instead of swift

@hfossli-agens
Copy link

I'm pretty sure you can do convert it line by line to objc

@elmcapp
Copy link

elmcapp commented May 27, 2022

I'm pretty sure you can do convert it line by line to objc

Would you be willing to convert this.
I have no experience with swift or objective-c. I'm currently learning

@hfossli
Copy link
Author

hfossli commented May 27, 2022

No

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment