ブラウザでnpmパッケージを機能させる方法







CodeSandboxプロジェクトの初期開発中、npm依存関係のサポートを常に無視しました。 任意のランダムな数のパケットをブラウザにインストールすることは不可能だと思いましたが、私の頭はそれについて考えることを拒否しました。







現在、npmサポートはCodeSandboxの特徴的な機能の1つであるため、なんとか実装できました。 この機能がどのようなシナリオでも機能するためには、何度もコードを何度も書き直さなければならず、今日でもロジックを改善することができます。 npmサポートを開始した方法、今日のサポート内容、およびそれを改善するためにできることを説明します。







「最初の」バージョン



これに取り組む方法がわからなかったので、npmサポートの非常に単純なバージョンから始めました。









最初のバージョン、定型化されたコンポーネントとReactのインポート(2016年11月25日)







このバージョンは非常にシンプルでした。 そのため、実際にはサポートがありませんでした。依存関係をローカルにインストールし、インストール済みの依存関係の形式で各呼び出しのスタブを作成しました。 もちろん、異なるバージョンの最大40万個のパッケージまでの拡張性はここでは臭いがしません。







このバージョンは役に立たないが、サンドボックス環境で少なくとも2つの依存関係を機能させることができて良かった。







Webpackバージョン



私は最初のバージョンに満足し、MVP(CodeSandboxの最初のリリース)には十分であるように思えました。 一般的に、魔法を使用せずに依存関係を確立できるとは思いもしませんでした。 https://esnextb.in/に出会うまで。 連中は既にnpmからの依存関係をサポートしており、package.jsonでそれらを定義するだけで十分でした-そしてすべてが魔法のように機能しました!







これは私にとって素晴らしい教訓でした。 私の考えでさえ、私はそれを非現実的だと思ったので、そのようなnpmサポートを試みませんでした。 しかし、現実のライブ証明を見て、私はそれについて多くを考え始めました。 最初に、アイデアを捨てる前に可能性を探さなければなりませんでした。







私の考えでは、タスクが複雑すぎました。 私の最初のバージョンは思いつかなかったので、図を描きました。









最初のアイデアはおそらく間違いです







このアプローチには1つの利点がありました。実際の実装は予想よりもはるかに簡単です。







WebPack DLLプラグインは、バンドル内の依存関係を収集し、マニフェストでJSパッケージ(バンドル)を発行できることがわかりました。 このマニフェストは次のようになりました。







{ "name": "dll_bundle", "content": { "./node_modules/fbjs/lib/emptyFunction.js": 0, "./node_modules/fbjs/lib/invariant.js": 1, "./node_modules/fbjs/lib/warning.js": 2, "./node_modules/react": 3, "./node_modules/fbjs/lib/emptyObject.js": 4, "./node_modules/object-assign/index.js": 5, "./node_modules/prop-types/checkPropTypes.js": 6, "./node_modules/prop-types/lib/ReactPropTypesSecret.js": 7, "./node_modules/react/cjs/react.development.js": 8 } }
      
      





各パスはモジュールIDに関連付けられています。 反応する必要がある場合は、 dll_bundle(3)



を呼び出すだけで、Reactを取得できます! それは私たちにとって完璧だったので、私はこの実際のシステムを思いつきました:









サービスのソースコードはここにあります このサービスには、npmでサンドボックスを公開するためのコードも含まれています。後でこの機能を放棄しました







パッカーへのリクエストごとに、 /tmp/:hash



に新しいディレクトリを作成し、 yarn add ${dependencyList}



を実行して、WebPackにパッケージをビルドさせました。 結果をgcloudにキャッシュとして保存しました。 主に依存関係のインストールをYarnに置き換え、WebPackを使用してパッケージに入れたため、図よりもはるかに簡単になりました。







実行する前に、サンドボックスをロードするとき、最初にマニフェストとパッケージをチェックしました。 分析中、依存関係ごとにdll_bundle(:id)



を呼び出しdll_bundle(:id)



。 このソリューションはうまくいきました。npm依存関係を通常サポートする最初のバージョンを作成しました!









やった! マテリアルデザインスタイルのインターフェイスと動的に実行可能なReact! (2016年12月24日)







システムにはまだ大きな制限がありました。WebPack依存関係グラフにないファイルはサポートしていませんでした。 つまり、たとえばrequire('react-icons/lib/fa/fa-beer')



は機能しません。これは、入力依存点によって最初に要求されることはないためです。







それにもかかわらず、私はそのようなサポートでCodeSandboxリリースを作成し、 WebPackBinの著者Christian Alfoniに連絡しました 。 npm依存関係をサポートするために、非常に類似したシステムを使用し、同じ制限に直面しました。 したがって、私たちは力を合わせて絶対的なパッカーを作成することにしました!







投稿付きWebPack



「絶対」パッカーは、重要度に応じてファイルをパッケージに追加するクリスチャンによって作成されたアルゴリズムを除き、以前のものと同じ機能を受け取りました。 手動でエントリポイントを追加して、WebPackがこれらのファイルもパックするようにしました。 システム設定を繰り返した後、どの(?)組み合わせでも機能し始めました。 そのため、既にReactアイコンとCSSファイルをリクエストできます。







