モダンビルドの作成

こんにちは、Habr!



最新の各ブラウザーでは、 ES6モジュールを使用できるようになりました。



一見、これは完全に役に立たないようです-結局のところ、私たちはすべて、インポートを内部の課題に置き換えるコレクターを使用しています。 しかし、仕様を掘り下げると、それらのおかげで、最新のブラウザー用に別のアセンブリを提供できることがわかります。



猫の下には、古いブラウザと私の神経を損なうことなく、アプリケーションのサイズを11%削減できたという話があります。











ES6モジュールの機能



ES6モジュールは、広く知られ広く使用されているモジュラーシステムです。



/* someFile.js */ import { someFunc } from 'path/to/helpers.js'
      
      





 /* helpers.js */ export function someFunc() { /* ... */ }
      
      





ブラウザでこのモジュラーシステムを使用するには、各スクリプトタグにモジュールタイプを追加する必要があります。 古いブラウザでは、タイプがtext / javascriptと異なることがわかり、ファイルをJavaScriptとして実行しません。



 <!--        ES6 Modules --> <script type="module" src="/path/to/someFile.js"></script>
      
      





仕様には、スクリプトタグのnomodule属性もあります。 ES6モジュールをサポートするブラウザーはこのスクリプトを無視し、古いブラウザーはそれをダウンロードして実行します。



 <!--       --> <script nomodule src="/path/to/someFileFallback.js"></script>
      
      





簡単に2つのアセンブリを作成できることがわかります。1つは最新のブラウザー用のモジュールタイプ(Modern Build)、もう1つは古いブラウザー用のnomodule(フォールバックビルド)です。



 <script type="module" src="/path/to/someFile.js"></script> <script nomodule src="/path/to/someFileFallback.js"></script>
      
      





なぜそれが必要ですか



プロジェクトを本番に送信する前に、次のことを行う必要があります。





私のプロジェクトでは、ブラウザの最大数、場合によってはIE 10をサポートしようとしています。 したがって、ポリファイルのリストは、es6.promise、es6.object.valuesなどの基本的なもので構成されています。 ただし、 ES6モジュールを搭載したブラウザーはすべてのES6メソッドをサポートしており、余分なキロバイトのポリフィルは必要ありません。



また、トランスパイレーションにより、ファイルサイズに顕著なマークが残ります。babel/ preset-envは、25個のトランスフォーマーを使用してほとんどのブラウザーをカバーします。それぞれがコードのサイズを大きくします。 同時に、 ES6モジュールをサポートするブラウザーの場合、トランスフォーマーの数は9に削減されます。



そのため、最新のブラウザーのアセンブリでは、不要なポリファイルを削除してトランスフォーマーの数を減らすことができます。これにより、結果のファイルのサイズに大きな影響が出ます。



ポリファイルを追加する方法



最新のブラウザ向けにModern Buildを準備する前に、プロジェクトにポリフィルを追加する方法について言及する価値があります。











通常、プロジェクトはcore-jsを使用して、可能なすべてのポリフィルを追加します。



もちろん、このライブラリの88Kバイトのポリファイルすべてが必要なわけではなく、ブラウザリストに必要なものだけが必要です。 この機能は、babel / preset-envおよびそのuseBuiltInsオプションを使用して使用できます。 エントリに設定すると、core-jsのインポートは、ブラウザに必要な個々のモジュールのインポートに置き換えられます。



 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'entry', /* ... */ }] ], /* ... */ };
      
      





 /*   */ import 'core-js';
      
      





 /*   */ import "core-js/modules/es6.array.copy-within"; import "core-js/modules/es6.array.fill"; import "core-js/modules/es6.array.find"; /*   -  */
      
      





しかし、このような変換により、不要な非常に古い多類の一部のみを取り除きました。 TypedArray、WeakMap、およびプロジェクトで決して使用されない他の奇妙なもののための多相性がまだあります。



この問題を完全に克服するには、useBuiltInsオプションの値をusageに設定します。 コンパイルの段階で、babel / preset-envは、選択したブラウザーで使用できない機能を使用するためにファイルを分析し、それらにポリファイルを追加します。



 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', /* ... */ }] ], /* ... */ };
      
      





 /*   */ function sortStrings(strings) { return strings.sort(); } function createResolvedPromise() { return Promise.resolve(); }
      
      





 /*   */ import "core-js/modules/es6.array.sort"; import "core-js/modules/es6.promise"; function sortStrings(strings) { return strings.sort(); } function createResolvedPromise() { return Promise.resolve(); }
      
      





