Atom Editorを改造してReactアプリを作る手順

(前回の続き: [Atomコード再利用に向けた最新版の仕様調査](https://qiita.com/craftzdog/items/f61116c379199a0a45a2))

個人で作っている[Inkdrop](https://inkdrop.app/)というMarkdownノートアプリの改良に向けて、Atomのコードを再利用したい。
せっかく内部仕様を理解したし、いっそのことAtomをベースにアプリを組み直したら面白いんじゃないか？と思い立って、Atomを改造してReactが動くようにした。
GitHubにソースを公開したので、手っ取り早く手元で試したい人はそちらをどうぞ：

* [craftzdog/atom-react-app: An empty React app based on Atom Editor](https://github.com/craftzdog/atom-react-app)
* [変更部分のdiff](https://github.com/craftzdog/atom-react-app/commit/98ba122cc3eff2aaea7180cad970148bd85cc0da)

<img width="912" alt="68747470733a2f2f6769746875622e636f6d2f63726166747a646f672f61746f6d2d72656163742d6170702f626c6f622f6d61737465722f646f63732f72656163742d6170702e706e673f7261773d74727565.png" src="https://qiita-image-store.s3.amazonaws.com/0/70028/847af690-932c-2c3c-46ed-1df248d5b13e.png">

**誰向けなのか**： 今から新しいアプリを作ろうとしていて、Atomの高度なパッケージ拡張機能やテーマ機能を組み込みたい、あるいは真似したいという人は覗いてみると参考になるかもしれない。

動かし方は[公式ドキュメント](https://flight-manual.atom.io/hacking-atom/sections/hacking-on-atom-core/)と同じ:

```bash
$ git clone git@github.com:craftzdog/atom-react-app
$ cd atom-react-app
$ script/bootstrap
$ atom --dev --foreground
```

改造してからしばらく経つので、もし動かなければ自分で解決してください。その技量がなければおすすめしない。

結果的にInkdropをこのプロジェクトに移植するのは諦めた。理由は次のとおり：

* 全ソースコードがアプリにバンドルされる仕様になっている
  * プロプライエタリなアプリ開発者としては、プライベートなコードを難読化させておきたい
  * そのためにビルド手順を変更するのは結構たいへんそう (途中までやった)
* 完全にイチからアプリを組み直すのは時間がかかりすぎて頓挫しそう
  * Inkdropもなんだかんだで3年開発しているので、細かなところまでイチから組み直すのは相当大変
  * 既存のコードを改善して部分的に拝借して再利用するのが現実的

ただ、今回の取り組みによってAtomの深いところまで理解できたので良かった。既にその意義は実感している。
例えば起動スピードを高速化するためにv8 snapshotをどのように生成して組み込んでいるのか、とか。
[IPCのヘルパーモジュール](https://github.com/craftzdog/atom-react-app/blob/master/src/ipc-helpers.js)なんかは一般的なElectronアプリにも広く使える便利なものだと思う。

# 作業記録

以降は、[前回](https://qiita.com/craftzdog/items/f61116c379199a0a45a2)の続きの改造作業メモ。
Inkdropを組む前提でメモっていて、文脈が分かりづらいかもしれないので注意。

## やっぱビューは自分で実装した方が良さげ

Atomのエディタは魅力的だが、現在のプラグインとの互換性は無くなるし、ヘディングのフォントサイズを大きくしたりとかは出来なさそうな感じがする。
Reactでレンダリングした方が自分のコード資産を再利用できる。
React Nativeとのコード共用性は保持したい。
Flowも使いたい。
そうすると `compile-cache` 周りの手入れが必要になってくるな。

とりあえずAtomの標準package群を全て外して空っぽにする。
`package.json` を `"packageDependencies":{}`と書き換え。
アプリを起動すると空っぽのウインドウが表示されるだけになった。エラーは特にない。良い。

## プラグインのインストールUI調査

`apm`はコマンドラインツールだがAtomはGUIでもパッケージを管理できる。
それはどうやっているのか調べる。

`settings-view` パッケージが担当しているっぽい。

`node_modules/settings-view/lib/install-panel.js` にインストールUIがある。

`node_modules/settings-view/lib/package-manager.coffee` にパッケージの管理ロジックが書かれている。
直接apmコマンドを実行しているっぽい：

```coffee
61   runCommand: (args, callback) ->
62     command = atom.packages.getApmPath()
63     outputLines = []
64     stdout = (lines) -> outputLines.push(lines)
65     errorLines = []
66     stderr = (lines) -> errorLines.push(lines)
67     exit = (code) ->
68       callback(code, outputLines.join('\n'), errorLines.join('\n'))
69
70     args.push('--no-color')
71
72     if atom.config.get('core.useProxySettingsWhenCallingApm')
73       bufferedProcess = new BufferedProcess({command, args, stdout, stderr, exit, autoStart: false})
74       if atom.resolveProxy?
75         @setProxyServersAsync -> bufferedProcess.start()
76       else
77         @setProxyServers -> bufferedProcess.start()
78       return bufferedProcess
79     else
80       return new BufferedProcess({command, args, stdout, stderr, exit})
81
```

やっぱそうなってくるか・・
apmコマンドに`--json`オプションを付加して呼び出している。`apm install --json` とかも出来るっぽい。よいね。

## Atom上でReactを動かしてレンダリングする

`react` `react-dom` とかをインストール。

```diff
--- a/package.json
+++ b/package.json
@@ -34,7 +34,9 @@
     "autocomplete-snippets": "https://www.atom.io/api/packages/autocomplete-snippets/versions/1.12.0/tarball",
     "autoflow": "file:packages/autoflow",
     "autosave": "https://www.atom.io/api/packages/autosave/versions/0.24.6/tarball",
-    "babel-core": "5.8.38",
+    "babel-core": "6.26.3",
+    "babel-preset-env": "^1.7.0",
+    "babel-preset-react": "^6.24.1",
     "background-tips": "https://www.atom.io/api/packages/background-tips/versions/0.28.0/tarball",
     "base16-tomorrow-dark-theme": "file:packages/base16-tomorrow-dark-theme",
     "base16-tomorrow-light-theme": "file:packages/base16-tomorrow-light-theme",
@@ -137,8 +139,11 @@
     "pathwatcher": "8.0.1",
     "postcss": "5.2.4",
     "postcss-selector-parser": "2.2.1",
     "pouchdb": "7.0.0",
     "property-accessors": "^1.1.3",
     "random-words": "0.0.1",
+    "react": "^16.6.3",
+    "react-dom": "^16.6.3",
     "resolve": "^1.1.6",
     "scandal": "^3.1.0",
     "scoped-property-store": "^0.17.0",
```

babelが古くてトランスパイルが通らないので`babel-core`をアップデート。設定も変更:

```diff
--- a/static/babelrc.json
+++ b/static/babelrc.json
@@ -1,7 +1,11 @@
 {
-  "breakConfig": true,
   "sourceMap": "inline",
-  "blacklist": ["es6.forOf", "useStrict"],
-  "optional": ["asyncToGenerator"],
-  "stage": 0
+  "presets": [
+    ["env", {
+      "targets": { "electron": "2.0.12" },
+      "useBuiltIns": "usage",
+      "debug": false
+    }],
+    "react"
+  ]
 }
```

`atom-environment.js`を以下のように改造する：

```diff
--- a/src/atom-environment.js
+++ b/src/atom-environment.js
@@ -43,6 +43,8 @@ const TextEditor = require('./text-editor')
 const TextBuffer = require('text-buffer')
 const TextEditorRegistry = require('./text-editor-registry')
 const AutoUpdateManager = require('./auto-update-manager')
+const React = require('react')
+const { render, unmountComponentAtNode } = require('react-dom')

 let nextId = 0

@@ -271,6 +273,15 @@ class AtomEnvironment {
     this.observeAutoHideMenuBar()

     this.disposables.add(this.applicationDelegate.onDidChangeHistoryManager(() => this.history.loadState()))
+
+    const AppContainer = require('./components/app-container').default
+    const root = document.createElement('div')
+    root.classList.add('app-container')
+    document.body.append(root)
+    const element = React.createElement(AppContainer)
+
+    render(element, root)
+    this.element = element
   }
```

`src/components/app-container.js`を追加:

```js
/** @babel */
import * as React from 'react'

export default function AppContainer () {
  return <div>Inkdrop on Atom!</div>
}
```

上手く動いた。素晴らしい。

## スタイルの読み込み

`static/atom.less` がエントリポイント。`less-compile-cache`を通してオンザフライでCSSに変換して読み込んでいる。
ではこれはどこから読み込まれているのか？

`src/theme-manager.js`から:

```js
220   loadBaseStylesheets () {
221     this.reloadBaseStylesheets()
222   }
223
224   reloadBaseStylesheets () {
225     this.requireStylesheet('../static/atom', -2, true)
226   }
```

という訳で `atom.less`をいじる：

```diff
--- a/static/atom.less
+++ b/static/atom.less
@@ -27,3 +27,12 @@

 // Atom UI library
 @import "../node_modules/atom-ui/atom-ui.less";
+
+.app-container {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 30px;
+}
```

できた：

![Screenshot](https://qiita-image-store.s3.amazonaws.com/0/70028/cc592062-5aa7-4ad3-b4e2-08609a0512c4.png)


前回の開発ではAtomのソースコードの一部を拝借して構築したが、今回はAtomをフォークして開発したほうがいろいろ都合がいいかもしれない。
そして意外にもあっさりと実現可能性が確認できた。

## Reactを使用した状態でビルドするとエラーになる

```
Error: Cannot use `console` functions in the snapshot.
    at Object.consoleNoop (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:98521)
    at Object.../node_modules/scheduler/cjs/scheduler.production.min.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:14:1641204)
    at customRequire (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:99295)
    at Object.../node_modules/scheduler/index.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:14:695932)
    at customRequire (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:99295)
    at Object.../node_modules/react-dom/cjs/react-dom.production.min.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:14:128467)
    at customRequire (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:99295)
    at Object.../node_modules/react-dom/index.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:11:415063)
    at customRequire (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:99295)
    at Object.../src/atom-environment.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:102859)

  at checkExecSyncError (child_process.js:601:13)
  at Object.execFileSync (child_process.js:621:13)
  at electronLink.then (/Users/***/Developments/local/inkdrop/atom/script/lib/generate-startup-snapshot.js:94:18)
  at <anonymous>:null:null
```

エラーの箇所を見ると React が原因っぽい： `This browser doesn't support requestAnimationFrame.`
https://reactjs.org/docs/javascript-environment-requirements.html
なのでsnapshotを生成する時はReactのレンダリング部分を実行しないようにしてみる。
shimが必要・・？

```js
global.requestAnimationFrame = (callback) => {
    setTimeout(callback, 0);
};
```

## ソースコードは隠蔽できるか

現状では `app.asar`に全てのソースコードが同梱される仕様になっている。
なんとかそれは阻止できないだろうか。

`src/main-process/main.js` を読んでもソースコードを直接読み込んでるっぽい・・

`initialize-application-window.js` をwebpackなどでコンパイルすれば良いか。
でもそれだとsnapshotの効力がなくなってしまうのではないか？
いや、external設定で外部依存コードはバンドルしないようにすればいいか。

## 本番ビルドが立ち上がらない

`markdown-preview` パッケージが影響しているっぽい。原因は不明。
`markdown-preview`を含まない場合、不正なasarがoutputされる。asarをunpackしようとすると以下のようなエラーになる：

```sh
asar extract app.asar asarrr
undefined:1
{"files":{"benchmarks":{"files":{"benchmark-runner.js":{"size":2205,"offset":"0"}}},"dot-atom":{"files":{".gitignore":{"size":57,"offset":"2205"},"init.coffee":{"size":386,"offset":"2262"},"keymap.cson":{"size":1333,"offset":"2648"},"packages":{"files":{"README.md":{"size":60,"offset":"3981"}}},"snippets.cson":{"size":710,"offset":"4041"},"styles.less":{"size":712,"offset":"4751"}}},"exports":{"files":{"atom.js":{"size":1258,"offset":"5463"},"clipboard.js":{"size":275,"offset":"6721"},"ipc.js":{"size":273,"offset":"6996"},"remote.js":{"size":266,"offset":"7269"},"shell.js":{"size":263,"offset":"7535"},"web-frame.js":{"size":273,"offset":"7798"}}},"node_modules":{"files":{".dependencies-fingerprint":{"size":40,"offset":"8071"},"@atom":{"files":{"nsfw":{"files":{"build":{"files":{"Release":{"files":{".deps":{"files":{"Release":{"files":{"obj.target":{"files":{"nsfw":{"files":{"src":{"files":{"osx":{"files":{}}}}}}}}}}}},"nsfw.node":{"size":88796,"unpacked":true},"obj.target":{"files":{"nsfw":{"fil

SyntaxError: Unexpected end of JSON input
    at JSON.parse (<anonymous>)
    at Object.module.exports.readArchiveHeaderSync (/Users/***/.nvm/versions/node/v7.9.0/lib/node_modules/asar/lib/disk.js:90:24)
    at Object.module.exports.readFilesystemSync (/Users/***/.nvm/versions/node/v7.9.0/lib/node_modules/asar/lib/disk.js:95:25)
    at Object.module.exports.extractAll (/Users/***/.nvm/versions/node/v7.9.0/lib/node_modules/asar/lib/asar.js:191:27)
    at Command.<anonymous> (/Users/***/.nvm/versions/node/v7.9.0/lib/node_modules/asar/bin/asar.js:66:15)
    at Command.listener (/Users/***/.nvm/versions/node/v7.9.0/lib/node_modules/asar/node_modules/commander/index.js:315:8)
    at emitTwo (events.js:106:13)
    at Command.emit (events.js:194:7)
    at Command.parseArgs (/Users/***/.nvm/versions/node/v7.9.0/lib/node_modules/asar/node_modules/commander/index.js:654:12)
    at Command.parse (/Users/***/.nvm/versions/node/v7.9.0/lib/node_modules/asar/node_modules/commander/index.js:474:21)
```

ためしに `markdown-preview` パッケージの中身を空っぽにしてみたが、特に問題なくビルドできた。
何が影響しているのかわからない。たぶんasarパッケージャのバグを踏んだのかもしれない。

無事 WindowsでもUbuntuでもビルドできた。

## Workspaceの理解

## Todo

 * eslint/prettierとかの導入をどうするか
 * webpack
 * demo版のビルド
 * code signingの理解


## Webpack

webpackでビルドしたjsがelectron-linkに通らない。

```
Error: ENOENT: no such file or directory, open 'markdown-preview'
  at Object.fs.openSync (fs.js:646:18)
  at Object.fs.readFileSync (fs.js:551:33)
  at /Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/generate-snapshot-script.js:30:27
  at Generator.next (<anonymous>:null:null)
  at step (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/generate-snapshot-script.js:3:191)
  at /Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/generate-snapshot-script.js:3:361
  at <anonymous>:null:null
```

正規のビルド方法と比較してみたところ、どうやら生成したjsそのものの問題ではなさそう。
`markdown-preview`の`package.json`のmainが違う！

```diff
--- /Users/***/Developments/local/inkdrop/atom/out.mine/app/node_modules/markdown-preview/package.json 2018-11-24 11:33:39.000000000 +0900
+++ /Users/***/Developments/local/inkdrop/atom/out/app/node_modules/markdown-preview/package.json      2018-11-24 11:22:15.000000000 +0900
@@ -16,20 +16,20 @@
   "name": "markdown-preview",
   "repository": {
     "type": "git",
     "url": "git+https://github.com/atom/markdown-preview.git"
   },
   "version": "0.159.25",
   "_atomModuleCache": {
     "version": 1,
     "dependencies": [],
     "extensions": {
-      ".coffee": [
-        "lib/main.coffee"
+      ".js": [
+        "lib/main.js"
       ],
       ".json": [
         "package.json"
       ]
     },
     "folders": []
   }
 }
```

`_atomModuleCache`がcoffeeのままだ。
babelとcoffee-scriptによるトランスパイルを実行していないからではないか。
あたり！無事`markdown-preview`がrequireできた。
しかし別の問題が見つかった：

```
Unable to transform source code for module /Users/***/Developments/local/inkdrop/atom/out/app/node_modules/argparse/lib/argument_parser.js.
Error: /Users/***/Developments/local/inkdrop/atom/out/app/node_modules/argparse/lib/argument_parser.js
Cannot replace with lazy function because the supplied node does not belong to an assignment expression or a variable declaration!
    at FileRequireTransform.replaceAssignmentOrDeclarationWithLazyFunction (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/file-require-transform.js:180:11)
    at Context.visitIdentifier (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/file-require-transform.js:72:18)
    at Context.invokeVisitorMethod (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:344:49)
    at Visitor.PVp.visitWithoutReset (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:196:32)
    at visitChildren (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:246:25)
    at Visitor.PVp.visitWithoutReset (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:204:20)
    at visitChildren (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:246:25)
    at Visitor.PVp.visitWithoutReset (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:204:20)
    at visitChildren (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:246:25)
    at Visitor.PVp.visitWithoutReset (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:204:20)
    at NodePath.each (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path.js:101:26)
    at visitChildren (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:219:18)
    at Visitor.PVp.visitWithoutReset (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:204:20)
    at visitChildren (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:246:25)
    at Visitor.PVp.visitWithoutReset (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:204:20)
    at visitChildren (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:246:25)
    at Visitor.PVp.visitWithoutReset (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:204:20)
    at Visitor.PVp.visit (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:133:29)
    at Object.visit (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/node_modules/ast-types/lib/path-visitor.js:101:55)
    at FileRequireTransform.replaceDeferredRequiresWithLazyFunctions (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/file-require-transform.js:68:20)
    at FileRequireTransform.apply (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/file-require-transform.js:25:10)
    at /Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/generate-snapshot-script.js:57:56
    at Generator.next (<anonymous>:null:null)
    at step (/Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/generate-snapshot-script.js:3:191)
    at /Users/***/Developments/local/inkdrop/atom/script/node_modules/electron-link/lib/generate-snapshot-script.js:3:361
    at <anonymous>:null:null
```

`require.resolve`しているやつが必要としているモジュールが、electron-linkに対応していない。
このあたり： `src/workspace.js`:

```js
1994         const task = Task.once(
1995           require.resolve('./replace-handler'),
1996           outOfProcessPaths,
1997           regex.source,
1998           flags,
1999           replacementText,
2000           () => {
2001             outOfProcessFinished = true
2002             checkFinished()
2003           }
2004         )
```

`scandal` というパッケージを例外リストに入れてみる。
通った！しかし、`require.resolve` を使っている箇所が沢山見つかるのが気になる・・おそらく実行時にエラーになりそうな予感。
次の問題：

```
Minifying startup scriptSyntaxError: Invalid assignment
  at JS_Parse_Error.get (<anonymous>:75:23)
  at process.<anonymous> (/Users/***/Developments/local/inkdrop/atom/script/build:56:19)
  at emitTwo (events.js:126:13)
  at process.emit (events.js:214:7)
  at emitPendingUnhandledRejections (internal/process/promises.js:94:22)
  at runMicrotasksCallback (internal/process/next_tick.js:124:9)
  at _combinedTickCallback (internal/process/next_tick.js:131:7)
  at process._tickCallback (internal/process/next_tick.js:180:9)
```

おい・・。ES6に対応していないのでは。
[terser-js/terser: JavaScript parser, mangler, optimizer and beautifier toolkit for ES6+](https://github.com/terser-js/terser)を使っている。これはES6に対応しているはずなんだが。
最新版を使うようにしてみる。・・・no luck.
webpackに以下を指定することで解決：

```js
      optimization: {
        nodeEnv: false
      }
```

次の問題：

```
Verifying if snapshot can be executed via `mksnapshot`
/Users/***/Developments/local/inkdrop/atom/out/startup.js:1
var snapshotAuxiliaryData={};function generateSnapshot(){let process={};function get_process(){return process}function createElement(e){return{innerHTML:"",style:{}}}Object.defineProperties(process,{platform:{value:"darwin",enumerable:!1},argv:{value:[],enumerable:!1},env:{value:{NODE_ENV:"production"},enumerable:!1}});let documentElement={textContent:"",style:{cssFloat:""}},document={};function get_document(){return document}Object.defineProperties(document,{createElement:{value:createElement,enumerable:!1},addEventListener:{value:function(){},enumerable:!1},documentElement:{value:documentElement,enumerable:!1},oninput:{value:{},enumerable:!1},onchange:{value:{},enumerable:!1}});let global={};function get_global(){return global}Object.defineProperties(global,{document:{value:document,enumerable:!1},process:{value:process,enumerable:!1},WeakMap:{value:WeakMap,enumerable:!1},isGeneratingSnapshot:{value:!0,enumerable:!1}});let window={};function get

Error: Cannot require module "crypto".
To use Node's require you need to call `snapshotResult.setGlobals` first!
    at require (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:1662)
    at customRequire (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:2113)
    at Object.crypto (/Users/***/Developments/local/inkdrop/atom/out/startup.js:22:174800)
    at __webpack_require__ (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:2443)
    at Object.../src/atom-environment.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:13981)
    at __webpack_require__ (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:2443)
    at Object.../src/initialize-application-window.coffee (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:212614)
    at __webpack_require__ (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:2443)
    at /Users/***/Developments/local/inkdrop/atom/out/startup.js:1:3515
    at Object.../src/initialize-application-window.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:3604)
Error: Command failed: /Users/***/Developments/local/inkdrop/atom/out/Atom Dev.app/Contents/MacOS/Atom Dev /Users/***/Developments/local/inkdrop/atom/script/verify-snapshot-script /Users/***/Developments/local/inkdrop/atom/out/startup.js
/Users/***/Developments/local/inkdrop/atom/out/startup.js:1
var snapshotAuxiliaryData={};function generateSnapshot(){let process={};function get_process(){return process}function createElement(e){return{innerHTML:"",style:{}}}Object.defineProperties(process,{platform:{value:"darwin",enumerable:!1},argv:{value:[],enumerable:!1},env:{value:{NODE_ENV:"production"},enumerable:!1}});let documentElement={textContent:"",style:{cssFloat:""}},document={};function get_document(){return document}Object.defineProperties(document,{createElement:{value:createElement,enumerable:!1},addEventListener:{value:function(){},enumerable:!1},documentElement:{value:documentElement,enumerable:!1},oninput:{value:{},enumerable:!1},onchange:{value:{},enumerable:!1}});let global={};function get_global(){return global}Object.defineProperties(global,{document:{value:document,enumerable:!1},process:{value:process,enumerable:!1},WeakMap:{value:WeakMap,enumerable:!1},isGeneratingSnapshot:{value:!0,enumerable:!1}});let window={};function get

Error: Cannot require module "crypto".
To use Node's require you need to call `snapshotResult.setGlobals` first!
    at require (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:1662)
    at customRequire (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:2113)
    at Object.crypto (/Users/***/Developments/local/inkdrop/atom/out/startup.js:22:174800)
    at __webpack_require__ (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:2443)
    at Object.../src/atom-environment.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:13981)
    at __webpack_require__ (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:2443)
    at Object.../src/initialize-application-window.coffee (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:212614)
    at __webpack_require__ (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:2443)
    at /Users/***/Developments/local/inkdrop/atom/out/startup.js:1:3515
    at Object.../src/initialize-application-window.js (/Users/***/Developments/local/inkdrop/atom/out/startup.js:1:3604)

    at checkExecSyncError (child_process.js:601:13)
    at Object.execFileSync (child_process.js:621:13)
    at electronLink.then (/Users/***/Developments/local/inkdrop/atom/script/lib/generate-startup-snapshot.js:101:18)
    at <anonymous>:null:null
```

ちょっと待て。electron-linkもwebpackと同じように依存モジュールをbundleしてるじゃん・・。
という事は、electron-link + minifyで作ったファイルを普通にアプリに含めればいいだけでは。
もちろん、`require.resolve`問題はまだ残るが。

## プラン2

ちょっとこれ以上現状のビルド手順に手を加えると、最新版にキャッチアップする時に死にそうではある。
そこで、自分の書いたコードだけminifyして難読化すると良いのではないか？
copy assetsでは自分のコードはコピーしない。
自分のコードだけをwebpackなどであらかじめパッケージするとか。

あるいは、もう隠すのは諦めるとか。
悪用されたところで対して影響ないし。
セキュリティ上問題なければそれでいいかな。

あるいはコピーしない。

electron-link化されたやつから外部依存コードが取れたらいいんだけど。

## electron-linkでバンドルする

electron-linkは`node_modules`にある依存ファイルもバンドルしてくれることが判った。
という事は、もうwebpackを使わずにこれを使ってバンドルすれば良いことになる。
minifyも上手く動くようだし。

問題は、もう一つの作業ディレクトリを用意する必要があることだ。
babelやcoffeeなどをトランスパイルしたファイルを置く場所が必要になる。
一度トランスパイルしたファイルを出力して、それをelectron-linkにかける。
その後に、electron-linkでintermediate dirに出力する。

その他のファイルのコピーをどうするか問題

## そもそも `src` 配下の `initialize-application-window.js` は要らない

だから以下の手順を取れば良い：

1. アプリのpackagingの前にelectron-linkでバンドリング
2. 不要なファイルを削除する
3. アプリのパッケージ作成

一旦この構成で組んでみることにする。

## mainプロセスのソースファイルバンドル問題

dynamic importを多用しているので、エントリポイントを単純にwebpackでバンドルしても上手く動かない。
だからatomのコードはそのままにしといて、inkdropのコードだけをwebpackでバンドルするようにする。
今のところはそれを検証できないので、一旦そうするつもりで進めてみることにする。

別にatomのコードはminifyしなくても良いかもしれない。それは今後どんな変更を加えるかによるが。

## `atom-workspace`の理解

itemsは表示できる領域すべてを指すようだ。

```js
218     this.panelContainers = {
219       top: new PanelContainer({viewRegistry: this.viewRegistry, location: 'top'}),
220       left: new PanelContainer({viewRegistry: this.viewRegistry, location: 'left', dock: this.paneContainers.left}),
221       right: new PanelContainer({viewRegistry: this.viewRegistry, location: 'right', dock: this.paneContainers.right}),
222       bottom: new PanelContainer({viewRegistry: this.viewRegistry, location: 'bottom', dock: this.paneContainers.bottom}),
223       header: new PanelContainer({viewRegistry: this.viewRegistry, location: 'header'}),
224       footer: new PanelContainer({viewRegistry: this.viewRegistry, location: 'footer'}),
225       modal: new PanelContainer({viewRegistry: this.viewRegistry, location: 'modal'})
226     }
```

たぶん入れ子に出来るようになっている。
しかし明示的な名前は無いような気がする。dockとは何なのだろうか。
たぶんドラッグとかにも対応しているので、かなり機能豊富な印象。
これをわざわざ再利用しなくてもいいんじゃないだろうか。

## eslint、prettier関連をどうするか

とりあえずAtomのコードは地道に全て.eslintignoreに加える。

## prod用のdependenciesだけコピーする

```diff
diff --git a/script/lib/copy-assets.js b/script/lib/copy-assets.js
index 27b5f086c..8dbf666ee 100644
--- a/script/lib/copy-assets.js
+++ b/script/lib/copy-assets.js
@@ -3,6 +3,7 @@

 'use strict'

+const execSync = require('child_process').execSync
 const path = require('path')
 const fs = require('fs-extra')
 const CONFIG = require('../config')
@@ -11,16 +12,23 @@ const includePathInPackagedApp = require('./include-path-in-packaged-app')

 module.exports = function () {
   console.log(`Copying assets to ${CONFIG.intermediateAppPath}`)
+
+  let depFiles = execSync('npm ls --prod --parseable')
+    .toString()
+    .split('\n')
+    .slice(1)
+    .slice(0, -1)
+
   let srcPaths = [
     path.join(CONFIG.repositoryRootPath, 'benchmarks', 'benchmark-runner.js'),
     path.join(CONFIG.repositoryRootPath, 'dot-atom'),
     path.join(CONFIG.repositoryRootPath, 'exports'),
-    path.join(CONFIG.repositoryRootPath, 'node_modules'),
     path.join(CONFIG.repositoryRootPath, 'package.json'),
     path.join(CONFIG.repositoryRootPath, 'static'),
     path.join(CONFIG.repositoryRootPath, 'src'),
     path.join(CONFIG.repositoryRootPath, 'vendor')
   ]
+  srcPaths = srcPaths.concat(depFiles)
   srcPaths = srcPaths.concat(glob.sync(path.join(CONFIG.repositoryRootPath, 'spec', '*.*'), {ignore: path.join('**', '*-spec.*')}))
   for (let srcPath of srcPaths) {
     fs.copySync(srcPath, computeDestinationPath(srcPath), {filter: includePathInPackagedApp, dereference: true})
```

以上。