質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

WSH

WSH(Windows Script Host)とは、Windows上でテキストファイルに記述したJavaScriptやVBScriptなどのスクリプトを実行するホスト環境のことです。COMを通じたレジストリ操作やWMIへのアクセスが可能で、複雑な処理も行うことができます。

Q&A

解決済

3回答

3281閲覧

WSH/JScriptでもrequire()ライクなモジュール管理をしたい

torigara

総合スコア12

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

WSH

WSH(Windows Script Host)とは、Windows上でテキストファイルに記述したJavaScriptやVBScriptなどのスクリプトを実行するホスト環境のことです。COMを通じたレジストリ操作やWMIへのアクセスが可能で、複雑な処理も行うことができます。

1グッド

0クリップ

投稿2020/07/04 06:28

前提・実現したいこと

 WSH/JScriptでも、Node.jsにおけるrequire()のように細かく機能別にモジュールとして分割管理をしたいと考えております。
最終的には、本家のrequire()と同等に動作する関数を用意することで、作成したモジュールをNode.jsでも共用利用できることを目指しています。

 元々は、WSFを採用することで、モジュール単位で複数に分割したファイルを<script>タグで呼び出すという形で上記の目的の一部は達成できていました。
しかし、Node.jsとの共用利用という意味では各モジュールをUMD化する必要性もあるし、何よりモジュールから別のモジュールを呼び出すということができません。
そのため、ADODB.Streamを利用してファイルを読み込んでeval()することにより、疑似的に動作する関数を定義してみたのですが、モジュールから別のモジュールを呼び出す際のパス指定において問題が生じたので質問させていただきます。

発生している問題

読み込み対象が絶対パス、もしくはWSFファイルを起点とした相対パスで指定された場合には、希望通り動作します。
一方で、モジュールから別のモジュールを呼び出すといった場合に、本家のrequire()の場合は「呼び出し元を起点とした相対パス」で呼び出し先を指定できますが、現状ではWSFファイルを起点とした相対パスでしか指定できないといった問題が生じており、モジュールの再利用性が損なわれています。

該当のソースコード

ファイル構成

current L module | L echo | | L echo.js | | | L test.js | L require-test.js L require-test.wsf

require-test.wsf

wsf

