Rock Book

history | grep `future`

JS周りのリハビリ備忘録(React + Babel + Webpack4)

今さらながらReact 入門しようと思ったんですが、JS 界隈の時代の流れに完全に置いてけぼりになっていました。

https://html5hive.org/react-tutorial/

↑ これをやってるときに、「あれ、browser-sync で表示されるのはいいとして、普通にfile open で開けないんだっけ?」と思っていろいろ調べたのがきっかけです。

※ ちなみに、browser-sync

$ browser-sync start --server . --files "**/*"

単純にfile open すると、ブラウザのconsole に下記エラーが表示されていました。

Access to XMLHttpRequest at 'file://~/tmp/my_react_tutorial2/main.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, https.
transform.load @ browser.min.js:4

次に、

<script type="text/babel" src="main.js"></script>

type="text/babel" を消してみたところ、

Uncaught SyntaxError: Unexpected token <

というエラーが出ました。以下、リハビリのためにこのあたりを解消していった備忘録です。

サマリ

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.13.6
BuildVersion:   17G4015
$ npm -v
5.6.0
$ npx -v
9.7.1

before (ファイル構成)

$ tree .
.
├── assets
│   ├── css
│   │   └── base.css
│   └── images
│       └── home.jpg
├── index.html
└── main.js

3 directories, 4 files

作業

$ npm i -D babel-core babel-loader@7 babel-preset-react webpack webpack-cli
$ npm list --depth=0
webpack-demo@1.0.0 ~/tmp/my_react_tutorial2
├── babel-core@6.26.3
├── babel-loader@7.1.5
├── babel-preset-react@6.24.1
├── lodash@4.17.11
├── webpack@4.28.2
└── webpack-cli@3.1.2
$ cat webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['react']
            }
          }
        ],
      }
    ]
  }
};
$ cat package.json
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-preset-react": "^6.24.1",
    "webpack": "^4.28.2",
    "webpack-cli": "^3.1.2"
  }
}
$ mkdir src
$ mv main.js src/index.js
$ tree . -L 1
.
├── babel.config.js
├── dist
├── node_modules
├── package-lock.json
├── package.json
├── src
└── webpack.config.js

3 directories, 4 files

$ tree dist/ src
dist/
├── assets
│   ├── css
│   │   └── base.css
│   └── images
│       └── home.jpg
├── index.html
└── main.js
src
└── index.js

3 directories, 5 files

$ tree dist/ src/
dist/
├── assets
│   ├── css
│   │   └── base.css
│   └── images
│       └── home.jpg
├── index.html
└── main.js
src/
└── index.js

3 directories, 5 files

webpack した結果をfile open

$ npx webpack
$ mv index.html assets/ dist/
$ open dist/index.html

これで、browser-sync したときと同じように表示されました。

以下、調査メモ

まず、手動でbabel でtranspile するとどうなるのか。

babeljs.io

$ npm install --save-dev @babel/core @babel/cli @babel/preset-env
$ npm install --save @babel/polyfill

$ cat babel.config.js
const presets = [
  [
    "@babel/env",
    {
      targets: {
        edge: "17",
        firefox: "60",
        chrome: "67",
        safari: "11.1",
      },
      useBuiltIns: "usage",
    },
  ],
];

module.exports = { presets };
$ ./node_modules/.bin/babel src --out-dir lib