新しいシステムは、アーキテクチャのアップグレードを受け取りました。ロードバランサーとキャッシュを提供するDLLサービスは1つだけでした。 パッケージ化は、動的に追加できる複数のパッカーによって行われました。













パッケージサービスをすべての人に手頃な価格で提供したいと考えました。 そのため、彼らはサービスとユースケースの操作を説明するウェブサイトを作りました。 それは私たちに名声をもたらしました、私たちはCodePenブログでも言及されました







しかし、「絶対的な」パッカーにはいくつかの制限と欠点がありました。 人気が高まるにつれて、コストは指数関数的に増加し、パッケージの組み合わせをキャッシュしました。 これは、依存関係を追加するときに、組み合わせ全体を再構築する必要があったことを意味します。







サーバーレス処理



私はいつもこのクールなテクノロジー、サーバーレス処理を試してみたかったです。 その助けを借りて、要求に応じて実行される機能を決定できます。







起動し、リクエストを処理し、しばらくすると自分自身を殺します。 これは、非常に高いスケーラビリティを意味します。1,000の同時リクエストを受信した場合、即座に1000のサーバーを起動できます。 しかし、同時にサーバーのリアルタイムに対してのみ支払います。







これは私たちのサービスに最適です。常に機能するわけではなく、多数のリクエストを同時に受信する場合に高い一貫性が必要です。 そこで、私はServerlessと呼ばれるフレームワークの実験を始めました。







サービスの変更は静かに行われました(Serverlessのおかげです!)、2日後には機能するバージョンができました。 3つのサーバーレス関数を作成しました。







  1. メタデータハンドラ:このサービスはバージョンとpeerDependenciesを解決し、packer関数も要求しました。
  2. Packer:このサービスは、パッケージ内の依存関係をインストールおよび収集しました。
  3. Uglifierは、結果のパケットの非同期uglifyingを担当しました。


古いサービスの隣に新しいサービスを開始しましたが、すべてが完璧に機能しました! 1か月あたり0.18ドル(以前の100ドルと比較して)の支出を予測し、応答時間を40〜700%改善しました。













数日後、1つの制限に気付きました。ラムダ関数には500 MBのディスクスペースしかありませんでした。 そのため、依存関係のいくつかの組み合わせをインストールできませんでした。 これは受け入れられなかったので、再び図の描画に戻りました。







サーバーレス処理の改訂



数か月が経ち、CodeSandboxの新しいラッパーをリリースしました。 非常に強力で、VueやPreactなどのライブラリをサポートしていました。 これにより、いくつかの興味深いリクエストがあります。 例:PreactでReactライブラリを使用する場合、 require('react')



require('preact-compat')



に関連付けるrequire('react')



があります。 Vueの場合、サンドボックスファイルの@/components/App.vue



を解決する必要がある場合があります。 私たちのパッカーは中毒のためにこれを行いませんが、他の人はそうします。







パッケージングタスクをブラウザパッカーに移すのではないかと思い始めました。 適切なファイルをブラウザに単に送信すると、その結果、そのパッカーはパッケージ内の依存関係を収集します。 パッケージ全体ではなく一部のみを処理するため、これは高速になります。







このアプローチには大きな利点があります。依存関係を個別にインストールしてキャッシュできます。 または、単にクライアント上の依存関係ファイルをマージします。 つまり、既存の依存関係の上に新しい依存関係を要求する場合、新しい依存関係のファイルを収集するだけで済みます! これにより、1つの依存関係のみをインストールするため、AWS Lambdaの500 MBの問題が解決します。 WebPackは、関連するファイルの計算と送信を完全に担当するようになったため、WebPackをパッカーから除外できます。









依存パッケージングの並列化







注:パッカーをドロップして、unpkg.comから各ファイルを動的に要求できます。 おそらくこれは私のアプローチよりも速いでしょう。 しかし、オフラインのサポートを提供したいので、今のところ(少なくとも編集者のために)パッカーを辞めることにしました。 これは、可能なすべての関連ファイルがある場合にのみ可能です。







実際に働く



依存関係の組み合わせを要求する場合、まずS3に既に格納されているかどうかを確認します。 そうでない場合、APIサービスに組み合わせを要求し、依存関係ごとにすべてのパッカーを個別に要求します。 応答で200 OKを取得した場合、S3を再度要求します。







