これまでは、Yarn Workspacesの基本的な使い方 、Yarn Workspacesで複数のExpoプロジェクトを管理する方法 を記事にしてきました。
blog.kimizuka.org
blog.kimizuka.org
今回は、Yarn Workspacesを使って複数のNext.js + Electronプロジェクトを管理します。
Next.js + Electronのプロジェクトの始めかた
こちらの example を使えば楽々です。
ターミナルで、
yarn create next-app --example with-electron-typescript アプリ名
を実行するだけで、Next.js + Electron + TypeScriptのプロジェクトが作成できます。
Yarn WorkspacesでNext.js + Electronを管理する
しかし、Next.js + Electronを単純にYarn Workspacesで管理するとうまく実行できません。
「monorepo-next-electron」ディレクトリ配下で「next-electron-a」と「next-electron-b」を管理する前提で、順を追ってセットアップしていきます。
❶ package.jsonをつくる
yarn init -y monorepo-next-electron
で、package.jsonを作成し、下記のように編集します。
{ "name": "monorepo-next-electron", "version": "1.0.0", "license": "MIT", "private": true, "workspaces": [ "next-electron-a", "next-electron-b" ], "scripts": { "dev:a": "yarn workspace next-electron-a dev", "dev:b": "yarn workspace next-electron-b dev" } }
❷ next-electron-a、next-electron-bを作成する
yarn create next-app --example with-electron-typescript next-electron-a
yarn create next-app --example with-electron-typescript next-electron-b
❸ next-electron-a/package.json と next-electron-b/package.json に「name」と「version」を追加する
nameとversionがないとエラーになるため追加します。
next-electron-a/package.json
{ "name": "next-electron-a", "version": "1.0.0", "private": true, "main": "main/index.js", "productName": "ElectronTypescriptNext", "scripts": { "clean": "rimraf dist main renderer/out renderer/.next", "dev": "npm run build-electron && electron .", "build-renderer": "next build renderer && next export renderer", "build-electron": "tsc -p electron-src", "build": "npm run build-renderer && npm run build-electron", "pack-app": "npm run build && electron-builder --dir", "dist": "npm run build && electron-builder", "type-check": "tsc -p ./renderer/tsconfig.json && tsc -p ./electron-src/tsconfig.json" }, "dependencies": { "electron-is-dev": "^1.1.0", "electron-next": "^3.1.5", "next": "^13.0.7", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "^14.14.6", "@types/react": "^16.9.9", "@types/react-dom": "^16.9.9", "electron": "^13", "electron-builder": "^23.0.3", "next": "latest", "rimraf": "^3.0.0", "typescript": "^4.0.5" }, "build": { "asar": true, "files": [ "main", "renderer/out" ] } }
next-electron-b/package.json
{ "name": "next-electron-b", "version": "1.0.0", "private": true, "main": "main/index.js", "productName": "ElectronTypescriptNext", "scripts": { "clean": "rimraf dist main renderer/out renderer/.next", "dev": "npm run build-electron && electron .", "build-renderer": "next build renderer && next export renderer", "build-electron": "tsc -p electron-src", "build": "npm run build-renderer && npm run build-electron", "pack-app": "npm run build && electron-builder --dir", "dist": "npm run build && electron-builder", "type-check": "tsc -p ./renderer/tsconfig.json && tsc -p ./electron-src/tsconfig.json" }, "dependencies": { "electron-is-dev": "^1.1.0", "electron-next": "^3.1.5", "next": "^13.0.7", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/node": "^14.14.6", "@types/react": "^16.9.9", "@types/react-dom": "^16.9.9", "electron": "^13", "electron-builder": "^23.0.3", "next": "latest", "rimraf": "^3.0.0", "typescript": "^4.0.5" }, "build": { "asar": true, "files": [ "main", "renderer/out" ] } }
❹ next-electron-a と next-electron-b に next を追加する
yarn workspace next-electron-a add next
yarn workspace next-electron-b add next
❺ next-electron-a/electron-src/index.ts、next-electron-b/electron-src/index.tsを編集する
Yarn Workspacesを使うとパスが変わってくるので変更します。
next-electron-a/electron-src/index.ts
// Native import { join } from 'path' import { format } from 'url' // Packages import { BrowserWindow, app, ipcMain, IpcMainEvent } from 'electron' import isDev from 'electron-is-dev' import prepareNext from 'electron-next' // Prepare the renderer once the app is ready app.on('ready', async () => { await prepareNext('./next-electron-a/renderer') // パスを変更 const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, contextIsolation: false, preload: join(__dirname, 'preload.js'), }, }) const url = isDev ? 'http://localhost:8000/' : format({ pathname: join(__dirname, '../renderer/out/index.html'), protocol: 'file:', slashes: true, }) mainWindow.loadURL(url) }) // Quit the app once all windows are closed app.on('window-all-closed', app.quit) // listen the channel `message` and resend the received message to the renderer process ipcMain.on('message', (event: IpcMainEvent, message: any) => { console.log(message) setTimeout(() => event.sender.send('message', 'hi from electron'), 500) })
next-electron-b/electron-src/index.ts
// Native import { join } from 'path' import { format } from 'url' // Packages import { BrowserWindow, app, ipcMain, IpcMainEvent } from 'electron' import isDev from 'electron-is-dev' import prepareNext from 'electron-next' // Prepare the renderer once the app is ready app.on('ready', async () => { await prepareNext('./next-electron-b/renderer') // パスを変更 const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, contextIsolation: false, preload: join(__dirname, 'preload.js'), }, }) const url = isDev ? 'http://localhost:8000/' : format({ pathname: join(__dirname, '../renderer/out/index.html'), protocol: 'file:', slashes: true, }) mainWindow.loadURL(url) }) // Quit the app once all windows are closed app.on('window-all-closed', app.quit) // listen the channel `message` and resend the received message to the renderer process ipcMain.on('message', (event: IpcMainEvent, message: any) => { console.log(message) setTimeout(() => event.sender.send('message', 'hi from electron'), 500) })
これにて準備完了です。
あとは、
yarn dev:a
yarn dev:b
で、「next-electron-a」、「next-electron-b」が起動します。
distにも対応する
ここまでで、開発には困らなくなったのですが、
このままだと、
yarn workspace next-electron-a dist
yarn workspace next-electron-b dist
を実行した際に、
Cannot compute electron version from installed node modules - none of the possible electron modules are installed and version ("^13") is not fixed in project. See https://github.com/electron-userland/electron-builder/issues/3984#issuecomment-504968246
というエラーが出ます。
色々試した結果、
"electron": "^13"
を、
"electron": "13"
に修正することでbuildできるようになることがわかりました。
ただ、buildが成功しても、アプリを起動するとwebpackが生成したJSファイルがことごとくリンクが切れています。
こちらも色々試した結果、devとdistでprepareNextに渡すpathを変更する必要があることがわかったので、isDevでpathを切り替えるように修正しました。
修正後
next-electron-a/electron-src/index.ts
// Native import { join } from 'path' import { format } from 'url' // Packages import { BrowserWindow, app, ipcMain, IpcMainEvent } from 'electron' import isDev from 'electron-is-dev' import prepareNext from 'electron-next' // Prepare the renderer once the app is ready app.on('ready', async () => { await prepareNext(isDev ? './next-electron-a/renderer' : './renderer') // devのときはパスを変更 const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, contextIsolation: false, preload: join(__dirname, 'preload.js'), }, }) const url = isDev ? 'http://localhost:8000/' : format({ pathname: join(__dirname, '../renderer/out/index.html'), protocol: 'file:', slashes: true, }) mainWindow.loadURL(url) }) // Quit the app once all windows are closed app.on('window-all-closed', app.quit) // listen the channel `message` and resend the received message to the renderer process ipcMain.on('message', (event: IpcMainEvent, message: any) => { console.log(message) setTimeout(() => event.sender.send('message', 'hi from electron'), 500) })
next-electron-b/electron-src/index.ts
// Native import { join } from 'path' import { format } from 'url' // Packages import { BrowserWindow, app, ipcMain, IpcMainEvent } from 'electron' import isDev from 'electron-is-dev' import prepareNext from 'electron-next' // Prepare the renderer once the app is ready app.on('ready', async () => { await prepareNext(isDev ? './next-electron-b/renderer' : './renderer') // devのときはパスを変更 const mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: false, contextIsolation: false, preload: join(__dirname, 'preload.js'), }, }) const url = isDev ? 'http://localhost:8000/' : format({ pathname: join(__dirname, '../renderer/out/index.html'), protocol: 'file:', slashes: true, }) mainWindow.loadURL(url) }) // Quit the app once all windows are closed app.on('window-all-closed', app.quit) // listen the channel `message` and resend the received message to the renderer process ipcMain.on('message', (event: IpcMainEvent, message: any) => { console.log(message) setTimeout(() => event.sender.send('message', 'hi from electron'), 500) })
package.json
{ "name": "monorepo-next-electron", "version": "1.0.0", "license": "MIT", "private": true, "workspaces": [ "next-electron-a", "next-electron-b" ], "scripts": { "dev:a": "yarn workspace next-electron-a dev", "dist:a": "yarn workspace next-electron-a dist", "dev:b": "yarn workspace next-electron-b dev" "dist:b": "yarn workspace next-electron-b dist", } }
これにて、無事にdevもdistも実行できるようになりました。