上記の例では、babel / preset-envがソート関数に親友を追加しました。 JavaScriptで関数に渡されるオブジェクトのタイプを見つけることはできません。これは、ソート関数を含む配列またはクラスオブジェクトになりますが、babel / preset-envは最悪のシナリオを選択し、ポリファイルを挿入します。



babel / preset-envが間違っている状況は常に起こります。 不要なpolyphilesを削除するには、excludeオプションを使用して、余分なpolyphilesをインポートおよび削除します。



 /* .babelrc.js */ module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', //   ,  ,     debug: true, //      exclude: ['es6.regexp.to-string', 'es6.number.constructor'], /* ... */ }] ], /* ... */ };
      
      





私はfast-asyncを使用しているので、regenerator-runtimeモジュールを考慮しません( そして、私は皆に助言します )。



モダンビルドを作成する



Modern Buildをセットアップしましょう。



プロジェクトに、必要なすべてのブラウザーを説明するbrowserslistファイルがあることを確認します。



 /* .browserslistrc */ > 0.5% IE 10
      
      





ビルド中にBROWSERS_ENV環境変数を追加します。これは、フォールバック(フォールバックビルドの場合)およびモダン(モダンビルドの場合)の値を取ることができます。



 /* package.json */ { "scripts": { /* ... */ "build": "NODE_ENV=production webpack /.../", "build:fallback": "BROWSERS_ENV=fallback npm run build", "build:modern": "BROWSERS_ENV=modern npm run build" }, /* ... */ }
      
      





