ktty1220 / cheerio-httpcli

iconvによる文字コード変換とcheerioによるHTMLパースを組み込んだNode.js用HTTPクライアントモジュール
MIT License
262 stars 28 forks source link

Error: Cannot find module '**/node_modules/os-locale/index.js' #31

Closed forest1102 closed 5 years ago

forest1102 commented 5 years ago

問題

cheerio-httpcliをインポートしたファイルをWebpackでバンドルしたものを実行しようとすると以下のようにエラーが出てしまいます。 何か解決法はありますか?

Error: Cannot find module '**//basblu/node_modules/os-locale/index.js'
    at webpackEmptyContext (**//basblu/lib/bundle.js:67218:10)
    at ./node_modules/require-uncached/index.js.module.exports (**//basblu/lib/bundle.js:67264:78)
    at Object../node_modules/cheerio-httpcli/lib/client.js (**//basblu/lib/bundle.js:15494:23)
    at __webpack_require__ (**//basblu/lib/bundle.js:20:30)
    at Object../node_modules/cheerio-httpcli/lib/core.js (**//basblu/lib/bundle.js:16104:21)
    at __webpack_require__ (**//basblu/lib/bundle.js:20:30)
    at Object../node_modules/cheerio-httpcli/index.js (**//basblu/lib/bundle.js:14206:18)
    at __webpack_require__ (**//basblu/lib/bundle.js:20:30)
    at Object../src/index.ts (**//basblu/lib/bundle.js:84295:16)
    at __webpack_require__ (**//basblu/lib/bundle.js:20:30)

webpack.config.js

const path = require('path')

module.exports = {
  mode: 'development',
  entry: './src/index.ts',
  target: 'node',
  devtool: 'inline-source-map',
  module: {
    rules: [
      // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
      { test: /\.tsx?$/, loader: 'awesome-typescript-loader' },

      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
      { enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
    modules: ['node_modules'],
    alias: {
      'os-locale': './node_modules/os-locale'
    }
  },

  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'lib'),
    publicPath: './'
  },
  node: {
    __dirname: false,
    'os-locale': 'mock'
  }
}

./src/index.ts

import * as client from 'cheerio-httpcli'
import { log } from 'util'

client
  .fetch('https://hogehoge.com')
  .then(({ $ }) => console.log($('body').text()))
  .catch(err => console.log(err)
ktty1220 commented 5 years ago

エラーの原因となっているos-localeですが、ちょっと事情があって普通にrequireしているのではなく、require-uncachedモジュールを介してロードしています。

cheerio-httpcli/lib/client.jsから抜粋

// os-localeは最初に実行した段階で結果をcacheに持つので
// fetch()前に別のモジュールがos-localeを実行していた場合に
// オプション内容によっては予期しない結果が返ることがある
var requireUncached = require('require-uncached');
var osLocale        = requireUncached('os-locale');

おそらくWebpack側ではrequireは解決してもrequireUncachedの内部までは上手く解決できていないような気がします。(node_modules/os-locale/index.jsが存在するのにError: Cannot find moduleになるのはよく分かりませんがWebpackの深層に潜るのはしんどそうだったので詳しくは追ってません)

で、解決方法なんですが、ちょっと強引ですがwebpack.config.jsを以下のようにします。

const path = require('path')

module.exports = {
  mode: 'development',
  entry: './src/index.ts',
  target: 'node',
  devtool: 'inline-source-map',
  module: {
    rules: [
      // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
      { test: /\.tsx?$/, loader: 'awesome-typescript-loader' },

      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
      { enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
    modules: ['node_modules'],
//不要    alias: {
//不要      'os-locale': './node_modules/os-locale'
//不要    }
  },
//追加↓↓↓
  externals: [{
    'require-uncached': 'function(){}'
  }],
//追加↑↑↑
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'lib'),
    publicPath: './'
  },
  node: {
    __dirname: false,
//不要    'os-locale': 'mock'
  }
}

何をしているのかというと、require-uncachedを発見した時に読み込むモジュールを無理やり空関数を返すダミーモジュールに書き換えるように設定しています。

こうすると、bundle.js内でrequire-uncachedは以下のようになります。

bundle.js

 .
 .
 .
/***/ "querystring":
/*!******************************!*\
  !*** external "querystring" ***!
  \******************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = require("querystring");  // ←←← 普通はこうなる

/***/ }),

/***/ "require-uncached":
/*!*********************************!*\
  !*** external "function () {}" ***!
  \*********************************/
/*! no static exports found */
/***/ (function(module, exports) {

module.exports = function(){};  // ←←← 空関数に書き換わっている

/***/ }),
 .
 .
 .

ただ、こうなるとrequire-uncachedが本来の役割を果たさなくなりますが、今回に限り問題はありません。 というのも、require-uncachedが空関数になった結果、

var requireUncached = require('require-uncached');
var osLocale        = requireUncached('os-locale');

上記のrequireUncachedは空関数を返すダミーモジュールに、osLocaleは中身が空になります。

osLocale変数は、その後、以下の箇所のみで使用されています。

// OSのロケールからAcceptLanguageを設定
if (! this.hasHeader('Accept-Language')) {
  try {
    // spawn: trueだとexecFileSyncを使うのでできればfalseにしたいが
    // windowsだとロケールを取得できる確実な環境変数がなさそう
    var locale = osLocale.sync({ spawn: (process.platform === 'win32') });
     .
     .
     .
}

使用者側でAccept-Languageリクエストヘッダを指定していない場合に、osLocaleを使用してHTTPリクエスト時のAccept-Languageヘッダの値を設定するようにしています。 したがって、index.tsを以下のようにすればこのロジックを通らなくなり、問題は発生しません。

index.js

import * as client from 'cheerio-httpcli'
import { log } from 'util'

// ↓↓↓追加
client.set('headers', {
  'Accept-Language': 'ja,en-US'
})
// ↑↑↑追加
client
  .fetch('https://hogehoge.com')
  .then(({ $ }) => console.log($('body').text()))
  .catch(err => console.log(err)

というように、今回のケースのみに使える強引な対処方法ですが、他に解決方法は浮かばなかったのでこちらの方法を提案しましたがいかがでしょうか。

捕捉

今回提案した強引なexternalsの指定方法ですが、Webpackのexternals関連のソースを確認した所、一応想定内の指定として処理されているような感じだったので大丈夫だとは思いますが、もしかしたら将来的にこの方法が通用しなくなる可能性はあります。

forest1102 commented 5 years ago

回答いただきありがとうございます。 成功はしたのですが、index.tsをエクスポートして他のファイルでインポートしようとwebpack.config.jsを変更すると以下のようにエラーが出るので、申し訳ないですが見ていただけますか?

Error: Cannot find module 'function(){}'
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15)
    at Function.Module._load (internal/modules/cjs/loader.js:507:25)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)
    at Object.require-uncached (**/sidework/robots/lib/bundle.js:161152:18)
    at __webpack_require__ (**/sidework/robots/lib/bundle.js:21:30)
    at Object../node_modules/cheerio-httpcli/lib/client.js (**/sidework/robots/lib/bundle.js:48684:23)
    at __webpack_require__ (**/sidework/robots/lib/bundle.js:21:30)
    at Object../node_modules/cheerio-httpcli/lib/core.js (**/sidework/robots/lib/bundle.js:49295:21)
    at __webpack_require__ (**/sidework/robots/lib/bundle.js:21:30)

webpack.config.js

const path = require('path')

module.exports = {
  mode: 'development',
  entry: './index.js',
  target: 'node',
  devtool: 'inline-source-map',
  module: {
    rules: [
      // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
      { test: /\.tsx?$/, loader: 'awesome-typescript-loader' },

      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
      { enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', '.js'],
    modules: ['node_modules']
  },

  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'lib'),
    publicPath: './',
//  ↓追加
    library: 'default',
    libraryTarget: 'commonjs'
// ↑追加
  },
  node: {
    __dirname: false
  },

  externals: [
    {
      'require-uncached': 'function(){}'
    }
  ]
}
ktty1220 commented 5 years ago

library関連の指定を入れるとWebpackの挙動が変わるっぽいです。

以下のようにすればいけそうです。

externals: [
  {
    'require-uncached': 'var function(){}'
  }
]

いずれにしても対応としては何か胡散臭いので、近々cheerio-httpcli側に修正を入れてWebpack環境下でもある程度動くように調整はしようと思います。

あと、今回のWebpack側の設定で何とかする対応だと、cheerio-httpcliの自動エンコーディング判定が上手く動かないようで、UTF-8以外のページを見に行くとエラーになります。

それに関しては現段階ではどうにもならないので、cheerio-httpcliの修正をお待ちください(今週中になんとか)。

ktty1220 commented 5 years ago

Webpackに特殊な設定をしなくてもある程度動作するように修正したバージョンをnpmにアップしました(0.7.4)。

Webpackの仕様上、どうにもならない制限は以下にまとめてあります。

https://github.com/ktty1220/cheerio-httpcli#webpack%E7%92%B0%E5%A2%83%E4%B8%8B%E3%81%A7%E3%81%AE%E5%88%B6%E9%99%90

出来る限りの対応が完了したのでこちらは一旦closeします。 また何か不具合など発生したらお知らせいただければと思います。

forest1102 commented 5 years ago

ありがとうございます。 今回は親切に教えてくださりありがとうございました。 これからも色々な場面で使っていく予定ですのでまたお世話になるかもしれません。