1<?xml version="1.0" encoding="utf-8" standalone="yes" ?> 2<package> 3<job> 4 <script language="JScript" src="require-test.js"></script> 5 <script language="JScript"><![CDATA[ 6 var test = require('module/test.js'); 7 test(); 8 9 var echoFromCache = require('module/echo/echo.js'); 10 echoFromCache('echo by cached module'); // キャッシュの再利用性の確認 11 ]]></script> 12</job> 13</package>

require-test.js

js

1var require = function(modulePath) { 2 var moduleName = modulePath.replace(/^(?:.*?[/\])?([^/\]+?)(?:.js)?$/, '$1'); // キャッシュのためのモジュール名 3 4 // 1.キャッシュから呼び出し 5 if (require.cache.hasOwnProperty(moduleName)) { 6 WScript.Echo('"' + moduleName + '" is cached'); 7 return require.cache[moduleName]; 8 } 9 10 // 2.ファイルから読み込み 11 var readFile = function(filePath) { 12 var st = new ActiveXObject('ADODB.Stream'); 13 st.mode = 3; // 読み取り/書き込み両方モード 14 st.type = 2; // テキストデータ 15 st.charset = 'UTF-8'; 16 st.Open(); 17 try { 18 st.LoadFromFile(filePath); 19 var textData = st.ReadText(); 20 } catch(e) { 21 throw new Error(e.message + '\n' + 'filePath = ' + filePath); 22 } 23 st.Close(); 24 return textData; 25 }; 26 27 // TODO: Node require のパス探査機構の構築 28 // 1.上位ディレクトリをたどって、node_modules/{MODULE} を探す。 29 // 2.拡張子省略時に、{MODULE}.js -> {MODULE}.json -> {MODULE}/index.js -> {MODULE}/index.json も探す。 30 var loadedString = readFile(modulePath); 31 32 var evalCode = ( 33 '(function() {' + 34 /**/'var _module = {}, _exports = _module.exports = {};' + 35 /**/'(function(require, module, exports) {' + 36 /******/loadedString + 37 /**/'})(require, _module, _exports);' + 38 /**/'return _module.exports;' + 39 '})();' 40 ); 41 var evalModule = eval(evalCode); // module.exports 42 43 WScript.Echo('"' + moduleName + '" load from file'); 44 return require.cache[moduleName] = evalModule; // キャッシュ 45}; 46 47require.cache = {}; // モジュールのキャッシュ先

module/test.js

js

1var echo = require('module/echo/echo.js'); // wsfファイルのあるディレクトリ基準となってしまう 2// var echo = require('echo/echo.js'); // test.jsからみた相対パス指定したい 3var test = function() { 4 echo('echo by test-function'); 5}; 6module.exports = test;

module/echo/echo.js

js

1module.exports = function echo(text) { 2 WScript.Echo(text); 3};

試したこと

WScript.ScriptFullName で取得できるのはWSFファイルのパスであり、各スクリプトファイルのパスを取得する方法がわかりません
上の例でいうと、test.jsのパスがわかれば、echo.jsへの相対パスを絶対パスに変換したり、カレントを変更してしまうといったことで対応できます。
Node.jsでいうところの __dirname が分かれば早いのですが・・・。

standard-soft👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

私も同じ気持ちを持っていたことがあるのですが結構苦労して、実行していました。が、最近その方向性を諦めました。

モジュール合算にはwebpack/rollup/parcel/browserifyなどのモジュールバンドラーのようなものを用いた方がいいと思います。nodeはモジュラーを自前でもっていますが、やや古いブラウザは持っていないのでそのためのwebpack等です、それのwsh版としてそれらが使えるか、あるいは自前で作るのがよいかもです。

私の作成しているライブラリ Parts.js ではver5系までwsh対応していました。denoに対応するためにver6以降はwsh対応を諦めました。babelの吐くソースがどうやってもwshで動かずだったです。

これはbabel&webpackで1ファイルにまとめてから利用するライブラリです。

https://github.com/standard-software/partsjs/tree/v5.8.2/test_exec/release_wsh

webpack出力したコードがes3(wsh相当)に対応しないために、polyfillしないといけなかったりして、やっかいですがなんとか動かしていました。rollupとか、percelがもっとよいかもしれません。試せてません。

また、それ以前のstsLib.js というライブラリも作っていました。こちらはbabel関係なくrequireの偽装化してモジュール化しています。このあたり見てみてください。
https://github.com/standard-software/stsLib.js/tree/master/Source/stsLib.js/test

nodeやbrowserifyともソースコード共用しています。
wsh用のrequireでは、パス読み出しはせずに命名でグローバルオブジェクトにexportの内容を登録しています。
で、結局ファイルを合算するためにライブラリ利用側のコードを修正することになるので、一応仕組みは実現したのですが、あまりファイルごとでのモジュール分割はできませんでした。なので、stslib_core.js ってところにやたらコードが集中しています。

質問で頂いているパスの問題は解決できず、requireとして相対パスでファイルを読み込もうとしても、jsのエンジン(cscript.exeとかかな)が今動いているjsソースがどのパスにあるのかを検知する方法がないので、文字列登録でグローバルオブジェクトに登録して、requireで呼び出す仕組みです。

現代的なJSコードにはES最新化するためのBabel(あるいはTypeScript)は欲しくなるので、結局ビルド環境は必要になりますし、denoや、babel/webpack ではすでにimport&exportの時代になりつつ、こいつらはJS構文でどうしようもない感じもあるので最初に記載したように、トランスパイラ、モジュールバンドラーで環境を構築しての実装をおすすめします。

あるいは node に移行したほうが絶対幸せ感があるとも思います。

似たような別件ですが、下記のような質問をして回答もらったこともあります。WSHでがんばろう的なエンジニアは日本に何人いるんだろうと感じます。応援します。

javascript - @babel/polyfill が非推奨なので core-js に移行しようとしたが WSH 環境でうまく動かない - スタック・オーバーフロー
https://ja.stackoverflow.com/questions/65439/

投稿2020/07/04 13:39

編集2020/07/04 13:47
standard-soft

総合スコア197

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

torigara

2020/07/06 07:27

回答ありがとうございます。 以前よりWSH用のrequire()を模索されていた方からの回答とのことで、相対パスによる読み込みを諦める決心がつきました。 リンク先は本業が少し立て込んでいるため、まだザっとしか拝見できていませんが、今後の参考にさせていただこうかと思います。 一応、今後似たような思想を持った方がいたら参考になるように、今現在の方針を羅列しておきます。 Node.jsとWSHで共用利用できるように、require()には呼び出し元のファイルからの相対パスを引数として渡す。 WSH用のrequire()からは、引数を元にしたファイル呼び出しは行わない。 モジュールは、その相関関係をすべて把握した上で、すべて<script>タグから呼び出す。 モジュール自体はNMD化して、WSH用の分岐を用意。<script>タグから読み込む際の登録先をグローバルではなくrequire.cacheにしてグローバル汚染を防ぐ。 発想としては、モジュールが必要になった時点でrequire()実行によるファイル呼び出しをするのではなく、事前にすべて<script>タグでロードしておき、require()を実行した際にはモジュールの内容をcacheから参照するだけとする。 なにかアドバイス等あるなら、ご意見お待ちしております。
guest

0

さらに追記です。

先程アップしましたが、Parts.js ver 7.0.0 でWSH対応を復活させました。
https://github.com/standard-software/partsjs/tree/v7.0.0

これで、Deno/Node.js/GAS/WSH と、非常に多くのプラットホームで動作するライブラリになっています。

プロジェクトのコードを見てもらうと、Testコードを実装している方もビルドを通しているので、応用してもらうと、ライブラリ側のjs とアプリ側のjsとを区分けしてビルド環境を整えることもできるので、
WSH開発環境として、最新のJSを使えるので結構便利だと思います。

array.filter とか array.map とか、ご自身でPolyfillを組み込んでもありですが、Parts.js では、parts.array.filter とか parts.array.operation.filter とか part.array.map とか、一通りいわゆる最新のJSで使われるようなものは関数実装しているので、よかったらご利用ください。

投稿2020/07/22 10:39

standard-soft

総合スコア197

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

追加ですが、

モジュール分割された jsファイルをビルドして結合して、wshで動かすことのできるものにする環境をGitHubにアップしました。

node を使って、babel/webpack でビルド(1ファイルにまとめる)しています。

Project01 は CommonJS 形式でファイルをリンクしてnodeとwebとwsfで動かしています。
Project04 は ESModule 形式でファイルをリンクしてdenoとnodeとwebとwshで動かしています。

https://github.com/standard-software/parts-Node_Deno_ProjectTemplate/tree/v1.4.0/Backup

これで、最新のJSの構文でWSHでも動かすことができると思います。

先に紹介したParts.js でビルドを試していたところ、
DenoとWSHに両対応できなかったので、無理だと思い込んでいたのですが、何か別の要因だったみたいです。Parts.jsはソースが大きいので対応しない構文をつかっていてビルドが失敗したのかも。

上記のプロジェクトで最小限のビルド環境を整えて試したところ、Deno/Node/Browser/WSH、すべて動きました。

node/npm に使い慣れている人なら、scripts内のコマンドを順次実行していってビルドできるようにしています。

あまり整っていない荒削りなプロジェクトテンプレートですが、よかったらご参考ください。

投稿2020/07/09 14:26

編集2020/07/09 14:43
standard-soft

総合スコア197

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問