ProvidePluginとjQuery(+α)のベストプラクティス

# ProvidePluginはビルドタイム（node.jsの世界）のコンテキストだからsrcのものを参照したくない。じゃあどうする？


## tl;dr

* [LocalPaths](https://docs.npmjs.com/files/package.json#local-paths)を使ってjQuery(+α)を読み込ませちゃいなよ

## ProvidePluginとは？

* Webpack の ProvidePlugin は、node_modules 内 or ユーザ指定のパスのファイルで exports されたオブジェクトをグローバルに提供する

https://webpack.js.org/plugins/provide-plugin/

## 抱えている問題


* パス指定でファイルを用意する場合 ESModules の文脈は対応してくれない
* ということはソースファイルに置きたくない
* でもどこに置くのがベストだろう🤔

### 実装詳細

#### ディレクトリ構造

```
.
├── dist
│   ├── budle.js
│   └── index.html
├── package.json
├── src
│   ├── html
│   │   └── index.html
│   └── js
│       └── lib
│       │   └── jquery-with-plugin.js
│       └── index.js
├── webpack.config.js
└── yarn.lock
```

#### webpack.config.js

```javascript:webpack.config.js
const { resolve } = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugins');

const src = resolve(`${__dirname}/src`);
const dist = resolve(`${__dirname}/dist`);

module.exports = {
  devtool: '#inline-source-map',
  context: src,
  entry: './js/index.js',
  output: {
    path: dist,
    filename: 'budle.js'
  },
  resolve: {
    modules: [src, 'node_modules'],
    extensions: ['.js', '.jsx', '.json']
  },
  plugins: [
    new webpack.ProvidePlugin({
      jQuery: "jquery",
      $: "src/lib/jquery-with-plugins.js"
    }),
    new HtmlWebpackPlugin({
      template: './html/index.html'
    })
  ]
};

```

### jquery-with-plugins.js

```javascript
// import $ from 'jquery'; // 本当はこう書きたい
const $ = require('jquery');

// import 'jquery-ujs'; // え、いまさらjquery-ujs！？というツッコミは無し😇
require('jquery-ujs');

// export default $; // 本当はこう書きたい
module.exports = $;
```

### index.js

```javascript:index.js
$(() => {
  const app = document.createElement('div');
  app.setAttribute('id', 'app');
  document.body.appendChild(app);
  const $app = $('#app');
  console.log($app);
});
```

### 問題詳細

* ESModules で書けないものが src 以下にあるのはちょっとモニョる
* そもそも ProvidePlugin のユースケースは node_modules 配下のものを提供するのでは？
* だとするとそれと同等のレイヤに置きたい

## 解決策

* jquery-with-plugins を npm package にしちゃえば良い
* わざわざプライベートパッケージにするほど汎用的じゃない
*  [LocalPaths](https://docs.npmjs.com/files/package.json#local-paths)を使えばいいじゃん！！
* @azuさんの記事~~からパクリました~~を参考にさせていただきました<br>
https://efcl.info/2016/05/02/npm-package-mixed-multiple-versions-demo/

### ディレクトリ構造

```
.
├── dist
│   ├── budle.js
│   └── index.html
├── package.json
├── packages
│   └── jquery-with-plugins
│       ├── index.js
│       ├── package.json
│       └── yarn.lock
├── src
│   ├── html
│   │   └── index.html
│   └── js
│       └── index.js
├── webpack.config.js
└── yarn.lock
```

### ./package.json

```json
{
  "dependencies": {
    "html-webpack-plugin": "^3.2.0",
    "jquery": "^3.4.1",
    "webpack": "^4.35.3",
    "webpack-cli": "^3.3.5",
    "jquery-with-plugins": "file:packages/jquery-with-plugins"
  }
}
```

### packages/jquery-with-plugins/package.json

```json
{
  "name": "jquery-with-plugins",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "jquery": "^3.4.1",
    "jquery-ujs": "^1.2.2"
  }
}
```


### packages/jquery-with-plugins/index.js


```javascript:packages/jquery-with-plugins/index.js
const $ = require('jquery');
require('jquery-ujs');
module.exports = $;
```

### webpack.config.js

```diff:webpack.config.js
 const { resolve } = require('path');
 const webpack = require('webpack');
 const HtmlWebpackPlugin = require('html-webpack-plugins');
 
 const src = resolve(`${__dirname}/src`);
 const dist = resolve(`${__dirname}/dist`);
 
 module.exports = {
   devtool: '#inline-source-map',
   context: src,
   entry: './js/index.js',
   output: {
     path: dist,
     filename: 'budle.js'
   },
   resolve: {
     modules: [src, 'node_modules'],
     extensions: ['.js', '.jsx', '.json']
   },
   plugins: [
     new webpack.ProvidePlugin({
       jQuery: "jquery",
-      $: "src/lib/jquery-with-plugins.js"
+      $: "jquery-with-plugins"
     }),
     new HtmlWebpackPlugin({
       template: './html/index.html'
     })
   ]
 };

```

## 見てみる

* 下記コマンド入力後、ブラウザにて http://localhost:8080 参照

```bash
npm -g http-server
yarn
npx webpack --mode development
http-server ./dist
```

* hello world と表示され、chrome 上の debug console には `jQuery.fn.init [div#app]` の出力がされている


## 感想・驚いたこと

* runtime の世界と buildtime の世界は区別したい
* Node.js ESModules 標準搭載はよ
* LocalPaths ってそれように用意したディレクトリ側に node_modules 作るのかと思っていたらそうじゃなかったので優秀（symlinkと相性悪かった）