パッカーはYarnを使用して依存関係をインストールし、入力ポイントのディレクトリ内のすべてのファイルのASTをバイパスして、すべての関連ファイルを見つけます。 require式を検索し、ファイルのリストに追加します。 これは再帰的に行われ、結果として依存グラフが得られます。 サンプル出力( react@latest



):







 { "aliases": { "asap": "asap/browser-asap.js", "asap/asap": "asap/browser-asap.js", "asap/asap.js": "asap/browser-asap.js", "asap/raw": "asap/browser-raw.js", "asap/raw.js": "asap/browser-raw.js", "asap/test/domain.js": "asap/test/browser-domain.js", "core-js": "core-js/index.js", "encoding": "encoding/lib/encoding.js", "fbjs": "fbjs/index.js", "iconv-lite": "iconv-lite/lib/index.js", "iconv-lite/extend-node": false, "iconv-lite/streams": false, "is-stream": "is-stream/index.js", "isomorphic-fetch": "isomorphic-fetch/fetch-npm-browserify.js", "js-tokens": "js-tokens/index.js", "loose-envify": "loose-envify/index.js", "node-fetch": "node-fetch/index.js", "object-assign": "object-assign/index.js", "promise": "promise/index.js", "prop-types": "prop-types/index.js", "react": "react/index.js", "setimmediate": "setimmediate/setImmediate.js", "ua-parser-js": "ua-parser-js/src/ua-parser.js", "whatwg-fetch": "whatwg-fetch/fetch.js" }, "contents": { "react/index.js": { "requires": [ "./cjs/react.development.js" ], "content": "/* code */" }, "object-assign/index.js": { "requires": [], "content": "/* code */" }, "fbjs/lib/emptyObject.js": { "requires": [], "content": "/* code */" }, "fbjs/lib/invariant.js": { "requires": [], "content": "/* code */" }, "fbjs/lib/emptyFunction.js": { "requires": [], "content": "/* code */" }, "react/cjs/react.development.js": { "requires": [ "object-assign", "fbjs/lib/warning", "fbjs/lib/emptyObject", "fbjs/lib/invariant", "fbjs/lib/emptyFunction", "prop-types/checkPropTypes" ], "content": "/* code */" }, "fbjs/lib/warning.js": { "requires": [ "./emptyFunction" ], "content": "/* code */" }, "prop-types/checkPropTypes.js": { "requires": [ "fbjs/lib/invariant", "fbjs/lib/warning", "./lib/ReactPropTypesSecret" ], "content": "/* code */" }, "prop-types/lib/ReactPropTypesSecret.js": { "requires": [], "content": "/* code */" }, "react/package.json": { "requires": [], "content": "/* code */" } }, "dependency": { "name": "react", "version": "16.0.0" }, "dependencyDependencies": { "asap": "2.0.6", "core-js": "1.2.7", "encoding": "0.1.12", "fbjs": "0.8.16", "iconv-lite": "0.4.19", "is-stream": "1.1.0", "isomorphic-fetch": "2.2.1", "js-tokens": "3.0.2", "loose-envify": "1.3.1", "node-fetch": "1.7.3", "object-assign": "4.1.1", "promise": "7.3.1", "prop-types": "15.6.0", "setimmediate": "1.0.5", "ua-parser-js": "0.7.14", "whatwg-fetch": "2.0.3" } }
      
      





メリット



保存中



10月5日に新しいパッカーを立ち上げて、2日間、とんでもない0.02ドルを支払いました! そして、これはキャッシュを作成するためです。 月額100ドルに比べて大幅な節約。







より高い性能



依存関係の新しい組み合わせを3秒で取得できます。 任意の組み合わせ。 古いシステムでは、時々1分かかりました。 組み合わせがキャッシュされている場合、クイック接続で50ミリ秒後に受信します。 世界中のAmazon Cloudfrontを使用してキャッシュします。 また、関連するJSファイルのみを解析して実行するため、サンドボックスも高速に動作します。







より柔軟性



パッカーは依存関係をローカルファイルのように処理するようになりました。 これは、エラースタックトレースがよりクリーンになったことを意味します。依存関係(.scss、.vueなど)から任意のファイルを含めることができ、エイリアスを簡単にサポートできます。 依存関係がローカルにインストールされているかのように動作します。







発売日



キャッシュを構築するために、古いパッカーの横にある新しいパッカーを使用し始めました。 2日間で、2000の異なる組み合わせと1400の異なる依存関係がキャッシュされました。 完全に切り替える前に、新しいバージョンを集中的にテストしたいと思います。 設定でオンにしてみてください。









ソースコード それはまだ魅力的ではありません、私はすぐにそれをきれいにし、README.mdなどを書きます







サーバーレスを使用してください!



このテクノロジーには非常に感銘を受けました。サーバーのスケーリングと管理が非常に簡単になります。 いつも私を阻んだ唯一のことは非常に複雑なセットアップでしたが、 serverless.comの開発者はそれを大幅に簡素化しました。 彼らの仕事にとても感謝しています。サーバーレスは多くの形態のアプリケーションの未来だと思います。







未来



それでもシステムを大幅に改善できます。 埋め込みとオフライン永続性に必要な動的クエリを調べたいと思います。 バランスを維持することは困難ですが、可能であるべきです。 ブラウザで何ができるかによって、依存関係のキャッシュを個別に開始できます。 この場合、依存関係の異なる組み合わせで新しいサンドボックスにアクセスするときに、新しい依存関係をダウンロードする必要さえない場合があります。 また、依存関係の解決をよりよく調査したいと思います。 新しいシステムでバージョンの競合が発生する可能性がありますが、古いバージョンを破棄する前に除外したいと思います。







いずれにせよ、私は結果に非常に満足しており、CodeSandboxの革新に取り組むつもりです!








All Articles