{ SyntaxError: ~/tmp/my_react_tutorial2/src/main.js: Unexpected token (114:8)

  112 |       //as a prop, so we need a separate id for that.
  113 |       return (
> 114 |         <Home
      |         ^
  115 |           key={index}
  116 |           id={index}
  117 |           onToggleSave={toggleSave}
    at Parser.raise (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:4051:15)
    at Parser.unexpected (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:5382:16)
    at Parser.parseExprAtom (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6541:20)
    at Parser.parseExprSubscripts (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6104:21)
    at Parser.parseMaybeUnary (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6083:21)
    at Parser.parseExprOps (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:5968:21)
    at Parser.parseMaybeConditional (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:5940:21)
    at Parser.parseMaybeAssign (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:5887:21)
    at Parser.parseParenAndDistinguishExpression (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6699:28)
    at Parser.parseExprAtom (~/tmp/my_react_tutorial2/node_modules/@babel/parser/lib/index.js:6473:21)
  pos: 3211,
  loc: Position { line: 114, column: 8 },
  code: 'BABEL_PARSE_ERROR' }

JSX がパースできていないらしい。

React はJSX と使うのがいいよ って話でBabel が入っていると認識していたのに、Babel だけではJSX はパースできないとは何事だ。

stackoverflow.com

を見ていると、webpack の話が出てくる。

そもそもBabel って何者だっけ、とかwebpack とはどういう関係だっけ、とか思い始めて調べ始める。

qiita.com

mizchi.hatenablog.com

www.youtube.com

↑ このあたりはとても参考になりました。

とりあえず概念を掴んだので、webpack からbabel を呼ぼうとする

$ npm run webpack
npm ERR! missing script: webpack

npm ERR! A complete log of this run can be found in:
npm ERR!     ~/.npm/_logs/2018-12-26T07_26_02_958Z-debug.log

ちょっとよくわからない。あ、local にインストールしたからか、と思って --development をつけても一緒。

Getting Started

ここらへんをみると、どうやら最近は npx を使うようになっているらしいのでやってみる

$ npx webpack

Insufficient number of arguments or no entry found.
Alternatively, run 'webpack(-cli) --help' for usage info.

Hash: b98875b6bc270ed9ed54
Version: webpack 4.28.2
Time: 64ms
Built at: 2018/12/26 16:30:09

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in Entry module not found: Error: Can't resolve './src' in '~/tmp/my_react_tutorial2'

src ディレクトリは存在しているのに。。

ERROR in Entry module not found: Error: Can't resolve './src' in 'C:\Idessign' · Issue #6858 · webpack/webpack · GitHub

を見ると、設定ファイルが無いとデフォルトで src/index.js を見に行く、とあるので、rename してみる

$ mv src/main.js src/index.js
$ npx webpack
Hash: 2887e62448d27ce303a0
Version: webpack 4.28.2
Time: 285ms
Built at: 2018/12/26 16:34:37
 1 asset
Entrypoint main = main.js
[0] ./src/index.js 268 bytes {0} [built] [failed] [1 error]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in ./src/index.js 114:8
Module parse failed: Unexpected token (114:8)
You may need an appropriate loader to handle this file type.
|       //as a prop, so we need a separate id for that.
|       return (
>         <Home
|           key={index}
|           id={index}

元のエラーに戻った。

これでwebpack が使えるようになったっぽいので、元のStackoverflow に戻って、 見よう見まねでwebpack のconfig を書いて実行してみる

$ cat webpack.config.js
{
  test: /\.jsx?$/,
  exclude: /node_modules/,
  use: [
    {
      loader: 'babel-loader',
      options: {
        presets: ['react']
      }
    }
  ],
}

$ npx webpack
Unexpected token :

シンタックスエラー。リハビリ中なのでしょうがない。正しくは、上記を rules に入れなきゃいけなかった (Stackoverflow にも書いてあった)

module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['react']
            }
          }
        ]
      }
    ]
  }
};
$ npx webpack

Insufficient number of arguments or no entry found.
Alternatively, run 'webpack(-cli) --help' for usage info.

Hash: 53a7a87a9524e153353b
Version: webpack 4.28.2
Time: 49ms
Built at: 2018/12/26 16:41:37

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in Entry module not found: Error: Can't resolve 'babel-loader' in '~/tmp/my_react_tutorial2'

足りないモジュールを入れてリトライ