次にbabel / preset-envの構成を変更します。 プリセットでサポートされているブラウザを指定するには、オプションのターゲットがあります。 彼女には特別な略語-esmodulesがあります。 それを使用する場合、babel / preset-envはES6モジュールをサポートするブラウザを自動的に置き換えます



 /* .babelrc.js */ const isModern = process.env.BROWSERS_ENV === 'modern'; module.exports = { presets: [ ['@babel/preset-env', { useBuiltIns: 'usage', //  Modern Build     ES6 modules, //   Fallback Build     .browsersrc targets: isModern ? { esmodules: true } : undefined, /* ... */ }] ], /* ... */ ], };
      
      





Babel / preset-envは、すべての作業をさらに行います。必要なポリフィールと変換のみを選択します。



これで、コンソールからコマンドを実行するだけで、最新または古いブラウザ用のプロジェクトを構築できます!



バインドモダンとフォールバックビルド



最後のステップは、モダンビルドとフォールバックビルドを1つにまとめることです。



このようなプロジェクト構造を作成する予定です。



 //     dist/ //  html- index.html //   Modern Build' modern/ ... //   Fallback Build' fallback/ ...
      
      





index.htmlには、両方のアセンブリからの必要なjavascriptファイルへのリンクがあります。



 /* index.html */ <html> <head> <!-- ... --> </head> <body> <!-- ... --> <script type="module" src="/modern/js/app.540601d23b6d03413d5b.js"></script> <script nomodule src="/fallback/js/app.4d03e1af64f68111703e.js"></script> </body> </html>
      
      





この手順は3つの部分に分けることができます。



  1. 異なるディレクトリでモダンビルドとフォールバックビルドをビルドします。
  2. 必要なjavascriptファイルへのパスに関する情報を取得します。
  3. すべてのjavascriptファイルへのリンクを含むindex.htmlを作成します。


さあ始めましょう!



別のディレクトリでモダンビルドとフォールバックビルドをビルドする



最初に、最も簡単な手順を実行しましょう。distディレクトリ内の異なるディレクトリにModernとFallback Buildを収集します。



output.pathに目的のディレクトリを指定することは、単に不可能です。webpackには、distディレクトリに関連するファイルへのパスが必要であるためです(index.htmlはこのディレクトリにあり、他のすべての依存関係は、このディレクトリに関連付けられます)。



ファイルパスを生成するための特別な関数を作成します。



 /* getFilePath.js */ /*   ,       */ const path = require('path'); const isModern = process.env.BROWSERS_ENV === 'modern'; const prefix = isModern ? 'modern' : 'fallback'; module.exports = relativePath => ( path.join(prefix, relativePath) );
      
      





 /* webpack.prod.config.js */ const getFilePath = require('path/to/getFilePath'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { mode: 'production', output: { path: 'dist', filename: getFilePath('js/[name].[contenthash].js'), }, plugins: [ new MiniCssExtractPlugin({ filename: getFilePath('css/[name].[contenthash].css'), }), /* ... */ ], /* ... */ }
      
      





このプロジェクトは、Modern BuildとFallback Buildの異なるディレクトリに集まり始めました。



必要なjavascriptファイルへのパスに関する情報を取得する



収集されたファイルに関する情報を取得するには、webpack-manifest-pluginを接続します。 ビルドの最後に、ファイルへのパスに関するデータを含むmanifest.jsonファイルを追加します。



 /* webpack.prod.config.js */ const getFilePath = require('path/to/getFilePath'); const WebpackManifestPlugin = require('webpack-manifest-plugin'); module.exports = { mode: 'production', plugins: [ new WebpackManifestPlugin({ fileName: getFilePath('manifest.json'), }), /* ... */ ], /* ... */ }
      
      





これで、収集されたファイルに関する情報が得られました。



 /* manifest.json */ { "app.js": "/fallback/js/app.4d03e1af64f68111703e.js", /* ... */ }
      
      





すべてのjavascriptファイルへのリンクを含むindex.htmlを作成する



残っているのは、index.htmlを追加し、必要なファイルへのパスを挿入することだけです。



htmlファイルを生成するには、Modern Build中にhtml-webpack-pluginを使用します。 html-webpack-pluginは最新のファイル自体へのパスを挿入し、前の手順で作成したファイルからフォールバックファイルへのパスを取得し、小さなwebpackプラグインを使用してHTMLに貼り付けます。



 /* webpack.prod.config.js */ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ModernBuildPlugin = require('path/to/ModernBuildPlugin'); module.exports = { mode: 'production', plugins: [ ...(isModern ? [ //  html-  Modern Build new HtmlWebpackPlugin({ filename: 'index.html', }), new ModernBuildPlugin(), ] : []), /* ... */ ], /* ... */ }
      
      





 /* ModernBuildPlugin.js */ // Safari 10.1    nomodule. //      Safari   . //    : // https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc const safariFix = '!function(){var e=document,t=e.createE/* ...   ... */'; class ModernBuildPlugin { apply(compiler) { const pluginName = 'modern-build-plugin'; //    Fallback Build const fallbackManifest = require('path/to/dist/fallback/manifest.json'); compiler.hooks.compilation.tap(pluginName, (compilation) => { //    html-webpack-plugin, //      HTML compilation.hooks.htmlWebpackPluginAlterAssetTags.tapAsync(pluginName, (data, cb) => { //  type="module"  modern- data.body.forEach((tag) => { if (tag.tagName === 'script' && tag.attributes) { tag.attributes.type = 'module'; } }); //    Safari data.body.push({ tagName: 'script', closeTag: true, innerHTML: safariFix, }); //  fallback-   nomodule const legacyAsset = { tagName: 'script', closeTag: true, attributes: { src: fallbackManifest['app.js'], nomodule: true, defer: true, }, }; data.body.push(legacyAsset); cb(); }); }); } } module.exports = ModernBuildPlugin;
      
      





package.jsonを更新します。



 /* package.json */ { "scripts": { /* ... */ "build:full": "npm run build:fallback && npm run build:modern" }, /* ... */ }
      
      





npm run build:fullコマンドを使用して、Modern BuildとFallback Buildで1つのhtmlファイルを作成します。 これで、ブラウザは実行可能なJavaScriptを受け取ります。



アプリケーションにモダンビルドを追加する



私のソリューションを現実のものでテストするために、私はそれを自分のプロジェクトの一つに入れました。 構成のセットアップには1時間もかからず、JavaScriptファイルのサイズは11%減少しました。 シンプルな実装で素晴らしい結果。



最後まで記事を読んでくれてありがとう!



使用材料






All Articles