$ npm install babel-core babel-loader --save-dev
npm WARN babel-loader@8.0.4 requires a peer of @babel/core@^7.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN babel-loader@8.0.4 requires a peer of webpack@>=2 but none is installed. You must install peer dependencies yourself.
npm WARN my-awesome-package@1.0.0 No description
npm WARN my-awesome-package@1.0.0 No repository field.
npm WARN my-awesome-package@1.0.0 No license field.

+ babel-loader@8.0.4
+ babel-core@6.26.3
added 48 packages, removed 520 packages and moved 5 packages in 8.407s

$ npx webpack
One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:
 - webpack-cli (https://github.com/webpack/webpack-cli)
   The original webpack full-featured CLI.
We will use "npm" to install the CLI via "npm install -D".
Do you want to install 'webpack-cli' (yes/no): yes
Installing 'webpack-cli' (running 'npm install -D webpack-cli')...
npm WARN babel-loader@8.0.4 requires a peer of @babel/core@^7.0.0 but none is installed. You must install peer dependencies yourself.
npm WARN babel-loader@8.0.4 requires a peer of webpack@>=2 but none is installed. You must install peer dependencies yourself.
npm WARN webpack-cli@3.1.2 requires a peer of webpack@^4.x.x but none is installed. You must install peer dependencies yourself.
npm WARN my-awesome-package@1.0.0 No description
npm WARN my-awesome-package@1.0.0 No repository field.
npm WARN my-awesome-package@1.0.0 No license field.

+ webpack-cli@3.1.2
added 76 packages in 2.462s
{ Error: Cannot find module 'webpack-cli'
    at Function.Module._resolveFilename (module.js:555:15)
    at Function.Module._load (module.js:482:25)
    at Module.require (module.js:604:17)
    at require (internal/module.js:11:18)
    at runCommand.then (~/.npm/_npx/45193/lib/node_modules/webpack/bin/webpack.js:142:5)
    at <anonymous>
    at process._tickCallback (internal/process/next_tick.js:160:7) code: 'MODULE_NOT_FOUND' }

$ npm list | grep webpack-cli
└─┬ webpack-cli@3.1.2
npm ERR! peer dep missing: @babel/core@^7.0.0, required by babel-loader@8.0.4
npm ERR! peer dep missing: webpack@>=2, required by babel-loader@8.0.4
npm ERR! peer dep missing: webpack@^4.x.x, required by webpack-cli@3.1.2

webpack-cli は入ってるはずなのに、存在しないからインストールしようとしてくる。yes と答えてインストールしても、また同じエラーになる。

どういうことなの。。。

$ which npx
/usr/local/bin/npx

$ ll /usr/local/bin/npx

lrwxr-xr-x  1 admin  46 12 17  2017 /usr/local/bin/npx@ -> /usr/local/lib/node_modules/npm/bin/npx-cli.js
$ ll /usr/local/lib/node_modules/ | grep webpack
$ 

どうやらnpm のlocal にうまくwebpack が入っていなかったようで、もう一度npm i -D webpack する

$ ll node_modules/ | grep webpack
drwxr-xr-x    7 staff    224 12 26 16:48 webpack-cli/
#=> webpackが入ってない!
$ npx webpack
Hash: c0af3a1e4f45824bccdb
Version: webpack 4.28.2
Time: 263ms
Built at: 2018/12/26 16:55:19
 1 asset
Entrypoint main = main.js
[0] ./src/index.js 2.81 KiB {0} [built] [failed] [1 error]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Cannot find module '@babel/core'
 babel-loader@8 requires Babel 7.x (the package '@babel/core'). If you'd like to use Babel 6.x ('babel-core'), you should install 'babel-loader@7'.
    at Function.Module._resolveFilename (module.js:555:15)
    at Function.Module._load (module.js:482:25)
    at Module.require (module.js:604:17)
    at require (~/tmp/my_react_tutorial2/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
    at Object.<anonymous> (~/tmp/my_react_tutorial2/node_modules/babel-loader/lib/index.js:10:11)
    at Module._compile (~/tmp/my_react_tutorial2/node_modules/v8-compile-cache/v8-compile-cache.js:178:30)
    at Object.Module._extensions..js (module.js:671:10)
    at Module.load (module.js:573:32)
    at tryModuleLoad (module.js:513:12)
    at Function.Module._load (module.js:505:3)
    at Module.require (module.js:604:17)
    at require (~/tmp/my_react_tutorial2/node_modules/v8-compile-cache/v8-compile-cache.js:159:20)
    at loadLoader (~/tmp/my_react_tutorial2/node_modules/loader-runner/lib/loadLoader.js:13:17)
    at iteratePitchingLoaders (~/tmp/my_react_tutorial2/node_modules/loader-runner/lib/LoaderRunner.js:169:2)
    at runLoaders (~/tmp/my_react_tutorial2/node_modules/loader-runner/lib/LoaderRunner.js:362:2)
    at NormalModule.doBuild (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModule.js:280:3)
    at NormalModule.build (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModule.js:427:15)
    at Compilation.buildModule (~/tmp/my_react_tutorial2/node_modules/webpack/lib/Compilation.js:633:10)
    at moduleFactory.create (~/tmp/my_react_tutorial2/node_modules/webpack/lib/Compilation.js:1019:12)
    at factory (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModuleFactory.js:405:6)
    at hooks.afterResolve.callAsync (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModuleFactory.js:155:13)
    at AsyncSeriesWaterfallHook.eval [as callAsync] (eval at create (~/tmp/my_react_tutorial2/node_modules/tapable/lib/HookCodeFactory.js:32:10), <anonymous>:6:1)
    at AsyncSeriesWaterfallHook.lazyCompileHook (~/tmp/my_react_tutorial2/node_modules/tapable/lib/Hook.js:154:20)
    at resolver (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModuleFactory.js:138:29)
    at process.nextTick (~/tmp/my_react_tutorial2/node_modules/webpack/lib/NormalModuleFactory.js:342:9)
    at process._tickCallback (internal/process/next_tick.js:150:11)

バージョン依存があったらしい。勝手に解決してくれないのか。自分で指定して入れる。

npm i -D babel-loader@7
$ npx webpack
Hash: 75e3609ad92c952b5d1e
Version: webpack 4.28.2
Time: 636ms
Built at: 2018/12/26 16:59:07
 1 asset
Entrypoint main = main.js
[0] ./src/index.js 1.59 KiB {0} [built] [failed] [1 error]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error: Couldn't find preset "react" relative to directory "~/tmp/my_react_tutorial/src"
    at ~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:293:19
    at Array.map (<anonymous>)
    at OptionManager.resolvePresets (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:275:20)
    at OptionManager.mergePresets (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:264:10)
    at OptionManager.mergeOptions (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:249:14)
    at OptionManager.init (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/options/option-manager.js:368:12)
    at File.initOptions (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/index.js:212:65)
    at new File (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/file/index.js:135:24)
    at Pipeline.transform (~/tmp/my_react_tutorial/node_modules/babel-core/lib/transformation/pipeline.js:46:16)
    at transpile (~/tmp/my_react_tutorial/node_modules/babel-loader/lib/index.js:50:20)
    at Object.module.exports (~/tmp/my_react_tutorial/node_modules/babel-loader/lib/index.js:173:20)

今度はreact が見つからない、とのことなので、関係ありそうなモジュールを追加

$ npm i -D babel-preset-react
$ npx webpack
Hash: 3500f1c5383324b5f4a0
Version: webpack 4.28.2
Time: 412ms
Built at: 2018/12/26 17:00:28
  Asset      Size  Chunks             Chunk Names
main.js  3.22 KiB       0  [emitted]  main
Entrypoint main = main.js
[0] ./src/index.js 3.84 KiB {0} [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/

これで、ようやく dist 配下に、bundle されたmain.js が置かれた。

時代の流れについていくのは大変だなぁ。

あれ、何しようとしてたんだっけ